Sandbox
@edouard-claude/snip

CLI proxy for filtered shell output in coding agents

snip sits between your agent and the shell, runs the command, and rewrites the output through YAML filter pipelines. The result is shorter command output with the same useful signal, so the agent spends fewer tokens on noise.

440 stars43 forksGoUpdated 10d ago
Who it's for

Builders who want their coding agent to see concise shell output instead of raw command logs.

What it delivers

You can keep agent context smaller while still giving it the useful result of each command.

What it does

Declarative YAML filters

Defines command matching, injected args, and output pipelines in YAML files under `filters/` and user config directories.

Agent hooks and prompt files

Installs native hooks or prompt-injection files for Claude Code, Cursor, Codex, Copilot, Gemini CLI, Windsurf, Cline, Kilo Code, Grok, and Pi.

Token savings reporting

Shows savings with `snip gain`, including totals, trends, top commands, and quota views.

Discovery and learning

Finds missed savings with `snip discover` and detects CLI correction patterns with `snip learn`.

Trust and verification

Lets you trust project filters with `snip trust`, inspect matches with `snip check`, and verify filters with `snip verify`.

Config and bypass controls

Supports user, project, and plugin config files, plus bypass lists and global output caps.

How to get it

  1. 1Run
    brew install edouard-claude/tap/snip
  2. 2Download 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/
  3. 3Run
    go install github.com/edouard-claude/snip/cmd/snip@latest
  4. 4Or build locally
    git clone https://github.com/edouard-claude/snip.git
    cd snip && make install

README

Release CI License Go

snip - Reduce LLM Token Usage by 60-90%

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, Pi, Grok Build, Kilo Code, Antigravity, OpenCode, OpenClaw, 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 Start

# 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.

How It Works

Before — Claude Code sees this (275 tokens):

$ go test ./...
ok  	github.com/edouard-claude/snip	13.697s
?   	github.com/edouard-claude/snip/cmd/snip	[no test files]
ok  	github.com/edouard-claude/snip/internal/cli	1.357s
ok  	github.com/edouard-claude/snip/internal/config	1.898s
ok  	github.com/edouard-claude/snip/internal/discover	4.271s
ok  	github.com/edouard-claude/snip/internal/display	2.516s
ok  	github.com/edouard-claude/snip/internal/economics	3.073s
ok  	github.com/edouard-claude/snip/internal/engine	5.262s
ok  	github.com/edouard-claude/snip/internal/filter	3.687s
ok  	github.com/edouard-claude/snip/internal/hook	4.555s
ok  	github.com/edouard-claude/snip/internal/hookaudit	4.560s
ok  	github.com/edouard-claude/snip/internal/initcmd	4.534s
ok  	github.com/edouard-claude/snip/internal/inspect	4.598s
ok  	github.com/edouard-claude/snip/internal/learn	4.474s
ok  	github.com/edouard-claude/snip/internal/tee	4.430s
ok  	github.com/edouard-claude/snip/internal/tracking	4.055s
ok  	github.com/edouard-claude/snip/internal/trust	4.544s
ok  	github.com/edouard-claude/snip/internal/utils	4.528s
ok  	github.com/edouard-claude/snip/internal/verify	4.517s

After — snip returns this (8 tokens):

1125 passed, 0 failed

That's 97% fewer tokens, measured on this very repository. The filter injects -json and counts individual test results, so the LLM gets more signal — 1125 tests passed, not just 18 packages — in a fraction of the space.

┌─────────────┐     ┌─────────────────┐     ┌──────────────┐     ┌────────────┐
│ 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.

Token Savings by Command

CommandBeforeAfterReduction
cargo test591 tokens5 tokens99.2%
go test ./...275 tokens8 tokens97.1%
git log371 tokens53 tokens85.7%
git status112 tokens16 tokens85.7%
git diff355 tokens66 tokens81.4%

