CLI proxy that reduces LLM token usage by 60-90%. Declarative YAML filters for Claude Code, Cursor, Copilot, Gemini. rtk alternative in Go.
# Add to your Claude Code skills
git clone https://github.com/edouard-claude/snipGuides for using cli tools skills like snip.
You are an expert at writing declarative YAML filters for snip, a CLI proxy that reduces LLM token consumption by filtering shell output.
filters/*.yaml (embedded in the binary at build time)~/.config/snip/filters/*.yaml (override built-in filters by name)filters.dir array in ~/.config/snip/config.toml (e.g. dir = ["~/.config/snip/filters", "${env.PWD}/.snip"]). Later directories take priority.Every filter is a YAML file with this structure:
name: "tool-subcommand" # Required. Unique identifier, used for registry lookup.
version: 1 # Schema version (always 1 for now).
description: "What this filter does" # Human-readable purpose.
match: # Required. When to apply this filter.
command: "tool" # Required. The CLI tool name (e.g., "git", "go", "npm").
subcommand: "sub" # Optional. First non-flag argument (e.g., "test", "log").
exclude_flags: ["-v", "--json"] # Optional. Skip filter if user passes any of these.
require_flags: ["--all"] # Optional. Only apply if user passes ALL of these.
inject: # Optional. Modify command args before execution.
args: ["--json"] # Arguments to append to the command.
defaults: # Flag defaults, only added if flag not already present.
"-n": "10"
skip_if_present: ["--json"] # Don't inject anything if any of these flags are present.
streams: ["stdout", "stderr"] # Optional. Which streams to filter. Default: ["stdout"].
# Use ["stderr"] for tools that output to stderr (e.g., bun test).
# Use ["stdout", "stderr"] to filter both streams merged together.
pipeline: # Required. Ordered list of transformation actions.
- action: "keep_lines"
pattern: "\\S"
- action: "head"
n: 20
on_error: "passthrough" # What to do if the pipeline fails: "passthrough" or "empty".
CLI proxy that filters shell output before it reaches your AI coding assistant's context window. Works with Claude Code, Cursor, Copilot, Gemini CLI, Windsurf, Cline, Codex, Kilo Code, Antigravity, Aider, and any tool that runs shell commands.
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. Write a YAML file, drop it in a folder, done. The extensible LLM token optimizer: filters are YAML data files, not compiled code.
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.
# Quick install (macOS/Linux)
curl -fsSL https://raw.githubusercontent.com/edouard-claude/snip/master/install.sh | sh
# Or via Homebrew
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.
No comments yet. Be the first to share your thoughts!
Top skills in this category by stars
commandsubcommand is matched against the first non-flag argument."-v" matches both -v and -verbose."command" or "command:subcommand".args are inserted before any -- separator, otherwise appended.defaults only apply if their flag key is not already present in the user's args.skip_if_present is found, the entire inject block is skipped.| Action | Params | Description |
|--------|--------|-------------|
| keep_lines | pattern (regex) | Keep only lines matching the pattern |
| remove_lines | pattern (regex) | Remove lines matching the pattern |
| head | n (int, default 10), overflow_msg (string, default "+{remaining} more lines") | Keep first N lines |
| tail | n (int, default 10) | Keep last N lines |
| dedup | normalize ([]string of regexes to strip before comparing), top (int, 0=all) | Deduplicate lines, output "text (xN)" for repeats |
| Action | Params | Description |
|--------|--------|-------------|
| truncate_lines | max (int, default 80), ellipsis (string, default "...") | Truncate long lines |
| strip_ansi | (none) | Remove ANSI escape codes |
| compact_path | (none) | Remove directory prefixes from file paths |
| Action | Params | Description |
|--------|--------|-------------|
| regex_extract | pattern (regex with capture groups), format (string using $0, $1, $2...) | Extract data via regex capture groups |
| group_by | pattern (regex with capture group), format (template, default "{{.Key}}: {{.Count}}"), top (int) | Group lines by capture group, count occurrences |
| aggregate | patterns (map of name->regex), format (Go template) | Count matches for named patterns across all input |
| state_machine | states (map of state definitions with keep, until, next) | Stateful line filtering with transitions |
| Action | Params | Description |
|--------|--------|-------------|
| json_extract | fields ([]string), format (template, optional) | Extract fields from JSON input |
| json_schema | max_depth (int, default 3) | Output JSON type schema |
| ndjson_stream | group_by (string field name), format (template with .Key, .Count, .Events) | Process newline-delimited JSON |
| Action | Params | Description |
|--------|--------|-------------|
| format_template | template (Go text/template, required) | Format output using Go template |
format_templateThe template receives:
{{.lines}} - all current lines joined with newlines{{.count}} - number of lines{{.groups}} - map from group_by action (if used earlier in pipeline){{.stats}} - map from aggregate action (if used earlier in pipeline)group_by sets metadata "groups" (map[string]int)aggregate sets metadata "stats" (map[string]int)format_template can access both via {{.groups}} and {{.stats}}keep_lines pattern "\\S" to strip blank lines early.inject to request machine-readable output (e.g., --json, --porcelain) then filter that structured data.exclude_flags to skip filtering when the user explicitly requests a different format.on_error: "passthrough" so raw output is returned if filtering fails.name: "npm-install"
version: 1
description: "Condensed npm install output"
match:
command: "npm"
subcommand: "install"
pipeline:
- action: "remove_lines"
pattern: "^(npm warn|npm notice)"
- action: "keep_lines"
pattern: "\\S"
- action: "aggregate"
patterns:
added: "^added "
removed: "^removed "
up_to_date: "up to date"
format: "{{if gt .up_to_date 0}}up to date{{else}}{{.added}} added, {{.removed}} removed{{end}}"
on_error: "passthrough"
name: "go-test"
version: 1
description: "Condensed go test output with pass/fail summary"
match:
command: "go"
subcommand: "test"
exclude_flags: ["-json", "-v", "-bench", "-run"]
inject:
args: ["-json"]
skip_if_present: ["-json", "-v", "-bench"]
pipeline:
- action: "keep_lines"
pattern: "\\S"
- action: "keep_lines"
pattern: "\"Test\":\""
- action: "keep_lines"
pattern: "\"Action\":\"(pass|fail)\""
- action: "aggregate"
patterns:
passed: '"Action":"pass"'
failed: '"Action":"fail"'
format: "{{if and (eq .passed 0) (eq .failed 0)}}No tests found{{else}}{{.passed}} passed, {{.failed}} failed{{end}}"
on_error: "passthrough"
name: "cargo-test"
version: 1
description: "Condensed cargo test output"
match:
command: "cargo"
subcommand: "test"
pipeline:
- action: "remove_lines"
pattern: "^\\s*(Compiling|Downloading|Downloaded|Updating|Running|Executable)"
- action: "keep_lines"
pattern: "\\S"
- action: "state_machine"
states:
start:
keep: "^(test |running |test result)"
until: "^failures"
next: "failures"
failures:
keep: "."
until: "^$"
next: "done"
- action: "aggregate"
patterns:
pass: "\\.\\.\\. ok$"
fail: "\\.\\.\\. FAILED$"
ignored: "\\.\\.\\. ignored$"
- action: "format_template"
template: "{{.lines}}"
on_error: "passthrough"
inject if so.~/.config/snip/filters/ and running the command through snip.filters/ in the repo and submit a PR.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 | Reduction |
|---------|-------:|------:|----------:|
| 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% |
Stop wasting tokens on noise. snip gives the LLM the same signal in a fraction of the context window.
brew install edouard-claude/tap/snip
Download 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@latest
Or build locally:
git clone https://github.com/edouard-claude/snip.git
cd snip && make install
Requires Go 1.25+.
snip integrates with every major AI coding assistant. One binary, universal compatibility.
| Tool | Install | Method |
|------|---------|--------|
| Claude Code | snip init | PreToolUse hook (native) |
| Cursor | snip init --agent cursor | beforeShellExecution hook (native) |
| GitHub Copilot | snip init --agent copilot | .github/copilot-instructions.md |
| Gemini CLI | snip init --agent gemini | GEMINI.md prompt injection |
| Codex (OpenAI) | snip init --agent codex | AGENTS.md prompt injection |
| Pi (pi.dev) | snip init --agent pi | PreToolUse hook (via pi-hooks) |
| Windsurf | snip init --agent windsurf | .windsurfrules prompt injection |
| Cline / Roo Code | snip init --agent cline | .clinerules prompt injection |
| Kilo Code | snip init --agent kilocode | .kilocode/rules/ prompt injection |
| Antigravity | snip init --agent antigravity | .agents/rules/ prompt injection |
| OpenCode | opencode-snip plugin | tool.execute.before hook |
| OpenClaw | openclaw plugins install openclaw-snip | plugin |
| Aider | shell aliases | prefix commands with snip |
snip init
This 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: 127 filters covering git, go, cargo, npm, yarn, pnpm, docker, kubectl, terraform, aws, gh, and 80+ more tools.
snip init --uninstall # remove the hook
snip init --agent cursor
This patches ~/.cursor/hooks.json with a beforeShellExecution hook. Works the same way as Claude Code.
snip init --agent cursor --uninstall # remove the hook
snip init --agent pi
This patches ~/.pi/agent/settings.json with a PreToolUse entry matching the bash tool. The runtime hook is interpreted by the community extension @hsingjui/pi-hooks, which mirrors Claude Code's hookSpecificOutput format (including command rewriting via updatedInput). Install it once:
pi install npm:@hsingjui/pi-hooks
Then run /reload (or restart Pi). Once active, snip rewrites supported commands transparently.
snip init --agent pi --uninstall # remove the hook
snip init --agent copilot # creates .github/copilot-instructions.md
snip init --agent gemini # creates GEMINI.md
snip init --agent codex # creates AGENTS.md
snip init --agent windsurf # creates .windsurfrules
snip init --agent cline # creates .clinerules
snip init --agent kilocode # creates .kilocode/rules/snip-rules.md
snip init --agent antigravity # creates .agents/rules/snip-rules.md
These agents use prompt injection: a markdown file instructs the LLM to prefix shell commands with snip. Project-scoped (created in the current directory).
Install the opencode-snip plugin by adding it to your OpenCode config (~/.config/opencode/opencode.json):
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-snip@latest"]
}
The plugin uses the tool.execute.before hook to automatically prefix all commands with snip. Commands not supported by snip pass through unchanged.
openclaw plugins install openclaw-snip
Use shell aliases to route commands through snip:
# 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 report
snip <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 --no-truncate # disable command truncation
snip gain --json # machine-readable output
snip gain --csv # CSV export
snip discover # find