CLI proxy that cuts LLM token waste from shell output.
AI coding agents burn tokens on verbose shell output that adds zero signal. A passing go test produces hundreds of lines the LLM will never use. git log dumps full commit metadata when a one-liner per commit suffices.
snip sits between your AI tool and the shell, filtering output through declarative YAML pipelines before it reaches the context window.
snip — Token Savings Report
══════════════════════════════
Commands filtered 128
Tokens saved 2.3M
Avg savings 99.8%
Efficiency Elite
Total time 725.9s
███████████████████░ 100%
14-day trend ▁█▇
Top commands by tokens saved
Command Runs Saved Savings Impact
───────────────────────── ──── ────── ─────── ────────────
go test ./... 8 806.2K 99.8% ████████████
go test ./pkg/... 3 482.9K 99.8% ███████░░░░░
go test ./... -count=1 3 482.0K 99.8% ███████░░░░░
Measured on a real Claude Code session — 128 commands, 2.3M tokens saved.
# Homebrew (macOS/Linux)
brew install edouard-claude/tap/snip
# Or with Go
go install github.com/edouard-claude/snip/cmd/snip@latest
# Then hook into Claude Code
snip init
# That's it. Every shell command Claude runs now goes through snip.Before — Claude Code sees this (689 tokens):
$ go test ./...
ok github.com/edouard-claude/snip/internal/cli 3.728s coverage: 14.4% of statements
ok github.com/edouard-claude/snip/internal/config 2.359s coverage: 65.0% of statements
ok github.com/edouard-claude/snip/internal/display 1.221s coverage: 72.6% of statements
ok github.com/edouard-claude/snip/internal/engine 1.816s coverage: 47.9% of statements
ok github.com/edouard-claude/snip/internal/filter 4.306s coverage: 72.3% of statements
ok github.com/edouard-claude/snip/internal/initcmd 2.981s coverage: 59.1% of statements
ok github.com/edouard-claude/snip/internal/tee 0.614s coverage: 70.6% of statements
ok github.com/edouard-claude/snip/internal/tracking 5.355s coverage: 75.0% of statements
ok github.com/edouard-claude/snip/internal/utils 5.515s coverage: 100.0% of statements
After — snip returns this (16 tokens):
10 passed, 0 failed
That's 97.7% fewer tokens. The LLM gets the same signal — all tests pass — without the noise.
┌─────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌────────────┐
│ Claude Code │────>│ snip intercept │────>│ run command │────>│ filter │
│ runs git │ │ match filter │ │ capture I/O │ │ pipeline │
└─────────────┘ └─────────────────┘ └──────────────┘ └─────┬──────┘
│
┌─────────────────┐ ┌──────────────┐ │
│ Claude Code │<────│ track savings│<──────────┘
│ sees filtered │ │ in SQLite │
└─────────────────┘ └──────────────┘
No filter match? The command passes through unchanged — zero overhead.
| Command | Before | After | Savings |
|---|---|---|---|
cargo test |
591 tokens | 5 tokens | 99.2% |
go test ./... |
689 tokens | 16 tokens | 97.7% |
git log |
371 tokens | 53 tokens | 85.7% |
git status |
112 tokens | 16 tokens | 85.7% |
git diff |
355 tokens | 66 tokens | 81.4% |
brew install edouard-claude/tap/snipDownload the latest binary for your platform from Releases.
# macOS (Apple Silicon)
curl -Lo snip.tar.gz https://github.com/edouard-claude/snip/releases/latest/download/snip_$(curl -s https://api.github.com/repos/edouard-claude/snip/releases/latest | grep tag_name | cut -d'"' -f4 | tr -d v)_darwin_arm64.tar.gz
tar xzf snip.tar.gz && mv snip /usr/local/bin/go install github.com/edouard-claude/snip/cmd/snip@latestOr build locally:
git clone https://github.com/edouard-claude/snip.git
cd snip && make installRequires Go 1.24+ and jq (for the hook script).
snip initThis installs a PreToolUse hook that transparently rewrites supported commands. Claude Code never sees the substitution — it receives compressed output as if the original command produced it.
Supported commands: git, go, cargo, npm, npx, yarn, pnpm, docker, kubectl, make, pip, pytest, jest, tsc, eslint, rustc.
snip init --uninstall # remove the hookCursor supports hooks since v1.7 via ~/.cursor/hooks.json:
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{ "command": "~/.claude/hooks/snip-rewrite.sh" }
]
}
}Use shell aliases:
# Add to ~/.bashrc or ~/.zshrc
alias git="snip git"
alias go="snip go"
alias cargo="snip cargo"Or instruct the LLM via system prompt to prefix commands with snip.
snip works without any AI tool:
snip git log -10
snip go test ./...
snip gain # token savings reportsnip <command> [args] # filter a command
snip gain # full dashboard (summary + sparkline + top commands)
snip gain --daily # daily breakdown
snip gain --weekly # weekly breakdown
snip gain --monthly # monthly breakdown
snip gain --top 10 # top N commands by tokens saved
snip gain --history 20 # last 20 commands
snip gain --json # machine-readable output
snip gain --csv # CSV export
snip -v <command> # verbose mode (show filter details)
snip proxy <command> # force passthrough (no filtering)
snip config # show config
snip init # install Claude Code hook
snip init --uninstall # remove hookFilters are declarative YAML files. The binary is the engine, filters are data — the two evolve independently.
name: "git-log"
version: 1
description: "Condense git log to hash + message"
match:
command: "git"
subcommand: "log"
exclude_flags: ["--format", "--pretty", "--oneline"]
inject:
args: ["--pretty=format:%h %s (%ar) <%an>", "--no-merges"]
defaults:
"-n": "10"
pipeline:
- action: "keep_lines"
pattern: "\\S"
- action: "truncate_lines"
max: 80
- action: "format_template"
template: "{{.count}} commits:\n{{.lines}}"
on_error: "passthrough"| Filter | What it does |
|---|---|
git-status |
Categorized status with file counts |
git-diff |
Stat summary, truncated to 30 files |
git-log |
One-line per commit: hash + message + author + date |
go-test |
Pass/fail summary with failure details |
cargo-test |
Pass/fail summary with failure details |
| Action | Description |
|---|---|
keep_lines |
Keep lines matching regex |
remove_lines |
Remove lines matching regex |
truncate_lines |
Truncate lines to max length |
strip_ansi |
Remove ANSI escape codes |
head / tail |
Keep first/last N lines |
group_by |
Group lines by regex capture |
dedup |
Deduplicate with optional normalization |
json_extract |
Extract fields from JSON |
json_schema |
Infer schema from JSON |
ndjson_stream |
Process newline-delimited JSON |
regex_extract |
Extract regex captures |
state_machine |
Multi-state line processing |
aggregate |
Count pattern matches |
format_template |
Go template formatting |
compact_path |
Shorten file paths |
snip init # creates ~/.config/snip/filters/
vim ~/.config/snip/filters/my-tool.yaml # add your filterUser filters take priority over built-in ones.
Optional TOML config at ~/.config/snip/config.toml:
[tracking]
db_path = "~/.local/share/snip/tracking.db"
[display]
color = true
emoji = true
[filters]
dir = "~/.config/snip/filters"
[tee]
enabled = true
mode = "failures" # "failures" | "always" | "never"
max_files = 20
max_file_size = 1048576- Startup < 10ms — snip intercepts every shell command; latency is critical
- Graceful degradation — if a filter fails, fall back to raw output
- Exit code preservation — always propagate the underlying tool's exit code
- Lazy regex compilation —
sync.Onceper pattern, reused across invocations - Zero CGO — pure Go SQLite driver, static binaries, trivial cross-compilation
- Goroutine concurrency — stdout/stderr captured in parallel without thread pools
| rtk (Rust) | snip (Go) | |
|---|---|---|
| Filters | Compiled into the binary | Declarative YAML — no code needed |
| Concurrency | 2 OS threads | Goroutines |
| SQLite | Requires CGO + C compiler | Pure Go driver — static binary |
| Cross-compilation | Per-target C toolchain | GOOS=linux GOARCH=arm64 go build |
| Contributing a filter | Write Rust, wait for release | Write YAML, drop in a folder |
make build # static binary (CGO_ENABLED=0)
make test # all tests with coverage
make test-race # race detector
make lint # go vet + golangci-lint
make install # install to $GOPATH/binFull documentation is available on the Wiki:
- Installation — Homebrew, Go, binaries (macOS/Linux/Windows), from source
- Integration — Claude Code, Cursor, Aider, standalone
- Gain Dashboard — Token savings reports and analytics
- Filters — Built-in filters, custom filters
- Filter DSL Reference — All 16 pipeline actions
- Configuration — TOML config, environment variables
- Architecture — Design decisions, internals
- Contributing — Dev setup, adding filters, conventions
Inspired by rtk by the rtk-ai team. rtk proved that filtering shell output before it reaches the LLM context is a powerful idea. snip rebuilds it in Go with a focus on extensibility — declarative YAML filters that anyone can write without touching the codebase.
MIT