Stop wasting tokens on noise. snip gives the LLM the same signal in a fraction of the context window.

Installation

Homebrew (recommended)

brew install edouard-claude/tap/snip

From GitHub Releases

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/

From source

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

make install and make install-lite use the first available destination: an explicit GOBIN, go env GOBIN, or the first go env GOPATH entry plus /bin. Use make upgrade or make upgrade-lite to replace the resolved snip on PATH instead. An explicit GOBIN overrides the upgrade destination.

Requires Go 1.25+.

Supported AI Tools

snip integrates with every major AI coding assistant. One binary, universal compatibility.

ToolInstallMethod
Claude Codesnip initPreToolUse hook (native)
Cursorsnip init --agent cursorbeforeShellExecution hook (native)
GitHub Copilotsnip init --agent copilotpreToolUse hook (native)
Gemini CLIsnip init --agent geminiGEMINI.md prompt injection
Codex (OpenAI)snip init --agent codexPreToolUse hook (native)
Pi (pi.dev)snip init --agent piPreToolUse hook (via pi-hooks)
Grok Build (xAI)snip init --agent grokPreToolUse hook (deny + re-run suggestion)
Windsurfsnip init --agent windsurf.windsurfrules prompt injection
Cline / Roo Codesnip init --agent cline.clinerules prompt injection
Kilo Codesnip init --agent kilocode.kilocode/rules/ prompt injection
Antigravitysnip init --agent antigravityPreToolUse hook (native)
OpenCodeopencode-snip plugintool.execute.before hook
OpenClawopenclaw plugins install openclaw-snipplugin
Aidershell aliasesprefix commands with snip

Claude Code

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: 132 filters covering 100 distinct commands: git, go, cargo, npm, yarn, pnpm, docker, kubectl, terraform, aws, gh, dotnet, and many more.

snip init --uninstall   # remove the hook

Cursor

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

Pi (pi.dev)

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

Grok Build (xAI)

snip init --agent grok

This writes ~/.grok/hooks/snip.json with a PreToolUse hook matching the shell tool. Grok Build hooks cannot rewrite commands in place (the hook contract is allow/deny only), so snip denies matched commands with a re-run suggestion ("…/snip" run -- <command>). Non-matching commands pass through untouched, and the hook is fail-open: if snip breaks, commands simply run unfiltered.

Prefer prompt injection instead? Grok Build reads AGENTS.md natively:

snip init --agent grok --mode prompt   # creates AGENTS.md
snip init --agent grok --uninstall     # remove the hook

AGENTS.md is shared with Codex, so --uninstall never deletes it; it prints a reminder instead.

Codex

snip init --agent codex

This patches ~/.codex/hooks.json with a native PreToolUse hook. Supported shell commands are transparently rewritten through snip with Codex's updatedInput contract, so the model runs commands normally and sees only the filtered output.

For safety, mixed commands containing unsupported segments, pipelines with uninspected tails, and command substitutions pass through unchanged so Codex keeps its native permission flow.

Use snip proxy -- <command> when full unfiltered output is required.

snip init --agent codex --uninstall

Codex CLI 0.131.0 or later is required for PreToolUse input rewriting. For older releases, use the legacy project-scoped prompt integration:

snip init --agent codex --mode prompt

GitHub Copilot

snip init --agent copilot

This patches ~/.copilot/hooks/snip.json with a native preToolUse hook that rewrites supported commands transparently, like Claude Code.

Prefer prompt injection instead? Use the legacy project-scoped mode:

snip init --agent copilot --mode prompt   # creates .github/copilot-instructions.md
snip init --agent copilot --uninstall     # remove the hook

Antigravity

PreToolUse is supported. This patches ~/.gemini/config/hooks.json.

snip init --agent antigravity

Gemini / Windsurf / Cline / Kilo Code

