Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"name": "token-saver",
"source": "./",
"description": "Automatically compresses verbose CLI output to save tokens. 21 specialized processors for git, docker, npm, terraform, kubectl, helm, ansible, and more.",
"version": "2.1.1",
"version": "2.2.1",
"author": {
"name": "ppgranger"
},
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "token-saver",
"description": "Automatically compresses verbose CLI output (git, docker, npm, terraform, kubectl, etc.) to save tokens in Claude Code sessions. 21 specialized processors with content-aware compression.",
"version": "2.1.1",
"version": "2.2.1",
"author": {
"name": "ppgranger",
"url": "https://github.com/ppgranger"
Expand Down
44 changes: 25 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,24 +237,28 @@ processor is in [`docs/processors/`](docs/processors/).
| 1 | **Package List** | 15 | pip list/freeze, npm ls, conda list, gem list, brew list | [package_list.md](docs/processors/package_list.md) |
| 2 | **Git** | 20 | status, diff, log, show, push/pull/fetch, branch, stash, reflog, blame, cherry-pick, rebase, merge | [git.md](docs/processors/git.md) |
| 3 | **Test** | 21 | pytest, jest, vitest, mocha, cargo test, go test, rspec, phpunit, bun test, npm/yarn/pnpm test, dotnet test, swift test, mix test | [test_output.md](docs/processors/test_output.md) |
| 4 | **Build** | 25 | npm/yarn/pnpm build/install, cargo build, make, cmake, gradle, mvn, pip install, tsc, webpack, vite, next build, turbo, nx, bazel, sbt, mix compile, docker build | [build_output.md](docs/processors/build_output.md) |
| 5 | **Lint** | 27 | eslint, ruff, flake8, pylint, clippy, mypy, prettier, biome, shellcheck, hadolint, rubocop, golangci-lint | [lint_output.md](docs/processors/lint_output.md) |
| 6 | **Network** | 30 | curl, wget, http/https (httpie) | [network.md](docs/processors/network.md) |
| 7 | **Docker** | 31 | ps, images, logs, pull/push, inspect, stats, compose up/down/build/ps/logs | [docker.md](docs/processors/docker.md) |
| 8 | **Kubernetes** | 32 | kubectl/oc get, describe, logs, top, apply, delete, create | [kubectl.md](docs/processors/kubectl.md) |
| 9 | **Terraform** | 33 | terraform/tofu plan, apply, destroy, init, output, state list/show | [terraform.md](docs/processors/terraform.md) |
| 10 | **Environment** | 34 | env, printenv (with secret redaction) | [env.md](docs/processors/env.md) |
| 11 | **Search** | 35 | grep -r, rg, ag, fd, fdfind | [search.md](docs/processors/search.md) |
| 12 | **System Info** | 36 | du, wc, df | [system_info.md](docs/processors/system_info.md) |
| 13 | **GitHub CLI** | 37 | gh pr/issue/run list/view/diff/checks/status | [gh.md](docs/processors/gh.md) |
| 14 | **Database Query** | 38 | psql, mysql, sqlite3, pgcli, mycli, litecli | [db_query.md](docs/processors/db_query.md) |
| 15 | **Cloud CLI** | 39 | aws, gcloud, az (JSON/table/text output compression) | [cloud_cli.md](docs/processors/cloud_cli.md) |
| 16 | **Ansible** | 40 | ansible-playbook, ansible (ok/skipped counting, error preservation) | [ansible.md](docs/processors/ansible.md) |
| 17 | **Helm** | 41 | helm install/upgrade/list/template/status/history | [helm.md](docs/processors/helm.md) |
| 18 | **Syslog** | 42 | journalctl, dmesg (head/tail with error extraction) | [syslog.md](docs/processors/syslog.md) |
| 19 | **File Listing** | 50 | ls, find, tree, exa, eza, rsync | [file_listing.md](docs/processors/file_listing.md) |
| 20 | **File Content** | 51 | cat, head, tail, bat, less, more (content-aware: code, config, log, CSV) | [file_content.md](docs/processors/file_content.md) |
| 21 | **Generic** | 999 | Any command (fallback: ANSI strip, dedup, truncation) | [generic.md](docs/processors/generic.md) |
| 4 | **Python Install** | 24 | pip install, poetry install/update/add, uv pip install, uv sync | [python_install.md](docs/processors/python_install.md) |
| 5 | **Build** | 25 | npm/yarn/pnpm build/install, cargo build, make, cmake, tsc, webpack, vite, next build, turbo, nx, bazel, sbt, mix compile, docker build | [build_output.md](docs/processors/build_output.md) |
| 6 | **Cargo Clippy** | 26 | cargo clippy (multi-line block grouping with span/help preservation) | [cargo_clippy.md](docs/processors/cargo_clippy.md) |
| 7 | **Lint** | 27 | eslint, ruff, flake8, pylint, clippy, mypy, prettier, biome, shellcheck, hadolint, rubocop, golangci-lint | [lint_output.md](docs/processors/lint_output.md) |
| 8 | **Maven/Gradle** | 28 | mvn, ./mvnw, gradle, ./gradlew (download stripping, task noise removal) | [maven_gradle.md](docs/processors/maven_gradle.md) |
| 9 | **Network** | 30 | curl, wget, http/https (httpie) | [network.md](docs/processors/network.md) |
| 10 | **Docker** | 31 | ps, images, logs, pull/push, inspect, stats, compose up/down/build/ps/logs | [docker.md](docs/processors/docker.md) |
| 11 | **Kubernetes** | 32 | kubectl/oc get, describe, logs, top, apply, delete, create | [kubectl.md](docs/processors/kubectl.md) |
| 12 | **Terraform** | 33 | terraform/tofu plan, apply, destroy, init, output, state list/show | [terraform.md](docs/processors/terraform.md) |
| 13 | **Environment** | 34 | env, printenv (with secret redaction) | [env.md](docs/processors/env.md) |
| 14 | **Search** | 35 | grep -r, rg, ag, fd, fdfind | [search.md](docs/processors/search.md) |
| 15 | **System Info** | 36 | du, wc, df | [system_info.md](docs/processors/system_info.md) |
| 16 | **GitHub CLI** | 37 | gh pr/issue/run list/view/diff/checks/status | [gh.md](docs/processors/gh.md) |
| 17 | **Database Query** | 38 | psql, mysql, sqlite3, pgcli, mycli, litecli | [db_query.md](docs/processors/db_query.md) |
| 18 | **Cloud CLI** | 39 | aws, gcloud, az (JSON/table/text output compression) | [cloud_cli.md](docs/processors/cloud_cli.md) |
| 19 | **Ansible** | 40 | ansible-playbook, ansible (ok/skipped counting, error preservation) | [ansible.md](docs/processors/ansible.md) |
| 20 | **Helm** | 41 | helm install/upgrade/list/template/status/history | [helm.md](docs/processors/helm.md) |
| 21 | **Syslog** | 42 | journalctl, dmesg (head/tail with error extraction) | [syslog.md](docs/processors/syslog.md) |
| 22 | **Structured Log** | 45 | stern, kubetail (JSON Lines grouping by level) | [structured_log.md](docs/processors/structured_log.md) |
| 23 | **File Listing** | 50 | ls, find, tree, exa, eza, rsync | [file_listing.md](docs/processors/file_listing.md) |
| 24 | **File Content** | 51 | cat, head, tail, bat, less, more (content-aware: code, config, log, CSV) | [file_content.md](docs/processors/file_content.md) |
| 25 | **Generic** | 999 | Any command (fallback: ANSI strip, dedup, truncation) | [generic.md](docs/processors/generic.md) |

## Configuration

Expand Down Expand Up @@ -341,11 +345,13 @@ Project settings are merged with global settings. Token-Saver walks up parent di
| `max_traceback_lines` | 30 | Max traceback lines before truncation |
| `db_prune_days` | 90 | Stats retention in days |
| `user_processors_dir` | `~/.token-saver/processors/` | Directory for custom processors |
| `disabled_processors` | `[]` | List of processor names to disable (env: comma-separated) |
| `max_chain_depth` | 3 | Maximum processor chain depth |
| `debug` | false | Enable debug logging |

## Custom Processors

You can extend Token-Saver with your own processors for commands not covered by the built-in 21.
You can extend Token-Saver with your own processors for commands not covered by the built-in 25.

1. Create a Python file with a class inheriting from `src.processors.base.Processor`
2. Implement `can_handle()`, `process()`, `name`, and set `priority`
Expand Down
34 changes: 34 additions & 0 deletions docs/processors/cargo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Cargo Processor

**File:** `src/processors/cargo.py` | **Priority:** 22 | **Name:** `cargo`

Dedicated processor for Rust's cargo build system.

## Supported Commands

cargo build, cargo check, cargo doc, cargo update, cargo bench.

## Strategy

| Subcommand | Strategy |
|---|---|
| **build/check** | Collapse `Compiling X v1.0` lines into count. Group warnings by type (unused_variable, unused_import, dead_code, unused_mut, lifetime, borrow_checker). Show first N examples per type. Keep ALL errors with full span context (`-->`, `|`, `^^` markers). Keep `Finished` summary |
| **doc** | Collapse `Documenting X` and `Compiling X` lines. Keep doc warnings, errors, `Finished`, and `Generated` lines |
| **update** | Show all major version bumps explicitly (breaking changes). Collapse minor/patch bumps into count. Keep `Adding` and `Removing` lines |
| **bench** | Keep benchmark result lines (`bench: N ns/iter`). Strip `Compiling` and `Running` noise. Keep `test result:` summary |

## Exclusions

- `cargo test` is routed to `TestOutputProcessor`
- `cargo clippy` is routed to `CargoClippyProcessor`

## Configuration

| Parameter | Default | Description |
|---|---|---|
| cargo_warning_example_count | 2 | Number of example warnings to show per category |
| cargo_warning_group_threshold | 3 | Minimum occurrences before warnings are grouped |

## Removed Noise

`Compiling X v1.0.0` lines, `Downloading X v1.0.0` lines, `Running` lines (bench), intermediate blank lines between warnings.
39 changes: 39 additions & 0 deletions docs/processors/cargo_clippy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Cargo Clippy Processor

**File:** `src/processors/cargo_clippy.py` | **Priority:** 26 | **Name:** `cargo_clippy`

Dedicated processor for Rust clippy lint output with multi-line block awareness.

## Supported Commands

cargo clippy (with any flags like `--all-targets`, `-- -W clippy::all`).

## Strategy

Parses clippy's multi-line warning blocks (header + `-->` span + code + `= help:` annotations) as coherent units. Groups warnings by clippy lint rule. Shows N example blocks per rule with full context. Preserves all errors in full.

| Output Type | Strategy |
|---|---|
| **Warnings** | Group by lint rule (e.g., `clippy::needless_return`). Show count + N example blocks per rule. Categorize as style/correctness/complexity/perf |
| **Errors** | Keep all error blocks in full with spans and context |
| **Checking/Compiling** | Collapse into count (e.g., `[12 checked, 3 compiled]`) |
| **Summary** | Keep `warning: X generated N warnings` summary line |

## Key Difference from Lint Processor

The generic `LintOutputProcessor` groups violations as single lines. Clippy output has multi-line blocks with `-->` spans, code snippets, and `= help:` annotations that need to be preserved as coherent units. This processor keeps the block structure intact.

## Configuration

| Parameter | Default | Description |
|---|---|---|
| cargo_warning_example_count | 2 | Number of example warning blocks to show per rule |
| cargo_warning_group_threshold | 3 | Minimum occurrences before warnings are grouped |

## Chaining

After clippy-specific processing, output is chained to the `lint` processor (`chain_to = ["lint"]`). This allows any non-clippy-specific warnings in the output to be grouped by the generic lint rule parser.

## Fallback

If this processor is disabled, `cargo clippy` falls back to the `LintOutputProcessor` which handles it at a line-by-line level.
36 changes: 36 additions & 0 deletions docs/processors/go.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Go Processor

**File:** `src/processors/go.py` | **Priority:** 23 | **Name:** `go`

Dedicated processor for Go toolchain commands.

## Supported Commands

go build, go vet, go mod tidy, go mod download, go generate, go install.

## Strategy

| Subcommand | Strategy |
|---|---|
| **build/install** | Keep all `file.go:line:col: message` errors. For multi-package builds with many `# package` headers, truncate to first 3. Pass through unchanged if no errors (successful builds produce no output) |
| **vet** | Group warnings by type (printf, unreachable, shadow, unused, nil, loop). Show first N examples per type. Keep `# package` headers for context |
| **mod tidy/download** | Collapse `go: downloading X v1.0` lines into count. Keep `go: added/upgraded/downgraded/removed` lines (important dependency changes) |
| **generate** | Collapse `running` lines into count. Keep errors and generator output |

## Exclusions

- `go test` is routed to `TestOutputProcessor`
- `golangci-lint` is routed to `LintOutputProcessor`

## Configuration

Uses existing parameters:

| Parameter | Default | Description |
|---|---|---|
| lint_example_count | 2 | Examples per warning type (go vet) |
| lint_group_threshold | 3 | Minimum occurrences before grouping |

## Removed Noise

`go: downloading X` lines, `# package` headers (when redundant), `running` lines from go generate.
29 changes: 29 additions & 0 deletions docs/processors/jq_yq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# JQ/YQ Processor

**File:** `src/processors/jq_yq.py` | **Priority:** 44 | **Name:** `jq_yq`

Compresses large JSON and YAML outputs from jq and yq.

## Supported Commands

jq, yq.

## Strategy

| Output Type | Strategy |
|---|---|
| **Small output** (< 50 lines) | Pass through unchanged |
| **jq JSON** | Parse as JSON, compress with `compress_json_value()` (truncate arrays > 5 items, summarize deeply nested objects). Re-serialize with indent |
| **jq streaming** (one JSON per line) | Detect repeated structure (same keys), show first 3 + count. Fallback to head/tail |
| **yq YAML** | Count top-level keys and list items. Collapse large arrays (> 3 items at same indent) to count. Add structure summary header |

## Configuration

| Parameter | Default | Description |
|---|---|---|
| jq_passthrough_threshold | 50 | Lines below which output passes through unchanged |

## Notes

- No runtime dependencies: JSON parsing uses stdlib `json` module, YAML uses heuristic analysis (no PyYAML dependency)
- Streaming jq output (one value per line) is detected and compressed separately from single-document output
40 changes: 40 additions & 0 deletions docs/processors/maven_gradle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Maven/Gradle Processor

**File:** `src/processors/maven_gradle.py` | **Priority:** 28 | **Name:** `maven_gradle`

Dedicated processor for Maven and Gradle build output.

## Supported Commands

mvn, ./mvnw, gradle, ./gradlew (all subcommands).

## Strategy

### Maven

| Output Type | Strategy |
|---|---|
| **Download lines** | Strip `[INFO] Downloading from` and `[INFO] Downloaded from` lines. Show count |
| **Module lines** | Count `[INFO] Building module-name` lines |
| **Errors** | Keep all `[ERROR]` and `[FATAL]` lines |
| **Warnings** | Keep first 5 `[WARNING]` lines, summarize rest |
| **Test results** | Keep `Tests run: N, Failures: N` lines |
| **Reactor summary** | Keep reactor summary block |
| **Build result** | Keep `BUILD SUCCESS`/`BUILD FAILURE` and timing |

### Gradle

| Output Type | Strategy |
|---|---|
| **Task lines** | Strip `UP-TO-DATE`, `NO-SOURCE`, `SKIPPED`, `FROM-CACHE` tasks. Keep executed tasks. Show counts |
| **Errors** | Keep `FAILURE:` blocks, error details, `What went wrong` sections |
| **Test results** | Keep test result summary lines |
| **Build result** | Keep `BUILD SUCCESSFUL`/`BUILD FAILED` and actionable task summary |

## Configuration

No dedicated configuration keys. Uses default compression thresholds.

## Removed Noise

Maven: `[INFO] Downloading/Downloaded` lines, separator lines (`-----`), empty `[INFO]` lines. Gradle: `UP-TO-DATE`/`NO-SOURCE` task lines, progress indicators.
29 changes: 29 additions & 0 deletions docs/processors/python_install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Python Install Processor

**File:** `src/processors/python_install.py` | **Priority:** 24 | **Name:** `python_install`

Dedicated processor for Python package installation output.

## Supported Commands

pip install, pip3 install, poetry install/update/add, uv pip install, uv sync.

## Strategy

| Tool | Strategy |
|---|---|
| **pip install** | Strip `Collecting` and `Downloading` lines. Remove progress bars. Count packages installed. Show `already satisfied` count. Preserve all errors and warnings. Show installed package summary (first 10 + count) |
| **poetry install/update/add** | Strip `Resolving dependencies` progress. Count installed/updated/removed packages. Show package names with versions. Preserve errors |
| **uv pip install/sync** | Strip download progress. Keep `Resolved N packages` and `Installed N packages` summaries. Preserve errors |

## Exclusions

- `pip list` and `pip freeze` are routed to `PackageListProcessor`

## Configuration

No dedicated configuration keys. Uses default compression thresholds.

## Removed Noise

`Collecting X>=1.0` lines, `Downloading X-1.0.whl` lines, pip progress bars, `Installing collected packages:` line, `Using cached` lines, `Resolving dependencies...` output from poetry.
29 changes: 29 additions & 0 deletions docs/processors/ssh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# SSH Processor

**File:** `src/processors/ssh.py` | **Priority:** 43 | **Name:** `ssh`

Handles non-interactive SSH and SCP command output.

## Supported Commands

- `ssh host 'command'` or `ssh host "command"` (non-interactive SSH with quoted remote command)
- `ssh -o Option=value host 'command'` (with SSH options)
- `scp` (all forms — always non-interactive)

## Not Supported

- `ssh host` (interactive SSH — no remote command) remains excluded from compression

## Strategy

| Command | Strategy |
|---|---|
| **SSH remote** | Apply log-style compression: keep first 10 + last 20 lines, preserve error lines with context in the middle section |
| **SCP** | Collapse progress bar lines (containing `%` and transfer rates) to final status per file. Keep error lines (permission denied, connection refused, etc.) |

## How It Works

The SSH/SCP exclusion in `hook_pretool.py` was narrowed from a blanket `ssh|scp` exclusion to only exclude interactive SSH (no quoted command). This allows:
- `ssh host 'ls -la'` — compressed (non-interactive)
- `scp file host:/path` — compressed (always non-interactive)
- `ssh host` — still excluded (interactive)
35 changes: 35 additions & 0 deletions docs/processors/structured_log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Structured Log Processor

**File:** `src/processors/structured_log.py` | **Priority:** 45 | **Name:** `structured_log`

Processor for JSON Lines log output from log tailing tools.

## Supported Commands

stern, kubetail.

## Strategy

| Content Type | Strategy |
|---|---|
| **JSON Lines (>50% valid JSON)** | Parse each JSON object. Group entries by log level (error/warn/info/debug/trace). Show count per level. Extract and display error messages (up to 10). Detect level from common keys: `level`, `severity`, `log_level`, `lvl` |
| **Non-JSON output** | Fall back to log compression (head/tail with error preservation) |

## Level Detection

Checks these JSON keys in order: `level`, `severity`, `log_level`, `loglevel`, `lvl`, `log.level`. Falls back to regex matching on message content for `ERROR`/`WARN` patterns.

## Message Extraction

Checks these JSON keys in order: `msg`, `message`, `text`, `log`, `body`. Truncates messages longer than 200 characters.

## Configuration

| Parameter | Default | Description |
|---|---|---|
| kubectl_keep_head | 5 | Lines to keep from start (non-JSON fallback) |
| kubectl_keep_tail | 10 | Lines to keep from end (non-JSON fallback) |

## Future Use

This processor can be activated via `chain_to` from other processors for outputs that contain embedded JSON Lines.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "token-saver"
version = "2.1.1"
version = "2.2.1"
requires-python = ">=3.10"

[project.optional-dependencies]
Expand Down
4 changes: 2 additions & 2 deletions scripts/hook_pretool.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def _load_compressible_patterns() -> list[str]:
EXCLUDED_PATTERNS = [
r"(?<!['\"])\|(?!['\"])", # unquoted pipe (complex pipelines)
r"^\s*(vi|vim|nano|emacs|code)\b",
r"^\s*(ssh|scp)\b",
r"^\s*ssh\s+(?:-\S+\s+)*\S+\s*$", # interactive ssh only (no remote command)
r"^\s*rsync\b.*\S+:\S+", # only exclude remote rsync (host:path)
r"(?:^|\s)token[-_]saver\s", # avoid wrapping token-saver CLI itself
r"wrap\.py",
Expand All @@ -106,7 +106,7 @@ def _load_compressible_patterns() -> list[str]:
r"<\(", # process substitution
r"^\s*sudo\b",
r"^\s*(vi|vim|nano|emacs|code)\b",
r"^\s*(ssh|scp)\b",
r"^\s*ssh\s+(?:-\S+\s+)*\S+\s*$", # interactive ssh only
r"^\s*rsync\b.*\S+:\S+", # only exclude remote rsync (host:path)
r"^\s*env\s+\S+=",
r"(?:^|\s)token[-_]saver\s",
Expand Down
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os

__version__ = "2.1.1"
__version__ = "2.2.1"


def data_dir() -> str:
Expand Down
Loading
Loading