{
  "markdown": "# shunt-anywhere\n\n> **This is a port of Spotify's [`shunt`](https://github.com/spotify/portal-ai-plugins/tree/main/plugins/shunt) plugin.**\n> The idea, the hook design, the two delegation scripts and the worker instructions are\n> Spotify's, from [portal-ai-plugins](https://github.com/spotify/portal-ai-plugins) and the\n> engineering post [Portal by Spotify cut my Claude Code token usage by 90%](https://engineering.atspotify.com/2026/9/portal-by-spotify-cut-my-claude-code-token-usage-by-90).\n> Their worker runs on AiKA in **Portal by Spotify**, a paid enterprise product, so you need a\n> Portal tenant to use it.\n>\n> What this fork changes: the worker is a CLI you already sign into, and the gate works in\n> Claude Code, Codex CLI and Gemini CLI instead of only Claude Code. Most files here are\n> still theirs — [NOTICE](NOTICE) lists the provenance file by file. Apache-2.0, same as upstream.\n\nYour coding agent spends most of its context on I/O, not thinking. Reading one\n1,400-line service file costs ~15,000 tokens of context that you then pay for on\nevery subsequent turn of the conversation.\n\nshunt-anywhere intercepts those reads. A hook refuses any whole-file read over\n350 lines and hands the agent a script instead. The script ships the files to a\ncheap worker model in a separate process and returns a few bullets. The file\nnever enters your agent's context.\n\nMeasured on this repo's own benchmark suite, with Claude Haiku 4.5 as the worker:\n\n| Scenario | Input | Without shunt | With shunt | Saved |\n|---|---|---|---|---|\n| one 602-line file | 602 lines | 12,006 tk | 217 tk | **98%** |\n| three files, cross-read | 692 lines | 12,686 tk | 304 tk | **97%** |\n| source + test pair | 90 lines | 680 tk | 225 tk | 66% |\n| generate a test file | codegen | 2,655 tk | 0 tk | **100%** |\n| **total** | | **28,027 tk** | **746 tk** | **97%** |\n\nReproduce it yourself with `bash plugins/shunt/evals/run.sh --benchmark`.\n\n## It runs on whatever you already have\n\nThe worker is one headless turn on a CLI you are already signed in to. No API\nkey, no SaaS account, no second bill.\n\n| Worker | Command it runs | Default model |\n|---|---|---|\n| `claude` | `claude -p` | `claude-haiku-4-5-20251001` |\n| `gemini` | `gemini -p` | `gemini-2.5-flash` |\n| `codex` | `codex exec` | your account default, at low reasoning effort |\n\nPick one with `SHUNT_WORKER`. Left unset, shunt prefers the host it is running\ninside, then the first of `claude`, `gemini`, `codex` on your `PATH`.\n\nThe gate itself works in all three hosts too, because a hook that exits 2 with\nits reason on stderr is a refusal Claude Code, Codex CLI and Gemini CLI all\nhonour.\n\n## Install\n\nNeeds [`jq`](https://jqlang.org) (`apt install jq` / `brew install jq`).\n\n**Claude Code**\n\n```bash\nclaude plugin marketplace add SalehB1/shunt-anywhere\nclaude plugin install shunt@shunt-anywhere\n```\n\n**Codex CLI** — in a session:\n\n```\n/plugin marketplace add SalehB1/shunt-anywhere\n/plugin install shunt@shunt-anywhere\n```\n\n**Gemini CLI**\n\n```bash\ngemini extensions install https://github.com/SalehB1/shunt-anywhere\n```\n\nThen, in a new session, ask it to read a file over 350 lines. You should see the\nrefusal, and then a `bulk-read` call.\n\n## The two scripts\n\n`bulk-read` — answer a question about files without reading them into context.\nEach file is wrapped in `<file path=\"...\">` so the worker sees clear boundaries.\n\n```bash\nbulk-read --question \"Which methods touch the database?\" --paths src/service.py src/repo.py\n```\n\n`code-write` — generate boilerplate that is mostly predictable from an existing\nfile. `--reference` is required: without a file to match, a worker writes\nplausible code that fits nothing in your project.\n\n```bash\ncode-write --spec \"tests for UserService.deactivate\" --reference tests/order.test.ts --target tests/user.test.ts\n```\n\nEvery call is one shot. Nothing is stored, nothing is replayed. To ask a\nfollow-up, ask again with the same `--paths` — the corpus goes to the worker, not\nto you, so re-sending it is free where it matters.\n\n## Configuration\n\nEnvironment variables, all optional:\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `SHUNT_WORKER` | auto-detect | `claude`, `gemini` or `codex` |\n| `SHUNT_WORKER_MODEL` | per worker, above | override the worker model |\n| `SHUNT_MIN_LINES` | `350` | line count above which a read is refused |\n| `SHUNT_TIMEOUT_SECONDS` | `180` | ceiling for one delegation |\n| `SHUNT_MAX_PAYLOAD_BYTES` | `600000` | refuse a corpus bigger than this (~150k tokens) |\n\nWhere to put them:\n\n```jsonc\n// Claude Code — ~/.claude/settings.json\n{ \"env\": { \"SHUNT_WORKER\": \"claude\", \"SHUNT_MIN_LINES\": \"500\" } }\n```\n\n```toml\n# Codex CLI — ~/.codex/config.toml\n[shell_environment_policy.set]\nSHUNT_WORKER = \"codex\"\nSHUNT_MIN_LINES = \"500\"\n```\n\n```bash\n# Gemini CLI — ~/.gemini/.env\nSHUNT_WORKER=gemini\nSHUNT_MIN_LINES=500\n```\n\n## What it does not delegate\n\nThe point is to spend your expensive context on thinking, so judgment stays\nhome:\n\n- **Editing.** A worker's summary has no reliable line numbers. Read the section\n  you are about to change with an offset and limit.\n- **Debugging.** A summary finds surface patterns. It will miss the subtle race\n  you are hunting.\n- **Small files.** Under the threshold, a 10-30 second subprocess costs more\n  than it saves. That is the 66% row in the table above.\n- **Architecture.** Not a summarization problem.\n\nTargeted reads pass the gate untouched: an offset/limit read, a `sed -n\n'120,180p'`, a pipe into `grep`, a redirect.\n\n## Tests\n\n```bash\nbash plugins/shunt/evals/run.sh              # 88 cases, offline, no worker needed\nbash plugins/shunt/evals/run.sh --benchmark  # also re-measures savings (needs a worker)\n```\n\nThe offline suite covers every hook routing decision for all three hosts' input\nshapes, and launches each worker against a stub CLI on `PATH` to check the exact\nargv — so a flag typo fails here rather than in production.\n\n## Known limits\n\n- **Codex has no read tool.** It reads files through the shell, so only the\n  shell gate applies there. That gate catches `cat`, `head`, `tail`, `less` and\n  `more`; everything else is treated as targeted.\n- **A ChatGPT-account Codex refuses `-m`.** The worker runs your account's\n  default model at low reasoning effort rather than a cheaper one.\n- **No gate on code-write.** Only reads are enforced by a hook. Generation\n  relies on the agent noticing the skill.\n- **Latency.** A delegation is a 10-30 second subprocess. That is the trade: wall\n  clock for context.\n\n## Credits\n\nSpotify, for the original. `shunt` is theirs: the hook-blocks-a-big-read idea, `bulk-read`\nand `code-write`, the worker instructions, the eval harness and its fixtures. See\n[portal-ai-plugins](https://github.com/spotify/portal-ai-plugins) and [their write-up](https://engineering.atspotify.com/2026/9/portal-by-spotify-cut-my-claude-code-token-usage-by-90).\n\nThis fork exists for one reason: upstream needs Portal by Spotify, and most people do not\nhave it. The transport was replaced with `claude -p` / `gemini -p` / `codex exec`, and the\nrefusal was changed to exit 2 + stderr so Codex and Gemini CLI honour it too. [NOTICE](NOTICE)\nrecords exactly which files are derived and what changed in each.\n\nApache-2.0, same as upstream.\n",
  "bytes": 7231,
  "sha": "aef60541a31552d1f8efa1199807ec247a42044f7b4aa666aa68e3745bacc383",
  "repo_slug": "salehb1/shunt-anywhere",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_salehb1_shunt_anywhere_20523a47/readme"
}