snip init --agent gemini       # creates GEMINI.md
snip init --agent windsurf     # creates .windsurfrules
snip init --agent cline        # creates .clinerules
snip init --agent kilocode     # creates .kilocode/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).

OpenCode

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

openclaw plugins install openclaw-snip

Aider

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.

Standalone

snip works without any AI tool:

snip git log -10
snip go test ./...
snip gain             # token savings report

Usage

snip <command> [args]       # filter a command (implicit)
snip run -- <command>       # same, with explicit separator
snip check -- <command>     # check if a command would be filtered
snip proxy <command>        # force passthrough (no filtering)
snip proxy -- <command>     # same, with explicit separator
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 --quota           # savings against plan quotas
snip gain --unfiltered      # opt-in report on unfiltered commands
snip gain --no-truncate     # disable command truncation
snip gain --json            # machine-readable output
snip gain --csv             # CSV export
snip cc-economics           # financial impact by pricing tier (configurable)
snip discover               # find missed savings in Claude Code history
snip discover --since 30    # scan last 30 days
snip discover --all         # scan all projects
snip learn                  # detect CLI error-correction patterns in sessions
snip learn --since 7        # scan last 7 days (default 30)
snip learn --all            # scan all projects
snip learn --generate       # write .claude/rules/cli-corrections.md
snip verify                 # run the filters' inline tests
snip config                 # show config
snip trust [path]           # trust project-local filter file(s) by SHA-256
snip untrust [path]         # remove file(s) from the trust store
snip hook                   # agent PreToolUse handler (used by the hooks)
snip hook-audit             # show recent hook activity (SNIP_HOOK_AUDIT=1)
snip init                       # install Claude Code hook
snip init --agent cursor        # install Cursor hook
snip init --agent pi            # install Pi (pi.dev) hook
snip init --agent copilot       # install Copilot hook
snip init --agent gemini        # install Gemini CLI integration
snip init --agent kilocode      # install Kilo Code integration
snip init --agent antigravity   # install Antigravity integration
snip init --uninstall           # remove hook

Global flags: -v/-vv (verbose, stackable), -u (ultra-compact), --skip-env, --version, --help.

Filters

Filters 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"

match.subcommand can be a scalar string (as above) or a list of exact subcommands:

match:
  command: "npm"
  subcommand: ["install", "add", "i"]

If subcommand is omitted, the filter matches every subcommand for that command. To match only a bare command invocation, include an explicit empty string, for example subcommand: ["", "install"] to match yarn and yarn install without matching yarn why.

132 Built-in Filters

snip ships with 132 declarative YAML filters covering all major developer tools:

CategoryFilters
Git (12)status, log, diff, show, add, commit, push, pull, branch, fetch, stash, worktree
GitHub CLI (3)gh pr, gh issue, gh run
Go (4)go test, go build, go vet, golangci-lint
Rust (7)cargo test/build/check/clippy/install/nextest, rustc
Python (11)pytest, ruff, mypy, basedpyright, ty, pip, poetry, uv add/lock/remove/sync
JavaScript/TypeScript (17)jest, vitest, eslint, tsc, biome, oxlint, prettier, next, playwright, nx, turbo, npm, npx, yarn, pnpm install/list, prisma
Ruby (6)rspec, rubocop, rake, bundle, rails migrate, rails routes
.NET (3)dotnet build/test/format
Elixir (2)mix compile, mix format
Docker/K8s (7)docker build/ps/images/logs/compose, kubectl get/logs
Cloud/Infra (6)terraform, tofu, helm, ansible-playbook, gcloud, aws
Build tools (14)make, gcc, g++, gradle, gradlew, gradlew.bat, mvn, swift, xcodebuild, just, task, pio, trunk, mise
Files/Search (7)ls, find, grep, rg, diff, wc, tree
Linting (6)shellcheck, hadolint, markdownlint, markdownlint-cli2, yamllint, pre-commit
Package managers (2)brew, composer
System/Network (14)curl, wget, psql, jq, ping, ssh, rsync, df, du, ps, systemctl, iptables, stat, fail2ban
Other (11)jira, jj, yadm, gt, ollama, sops, skopeo, shopify, quarto, liquibase, spring-boot

