Skip to content

edouard-claude/snip

Release CI License Go

snip

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.

Quick Start

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

How It Works

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.

Savings by Command

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%

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

Requires Go 1.24+ and jq (for the hook script).

Integration

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: git, go, cargo, npm, npx, yarn, pnpm, docker, kubectl, make, pip, pytest, jest, tsc, eslint, rustc.

snip init --uninstall   # remove the hook

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.

Cursor

Cursor supports hooks since v1.7 via ~/.cursor/hooks.json:

{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      { "command": "~/.claude/hooks/snip-rewrite.sh" }
    ]
  }
}

Aider / Windsurf / Other Tools

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.

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
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 hook

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"

Built-in Filters

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

16 Pipeline Actions

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

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.

Configuration

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

Design

  • 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 compilationsync.Once per 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

Why Go over Rust?

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

Development

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/bin

Documentation

Full documentation is available on the Wiki:

Credits

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.

License

MIT

About

CLI proxy that reduces LLM token consumption by 60-90% on common dev commands. Single Go binary

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors