Skip to content

Latest commit

 

History

19 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

opencode-smartsnip

Cuts shell-output tokens in opencode.

Routes supported shell commands through snip. Uncertain commands run unchanged.

npm version npm downloads GitHub stars License: MIT

Install · Numbers · How routing works · Configuration

smartsnip inserts snip only on the command segments it can safely filter, leaving everything else byte-identical

 

- git status && cat data.json | jq '.users | length'
+ snip git status && cat data.json | jq '.users | length'

Only git status gets the snip prefix. The jq pipe is left exactly as the agent wrote it. smartsnip works out which parts of a command are safe to wrap, wraps just those, and does nothing when it isn't sure.

Install

brew install edouard-claude/tap/snip
// ~/.config/opencode/opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opencode-smartsnip"]
}

That's it. No new tools, no prompt overhead. If snip isn't on PATH the plugin disables itself with a warning, so it's safe in a shared repo config.

Install from source instead
git clone https://github.com/carson2222/opencode-smartsnip.git
mkdir -p ~/.config/opencode/plugins
printf 'export { default } from "%s/src/index"\n' "$PWD/opencode-smartsnip" \
  > ~/.config/opencode/plugins/smartsnip.ts

Numbers

One audited day of real work on a real project: ~600 bash calls, every credited output replayed through the actual snip filters — no command re-runs.

git output −70%
pnpm output −69%
all bash output −11% ¹
context traffic avoided ~1M tokens

¹ Conservative floor: only single-segment commands are credited; the 158 chained commands that day counted as zero savings. Real reduction is higher.

Tool output gets re-sent on every turn that follows it, so a token saved at the source stays saved for the rest of the session — and through compaction. That multiplier is why an 11% cut on raw output erased ~1M tokens of cumulative context traffic in a single day. It was a browser-automation-heavy day; run the same conservative replay on your own history:

bun scripts/measure-savings.ts --days 7        # measured, replayed through snip
bunx opencode-smartsnip discover --days 30     # estimated, plus what to filter next

How routing works

flowchart LR
    A([bash command]) --> B{heredoc, loop,<br>unbalanced quotes?}
    B -- yes --> P([run as-is])
    B -- no --> C[split into<br>top-level segments]
    C --> D{builtin, piped,<br>redirected, subshell,<br>denied?}
    D -- yes --> P
    D -- no --> E{in snip's<br>filter table?}
    E -- no --> P
    E -- yes --> W([prefix with snip])
Loading

The allowlist is generated from snip's own filters, pinned to the v0.25.2 release (132 files, 173 command/subcommand rules), plus anything you drop in ~/.config/snip/filters/. exclude_flags and require_flags are matched the way snip matches them — against every argument, flag or not — so git log --format=..., git diff -p and gh pr diff all stay raw, and npm install wraps while npm view does not. #nosnip anywhere in a command skips the whole thing.

Filters are read once at startup. A new file in ~/.config/snip/filters/ needs an opencode restart before smartsnip routes to it. smartsnip doctor reports your installed snip version and warns when it differs from the release the table was generated from.

A wrong passthrough costs a few tokens. A wrong wrap breaks a command. The bias follows.

Configuration

Optional. ~/.config/opencode/smartsnip.json, overridable per project in .opencode/smartsnip.json:

{
  "deny": ["pnpm", "git diff"],
  "allow": ["curl", "mytool"],
  "toast": true,
  "stripMimicry": true
}
  • deny — never wrap these ("cmd" or "cmd subcommand")
  • allow — force wrap-eligibility, wins over deny
  • toast — once per session, a small TUI toast with tokens saved
  • stripMimicry — strip stray snip prefixes the agent picked up from history before re-deciding (default on). Turn off only if you wrap commands via snip filter dirs that smartsnip doesn't scan

ssh, curl, wget, psql, jq are denied by default. snip has filters for them, but they're blunt truncations and agents usually need that output verbatim. A truncated API response forces a re-run, which costs more than it saves. "allow": ["curl"] brings any of them back.

Getting raw output back

snip can tee the original to a local file and append [full output: /path.log] to the filtered result, which the agent can then Read instead of re-running the command. In ~/.config/snip/config.toml:

[tee]
mode = "always"

tee recovers most output, not all of it. On snip 0.25.2 the defaults are:

  • outputs under 500 bytes are not saved at all
  • files are capped at 1 MiB; anything past that is cut
  • only the 20 most recent files are kept, so a busy session drops its own older ones

So treat tee as a good chance of recovery, not a guarantee. When the full output actually matters, #nosnip is the reliable answer.

smartsnip doctor checks the tee mode, along with the rest of your setup.

One line in AGENTS.md makes agents use it well:

Shell output is auto-compressed. Append `#nosnip` to a command if you need raw output.
If you see `[full output: <path>]`, Read that file instead of re-running.

Finding what to filter next

bunx opencode-smartsnip discover --days 30

Replays your real opencode bash history (read-only, local) through the router:

2106 commands, ~893.5k est. tokens of stored output.

RAN UNDER SNIP (stored output is post-filter):
  git        418 calls   182.4k est. tokens

WRAP-ELIGIBLE, RAN RAW (routing would wrap these today):
  pnpm       184 calls   164.7k est. tokens

NO FILTER IN SNIP (largest stored output first):
  python3     49 calls    73.2k est. tokens

Every number is what opencode stored, so entries that already ran under snip are counted after filtering. Nothing here is a measured saving, and a large number is a place to look, not a filter worth writing. Pipelines, #nosnip calls and unparseable commands are reported on their own lines because their stored output says nothing about the command's own filter.

For an actual measurement, bun scripts/measure-savings.ts replays stored output through the real snip filters. bunx opencode-smartsnip install-command adds a /snip-filter command that writes and tests a new filter for you.

Why not wrap everything?

That's what opencode-snip does, and it's where this project started. The failure modes are all known issues there:

wrap everything smartsnip
snip: no filter for "X" noise #16 never from routing ¹
snip snip stacking #15 normalized away per segment
jq '.a | .b' quoted pipes #8 quote-aware parser
VAR=$(cmd) corruption #22 detected, passthrough
heredocs #6 detected, passthrough
permission rules see rewritten commands #7 only filterable commands change

The router is validated against a fixed corpus of 23k+ real bash commands from actual opencode sessions. Its wrap decisions were also checked against snip check on the pinned release; runtime verification remains the authority for commands outside that corpus.

¹ One feedback loop is unavoidable at this layer: opencode stores the rewritten command, so agents start typing snip themselves — sometimes on things snip can't filter (snip sed, … | snip python3). smartsnip strips those stray prefixes back off before running (stripMimicry, on by default), which also collapses any stacking. For the rare command too complex to parse, set quiet_no_filter = true under [display] in ~/.config/snip/config.toml as a backstop — smartsnip doctor checks for it.

Development

bun install
bun test                  # includes a replay of 656 sanitized real-world commands
bun run typecheck
bun run generate:filters  # re-sync allowlist from the pinned upstream snip release
bun run measure --days 7  # replay your real bash history through snip (the Numbers)

License

MIT

About

OpenCode plugin that trims noisy shell output before it reaches the model.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Contributors

Languages