Run snip discover to see which of your commands already have filters.

20 Pipeline Actions

ActionDescription
keep_linesKeep lines matching regex
remove_linesRemove lines matching regex
truncate_linesTruncate lines to max length
truncate_bytesHard cap on output size in bytes
strip_ansiRemove ANSI escape codes
head / tailKeep first/last N lines
group_byGroup lines by regex capture
dedupDeduplicate with optional normalization
json_extractExtract fields from JSON
json_schemaInfer schema from JSON
ndjson_streamProcess newline-delimited JSON
regex_extractExtract regex captures
state_machineMulti-state line processing
aggregateCount pattern matches
format_templateGo template formatting
compact_pathShorten file paths (see caveat below)
replaceRegex find and replace
match_outputConditional short-circuit (return message if pattern matches)
on_emptyReturn message if output is empty

compact_path emits paths that may not resolve. It strips a leading src/, lib/, internal/, pkg/ or vendor/ segment unconditionally and with no marker, so internal/soak/report.go becomes soak/report.go — which ENOENTs from the directory the command ran in. No bundled filter uses it. Reach for it only when the path is display-only and will never be opened.

Custom Filters

snip init                                    # creates ~/.config/snip/filters/
vim ~/.config/snip/filters/my-tool.yaml      # add your filter

User filters take priority over built-in ones. Later directories in the list override earlier ones.

