Skip to content

edouard-claude/snip

Repository files navigation

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

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

Stars

Watchers

Forks

Packages

 
 
 

Contributors