Filters under ~/.config/snip/ are always loaded. Filters anywhere else (for example a project's .snip/ directory) must be approved once with snip trust, otherwise they are skipped:

snip trust .snip/filters     # approve the project's filters (SHA-256 pinned)

Editing a trusted file invalidates its hash; run snip trust again after changes.

Configuration

Optional TOML config at ~/.config/snip/config.toml (override the path with SNIP_CONFIG):

# mode = "user"          # "project" in a .snip/config.toml lets that file
                         # override user settings (see Project Configuration)

[tracking]
db_path = "~/.local/share/snip/tracking.db"
track_unfiltered = false # opt-in: also record commands that had no filter

[display]
color = true
emoji = true
quiet_no_filter = false  # suppress "no filter" stderr messages
summary = false          # prepend a "[snip: ...]" line showing which filter/args were applied

[filters]
dir = "~/.config/snip/filters"

[filters.enable]
# git-diff = false       # disable a specific built-in filter

[filters.global]         # safety caps appended to every filter's pipeline (0 = unlimited)
# max_lines = 0          # cap the number of output lines
# max_line_length = 0    # cap each line's length
# max_output_bytes = 0   # hard cap on the bytes any filter emits.
                         # Applied last, cutting on a UTF-8 rune boundary and
                         # appending a "... truncated at N bytes" marker that is
                         # counted inside the cap.

[filters.override.dotnet-test]  # tune a single filter without rewriting it
# head = 200             # raise dotnet-test's cap from 40 to 200 lines
# stream_mode = "full"   # or skip this filter's pipeline entirely
# Other overridable keys: tail, truncate_lines, keep_lines, remove_lines

[filters.bypass]
# commands = ["dotnet publish"]  # always run these unfiltered

[economics.tiers]        # pricing for `snip cc-economics`, $ per 1M input tokens
# haiku = 1.00           # free-form names; defaults are current Anthropic list
# negotiated_opus = 3.10 # prices (haiku 1, sonnet 3, opus 5, fable 10) until set

[tee]
enabled = true
mode = "failures"    # "failures" | "always" | "never"
max_files = 20
max_file_size = 1048576
# project_marker = ".git"  # write tee files to <repo-root>/.snip/tee/ instead
                           # of the global dir when the marker is found by
                           # walking up from the working directory; a
                           # .gitignore is created there so logs are never
                           # committed, and snip falls back to the global dir
                           # if the repo root is not writable

Override values must be positive: head = 0 does not remove a filter's truncation, it is ignored. To get unlimited output from one filter, use stream_mode = "full" in its override block.

Full reference for every key, default and merge rule: Configuration wiki page.

Verify Your Configuration

Every setting above is reproducible. To prove one is active without touching your real config, point SNIP_CONFIG at a scratch file:

cd "$(mktemp -d)" && touch a.go b.md c.txt
export SNIP_CONFIG=$PWD/test.toml

printf '[filters.override.ls]\nhead = 2\n' > test.toml
snip ls    # 2 entries + "... +more entries (truncated by snip)"

printf '[filters.bypass]\ncommands = ["ls"]\n' > test.toml
snip ls    # raw ls output, no filtering

unset SNIP_CONFIG

snip config prints the user config snip loads, snip check -- <command> names the filter that would handle a command, and snip -v <command> shows the filter and injected args as it runs.

Project Configuration

A repo can ship its own .snip/config.toml; snip walks up from the working directory and uses the first one it finds. The file must be trusted once (snip trust .snip/config.toml) and declare mode = "project" to override the user config. Project keys that participate in the merge: filters.enable, filters.global, filters.override (project wins), and filters.bypass.commands (concatenated, regardless of mode). Typical corporate setup: defaults for every developer live in the repo, personal tweaks stay in ~/.config/snip/config.toml.

Plugin Configuration

An agent plugin (for example a Claude Code plugin) can ship a snip config that applies on every machine as the base of the cascade plugin < user < project. The plugin's hook exports the path:

SNIP_PLUGIN_CONFIG=${CLAUDE_PLUGIN_ROOT}/snip/config.toml

The file is a regular snip TOML restricted to the filter sections (filters.enable, filters.global, filters.override, filters.bypass, transparent_prefixes, and filters.dir so the plugin can ship its own filter YAMLs; plugin filters load first, so user filters win by name). A relative filters.dir resolves against the TOML's own directory, so dir = "filters" just works. The variab

Files in the repo

Repository payload27 top-level entries
  • .github
  • cmd
  • filters
  • internal
  • scripts
  • tests
  • .gitignore
  • .golangci-lint-version
  • .golangci.yml
  • .goreleaser.yaml
  • CLAUDE.md
  • CODE_OF_CONDUCT.md
  • CONTRIBUTING.md
  • doc.go
  • embed.go
  • go.mod
  • go.sum
  • install.sh
  • LICENSE
  • make_lint_test.go
  • Makefile
  • README.md
  • run_test.go
  • run.go
  • SECURITY.md
  • SKILL.md
  • source_install_test.go

Discussion (0)

Ask about usage, or say what you built with it

Sign in to join the discussion.

No comments yet. Be the first to say what this is good for.

More tools

JuliusBrussee/
caveman

🪨 why use many token when few token do trick — Claude Code skill that cuts 65% of tokens by talking like caveman

105k
1 add
MemPalace/
mempalace

The best-benchmarked open-source AI memory system. And it's free.

59k
stablyai/
orca

Orca is the ADE for working with a fleet of parallel agents. Run any coding agent with your own subscription. Available on desktop, mobile and remote runtime.

66k

A cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build & Hermes Agent. Only official website: ccswitch.io

132k

Never stop coding. Free MIT AI gateway: one endpoint, 352 providers (150+ free), 1200+ models Kimi, Claude, GPT, Gemini, GLM, DeepSeek, MiniMax. Works with Claude Code, Codex, Cursor, OpenCode, Cline & Copilot. Quota-aware auto-fallback, RTK+Caveman compression saves 15-95% tokens, MCP/A2A, Desktop/PWA. Built by 550+ contributors

64k
headroomlabs-ai/
headroom

Compress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.

71k