Token-Goat

Give the model what it needs, not everything you have.
85% smaller reads · 97.4% image compression · 180+ filter & interception rules · 94–99% skill overhead cut · compaction memory · prompt injection guard · 3.7 GB never reached the model · 1.1 Gt tokens saved
Reduces AI token use/costs by 40–90%, and improves its focus. Fully automated, always online.
Also defends against prompt injection. Every fetched page is scanned for attack patterns and wrapped in an untrusted-content fence before hitting the model. One config line to disable.
Your AI re-reads the same file three times. Every compaction causes amnesia. Every build log buries the one line that matters. You pay for all of it. Token-Goat fixes all of it — automatically.
Token-Goat sits silently between your AI and your tools. Re-read a file? It gets a one-line hint and a narrow-slice suggestion instead of the full file again. Grab a screenshot? A 100 KB copy reaches the model instead of 10 MB. Run pytest, npm install, docker build, or cargo? The thousands of progress bars and passing-test names are stripped to the failures before the output even reaches the context window. Open a PDF, a large Markdown doc, or a CSV? The hook intercepts it — heading tree, page count, or column preview — so the model never pays for the full file. Run gh run watch or next dev a second time? Prior output is recalled rather than re-run. Compact a long session? It gets a clean structured manifest of edited files and key symbols so nothing important is forgotten. Sessions drop 40–90%+ in cost. You change nothing about how you work.
Works with Claude Code, Gemini CLI, Qwen Code, Codex CLI, Aider, Cursor, Cline, Windsurf, Copilot CLI, Kimi Code, Grok CLI (xAI Grok Build), and OpenCode, plus pi (pi-coding-agent).
Ask your AI to install it fully (give it this GitHub link), or install in one command:
npm install -g token-goat && token-goat install
Restart your AI sessions. Run token-goat stats a couple of minutes after your next session to see the massive savings. It also doubles as a great tracker of your work. Welcome to token efficiency.
|  |
Built and continually improved, free, by one person. If it saves you tokens, drop a ⭐️ at the top of this page. One click. Makes my day. Also, if you’d like anything added, drop me a line.
Install · CLI · What gets installed? · Stats · Security & uninstall
Same requirements, smarter input: fewer input tokens, shorter answers, and context that stops compounding across rounds
Stats display — gradient bars, sparklines, and a calendar heatmap in 24-bit color
The problem
AIs read auth.py. Then reads it again. And again. Then a third time after compaction wipes the session. Then it can’t find what it wanted and searches other lines and files. You pay for every token and most of it is waste.
Long sessions accumulate waste five ways. Screenshots cross the model at full resolution. A single PNG can land at 10+ MB. The agent re-reads files it already parsed earlier in the same conversation. When a session compacts, the summary LLM doesn’t know which files were edited or which symbols mattered, so it preserves the wrong things. And every pytest, npm install, docker build, or git log dumps thousands of lines of progress bars, deprecation warnings, and passing-test names that bury the one line that actually matters.
The fifth waste is skills. A single large skill injects 10k–65k tokens every time. Run a five-iteration /improve loop and you’ve paid for five full copies of the same rules. Token-Goat now blocks repeat skill loads before they happen: a PreToolUse hook intercepts the second invocation, serves the cached compact (~400 tokens) instead, and only allows a reload when compaction may have evicted the skill from context. It also intercepts direct reads of skill files and ensures the compaction manifest carries the full skill index — so nothing is forgotten and the full body never re-enters context unnecessarily.
The fastest way to reduce AI token costs is fixing these five, not writing shorter prompts. Each one is preventable. Token-Goat intercepts all five, automatically.
What changes
| Without Token-Goat | With Token-Goat |
|---|---|
| 3.3 MB screenshot lands in model context | 84 KB compressed copy, 97.4% smaller |
| Agent re-reads files from earlier in the session | “Already read this” reminder with narrow slice suggestion |
| Agent re-reads a file edited mid-session | Unified diff injected as a hint — full Read avoided when the diff covers the change. Docs and source/style/data files (.md/.ts/.css/.json/…) by default; set serve_diff_on_reread = false to disable source diffs |
| Compaction forgets which files were edited | Structured session manifest injected before compact |
Same files re-read from scratch after /compact |
Recovery hint at SessionStart lists cached snapshot + bash + WebFetch IDs |
| Loaded skill body summarised away by compaction | ### Active Skills manifest section + **Skills**: recovery block list every loaded skill; full body recoverable via token-goat skill-body <name> without re-invoking |
| Large skill bodies re-injected each turn (6 active skills = 65k+ tokens) | <!-- COMPACT_END --> marker: everything above the marker is the compact form; token-goat detects it on load, caches the compact slice, and injects only that — typically ~400 tokens vs. 10k+ |
| Model reads a skill SKILL.md file directly mid-session (burning the full 10k–65k tokens again) | Pre-Read hook intercepts */.claude/skills/<name>/SKILL.md paths; if the skill is already cached this session it emits a token-goat skill-body <name> hint instead |
| Same large skill invoked twice in a session | PreToolUse hook blocks the reload; serves cached compact (~400 tokens) via additionalContext instead of the full 40–65k body. Allows the reload if compaction fired since the last load |
Skill invoked with first_load_compact=true and <!-- COMPACT_END --> present |
First load also blocked; only the curated compact section is served. Full body available via token-goat skill-body <name> on demand |
| Same docs URL fetched twice in a session | Re-fetch blocked at warm+ context pressure; cached body available via token-goat web-output <id> |
cat src/auth.py or Get-Content module.py run via Bash |
Pre-Bash hook detects whole-file reads of indexed source files and suggests token-goat read "file::Symbol", skeleton, or section — covers cat, bat, type, PowerShell Get-Content/gc |
rg pattern src/ or grep -rn run via Bash (first time) |
Pre-Bash hook suggests token-goat symbol <name> and token-goat semantic "<query>" as indexed alternatives to a full directory walk |
rg "^def" src/file.py or grep "class " module.ts — structural search on a single source file |
Pre-Bash hook redirects to token-goat skeleton "file" or outline "file" — all symbols with line numbers, no full-file read |
rg or grep run twice with the same pattern |
Pre-Bash dedup hint fires on repeated rg/grep/ag calls the same way it fires on the native Grep tool; repeat searches return a cached match-count hint instead of re-running |
Read tool targets tool-results/<id>.txt or tasks/<id>.output |
Pre-Read hook suggests token-goat bash-output <id> --tail N / --grep PATTERN / --section H; the filename stem is the output ID |
Repeated monitoring command run again (gh run watch, next dev, vitest, docker logs) |
Pre-bash recall hint: when a prior run is cached and its output exceeds 2 KB, a pointer to token-goat bash-output <id> --grep PATTERN is injected instead of re-running the command. Cache is keyed on the base command, so re-running with a different trailing pipe (e.g., | tail -40 then | grep error) still hits the same cache entry |
pnpm/yarn/bun install or build dumps full output |
pnpm, yarn, and bun compress filters now strip install noise and build logs the same way npm does; pnpm run/yarn run route through their own filter |
| Surgical-read command returns a 10k-line symbol or a full section dump | Capped at ~25k tokens; marker names the truncation ratio and narrowing command (symbol → file::Class.method; section → sub-heading; cached → --grep/--tail) |
| Full file read for one function or section | token-goat read file::symbol, about 85% smaller |
pytest dumps 150 PASSED lines + dots + tracebacks |
Failures-first view, 80 to 97% smaller |
npm install floods deprecation warnings + spinner |
Errors kept; warnings collapsed by package, ~90% smaller |
docker build emits sha256 digests + transfer progress |
Step headers + errors kept; noise dropped, ~75% smaller |
ruff / eslint / mypy repeat the same rule 50 times |
Grouped by rule with first 3 examples, ~80% smaller |
Same pytest / cargo / git log re-run mid-session |
Small prior outputs (≤8 KB) served inline on first repeat; larger outputs get a hint pointing at token-goat bash-output <id> |
Same Grep pattern re-run with hundreds of matches |
Pre-Grep dedup hint quotes the prior match count |
Grep in content mode repeats the same file path on every match line |
Post-Grep hook folds matches under one path header per file (lossless, path/line survive verbatim) |
| Same docs URL fetched twice | Re-fetch denied at warm+ context pressure (redirects to token-goat web-output <id>); advisory hint at cool |
token-goat section pyproject.toml::tool.ruff |
One TOML table extracted instead of the whole config; same for .yaml/.yml/.json/.ini/.cfg/.env/Dockerfile |
Typoed token-goat symbol getUserr |
symbol matches on exact name; a miss returns No matches for 'getUserr' (no fuzzy/auto-redirect) — use token-goat find getUserr for a typo-tolerant name lookup, or token-goat semantic "<what it does>" when you don’t know the name at all |
grep/rg returns 50+ match lines |
File-level summary: top 20 files by match count; full result cached, ~80% smaller |
| Same “already read” hint fires on every re-read | Suppressed after first injection; SHA-256 fingerprinting prevents the same nag twice per session |
| A file already read in another session is requested unchanged | With hints.cross_session_read_dedup = true, a project-scoped SHA-256 evidence record adds a compact advisory instead of replaying content. Changed files are named at startup for a fresh surgical read; cached bodies are never injected |
token-goat semantic finds no indexed source match |
It falls back to up to 100 recent, project-scoped, secret-redacted workspace-evidence entries. Vectors are created only for this explicit command and cached locally; results are labeled as cached evidence rather than live source |
| Same bash command runs 3+ times in one session | Escalating warning: “ran 2×” on repeat, “WARNING: ran N×” by the third; output always cached |
| Agent starts cold with no git context in a dirty repo | Branch, change counts, and 5 recent commits injected at startup (~50 tokens) |
| Re-read hint shows only the line range | Hint includes previously-accessed symbol names: [symbols: login, refresh, …] |
| Manifest too large or unstructured after compaction | Manifest gains ### MUST_PRESERVE sealed block, ### What Worked (last 2 green test runs), inline git diffs, and ### TODOs from TaskList |
| CSV/JSON/JSONL/log file re-read when only structure changed | Pre-Read hint for structured files (CSV headers, JSON keys, log format), ~70% smaller than full read |
| Index-only files (lockfiles, source maps, bundles) read on every session | Pre-Read suppression for read-only files (package-lock.json, *.map, dist/), skipped unless explicitly edited |
| Large markdown file read in full (README.md, CHANGELOG.md, CLAUDE.md ≥8 KB) | Heading tree intercepted instead — H1–H3 with #2/#3 disambiguation; token-goat section shortcuts listed for well-known files; post-edit injects a re-read suggestion rather than the full file |
| PDF opened via Read | Full read denied; PDF shows page count and outline (token-goat pdf-extract pulls the actual text, optionally paged/sliced, when the outline isn’t enough) |
| Excel/PowerPoint/Word file (.xlsx/.pptx/.docx) opened via Read | Full read denied; redirects to the matching narrow-slice command family (xlsx-sheets/xlsx-head/xlsx-range/xlsx-query, pptx-outline/pptx-slide/pptx-notes/pptx-text, docx-outline/docx-text) instead of extracting the whole document as text |
| Other Office binary (.odt, .ods, .ott, .odp) opened via Read | Full read denied; redirects to pandoc for text extraction (no dedicated reader for these formats yet) |
| Large CSV or TSV file (≥10 KB) read in full | Column headers, row count, and 3 sample rows shown; token-goat csv-query projects columns and/or filters rows instead of a full read; duckdb query suggestion for very large tabular data |
| WebFetch returns a page’s full raw HTML | HTML-to-text extraction strips markup/scripts/styles before the model ever sees it — readable prose instead of a wall of tags |
| Large WebVTT/SRT transcript (≥10 KB) read in full | Duration, cue count, and detected speakers shown; token-goat transcript-outline gives a skimmable speaker/time overview and token-goat transcript slices by speaker/time range/pattern instead of a full read |
| Large TXT or log file (≥20 KB) read in full | Line count + first/last 5 lines shown; .log/.out files bias toward --tail 100 --grep; general catch-all for any file ≥100 KB |
| Subagent reads a 47–86 KB recon dump (or greps a 73 KB transcript) and overflows its window | pre_read denies a full Read at or above large_read_redirect_bytes (512 KB base, tightened by context pressure to as low as ~92 KB once the session is nearly full — the case that matters most for an already-strained subagent), and a content-mode Grep over one oversized file, redirecting both to surgical reads or a windowed offset/limit |
| Subagent overflows at “hello” with no idea why | token-goat baseline (--subagent for the terser variant a fresh subagent gets) prints a project map — file count, languages, top symbols, recent files — as quick orientation instead of an ls -R/full-repo read |
| MCP screenshot call lands 10 MB image in context because no file path was passed | pre_screenshot denies chrome-devtools and playwright screenshot calls without a filePath/file_path argument; redirects the model to re-issue with one, so the saved file flows through image-shrink (~39K tokens raw → ~8K compressed) |
claude-in-chrome’s computer/browser_batch return a raw, full-resolution base64 screenshot in-band, with no destination-file option to redirect through image-shrink |
Inline screenshot blocks are shrunk via the same image-shrink pipeline in place, and a repeated Tab Context: listing (appended to nearly every call, often unchanged) collapses to a placeholder once seen unchanged this session |
| Agent tool spawns a subagent with no orientation and no reuse hints | A PreToolUse handler appends a compact briefing pack to the prompt: a one-line project-map summary, 2-3 recent cached-output IDs, and a surgical-read reminder (~300 tokens) |
| Subagent’s own final report runs long and gets discarded once the parent moves on | Agent tool results ≥8000 characters get a recall pointer appended (token-goat recall); the original report always reaches the parent untouched |
| Large MCP tool result (≥2 KB) is a homogeneous array of objects, e.g. a list/search result | Deterministic structural compression: table-ified into one header row + tab-delimited rows, with columns constant across every row hoisted into a single constant: line instead of repeated per row; only applied when it saves ≥15%. Full original always recoverable via token-goat bash-output <id> (labeled [token-goat: compressed, full via mcp-output <id>]). Disable with TOKEN_GOAT_MCP_COMPRESS=0 |
Large MCP tool result (≥2 KB) doesn’t table-ify (an object-rooted config dump, a single-resource response, a nested settings tree) but most of its bytes are null/""/[]/{} |
Deterministic empty-value pruning: recursively drops those four empty shapes to a fixed point (a container left empty by its own dropped children is dropped too) and renders the remainder as compact JSON with a trailing dropped N empty value(s) (null, "", [], {}) summary line; 0 and false are never dropped; only applied when it saves ≥15%. Same full-recovery-by-id guarantee and TOKEN_GOAT_MCP_COMPRESS=0 opt-out as the table pass |
GitHub MCP tool result (list_pull_requests, list_issues, search_code, get_file_contents, pull_request_read, …) carries dozens of boilerplate fields per object |
GitHub compression pack strips _links, node_id, gravatar_id, site_admin, and every *_url field (avatar_url, html_url, events_url, gists_url, followers_url, …) except download_url/git_url/clone_url/ssh_url, before handing the shrunk JSON to the same table-ifying pass — same TOKEN_GOAT_MCP_COMPRESS=0 opt-out and full-recovery-by-id guarantee |
Browser-automation MCP tool result (claude-in-chrome’s read_console_messages/read_network_requests, chrome-devtools-mcp’s list_console_messages/list_network_requests) carries verbose CDP plumbing per entry |
Browser compression pack strips console stackTrace frames and network requestHeaders/responseHeaders/timing/initiator/securityDetails/cookie fields, keeping url/method/status/resourceType/mimeType/reqid and the actual log text, before the same table-ifying pass runs — same opt-out and full-recovery-by-id guarantee |
curl -v dumps TLS handshake + all request/response headers |
Verbose lines stripped; request line, HTTP status, content-type, and body kept — typically 70–90% smaller |
jest --verbose / vitest --verbose emits one ✓ line per passing test |
Consecutive passing-test lines collapsed to a count per file; failures kept verbatim, ~95% smaller on passing suites |
go test -v emits --- PASS: TestName (Ns) for every passing test |
PASS lines collapsed to a count per package; FAIL lines and panic output kept, ~90% smaller on clean runs |
| Python script raises and dumps a 30-frame traceback | Intermediate frame pairs collapsed to a count; outermost frame, exception type, and message kept |
tsc --noEmit emits hundreds of type errors across many files |
Errors grouped by file, up to 3 examples per file shown, rest counted; ~70–90% smaller |
make/cmake/ninja emits hundreds of [N%] Building … progress lines |
Progress lines collapsed to a count; warnings, errors, and Built target lines kept, ~85% smaller on clean builds |
| Command writes JUnit XML and prints the path | XML parsed directly; compact summary (totals + failed test names/messages) injected — raw XML never enters context |
grep/rg matches a line in a .min.js or .min.css file |
Matching line truncated to 200 chars; filename and line number preserved |
| Claude Code writes async-task output to a temp file | pre_read intercepts the path and redirects to token-goat bash-output <id> with --head/--tail/--grep support |
| Re-read hints fire immediately after conversation compaction | Grace period suppresses deny hints for the first few reads after a compact so the model can re-orient |
| Large reference doc (CLAUDE.arch.md, API spec) re-read in full every new session | token-goat compact-doc <path> builds a deterministic extractive sidecar (headings + first N lines per section); pre_read serves it in place of the full file — 80–95% smaller. Sidecar is automatically marked stale when the source is edited. |
| Re-read denial fires as an advisory hint the model can ignore | When deny_reread is on (default), pre_read actively denies re-reads of files confirmed in the current context window, not just nudges; the advisory still fires for older reads that may have scrolled out |
| Unchanged files produce duplicate hints across sessions | Hint fingerprint includes file path; unchanged-file short-circuit skips re-read pre-check entirely |
| Bash dedup hints conflict with other compression | token-goat compress can be called as dedup-vs-hint filter; one-call access to cached output |
| Large manifest sections with no useful signal | Drop empty sections, strip project name from paths (cleaner relative paths in manifest) |
| Manifest git-history section loses signal on clean main | Inline git diffs + skip git log when on clean main branch; session-awareness improves manifest hygiene |
| Skill body lost after compaction but recovery too verbose | Recovery hint deduped skills by content_sha (same skill loaded twice = one entry); inline skill checklist |
| Recovery hints omit critical paths when space is tight | Skip bash snippet when recall available |
| AVIF format not supported despite better compression | AVIF image-shrink via sharp (when libvips is built with libaom); WebP fallback; codec auto-detection in docker |
Token-savings invisible until you run stats |
Token-savings benchmark (slow-marked test suite) locks in measured wins; token-goat stats reports net-positive impact |
| Hook crash leaves agent waiting for response | Every way the shim can fail prints {} and exits 0, leaving the tool call to proceed untouched: an event name it does not know, stdin it cannot read or parse, an in-process load that throws, a token-goat child that exits non-zero or prints nothing, and a catch around the whole run |
| Concurrent edits lose update counts mid-session | Session CAS + mtime-based retry prevent lost edits in manifest |
| Dirty queue appends corrupt on concurrent writes | Each entry is one O_APPEND line, and an append that finds the file not ending in a newline starts with one, so a line torn by an earlier crash cannot merge with the next path into a single garbage entry |
| Worker claim file blocks all re-spawns on crash | Mtime staleness check (>60s) auto-recovers zombie claim files |
| Compaction hook subprocess ~190 ms cold | Lazy imports of heavy modules in hooks_session and compact; compaction path ~110 ms cold (~42% faster) |
| Pre-compact subprocess runs on every session | Compact-skip sentinel on disk: if session file is <5 min old and no edits logged, subprocess exits in <1 ms |
| Git ops slow manifest build in non-repo dirs | git diff / git log calls skipped when cwd is not inside a git repo (saves 60–100 ms per hook fire) |
terraform init downloads 30+ provider plugins |
Provider install lines collapsed to a count note; generic progress lines head/tail compressed (5+5 kept); Init complete! preserved |
terraform show dumps a full resource block |
Noise attributes (id, arn, timeouts, tags) stripped per resource block; high-signal fields kept with a suppression note |
kubectl events lists raw repetitive events |
Events grouped by REASON with a per-group count; field-selector hint added to narrow scope |
kubectl describe floods labels and annotations |
Labels/annotations blocks collapsed to line counts; Conditions table kept in full; container resource fields preserved |
npm install verbose output with sill/http/verb/spinner lines |
Verbose timing, sill, http, verb lines suppressed; warn lines beyond first 3 collapsed; braille spinner reify lines dropped |
| Fetched web content lands raw in model context | Scanned for attack patterns, wrapped in an untrusted-content fence; matched pattern name written to the log |
| Chatty log repeats the same error or event thousands of times | token-goat logfold collapses consecutive duplicates to [Nx] counts; same event logged with different timestamps or request IDs folds correctly — ~90–95% smaller on repetitive logs |
| Reading poetry.lock or package-lock.json to find a pinned version | token-goat lockdeps returns a name/version table of direct dependencies; optional packages and transitive entries excluded |
On a per-token API plan, 100K wasted tokens per session runs about $0.30. Five sessions a week is ~$450/year. AI coding cost reduction at that scale comes from fixing the waste, not from using the product less. Token-goat is free. And on subscription plans, it can result in limits feeling 10x higher.
Not just source repos
token-goat semantic works on any folder of markdown, not only source code — a notes vault, an agent-memory directory, a docs folder. Project-root resolution falls back to treating any directory as an ad-hoc project when no .git/package.json/other marker is present, so there’s no setup beyond indexing the folder.
cd ~/notes # or any plain folder of .md files, no .git required
token-goat index . --walk # non-git folders need --walk (git repos: plain `token-goat index .`)
token-goat semantic "how long to steep cold brew"
Returns relevance-ranked, distance-scored hits straight from the notes, the same surgical-read path used for code.
Token savings, measured
Numbers below come from synthetic-fixture benchmarks in the test suite. Each row points at the source file where the measurement is reproduced.
| Source | Improvement | Measured impact | Where |
|---|---|---|---|
| Image shrink | WebP encoder beats JPEG on screenshot-shaped images | ~39% smaller than the same image at JPEG quality 85 | src/image_shrink.ts (codec selection) |
| Repomap output | --compact trims the top-symbols list to 10 (vs 30) and drops the recent-files section and per-symbol locations |
Denser overview for the same byte budget | src/baseline.ts (buildProjectMap, token-goat map --compact) |
| DB reindex | Batched single transaction + composite indexes on (file_id, kind) |
100 files / 10K rows: 84 s → 1 s (~80× faster) | src/parser.ts, src/db.ts (index migration) |
| Hook cold-start | Lazy import of heavy modules; unknown events short-circuit | 86 ms → 30 ms (~65% faster); unknown-event dispatch <1 ms | src/hooks_cli.ts |
| Symbol start_line | TypeScript decorators captured in symbol span | One token-goat read returns the decorator + signature + body; no re-read |
src/parser.ts (TypeScript adapter) |
| Section extraction | Setext headings, h5/h6, anchor IDs, and __frontmatter__ |
token-goat section resolves more headings without falling back to a full file read |
src/parser.ts (Markdown adapter) |
| Image cache | Repeat Read of an unchanged image serves the stored re-encode, keyed on path + size + mtime, instead of running sharp again |
Skips the re-encode entirely on a hit; the same bytes reach the model, so the reported saving is identical either way | src/image_shrink.ts (findCachedShrink) |
| Monorepo defaults | Reindex batch 500 → 2000; compact min_events 5 → 3 |
Fewer worker wakeups; compact manifests fire on shorter sessions | src/config.ts defaults |
| Miss suggestions | read / section print “Did you mean…?” on a miss; section also auto-redirects on an unambiguous heading-prefix match |
Keeps agents on the surgical-read path instead of falling back to full-file Read |
src/read_commands.ts |
Token-savings examples
Concrete before/after for the four interception points. Token counts use the ~4-chars-per-token rule of thumb.
1. Image — screenshot interception
$ ls -lh screenshot.png
-rw-r--r-- 1 user user 1.2M screenshot.png
# Without token-goat: Claude reads the 1.2 MB PNG.
# With token-goat: hook re-encodes as WebP and substitutes the cached copy.
$ token-goat image-shrink screenshot.png
out: ~74 KB WebP (94% smaller)
The same image at JPEG quality 85 lands around 120 KB. WebP wins by another ~39% on screenshot-shaped content (large flat regions, sharp text edges).
2. Surgical read — one function, not the whole file
# Without token-goat: full file read.
$ wc -l src/auth.py
512 src/auth.py # ~12,000 tokens
# With token-goat: pull just the function.
$ token-goat read "src/auth.py::login"
out: 38 lines # ~300 tokens (97% smaller)
Same applies to token-goat section "README.md::Install" — one heading instead of the whole document. Anchor IDs and setext headings resolve too, so section "doc.md::Quick-start" works when the file uses Quick start as an <h2> with an explicit {#quick-start} anchor.
3. Compact manifest — preserve what mattered
# Without token-goat: PreCompact fires with no extra context.
# The summarizer LLM picks what to keep, often loses the edit set.
# With token-goat: PreCompact hook injects a structured manifest.
$ token-goat compact-hint --session-id <id>
out: ~280 tokens covering 8 edited files + 12 symbols accessed + 4 key reads
The 280-token manifest is one-shot during compaction. The win is downstream: post-compaction, the agent doesn’t re-read files it had already edited, saving a full-file Read pass on each one.
4. Repomap — orientation without an ls -R dump
# Without token-goat: recursive ls + a handful of Read calls to figure out the repo.
$ ls -R . | wc -c
51234 # ~50 KB of raw paths, no signal about importance
# With token-goat: a ranked orientation summary instead of raw paths.
$ token-goat map --compact
out: ~1 KB # top-ranked classes/functions, no locations (98% smaller)
token-goat map ranks headline symbols by kind (classes/interfaces first) and body size. --compact trims that list to the top 10 symbols (name + kind only, no file/line) and drops the recent-files section, for a denser orientation than the full form.
5. Bash output compression
# Without token-goat: pytest dumps every PASSED line + dots + tracebacks.
$ pytest -v tests/
... (3 KB of output, 150 PASSED lines, 1 FAILED at the bottom)
# With token-goat: the PreToolUse hook rewrites the command to
# `token-goat compress --filter pytest`. The wrapper runs pytest, captures
# stdout+stderr, applies the per-tool filter, and prints failures first.
$ token-goat compress --filter pytest --cmd "pytest -v tests/"
= test session starts =
collected 150 items
FAILED tests/test_x.py::test_one
= 1 failed, 149 passed in 2.3s =
[token-goat: collapsed 149 PASSED lines]
[token-goat: pytest filter compressed 4.8 KiB to 0.1 KiB (97% saved)]
Built-in output compression covers 130+ dev tool CLIs: pytest, jest / vitest, cargo, npm / pnpm / yarn / bun, docker, kubectl / helm, aws, ruff / eslint / mypy / pylint / oxlint, git, make / gradle / mvn / ant / bazel, go test / golangci-lint, terraform / pulumi / cdk, pip / uv / conda, python, gh, ansible, pre-commit, grep, eza / ls, fd, bat, jq, yq, curl / wget, rsync, dotnet, cmake / ctest, swift / xcodebuild, ruby / bundler, elixir / mix, php / composer, flutter / dart, rust / cargo, kotlin / ktlint, zig, crystal, haskell / cabal, nix, R, c++ (conan / vcpkg / cppcheck / clang-tidy), wrangler / hardhat / serverless, erlang, fly.io, forge, elm, julia, tox, vault, packer, nx / lerna / turbo, prettier / biome, sass, wasm-pack, deno, and AI tool CLIs: aider, gemini, claude, gh copilot, copilot, cursor, windsurf (incl. Cascade), opencode, continue, cline. Each filter strips ANSI escapes, collapses \r progress bars, dedupes repeated lines, groups linter issues by rule, keeps every error block verbatim, and caps total output at 1000 lines / 64 KiB. Compound commands (cmd1 && cmd2) are wrapped per segment, so git diff && git log compresses both halves. Disable globally with TOKEN_GOAT_BASH_COMPRESS=0, per-filter via [bash_compress] disabled_filters = ["docker"] in config.toml, or preview the output of any command with token-goat compress --cmd '<your command>'. To exclude project-specific directories from indexing (temporary venvs, build sandboxes), add [indexing] skip_dirs = ["my-tmpdir"] to config.toml. To exclude specific generated files by basename instead (coverage reports, bundler stats), set [indexing] skip_files = [...]; it defaults to ["coverage.json", "coverage-final.json"], so a project with a legitimately-named coverage.json it wants indexed can override the list to drop that entry.
gh api responses get an extra pass: boilerplate *_url fields (followers_url, gists_url, starred_url, and around a dozen others) are stripped from JSON objects; html_url, avatar_url, clone_url, and ssh_url are kept. User and repo objects typically shrink 60–80%. When token-goat sees a GitHub permission error in the output or a non-zero exit on a security endpoint, it injects a system message suggesting gh auth refresh -s security_events.
A failing pytest / jest / vitest / go test / cargo test run (including bare npm test/npm run test/yarn test/pnpm test) also gets a one-line advisory naming the exact token-goat bash-output <id> | token-goat failures command to run instead of re-reading the raw dump. Silent on a passing run, a non-test command, or output too small to be worth reducing.
6. Context pressure
Token-goat tracks how close a session is to the autocompact trigger and tightens its hints as the window fills. Surgical-read suggestions kick in on progressively smaller files as pressure builds (500 lines at cool, down to 50 at critical), so large reads get flagged before they tip the session over. The PreCompact manifest also shrinks: capped at 500 tokens once the window runs hot, 300 once critical, so it stops contributing to the pressure it measures. The denominator is always the fixed 660,000-token autocompact trigger budget, not the model’s raw context window, so the same thresholds apply across models. Run token-goat doctor --context to see the current footprint.
For recurring scheduler loops, the 25th, 100th, and 250th observed delivery in a session receive a one-time checkpoint/fresh-session reminder; it prevents further accumulation but cannot reclaim input already injected. The count is independent of the scheduler’s bracketed identifier, which some hosts repeat for every delivery. Before a direct test command without a focused selector or explicit timeout, the Bash hook either preserves its existing compressor timeout or gives an advisory when compression cannot apply; it never stops the test.
Install
Easiest install: paste this repo’s URL into your AI and ask it to install token-goat properly. It will run the commands, check codecs, and confirm everything is working.
Requirements: Node.js 22 or later (all platforms)
npm install -g token-goat
token-goat install
token-goat doctor # confirms hooks and sharp are working; look for "sharp: ok"
Three commands. Done. Hooks register and start working immediately; no terminal popups, no tray icon, no service to babysit.
Agents choose the commands
People install token-goat. Agents use it. You do not need to memorize its commands or tell the agent which file type it has.
Installation adds a short routing guide to the agent’s instructions. When the agent tries to read a supported binary document, a hook identifies the extension and returns the right next step. The agent starts with an inventory, then reads only the relevant part.
| Task | Agent flow |
|---|---|
| Review a PDF | pdf-meta and pdf-outline, then pdf-extract --pages |
| Review a Word document | docx-outline, then docx-text |
| Review a slide deck | pptx-outline, then pptx-slide or pptx-notes |
| Review a workbook | xlsx-sheets, then xlsx-head, xlsx-range, or xlsx-query |
Give the agent the file and the task: “Review manual.pdf for warranty exceptions.” It selects the bounded reader. If no routing rule fits, it can run token-goat commands instead of guessing.
The commands stay separate so every retrieval is visible, repeatable, and easy to narrow. The agent chooses the sequence; the developer can still inspect or run any step directly.
For bounded archive/document comparisons after setup, see the CLI comparison workflow.
Important — sharp is part of the install. The biggest single win (~39% smaller than JPEG, ~97% smaller than raw PNG) comes from WebP encoding. sharp ships with prebuilt native binaries on Windows, macOS, and most Linux platforms, so a standard npm install -g token-goat includes it. On Alpine musl and some ARM boards, npm compiles from source against libvips — that may add a few seconds. token-goat doctor will report sharp: ok when the image pipeline is working; if it flags a failure, follow the Image support section below before considering the install complete. AI agents performing this install: run token-goat doctor after token-goat install and resolve any sharp failure as part of the same task — do not treat it as a follow-up.
Two things change how Claude Code sessions behave: hooks fire automatically (image shrink, re-read dedup, compact manifests), and a delimited routing block written to ~/.claude/CLAUDE.md plus a registered skill gate the agent’s reads — before any file read it must ask whether a token-goat read / symbol / section returns just what it needs, and the block explicitly subordinates the harness’s own Read/Grep tool-preference rules to the fallback choice once token-goat is ruled out. Install writes no permission entry: whether token-goat commands need a per-call approval prompt is left to your own settings.json, unchanged.
Keep that block where install put it. It’s plain markdown in a file you own, so moving it into a tidier reference file is tempting — but install and uninstall resolve one hardcoded path (~/.claude/CLAUDE.md). A relocated copy is never refreshed, so it freezes at whatever version was current when it moved, and the next install sees CLAUDE.md missing its block and appends a fresh one — leaving the guidance duplicated across two files with only one of them live. token-goat doctor warns when it finds a block outside CLAUDE.md, naming the file; install warns at write time and uninstall reports what it couldn’t remove. None of them edit a file token-goat doesn’t own, so cleanup stays your call. A pointer that merely mentions the markers in prose is fine — detection requires both markers on their own lines.
The background indexer is not started by install. Run token-goat worker start on any platform to launch it as a detached process; token-goat worker status / token-goat worker stop manage it from there.
Companion CLI tools (recommended — install these too)
token-goat covers the narrow-read half of cheap context: pulling one symbol, one section, one cached command output instead of a whole file. It does not cover the deterministic-transform half — searching wide, rewriting code structurally, converting data, running language tooling. Those belong in utilities, not in model output: an operation with a defined algorithm is reproducible, cheaper, and checkable against a spec rather than re-read for plausibility. Install these alongside token-goat so an agent has a real tool for each job instead of burning tokens simulating one.
Priority tier — the three that close actual gaps in a token-goat-only setup:
| Tool | Why it matters next to token-goat |
|---|---|
ast-grep |
The symbol-aware write half. token-goat reads by symbol; ast-grep matches the AST and rewrites it (--rewrite, YAML rule files). Repo-wide renames, call-shape changes, and codemods become a reviewable diff instead of a model regenerating files. Unlike rg/sd it ignores comments and strings. |
uv |
One Rust binary replacing pip, pyenv, virtualenv, and pipx. Every Python env probe and validation cycle gets an order-of-magnitude faster, so verification stops being the slow step agents skip. |
ruff |
Python lint + format in one binary. Agent environment probes commonly emit ruff check as the Python verify command; without it installed that path silently degrades to no check at all. |
Base stack — assumed by the read/search guidance token-goat writes into your agent config:
rg (search) · fd (file discovery) · bat (paged/piped reads) · eza (listings) · delta (diff rendering) · jq / yq (JSON / YAML) · sd (find-replace) · mlr (CSV/TSV/JSON records) · sqlite3 (structured queries) · gh (PRs, issues, CI) · hyperfine (benchmarks) · fzf, lazygit (interactive)
Optional but useful: difft (difftastic — syntax-aware diff, so reformats and moved blocks stop generating review noise), just (task runner, keeps verify commands discoverable), typos (deterministic spellcheck).
For archive/document work specifically, token-goat’s bounded SQLite, XLSX, and PDF readers are documented in the CLI comparison workflow; keep rendering and schema-specific lineage interpretation in dedicated document tooling.
# macOS / Linux (Homebrew)
brew install ast-grep uv ruff ripgrep fd bat eza git-delta jq yq sd miller sqlite gh hyperfine fzf lazygit
# Debian / Ubuntu — note the binary renames: rg=ripgrep, fd=fdfind, bat=batcat
sudo apt install -y ripgrep fd-find bat jq sqlite3 fzf
curl -LsSf https://astral.sh/uv/install.sh | sh # uv, then: uv tool install ruff
npm install -g @ast-grep/cli
# Windows (winget)
winget install BurntSushi.ripgrep.MSVC sharkdp.fd sharkdp.bat eza-community.eza `
dandavison.delta jqlang.jq MikeFarah.yq chmln.sd Miller.Miller `
SQLite.SQLite GitHub.cli sharkdp.hyperfine junegunn.fzf JesseDuffield.lazygit
winget install astral-sh.uv # then: uv tool install ruff
npm install -g @ast-grep/cli # provides `ast-grep` (the old `sg` alias is deprecated)
If winget is unavailable (common when a session runs under a service account rather than an interactive login), uv also installs via python -m pip install uv, and ast-grep only needs npm. Verify the whole set in one pass:
for t in token-goat ast-grep uv ruff rg fd bat eza delta jq yq sd mlr sqlite3 gh hyperfine; do
command -v "$t" >/dev/null 2>&1 && echo "$t ok" || echo "$t MISSING"
done
Codex CLI users
token-goat install --codex
The --codex flag patches both Claude Code and Codex CLI in one pass.
Gemini CLI users
token-goat install --gemini
This writes hook entries into ~/.gemini/settings.json using Gemini CLI’s BeforeTool / AfterTool / PreCompress event names. Token-goat translates between Gemini’s snake_case tool names (run_shell_command, read_file, grep_search, etc.) and its internal format automatically. Image shrinking, session hints, post-edit indexing, compact assist, and bash output compression all work. To remove: token-goat uninstall --gemini.
Qwen Code users
token-goat install --qwen
This writes hook entries into ~/.qwen/settings.json. Unlike Gemini CLI (its own ancestor, with a custom BeforeTool/AfterTool/PreCompress event/matcher scheme), Qwen Code’s hooks system diverged and now mirrors Claude Code’s own natively — PreToolUse/PostToolUse/PreCompact/UserPromptSubmit/SubagentStop event names and snake_case stdin JSON — so token-goat wires all five events with no wire-format translation needed. Qwen Code’s own tool-name taxonomy is only partially documented, so token-goat uses a catch-all matcher per event rather than an incomplete per-tool list. Image shrinking, session hints, post-edit indexing, compact assist, and bash output compression all work. This bridge was built from QwenLM/qwen-code’s published docs, not dogfooded against a live Qwen Code install — if hooks aren’t firing, token-goat doctor and the settings.json contents are the first things to check. To remove: token-goat uninstall --qwen.
Kimi Code users
token-goat install --kimi
This writes [[hooks]] entries into ~/.kimi-code/config.toml (or $KIMI_CODE_HOME/config.toml), covering Kimi Code’s PreToolUse, PostToolUse, PreCompact, UserPromptSubmit, SubagentStop, and SessionStart events. Kimi Code sends a Claude-Code-shaped snake_case payload on stdin, but it reads a different response: only a top-level message and hookSpecificOutput.permissionDecision / permissionDecisionReason. So the install also writes a small shim at ~/.kimi-code/hooks/token-goat-shim.js that translates token-goat’s answer into that contract, turns a hint into message, and writes nothing at all for a no-op. Image shrinking, session hints, post-edit indexing, compact assist, and bash output compression all work. Notification and Stop are not wired, because token-goat has no handler for them. Input and output rewriting are not wired either: Kimi Code offers no channel to replace a tool’s input or its result. This bridge was built from MoonshotAI/kimi-code’s own source and docs, not dogfooded against a live Kimi Code install, so if hooks are not firing, token-goat doctor and the config.toml contents are the first things to check. To remove: token-goat uninstall --kimi.
opencode users
token-goat install --opencode
The --opencode flag patches Claude Code and drops a TypeScript bridge plugin into opencode’s plugins directory — one command, no separate base install. Image shrinking, post-edit indexing, and compact assist work. Session hints don’t — opencode’s plugin API has no way to inject context before a tool read.
openclaw users
token-goat install --openclaw
The --openclaw flag patches Claude Code and registers a TypeScript bridge plugin with OpenClaw’s gateway: it drops ~/.openclaw/plugins/token-goat.ts and adds it to ~/.openclaw/openclaw.json’s plugins.load.paths / plugins.entries (existing config is merged, never overwritten). OpenClaw’s plugin SDK does support before_tool_call/after_tool_call hooks with the block/rewrite shape token-goat needs; unlike the other bridges, no argument-key remapping is needed at all, since OpenClaw’s tool-call params are already snake_case (file_path, command, etc.) — the same keys token-goat’s own tool_input uses.
What works: bash output compression, re-read denial and surgical-read redirects for oversized first reads, image shrinking, and post-edit indexing (all via before_tool_call/after_tool_call). What doesn’t: session hints — OpenClaw’s tool-call hooks have no context-injection channel, only param rewriting — and the compaction manifest — OpenClaw’s before_compaction/after_compaction are observation-only, with no return-value mechanism to inject a manifest into the next turn the way pi’s compaction hooks do.
This bridge has not been validated against a live OpenClaw instance — it’s built from OpenClaw’s documented plugin SDK and hook event types, not dogfooded against a real running gateway. If tool calls aren’t being intercepted, the built-in tool name list in openclaw.ts’s TOOL_TO_TG map is the first thing to check. To remove: token-goat uninstall --openclaw.
pi users
token-goat install --pi
The --pi flag patches Claude Code and drops a TypeScript extension into pi’s global extensions directory (~/.pi/agent/extensions/token-goat.ts). pi auto-discovers it on the next launch (approve the project-trust prompt the first time). The extension is a normal pi extension — a default-exported factory that subscribes to session_start, tool_call, tool_result, session_before_compact, and session_compact — and bridges those events into token-goat’s token-goat hook <event> subprocess protocol.
What works: bash output compression (the bash command is rewritten in tool_call), re-read denial and surgical-read redirects for oversized first reads (both return { block, reason } from tool_call — a confirmed re-read, or a first read at/above the pressure-scaled large_read_redirect_bytes gate, pointing at token-goat skeleton/section/symbol instead), image shrinking (tool_call rewrites the read path in place to a materialized shrunk copy), post-edit indexing and output caching (tool_result), and the compaction manifest (captured at session_before_compact, re-injected after session_compact since pi’s compaction replaces rather than appends). Skill-overhead preservation does not apply — pi has no Skill tool; skills are template expansions. To remove: token-goat uninstall --pi.
Project-local install (single project only). pi also loads extensions from a project’s .pi/extensions/ directory (after the project is trusted). To install for one project without touching the global directory, drop the extension there:
npx token-goat install --pi --local
This writes .pi/extensions/token-goat.ts in the current project only. Remove it by deleting that file.
Copilot CLI users
token-goat install --copilot
The --copilot flag patches Claude Code and registers a Copilot CLI hook config: ~/.copilot/hooks/token-goat.json (a { version, hooks } file registering sessionStart, preToolUse, postToolUse, preCompact, agentStop, subagentStop, and userPromptSubmitted, per Copilot’s own hooks reference) plus the shim script it points at, ~/.copilot/hooks/token-goat-shim.js. Unlike Codex, Copilot’s event names and response schema (permissionDecision/modifiedArgs for preToolUse, modifiedResult/additionalContext for postToolUse, decision/reason for agentStop/subagentStop) genuinely differ from Claude Code’s, so the shim translates rather than passes through.
What works: the command-routing reminder (sessionStart returns additionalContext, so Copilot is told token-goat exists before it picks its first read tool — this is the one channel that lands ahead of that decision), bash output compression and re-read denial (preToolUse returns modifiedArgs or permissionDecision: "deny"), image shrinking and post-edit indexing (postToolUse returns additionalContext), and stop-hallucination logging (agentStop/subagentStop map a token-goat deny onto decision: "block", everything else onto decision: "allow"). preCompact and userPromptSubmitted are notification-only on real Copilot CLI, per its docs: Copilot never reads a response body for either, so token-goat’s compaction manifest and prompt-context hints have no surfacing channel there. The shim still calls through for both so token-goat’s internal side effects keep running, but nothing gets injected back into the agent. Copilot’s built-in tool names (view, edit, create, bash/powershell, web_fetch, grep, glob, memory, and MCP-server calls) are remapped onto token-goat’s internal names where a clear match exists (view→Read, edit→Edit, create→Write, bash/powershell→Bash, web_fetch→WebFetch, grep→Grep, glob→Glob); memory, task, ask_user, and MCP tool calls pass through unmapped and simply no-op.
No ambient environment variable documents “this process is running under Copilot CLI” the way Codex/opencode set one, so the shim sets TOKEN_GOAT_HARNESS_OVERRIDE=copilot_cli itself before calling token-goat hook (same workaround --pi uses). Install also writes a token-goat routing block into ~/.copilot/copilot-instructions.md (the same delimited-block gate written to ~/.claude/CLAUDE.md and ~/.codex/AGENTS.md), merged idempotently so any hand-written content outside the markers is preserved byte-for-byte. If you set COPILOT_HOME, install follows it — hooks go to $COPILOT_HOME/hooks/ and the routing block to $COPILOT_HOME/copilot-instructions.md, matching where Copilot CLI actually reads them. To install for one project instead of user scope: token-goat install --copilot --local (writes .github/hooks/token-goat.json and .github/copilot-instructions.md in the current project). To remove: token-goat uninstall --copilot.
If Copilot CLI starts denying every tool call with Denied by preToolUse hook ... (hook errored): this is Copilot’s own fail-closed behavior for a preToolUse hook that crashes, exits non-zero, or returns unparseable output – it isn’t limited to token-goat’s own tool calls, since a fail-closed preToolUse hook blocks the whole session. Copilot caches hook configs at session start, so renaming or reinstalling the hook mid-session has no effect – the only recovery is: run token-goat install --copilot (or token-goat doctor, which now checks the installed hook end-to-end and calls out a stale node-binary path from an nvm/fnm/volta upgrade specifically), then fully restart Copilot CLI.
Grok CLI (xAI Grok Build) users
Grok Build already reads Claude Code’s ~/.claude/settings.json as a “Harness Compatibility” source out of the box (confirmed against grok 0.2.93 and its own hooks doc), so token-goat install alone already gets most of the integration working — image shrinking, session hints, post-edit indexing, and bash output compression all fire. The one gap: Grok’s own PreToolUse hook contract documents only {"decision":"allow"} / {"decision":"deny","reason":"..."}, never token-goat’s harness-independent {"decision":"block","reason":"..."} shape (unlike Gemini CLI, whose docs explicitly confirm "block" as an accepted alias for "deny"), so re-read denial and oversized-first-read redirects don’t reliably block on the Claude Code compat path alone.
token-goat install --grok
The --grok flag patches Claude Code and additionally writes a standalone hook config at ~/.grok/hooks/token-goat.json (global scope only — Grok’s own project-scoped <project>/.grok/hooks/*.json requires a separate manual /hooks-trust grant this bridge can’t perform for you) plus the shim it points at, ~/.grok/hooks/token-goat-shim.js. The shim’s only job is translating that one response shape: a token-goat {"decision":"block",...} deny becomes Grok’s documented {"decision":"deny",...} (with exit code 2, matching Grok’s own “explicit deny” convention), and every other event’s response is forwarded through unmodified — Grok already sends the raw camelCase wire payload (toolName/toolInput/sessionId) token-goat’s built-in grok harness detection (GROK_SESSION_ID, set on every hook subprocess Grok spawns) already normalizes correctly.
To remove: token-goat uninstall --grok.
Cline, Windsurf, Cursor, and other AI tool CLIs
No separate install step needed. Token-goat compresses the terminal output of these tools automatically as soon as they appear on your PATH. Run token-goat doctor to confirm they are detected — the “Third-party AI tools” section will show detected — bash output compression active.
Filters are built in for: Cline (cline / claude-dev), Windsurf (windsurf, including Cascade AI patterns), Cursor (cursor), GitHub Copilot CLI (gh copilot explain/suggest and the standalone copilot binary — this passive output filter is separate from the --copilot hook bridge above; it works with no install step and covers Copilot CLI’s own terminal chrome, not the hook-driven read/index integrations), Aider (aider), Continue (continue), OpenCode (opencode). Each filter strips version banners, spinner/thinking lines, token-usage boilerplate, and tool-call progress noise while keeping the AI response body, error signals, and any user-approval prompts verbatim.
Updating
There is no auto-update mechanism — token-goat never schedules or runs anything on its own. Updating is always a manual npm install -g token-goat@latest.
| When | Command |
|---|---|
| Update now | npm install -g token-goat@latest |
| Reinstall from scratch (broken install, sharp failure) | npm install -g token-goat@latest |
Upgrading from the Python version
The old Python package (pip install token-goat) wrote hook entries into settings.json with commands containing token_goat (underscore), invoking Python directly: something like pythonw.exe -m token_goat.cli hook pre_tool_use. The npm package invokes a generated shim instead ("<node>" "~/.claude/hooks/token-goat-shim.js" pre_tool_use "<entry>").
Both install and uninstall recognize the older command spellings — token_goat, tokenwise, tg-hook, token-goat-hook, and the pre-shim token-goat hook — so you do not need to hand-edit settings.json. Installing replaces a stale entry in place rather than leaving a dead one beside the new one, and uninstalling removes it.
To upgrade cleanly:
- Remove the Python package:
pip uninstall token-goat - Install and register the npm package:
npm install -g token-goat && token-goat install - Verify: re-run
token-goat install, which should now report hooks already up to date, or use thejqcheck under Verify.
AI agents handling an upgrade: complete all three steps in sequence. After step 3, confirm no token_goat entries remain in settings.json before reporting the upgrade done.
CLI
Every command accepts a global --cwd <path>, which runs it as if invoked from that directory. It exists so a caller can name a project root without making that root its own working directory — a launcher should never resolve a binary name against a directory the workspace controls. It is applied before anything resolves the project root or loads config, so --cwd selects which .token-goat.toml applies.
Archive/document comparison workflow
These are agent-selected primitives, not a manual checklist. Give the agent the file and the question. The installed routing guide and read hook select the matching format flow; the commands below show the steps it can take without loading whole files:
token-goat sqlite-schema catalog.db
token-goat sqlite-query catalog.db "SELECT file_path, name FROM files WHERE name LIKE '%owner%' LIMIT 20" --json
token-goat xlsx-sheets link-map.xlsx
token-goat xlsx-query link-map.xlsx --sheet Links --columns publication,source,target --head 50
token-goat pdf-meta manual.pdf
token-goat pdf-outline manual.pdf
token-goat pdf-extract manual.pdf --pages 12-15 --layout --head 120
token-goat intentionally does not render PDF pages or infer XML publication lineage: those operations produce binary/visual output or require schema-specific interpretation. Keep those steps in the document/PDF tooling, then pass only the bounded paths, rows, and page text needed for comparison.
| Command | What it does |
|———|————-|
| token-goat symbol [name] | Jump to a symbol definition. -p, --project [path] scopes the search to one project root instead of the default global (cross-project) index — pass no value to use the current directory’s project root, or a path to scope to a different one. --json’s filePath renders root-relative when a project root resolves, absolute when none does — matching human output and the outline/skeleton/refs --json convention. --grep <pattern> searches project-wide by NAME PATTERN instead of an exact name (regex, falling back to a literal substring match when the pattern is not valid regex) — the positional name is omitted in that mode, and the two are mutually exclusive since regex-filtering an already-exact name can only match everything or nothing. This is the only project-wide symbol-name pattern search: skeleton/outline/exports --grep are per-file, types --grep covers only type-like kinds, and dead --grep only zero-reference symbols. The filter is applied before the --limit slice, so --limit N --grep P returns up to N matching rows; when it matches nothing among symbols that are in scope, the output names the active filter instead of reading as an empty project. --exclude-tests hides symbols DEFINED in a test file (opt-in — omitted, output is unchanged), the same definition-site sense dead --exclude-tests uses rather than the call-site sense of refs/callers. The high-value case is a common helper name that is mostly defined in tests: against this repo’s own index symbol run returns 18 rows of which 14 are test-file definitions, and symbol capture returns 9 of 9. Composable with --grep (a symbol must satisfy both) and applied before the --limit slice, so the flag selects from the whole match set rather than an already-capped page — without that ordering a --limit N window filled by test-file rows would report nothing for a symbol that is plainly indexed in src. When it hides every match there was, the output names how many were hidden and exits 0, instead of the exit-1 No matches a genuinely unindexed name returns. --stats adds a per-result reference count and doc-coverage flag, computed live from the index — the same flag read/skeleton/outline already carry, useful here for picking which of several same-named candidates is the real one. Note its known limitation: the count is keyed by symbol NAME project-wide, not by definition site, so under --grep several same-named symbols in different files all show the identical count. |
| token-goat read "file::symbol" | Pull one function or class, not the whole file. Supports qualified lookups (read "file.py::Class.method") and line ranges: read "file.py@10-40" for lines 10 to 40 inclusive, or read "file.py@42" for one line. Line ranges read straight from disk, so they work on any file, including paths outside an indexed project. A trailing @LINE on the symbol itself (read "file.py::run@42", or combined with a qualifier as read "file.py::Class.method@42") anchors an ambiguous spec to the one candidate starting on that exact line — for a top-level definition with no enclosing Class.method qualifier, this is the only way to pick it out when its bare name also matches something else in the same file; every ambiguity error’s retry suggestions already use this form where a plain qualifier wouldn’t be unique. Pass a comma-separated spec (file::a,b) to merge several symbols’ bodies from one file into a single call, each headed by its symbol name. Segments may also carry their own file (a.ts::x,b.ts::y) to merge symbols across several files in one call; a bare segment inherits the file to its left (a.ts::x,b.ts::y,z reads z from b.ts), and once more than one file is involved each block is headed by the full file::symbol so two files contributing the same symbol name stay distinct. --force-refresh reparses the file from disk and updates the index before querying — for files touched by git operations, external tools, or direct filesystem writes that bypass the normal post-edit indexing hook. --stats adds a per-symbol reference count and doc-coverage flag, computed live from the index. |
| token-goat replace <file> | Replace one string in a file using --old-from/--new-from or --old-b64/--new-b64; --all replaces every match. If the exact match fails but a unique match exists once CRLF/LF differences are ignored, it heals automatically, writing the replacement back in the file’s line-ending convention at that location. --normalize-newlines converts the old/new text’s CRLF/LF to match the target file’s dominant line ending before matching, for forcing normalization proactively. |
| token-goat insert-section <file> --after <heading> | Insert content immediately after a matched section (--content-from <source> or --content-b64 <payload>), resolved the same way section resolves headings (exact, normalized, or an unambiguous prefix) — avoids the stale byte-exact anchor replace would otherwise need for an append-to-a-running-log edit. |
| token-goat note-add <file> [--symbol NAME] | Attach a free-text architecture/rationale note (Markdown, --content-from <source> or --content-b64 <payload>) to a file, or to one specific indexed symbol within it. Captures a fingerprint of what the note describes (the symbol’s current body, or a digest of the file’s current top-level symbol manifest) so staleness can be detected later — re-running note-add for the same file/symbol overwrites rather than duplicates. |
| token-goat note-get <file> [--symbol NAME] | Read back the note attached to a file or one indexed symbol within it. Flags whether the note has gone stale (the underlying code changed since it was written) via a stale field under --json. |
| token-goat note-list [--stale-only] | List every recorded architecture note. --stale-only shows just the notes whose fingerprint no longer matches the current index — i.e. the file/symbol they describe changed since the note was written. Staleness is purely advisory: nothing here auto-rewrites or deletes a note. |
| token-goat write-file <dest> | Write exact bytes to a file, sidestepping shell-escaping trouble with backticks, quotes, $vars, and CRLF. --from <source> copies bytes from a source file; --b64 <payload> decodes a base64 payload; with neither, reads from stdin. |
| token-goat section "doc.md::Heading" | Pull one Markdown section by heading. A miss that is an unambiguous prefix of exactly one heading, or a distinctive suffix/word-subset of exactly one heading (e.g. Setup → “Installation and Setup”, Config Options → “Configuration Options”), auto-redirects with a (redirected from: …) marker (and a redirectedFrom field under --json); a query matching 2+ headings is never guessed and reports a miss instead. A genuine miss lists only headings similar to the query as “Did you mean” suggestions, not every heading in the file. Disambiguate duplicates with "doc.md::Heading#2". Comma-separated "doc.md::A,B" fetches several sections from one file in a single call, mirroring read’s file::a,b multi-symbol grammar. Cross-file "a.md::Heading1,b.md::Heading2" fetches sections from several files in one call, mirroring read’s a.ts::x,b.ts::y cross-file grammar — a bare heading after a file::Heading segment inherits the previous file, and each section is keyed by its full file::Heading pair so two files sharing a heading name cannot overwrite each other. token-goat section doc.md --list lists every heading in the file instead of reading one; --grep <pattern> narrows that list to headings matching a regex (falls back to a literal substring match if the pattern doesn’t compile), same convention as outline/types/exports’s own --grep. |
| token-goat skill-section "<name>::<heading>" | Extract a named section from an installed skill without reading the full skill file. |
| token-goat skeleton "file" | Show all signatures in a file without bodies — typically 70–90% fewer tokens than a full read. --force-refresh reparses from disk first, bypassing a stale index. --stats adds a per-symbol reference count and doc-coverage flag, computed live from the index. --grep <pattern> narrows to symbols whose name matches a regex (a literal substring when the pattern is not valid regex), which is how you skim one area of a large file without dumping its whole symbol list; --min-lines <n> drops symbols shorter than N lines. Both compose, and if a filter removes everything the output says so and names the filter, rather than looking like a file with no symbols. Accepts a comma-separated file list ("a,b,c") to cover several files in one call, one clearly-headed block per file; extra space-separated file arguments are reported in a note naming that comma form instead of being silently dropped. With --json, a comma-separated list returns one merged document (rows carry their own filePath), not one document per file. |
| token-goat outline "file" | List top-level symbols with line ranges and docstring hints — one-glance file map. --force-refresh reparses from disk first, bypassing a stale index. --stats adds a per-symbol reference count and doc-coverage flag, computed live from the index. --grep <pattern> narrows to symbols whose name matches a regex (a literal substring when the pattern is not valid regex), which is how you skim one area of a large file without dumping its whole symbol list; --min-lines <n> drops symbols shorter than N lines. Both compose, and if a filter removes everything the output says so and names the filter, rather than looking like a file with no symbols. Accepts a comma-separated file list ("a,b,c") to cover several files in one call, one clearly-headed block per file; extra space-separated file arguments are reported in a note naming that comma form instead of being silently dropped. With --json, a comma-separated list returns one merged document (rows carry their own filePath), not one document per file. |
| token-goat yaml-outline <file> | Structural summary of a YAML document (array shape / object key types) instead of a raw Read. Multi-document streams (----separated) outline as an array of documents. |
| token-goat yaml-query <file> <path> | Extract one value or a projected/filtered subset from a YAML document by dot-path instead of a raw Read (same grammar as json-query: [n] index, [*] wildcard, [field=value] filter — e.g. items[status=active].name). --head <n> caps a projected/filtered result. |
| token-goat xml-outline <file> | Structural summary of an XML document (element tag hierarchy, attribute keys, child counts) instead of a raw Read. |
| token-goat xml-query <file> <path> | Extract one value, element text/XML, or a projected/filtered subset from an XML document by XPath-like dot-path instead of a raw Read (same grammar as json-query/yaml-query: element tags, @attr, [n] index, [*] wildcard, [attr=value] filter, --head <n>). |
| token-goat json-outline <file> | Structural summary of a JSON document (array shape / object key types) instead of a raw Read. |
| token-goat json-query <file> <path> | Extract one value or a projected/filtered subset from a JSON document by dot-path instead of a raw Read: dot-separated keys with optional bracket segments — [n] index, [*] wildcard (projects every element/value), [field=value] filter. Examples: data.items[3].name, items[*].id, items[status=active]. |
| token-goat brief "file::symbol" | Bundle a symbol’s body, resolved callers (grouped by enclosing function), and its containing doc section into one round-trip instead of three separate read/callers/section calls. --limit <n> caps the callers shown per symbol (default 20; the true caller count is reported even when truncated). Comma-separated "file::a,b" fetches several symbols’ bundles from one file in a single call, mirroring read’s file::a,b multi-symbol grammar. Cross-file "a.ts::x,b.ts::y" bundles symbols from several files in one call, mirroring read’s cross-file grammar — a bare segment inherits the file to its left, and once more than one file is involved each bundle is keyed by the full file::symbol so two files contributing the same symbol name stay distinct. Also accepts read’s symbol@LINE anchor to pick out an otherwise-ambiguous candidate. -C, --context <n> adds N lines of real call-site source around each entry of the caller block. --json’s symbol.filePath and callers[].file render root-relative when a project root resolves, absolute when none does — matching the plain-text block above. --exclude-tests hides callers whose call site is in a test file, matching refs/callers; the caller count and the elided tail both count the filtered set, so they never disagree with the rows shown, and when the filter empties the block it says so instead of reporting a bare zero that would read as “nothing calls this”. --json adds hiddenByExcludeTests only when the filter actually hid something. --grep <pattern> narrows the caller block to callers whose enclosing symbol name matches this regex (literal substring if it is not valid regex), the same filter refs --grep/call-chain --grep apply to their own results — useful for a high-fanout symbol whose default 20-caller window is otherwise mostly noise; composes with --exclude-tests, and reports hiddenByGrep under --json only when it hid something. |
| token-goat scope "file:line" | Show symbols in scope at a given line — avoids reading the whole file to understand locals. |
| token-goat exports "file" | List public (exported) symbols with types, docstring hints, and line ranges ((lineStart-lineEnd) in text mode, lineStart/lineEnd fields under --json). Names caught only by the source-text scan (no corresponding index row — e.g. certain re-export forms) report no location: omitted from text mode, null under --json. Accepts a comma-separated file list ("a,b,c") to cover several files in one call, one clearly-headed block per file; extra space-separated file arguments are reported in a note naming that comma form instead of being silently dropped. --grep <pattern> only shows exported symbols whose NAME matches this regex (literal substring if it is not valid regex), applied before output is built; when it matches nothing among real exports, the output names the active filter instead of reading like the file has no exports at all. |
| token-goat refs "<name>" | Show all files and line numbers where a symbol is referenced. Pass a comma-separated spec (a,b,c or file::a,b) to merge several symbols’ references into one call, each group headed by its symbol name. Segments may also carry their own file (a.ts::x,b.ts::y) to merge references across several files in one call, mirroring read’s cross-file grammar — a bare segment inherits the file to its left, and once more than one file is involved each block is headed by the full file::symbol so two files contributing the same symbol name stay distinct. --top <n> groups references by file (count only) and shows just the top N by reference count with an elision note, instead of a per-line dump — for high-fanout symbols referenced in hundreds of places. -C, --context <n> shows N lines of real call-site source either side of each hit, rendered exactly like grep -C; omit it (or pass 0) and output is unchanged. Under --json each item gains a contextLines array alongside the existing context field (which names the enclosing symbol, not source text). --exclude-tests hides references whose call site is a test file (opt-in — omitted, output is unchanged); the summary line reports the filtered count plus how many were hidden. --grep <pattern> only shows references whose call-site FILE PATH matches this regex (literal substring if it is not valid regex) — rows render as file:line: symbol, so this is the field each row is keyed on. The pattern is tested against the path exactly as the row renders it, so an anchored --grep "^src/" matches what you see, identically in the single, multi-symbol and cross-file forms. Every form renders a call-site path the same way – root-relative when a project root resolves, absolute when none does, never cwd-dependent – and --json carries that same spelling in filePath (and in --top’s fileCounts[].file), so a payload is reproducible rather than tied to one machine’s drive-letter casing. The high-value case is narrowing a wide-fanout symbol to drop test/vendored hits. Applied before --top’s grouping and before any --limit slice, so it selects from the whole reference set, not an already-capped page; when it matches nothing among references that do exist, the output names the active filter instead of reading like the symbol is unreferenced. A bare name that isn’t indexed at all reports Symbol not found: <name> (with a Did you mean: suggestion when a near-name candidate is indexed) instead of the misleading “no references found”, which is reserved for a real, indexed symbol that genuinely has zero references. |
| token-goat callers <symbol> | Show which functions call a given symbol, grouped by caller with file, caller name, and every invoking line. Complements refs, which shows raw reference sites without grouping by enclosing function. Accepts file::symbol to disambiguate WHICH same-named definition is meant when several files define a symbol with that name — the file only narrows which definition, callers can still be found in any file. -C, --context <n> shows N lines of real call-site source either side of each hit, rendered exactly like grep -C; omit it (or pass 0) and output is unchanged. Under --json each item gains a contextLines array alongside the existing context field (which names the enclosing symbol, not source text). --exclude-tests hides callers whose call site is a test file (opt-in — omitted, output is unchanged); prints a note naming how many were hidden. --grep <pattern> only shows callers whose enclosing symbol NAME matches this regex (literal substring if it is not valid regex) — rows render as symbol<TAB>file:line, so this is the field each row is keyed on. Applied before the --limit slice, so it selects from the whole caller set, not an already-capped page; when it matches nothing among callers that do exist, the output names the active filter instead of reading like the symbol has no callers. A bare name that isn’t indexed at all reports Symbol not found: <name> (with a Did you mean: suggestion when a near-name candidate is indexed) instead of the misleading “no references found”, which is reserved for a real, indexed symbol that genuinely has zero callers. --json emits each item’s path under both file and filePath with the identical value; file is kept for this release only and will be removed in a future one, so filePath is the spelling to migrate to (matching symbol/types --json). --json emits the shared {items, truncated, totalCount} envelope — the same shape symbol/refs/skeleton/outline --json return, present whether or not truncation occurred, so a script never has to branch on shape. |
| token-goat call-chain <symbol> | Trace every caller layer from a symbol back to the entry points — one step deeper than callers. Use when you need to know what reaches a function across the whole call graph, not only who invokes it directly. Pairs with impact for the downstream direction. Accepts file::symbol to disambiguate WHICH same-named definition the chain starts from — the file only narrows which definition, callers can still be found in any file. --exclude-tests prunes callers whose call site is a test file BEFORE they’re admitted to the traversal, so nothing walks through a test node either (opt-in — omitted, output is unchanged); when every caller was a test, the “no callers” line names how many were hidden instead of reading as genuinely unreferenced. Symbol not found: <symbol> for an unindexed name (bare or file::symbol) now carries a Did you mean: suggestion when a near-name candidate is indexed. --grep <pattern> keeps only completed chains containing a symbol name matching this regex (literal substring if it is not valid regex) — the BFS still walks the full graph, this only narrows which finished chains are reported, so a chain passing through a matching symbol on its way to an unrelated root still surfaces; when it matches none of the chains that do exist, the output names how many were filtered out rather than reading as genuinely caller-less. |
| token-goat impact <symbol> | Walk the call-reference graph forward (breadth-first) and list every function that depends on a symbol, with hop depth; module-scope callers are surfaced as (module scope) <file> entries. Run before a refactor to size up the blast radius without starting a build. Accepts file::symbol to disambiguate WHICH same-named definition the walk starts from — the file only narrows which definition, callers can still be found in any file. --exclude-tests prunes callers (including module-scope entries) whose call site is a test file BEFORE they’re enqueued for further traversal, so nothing walks through a test node either (opt-in — omitted, output is unchanged); when every caller was a test, the “no callers found” error names how many were hidden instead of reading as genuinely unreferenced. A bare name that isn’t indexed at all reports Symbol not found: <name> (with a Did you mean: suggestion when a near-name candidate is indexed) instead of the misleading “no callers found”, which is reserved for a real, indexed symbol that genuinely has zero impact. --grep <pattern> only shows impacted entries whose symbol name (or (module scope) <file> key) matches this regex (literal substring if it is not valid regex), the same filter call-chain --grep/dead --grep apply to their own results — applied BEFORE the --top slice, so it selects from the whole impacted set rather than an already-capped page; when it matches none of the impacted entries that do exist, the output names how many were filtered out instead of reading as genuinely impact-free. |
| token-goat context-for <task> | Takes a natural-language task description, runs semantic search across the indexed codebase, and emits a prioritized list of token-goat read commands trimmed to a token budget. Fetches only the relevant slices instead of loading entire files. --budget N sets the token ceiling; --top N limits the file count; --json for structured output. Every emitted command carries the file::symbol@LINE anchor, so a suggestion still runs when the same symbol name has more than one definition in its file; --json entries carry the matching line field. |
| token-goat ask "<question>" (experimental) | Retrieves relevant slices via full-text (BM25) search over the symbol index — not semantic/embedding search — and lists them as pointer-citations plus token-goat read commands. Set TOKEN_GOAT_ASK_BACKEND=claude or TOKEN_GOAT_ASK_BACKEND=codex to synthesize a short answer via that CLI (whatever model it defaults to; token-goat does not force Haiku or any particular tier); with the env var unset, or the named CLI missing from PATH, ask degrades to printing the retrieved pointers with no network call. --top N caps the number of FTS hits (default 8); --json for structured output. Answers are not cached — each call re-retrieves and re-synthesizes from scratch. Every emitted command carries the file::symbol@LINE anchor, so a suggestion still runs when the same symbol name has more than one definition in its file; --json entries carry the matching line field. |
| token-goat changed [<ref>] | List files (or --symbol for symbols) changed since a git ref, without reading the full diff. <ref> and --since <ref> are equivalent (default HEAD~5); --since wins if both are given. --json for structured output. --grep <pattern> only lists changed files whose path matches this regex (literal substring if it is not valid regex) — applied to the file list even in --symbol mode, before any downstream slicing; when it matches none of the files that did change, the output names the active filter instead of reading like nothing changed. --exclude-tests hides changed files that live in a test file (opt-in — omitted, output is unchanged), completing the flag family already on refs/callers/dead/call-chain/impact/semantic/symbol. --grep can only ever select a path, so there was no reliable way to ask for the non-test half of a diff: the negative-lookahead regex that expresses “not a test” silently degrades to a literal substring match whenever the regex-compile fallback fires. Test files are a large share of a typical diff — measured against this repo, 35–54% of changed files across the last 5, 10 and 20 commits. Like --grep it filters the file path, so it applies in --symbol mode too, and it prunes before the per-file index lookup rather than after, so a test file is never queried at all. Composable with --grep (a file must satisfy both; when both are active and --grep is what emptied the list, the --grep notice takes priority, and when --grep left only test files so that --exclude-tests emptied it, the message names both filters rather than claiming no non-test file changed). When it hides every changed file there was, the output names how many were hidden and exits 0, rather than a bare “No files changed.” that would read as a clean diff. Every zero-row path emits the shared {items, truncated, totalCount} envelope under --json. |
| token-goat diff "file::symbol" [range] | Show only the git diff hunk(s) that fall within one symbol’s line range, e.g. token-goat diff "file.ts::myFn" HEAD~3..HEAD, instead of the whole file’s diff. Also accepts read’s symbol@LINE anchor to pick out an otherwise-ambiguous candidate. |
| token-goat blame "file::symbol" | Git blame narrowed to a specific symbol’s lines — no whole-file blame needed. Also accepts read’s symbol@LINE anchor to pick out an otherwise-ambiguous candidate. |
| token-goat log "file::symbol" [ref] | Git commit history scoped to one symbol’s line range via git’s own -L line-range history, instead of a raw git log -- file dump of every commit that touched the whole file. --max-count <n> caps commits shown (default 20); --json for structured output. Also accepts read’s symbol@LINE anchor to pick out an otherwise-ambiguous candidate. |
| token-goat types ["file"] | List type definitions (TypedDict, Protocol, dataclass, Pydantic models) in a file or across the project. --grep <pattern> only shows type declarations whose NAME matches this regex (literal substring if it is not valid regex), applied before output is built; when it matches nothing among declarations that do exist, the output names the active filter instead of reading like there are none. --exclude-tests hides type declarations DEFINED in a test file (opt-in — omitted, output is unchanged), the same definition-site sense dead --exclude-tests uses. Applied before the per-kind --limit slice, so the flag selects from the whole matching set rather than an already-capped page; when it hides every declaration there was, the output names how many were hidden and exits 0, instead of the exit-1 No type declarations found a genuinely empty scope returns. --json’s filePath renders root-relative when a project root resolves, absolute when none does, matching plain-text output. --json emits the shared {items, truncated, totalCount} envelope — the same shape symbol/refs/skeleton/outline --json return, present whether or not truncation occurred, so a script never has to branch on shape. |
| token-goat openapi-outline <spec> | Per-operation listing (method, path, operationId, summary, tags) of an OpenAPI 3.x / Swagger 2.0 spec (JSON or YAML) instead of a raw Read. |
| token-goat openapi-op <spec> <operation> | Full detail (parameters, request body schema, response schemas, description) for exactly one OpenAPI operation instead of a raw Read. operation may be an operationId (exact match) or a "METHOD path" spec, e.g. "GET /users/{id}". |
| token-goat sqlite-schema <db> | Tables/views, columns, indexes, foreign keys, and row counts of a SQLite database instead of a raw Read. |
| token-goat sqlite-query <db> "<SELECT ...>" | Run a read-only SELECT against a SQLite database instead of a raw Read or shelling out to sqlite3 — rejects any non-SELECT statement. |
| token-goat imports "file" | Show the import graph for a file one level deep. Accepts a comma-separated file list ("a,b,c") to cover several files in one call, one clearly-headed block per file; extra space-separated file arguments are reported in a note naming that comma form instead of being silently dropped. --grep <pattern> only shows imports whose MODULE SPECIFIER matches this regex (literal substring if it is not valid regex), applied before --json’s truncation; when it matches nothing among real imports, the output names the active filter instead of reading like the file has no imports at all. |
| token-goat dep-docs <package> | Extract one installed npm package’s README, package.json metadata, and (if resolvable) a compact .d.ts signature outline, instead of grepping node_modules. |
| token-goat find "<query>" | Find the FILES defining a symbol whose name matches a pattern: a case-insensitive substring scan over indexed symbol names, emitting the distinct file paths. When no name contains the pattern, falls back to an edit-distance match so a mistyped name still lands (getUserr → getUser) — the same ranking Did you mean: uses. The fallback runs only when the substring pass found nothing, so an exact match is never reordered or displaced, and a query near nothing still reports a clean miss instead of unrelated names. A recovered match names what it actually matched on stderr rather than silently answering for a name you didn’t type; --json marks it with fuzzy: true and matchedNames, both absent on an exact hit. --limit <n> caps the file count. Matches on NAMES only — for meaning-based search over file content use token-goat semantic. |
| token-goat similar "file::symbol" | Find the top-k symbols most similar to a given symbol, via full-text search over symbol names and bodies. Also accepts read’s symbol@LINE anchor to pick out an otherwise-ambiguous candidate. |
| token-goat test-for "file" | Find test file(s) for an implementation file and list their test functions. --json’s testFile renders root-relative when a project root resolves, absolute when none does, matching plain-text output. --json emits the shared {items, truncated, totalCount} envelope — the same shape symbol/refs/skeleton/outline --json return, present whether or not truncation occurred, so a script never has to branch on shape. |
| token-goat dead | Surface functions, methods, and classes with no recorded callers in the project index. Private names and common entry points (main, app, etc.) are excluded by default. --include-private lifts the underscore filter; --kind narrows to specific symbol types, comma-separated for a union (--kind function,method) — an unrecognized kind errors instead of silently reading as a clean codebase; --top N caps output; --json for structured output. --exclude-tests hides dead symbols DEFINED in a test file (opt-in — omitted, output is unchanged); prints a note naming how many were hidden. --grep <pattern> only shows dead symbols whose NAME matches this regex (literal substring if it is not valid regex), applied before --top’s slice; when it matches nothing among dead symbols that do exist, the output names the active filter instead of reading like a genuinely clean codebase. Results are a heuristic lead — dynamic dispatch and external callers are invisible to static indexing. --json emits both file and filePath with the identical value, root-relative when a project root resolves, absolute when none does, matching plain-text output; file is retained for this release only and will be removed in a future release, filePath is the spelling to migrate to (matching symbol/types --json). --json emits the shared {items, truncated, totalCount} envelope — the same shape symbol/refs/skeleton/outline --json return, present whether or not truncation occurred, so a script never has to branch on shape. |
| token-goat coverage-gaps | Find callables in non-test source files that never appear in a test file’s reference records. Useful for spotting untested surface area before a refactor or release. --top N caps output; --json for structured output. |
| token-goat recent [N] | Show the N most recently edited/accessed files with their symbols. |
| token-goat grep "<pattern>" [paths...] | Built-in fallback regex search over files (no rg shell-out, no caching) — session-aware dedup for raw rg/grep Bash calls is a separate hook, not this command. Accepts zero or more paths: omit to walk cwd, or pass several to search them together with hits merged in argument order under one --max-lines cap. -C, --context <n> shows n lines before and after each match. --symbol annotates each hit with its enclosing indexed symbol — ` [name (kind)] appended in text mode, a symbol: {name, kind, lineStart, lineEnd} | null field per item under –json — null/no tag when the hit falls outside any indexed symbol (e.g. module-level code). |
| token-goat semantic “
Missed lookups recover surgically: read and section print a “Did you mean…?” list on a miss, and section auto-redirects on an unambiguous heading-prefix match — a typo costs at most one extra glance, not a re-read.
Skill efficiency — the <!-- COMPACT_END --> marker
When Claude Code invokes a skill, it re-injects the full skill body on every subsequent turn. A large skill file (e.g. a 10k-token /improve or /ralph) can cost 40–65k tokens per session across 6 active skills. The <!-- COMPACT_END --> marker solves this: place it in any skill file to split it into a compact form (above the marker, ~400 tokens) and a reference section (below). Token-goat detects the marker the first time the skill fires, caches only the compact slice, and injects that from then on — labelled --- compact form (N tokens) --- so the model knows to request the full body only when it needs the detail.
To add the marker to a skill, open the file and insert <!-- COMPACT_END --> on its own line where the “quick reference ends and the detail begins” — typically after the quick-start table and before step-by-step instructions. The full reference section is still reachable via token-goat skill-section "<name>::<heading>" or token-goat skill-body <name> when needed.
Re-load and direct-read protection. Even without the marker, token-goat protects against the two other ways large skills burn context in a long session:
- If the model tries to
Reada skill file directly (~/.claude/skills/improve/SKILL.md), the pre-read hook intercepts it and emits atoken-goat skill-body improvehint instead — the full 10k–65k tokens never enter context. - If the same skill is invoked a second time in the session (e.g.
/improvecalled again after a/compact), re-load detection fires: instead of re-caching the full body, token-goat emits the cached token count andskill-body/skill-sectionrecall hints. The model can retrieve any section it actually needs rather than absorbing the whole skill again.
To check overhead for your current skills: token-goat skill-size. To inspect compact freshness, run token-goat skill-list — the compact_stale column shows [stale] when a skill’s compact was generated from an older version of the file. Run token-goat skill-compact --all to refresh every stale compact in the current session in one pass.
token-goat install now pre-generates compacts for all installed skills as its final step, so compacts are ready from the first session. If you install new skills after the initial install, run token-goat skill-compact --all manually — or check token-goat doctor --context which reports how many skills were added since the last pre-gen pass and shows the exact command to run.
Memory analysis and cleanup
token-goat memory audits the CLAUDE.md files Claude Code loads for a project for wasted tokens: exact-duplicate lines within one file, duplicate headings, content that overlaps verbatim across files, and near-duplicate sibling auto-memory files (~/.claude/projects/<slug>/memory/*.md). Default mode is --analyze (read-only):
$ token-goat memory
# token-goat memory
Project: C:\Projects\example
## CLAUDE.md files (1)
C:\Projects\example\CLAUDE.md (842 tok)
exact-duplicate lines: 1
line 40 duplicates line 12: "Always run the full test suite before committing."
duplicate headings: none
cross-file overlaps: none
## Duplicate-content clusters (sibling auto-memory files)
none
--fix builds on --analyze. The only change it can apply automatically is removing exact-duplicate lines (keeping the first occurrence) — a pure structural dedup with no judgment call. Duplicate headings and cross-file overlaps are printed as advisory findings only; they often mean content should move into a path-scoped .claude/rules/ file or a subdirectory CLAUDE.md, but token-goat never picks where for you, so no diff is proposed for those.
Every proposed exact-duplicate-line fix is shown as a diff before anything is written, gated by the same confirm-before-write flow: pass --yes to apply non-interactively (scripts, CI), or run it from a terminal without --yes to be prompted per file. Running --fix without --yes from a non-interactive shell (no TTY) prints the diffs as a dry run and writes nothing.
Session waste ledger
token-goat waste parses the current project’s Claude Code session transcript — the JSONL file Claude Code writes under ~/.claude/projects/<slug>/*.jsonl — and attributes token cost to every tool call in it, then flags a few concrete waste signals: files that were Read once and never referenced again, and Bash commands run repeatedly without ever hitting token-goat’s own bash-output cache. By default it auto-discovers the most-recently-modified transcript for the current project; pass --transcript <path> to point at a specific one instead (useful when several sessions are open, or for CI/testing):
$ token-goat waste
# token-goat waste
Transcript: C:\Users\you\.claude\projects\C--Projects-example\a1b2c3d4-....jsonl
Total tokens: 18420
## Tokens by tool
Read: 9120 tok
Bash: 6210 tok
Grep: 2140 tok
Edit: 950 tok
## Top expensive tool calls
[3400 tok] Read: src/big_module.ts
[1800 tok] Bash: npm test
## Read once, never touched again
src/unrelated_helper.ts: 640 tok, never referenced again
## Repeated Bash commands not hitting the token-goat cache
"git status": ran 4 times, 210 tok each, 840 tok total, uncompressed
## Assistant output (re-send CEILING, not real spend)
42 turns, 21300 tok generated
Re-send upper bound: 187400 tok if every turn were resent at full price on every later request
Real cost is substantially lower: prompt caching bills resent conversation history at cache-read rates, not full input price.
--top <n> controls how many entries appear under “Top expensive tool calls” (default 10). --json prints the same report as machine-readable JSON instead.
The “Assistant output” section is separate from the tool-call ledger above it: generatedTokens is what was actually paid, once, to produce the assistant’s own text turns. resendCeilingTokens is a cache-unaware upper bound on how much re-sending those turns as conversation history on every later request could cost — not real spend, since Claude Code’s prompt caching bills a repeated conversation prefix at cache-read rates, a fraction of full input price. Treat it as a ceiling on how bad unbounded verbosity could get, not as a dollar figure.
Cross-cache recall
token-goat recall "<query>" searches every cached bash-output, web-output, and mcp-output entry at once, so you don’t need to remember which cache type holds the result you want — a single full-text query ranks hits across all three:
$ token-goat recall "eslint warnings"
[bash] a1b2c3d4e5f6a7b8 (token-goat bash-output a1b2c3d4e5f6a7b8)
npx eslint src tests
npx eslint src tests\n[token-goat: delta] 2 of 5 prior issues resolved; remaining: 3
[mcp ] mcp_9f8e7d6c5b4a3210 (token-goat mcp-output mcp_9f8e7d6c5b4a3210)
mcp:mcp__plugin_github_github__get_check_runs {"owner":"..."}
... eslint warnings found in 2 files during CI ...
Results are ranked by relevance (BM25 via SQLite FTS5, falling back to a plain substring scan if FTS5 is unavailable), newest indexed entries win ties. --type bash|web|mcp narrows to one cache type; --limit <n> caps the result count (default 10); --json emits { id, cacheType, label, snippet, storedAt }[] instead. The index is built incrementally as entries are cached — there is no separate rebuild step.
Run token-goat recall with no query to browse instead of search: every cached entry across all three types, newest first, in the same format and honouring the same --type/--limit/--json flags. This is the case where the index matters most — the ids have scrolled out of context and you have no term to search for, so the alternative is running bash-history, web-history, and mcp-history in turn.
Hint efficacy tracking
Every hint hook (the re-read/dedup/surgical-read nudges in the Bash, Read, and Edit hooks) is
worth its keep only if it’s actually followed. token-goat hint-stats reports, per hint
category: how many times it fired, how many times a later Bash command in the same session
actually invoked the specific token-goat command (or referenced the specific cached-output id)
the hint pointed at, the resulting efficacy percentage, whether the category is currently
auto-suppressed, and the spent column (bytes of hint text actually injected into context for
that category — the real cost of emitting it, not just how often it fired):
$ token-goat hint-stats
category emitted acted-on efficacy suppressed manual+ manual- spent
bash_redirect 42 9 21.4% no 0 0 3150
bash_recall 18 15 83.3% no 0 0 1080
read_reread_dedup 11 2 18.2% no 0 0 660
read_structural_nav 7 1 14.3% yes 0 1 420
edit_reread_suggest 3 0 0% no 0 0 180
TOTAL saved=48200 spent=5490 net=42710
spent (and the TOTAL line’s spent/net) render n/a instead of a fake 0 whenever a
category — or, for the total, the whole store — has no tracked spend figure at all: either
nothing has fired yet, or every emission predates this feature and was recorded before spend
tracking existed. A partially-tracked category shows the real sum plus how many legacy rows it
excludes, e.g. 120 (2 legacy), rather than silently blending unknown-cost rows into the total
as if they cost nothing.
A category is auto-suppressed for its harness once it has at least hint_stats.min_sample_size
emissions (default 5) AND its efficacy falls below hint_stats.suppress_threshold_pct (default
15%) — the sample-size floor exists so a category is never suppressed off a single unlucky
emission. Once suppressed, that hook stops emitting that category until token-goat hint-stats
--reset clears the tracked data. Configure both knobs with token-goat config set hint_stats.min_sample_size <n> / token-goat config set hint_stats.suppress_threshold_pct <pct>.
“Acted on” is a real, session-scoped signal (the exact file path or cached-output id the hint’s
own text pointed at is checked against the next few tool calls in that session) — not a guess —
but it is a proxy for correlation, not proof of causation: a match means the agent ran the
suggested command shortly after the hint, not that the hint necessarily caused it. A hint whose
text has no extractable path/id (a small minority of branches) is counted as emitted with no
automatic “acted on” credit. --mark-effective <category> / --mark-ineffective <category>
record a separate manual vote as a human override/supplement for exactly that gap — manual votes
are shown alongside the automatic percentage but never blended into it. --json emits
{ category, emitted, actedOn, efficacyPct, suppressed, manualEffective, manualIneffective }[].
Note that what this feature calls “harness” (Claude Code, Codex, Gemini, …) is not the same as
“which LLM model” — no bridge in this codebase exposes an LLM model identifier to hooks, so
harness is the closest real signal available.
MCP server
token-goat mcp-serve
Runs token-goat as an MCP (Model Context Protocol) stdio server, exposing surgical-read tools (including brief, a one-shot symbol orientation call bundling body, callers, and containing doc section, and index_status, which reports whether a project’s index has ever been populated, current file/symbol counts, dirty-queue depth, worker liveness, and embeddings availability — call it after an unexpectedly empty result from another tool, since an MCP-only client has no hook layer to warn it apart from a genuine “not found”) plus compress_text, retrieve_text, handoff_create, and handoff_resolve. These local-only tools use bounded, redacted storage; MCP never intercepts a client’s built-in file reads.
Generic compression and handoffs
token-goat compress-text "text to keep locally"
token-goat retrieve tg_<id>
token-goat handoff-create review-notes "text for another agent"
token-goat handoff-resolve review-notes
token-goat handoff-resolve review-notes --full
token-goat compress-text returns a stable opaque ID, size metadata, an estimated token delta, and a recovery command. The deflate/base64url payload tokenizes far worse per byte than plain text, so for most real inputs inlining it costs more tokens than the original: it is printed only when it genuinely wins, and --payload forces it for the self-contained case where you need a blob that decodes on another machine. A negative tokens_saved is a cost, not a saving. token-goat retrieve restores the locally cached text. Handoffs are created with token-goat handoff-create and resolved with token-goat handoff-resolve; they are named and project-local, and resolve compactly by default or in full with --full. Content is limited to 512 KiB and stored in a bounded local cache with secret redaction. token-goat stats includes these outcomes alongside existing savings.
token-goat retrieve <id> takes the same output filters as bash-output/web-output/mcp-output: --head <n>, --tail <n>, --grep <pattern>, --max-matches <n>, --section <heading>, and --full. With no filter at all, retrieve prints the stored text byte-verbatim – this is its lossless round-trip contract and it never applies the siblings’ default head/tail elision. Pass any filter and it switches to sibling semantics (including elision) for that call, so a large stored blob can be recalled a slice at a time instead of taking all of it.
VS Code — add it to .vscode/mcp.json under the "servers" key (this is the correct root key for VS Code’s MCP config; it is not "mcpServers"):
{
"servers": {
"token-goat": {
"type": "stdio",
"command": "token-goat",
"args": ["mcp-serve"]
}
}
}
token-goat install --vscode creates or idempotently updates VS Code’s
user-profile mcp.json by default (%APPDATA%\Code\User\mcp.json on
Windows, ~/Library/Application Support/Code/User/mcp.json on macOS,
~/.config/Code/User/mcp.json on Linux) — add -p/--project for the
project-local .vscode/mcp.json shown above instead. Either way it also adds
a delimited block to .github/copilot-instructions.md, preserving unrelated
JSON and user text. It fails clearly on malformed JSON, and refuses to
install into one scope if the other scope already has a token-goat-managed
entry (registering it twice would duplicate its tool schemas in that
workspace). token-goat uninstall --vscode (add -p/--project for the
project scope) removes only token-goat’s server entry and guidance block.
The optional source-controlled extension lives in vscode-extension/. Build
and install its VSIX manually; --vscode intentionally does not copy or
install extensions:
cd vscode-extension
npm install
npm run compile
npx @vscode/vsce package
code --install-extension token-goat-vscode-0.1.0.vsix
Its commands call the local CLI and use workbench.action.chat.open to
prefill chat. They never submit chat automatically.
Installing the extension is an alternative to install --vscode, not an
addition to it: the extension contributes the MCP decoder itself through VS
Code’s mcpServerDefinitionProviders contribution point, so VS Code starts
token-goat mcp-serve on demand and there is no mcp.json to write and no
window to reload. That path needs VS Code 1.101 or newer, which the
extension’s engines field requires. install --vscode remains the way to
configure the decoder without the extension — for Copilot in an editor that
has no extension installed, or for any other MCP client.
If the extension is running somewhere that contribution did not take effect,
it falls back to calling token-goat mcp-status --vscode (add
-p/--project for the workspace scope too) to check whether mcp.json
already configures the decoder, and offers to run install --vscode if not —
the same path resolver install/uninstall write against, so the two can
never drift on where mcp.json lives or what key name it looks for.
Copilot CLI — add it to ~/.copilot/mcp-config.json:
{
"mcpServers": {
"token-goat": {
"command": "token-goat",
"args": ["mcp-serve"]
}
}
}
Caveat. Registering the server does not force any harness to prefer it. Unlike the hook-based bridges elsewhere in this project — which intercept a Read/Grep/Glob call before it reaches the model and can redirect or deny it outright — an MCP tool is just one more option in the harness’s own tool-selection decision. Copilot (or any other MCP-aware client) decides for itself whether to call token-goat’s read tool or fall back to its own built-in file-read tool; there is no interception mechanism for MCP the way there is for hooks.
What gets installed?
token-goat install writes the following on your machine — nothing else, anywhere. Every entry is reversed by token-goat uninstall. Integrations for other harnesses are additive on the way out as well as in, so a plain uninstall does not touch one you installed with --codex, --copilot, or a sibling flag: rather than undo something you did not ask about, it names each one still present and the flag that removes it. Run token-goat doctor at any time to see which of these are currently present.
Claude Code integration (~/.claude/)
| Path | What |
|---|---|
~/.claude/settings.json |
Hook entries for SessionStart, PreToolUse (Read/Grep/Bash, Drive/WebFetch), PostToolUse (Edit/Write/MultiEdit, Read/Grep/Glob, Bash, WebFetch, Skill), and PreCompact. Hook entries only: install writes nothing under permissions, so it never grants the agent unprompted execution of anything. Existing hooks are preserved; a timestamped .bak is written before any change.The PreToolUse and PostToolUse matchers are narrowed to exactly the tools token-goat handles (plus ^mcp__), generated from the live hook registry rather than a fixed list, so they can’t fall out of date as handlers change. Claude Code starts a new process per matcher hit and most of that cost is process startup, so a catch-all matcher would make every unrelated tool call — TodoWrite, TaskUpdate, and friends — pay for a hook that has nothing to do. |
~/.claude/hooks/token-goat-shim.js |
The hook script those settings.json commands invoke ("<node>" "<shim>" <event> "<entry>"). It imports the hook library in-process instead of spawning a second process, and naming the node binary directly skips the npm bin wrapper — on Windows a cmd.exe layer every hook would otherwise pay for. Measured 480 ms → 324 ms per hook call. Regenerated on every install run. Always written here even for a --project install, since the command bakes in machine-specific absolute paths; a project-scope settings.json just points at this one. |
~/.claude/CLAUDE.md |
A delimited block (<!-- token-goat-begin --> … <!-- token-goat-end -->) telling the agent to prefer token-goat read / symbol / section over Read / Grep. Any existing content is preserved. |
~/.claude/skills/token-goat/SKILL.md |
The token-goat skill — the same routing guidance in skill form. |
Background worker. token-goat does not register any persistent OS-level autostart entry — no Windows registry Run key, no systemd user unit, no XDG .desktop entry, and no macOS launchd .plist. The worker that drains the reindex queue is started manually as a detached child process: token-goat worker start launches node <npm-prefix>/lib/node_modules/token-goat/dist/token-goat.mjs --worker-daemon and returns immediately, and the child keeps running independent of the parent shell. token-goat worker status reports whether it’s running; token-goat worker stop kills it. If it crashes or is killed while the machine stays up, the next edit hook detects it’s gone and respawns it automatically (checked on every edit, rate-limited to roughly once every 5 minutes). It does not survive a reboot or logout, though — re-run token-goat worker start after either.
There is no auto-update mechanism. Updating token-goat is always a manual npm install -g token-goat@latest.
Data directory (created on first run)
| Platform | Path |
|---|---|
| Windows | %LOCALAPPDATA%\dfk-helper\token-goat\ |
| Linux / WSL | ~/.local/share/token-goat/ |
| macOS | ~/Library/Application Support/dfk-helper/token-goat/ |
Contains the symbol index (global.db, per-project .db files), session cache, shrunken-image cache, cached skill bodies (5 MB cap, LRU-evicted), logs, locks, and the dirty-file queue. Nothing outside this directory and ~/.claude/ is written.
What the index actually holds, in plain terms. The point of a surgical read is returning a function body without the file around it, which means the database stores those bodies. symbols.body holds the source text of every indexed symbol, symbols.docstring its doc comment, refs.context the line around each reference, and chunks.text the passages that semantic search embeds. There is also a full-text index over the bodies and docstrings. So the database is not a list of names and line numbers: it is a substantial copy of your source, sitting in a plain unencrypted SQLite file outside the repository, at the path in the table above.
Three things follow, and they are worth knowing before you decide. It never leaves the machine: token-goat sends no telemetry of any kind, and the only outbound requests it makes at all are the ones listed in the security section, none of which carry index content. It is not protected by your repository’s access controls any more, so anything on the machine that can read your home directory can read it, and on Linux and macOS that directory sits under a home that backup and sync tools routinely copy. And it outlives an uninstall unless you say otherwise: token-goat uninstall --purge deletes both roots and tells you how much it reclaimed.
With --codex (Codex CLI integration)
| Path | What |
|---|---|
~/.codex/config.toml |
Hooks block with Codex-specific matchers (view_image|Bash, apply_patch, web_search) plus PreCompact/UserPromptSubmit/SubagentStop global hooks. Existing hooks preserved. |
~/.codex/AGENTS.md |
A delimited block (<!-- token-goat-codex-begin --> … <!-- token-goat-codex-end -->) with the same routing guidance, adapted for Codex tool names. |
~/.codex/hooks/token-goat-shim.js |
The hook script config.toml’s hook commands invoke (node "<path>" <event>). Strips internal _tg_* keys and injects hookSpecificOutput.hookEventName to satisfy Codex’s strict schemas. Regenerated on every install --codex run. |
With --gemini (Gemini CLI integration)
| Path | What |
|---|---|
~/.gemini/settings.json |
Hook entries under Gemini’s BeforeTool, AfterTool, and PreCompress events, using Gemini’s own snake_case tool-name matchers (run_shell_command, read_file, grep_search, etc.). Existing hooks preserved; a timestamped .bak is written before any change. |
With --qwen (Qwen Code integration)
| Path | What |
|---|---|
~/.qwen/settings.json |
Hook entries under Qwen Code’s PreToolUse, PostToolUse, PreCompact, UserPromptSubmit, and SubagentStop events (Claude-Code-native names and payload shape, not Gemini’s), using a catch-all matcher per event. Existing hooks preserved; a timestamped .bak is written before any change. |
With --kimi (Kimi Code integration)
| Path | What |
|---|---|
~/.kimi-code/config.toml |
[[hooks]] entries for Kimi Code’s PreToolUse, PostToolUse, PreCompact, UserPromptSubmit, SubagentStop, and SessionStart events. Each entry carries only event and command, the keys Kimi Code’s strict schema accepts. Existing hooks and other config keys preserved; a timestamped .bak is written before any change. |
~/.kimi-code/hooks/token-goat-shim.js |
The hook script those commands invoke. Rewrites a token-goat block into hookSpecificOutput.permissionDecision and a hint into a top-level message, and writes empty stdout for a no-op. Regenerated on every install --kimi run. |
~/.kimi-code/AGENTS.md |
A delimited block (<!-- token-goat-kimi-begin --> … <!-- token-goat-kimi-end -->) with the routing guidance, adapted for Kimi Code tool names. |
~/.kimi-code/skills/token-goat/SKILL.md |
The same guidance as a Kimi Code skill. |
With --opencode (opencode plugin)
| Path | What |
|---|---|
~/.config/opencode/plugins/token-goat.ts (Linux/macOS) or %APPDATA%\opencode\plugins\token-goat.ts (Windows) |
TypeScript bridge plugin. Fires on tool.execute.before, tool.execute.after, and experimental.session.compacting. Covers image shrinking, post-edit indexing, and compact assist. |
With --pi (pi extension)
| Path | What |
|---|---|
~/.pi/agent/extensions/token-goat.ts |
TypeScript extension (default-exported ExtensionAPI factory). Subscribes to session_start, tool_call, tool_result, session_before_compact, and session_compact. Covers bash compression, re-read denial, pressure-scaled surgical-read redirects for oversized first reads, image shrinking, post-edit indexing, output caching, and the compaction manifest. A project-local install writes <project>/.pi/extensions/token-goat.ts instead. |
With --copilot (Copilot CLI hook bridge)
| Path | What |
|---|---|
~/.copilot/hooks/token-goat.json |
Hook config ({ version, hooks }) registering preToolUse, postToolUse, preCompact, agentStop, and subagentStop, each pointing at the shim script below. Existing files elsewhere in the hooks directory are untouched. |
~/.copilot/hooks/token-goat-shim.js |
The shim token-goat.json’s hook commands invoke (node "<path>"). Translates Copilot’s event names and response schema (permissionDecision/modifiedArgs, additionalContext) to/from token-goat’s internal hook protocol. Regenerated on every install --copilot run. A project-local install (--copilot --local) writes <project>/.github/hooks/token-goat.json and <project>/.github/hooks/token-goat-shim.js instead. |
~/.copilot/copilot-instructions.md |
A delimited block (<!-- token-goat-begin --> … <!-- token-goat-end -->) with the same routing gate written to ~/.claude/CLAUDE.md and ~/.codex/AGENTS.md, naming Copilot CLI’s own view/grep/glob tools in the conflict-resolution clause. Merged idempotently — everything outside the markers is preserved byte-for-byte. A project-local install (--copilot --local) writes <project>/.github/copilot-instructions.md instead. |
With --grok (Grok CLI / xAI Grok Build hook bridge)
| Path | What |
|---|---|
~/.grok/hooks/token-goat.json |
Hook config ({ hooks }) registering PreToolUse, PostToolUse, PreCompact, UserPromptSubmit, and SubagentStop with an empty (match-everything) matcher, each pointing at the shim script below. Existing files elsewhere in the hooks directory are untouched; global scope only (Grok’s project-scoped .grok/hooks/ requires a separate manual /hooks-trust grant). |
~/.grok/hooks/token-goat-shim.js |
The shim token-goat.json’s hook commands invoke. Translates PreToolUse’s deny shape only ({"decision":"block",...} → Grok’s documented {"decision":"deny",...}, plus exit code 2); every other event’s response is forwarded unmodified. Regenerated on every install --grok run. |
With --vscode (VS Code MCP configuration; user scope by default, -p/--project for the workspace)
| Path | What |
|---|---|
%APPDATA%\Code\User\mcp.json (Windows) / ~/Library/Application Support/Code/User/mcp.json (macOS) / ~/.config/Code/User/mcp.json (Linux) — or <project>/.vscode/mcp.json with -p/--project |
Merges the token-goat stdio entry under VS Code’s servers root key, preserving unrelated servers and settings. Refuses to write if the other scope already has a token-goat-managed entry, to avoid a duplicate registration. |
<project>/.github/copilot-instructions.md |
Adds a delimited VS Code routing block that documents supported MCP selection and explicitly says MCP does not intercept built-in file reads. |
With --hermes (Hermes Agent integration)
| Path | What |
|---|---|
~/.claude/settings.json |
No new entries beyond the base Claude Code install. Hermes delegates tasks to Claude Code via claude -p '<task>', which loads hooks from this file normally. token-goat install --hermes verifies the hooks are present and reports the result. To remove the Hermes detection: token-goat uninstall --hermes (removes no files — Hermes shares the Claude Code hook entries). |
With --openclaw (OpenClaw plugin)
| Path | What |
|---|---|
~/.openclaw/plugins/token-goat.ts |
TypeScript bridge plugin (definePluginEntry registration). Subscribes to session_start, session_end, before_tool_call, after_tool_call, and before_compaction. Covers bash compression, re-read denial, pressure-scaled surgical-read redirects for oversized first reads, image shrinking, and post-edit indexing. Not validated against a live OpenClaw instance — see README’s “openclaw users” section. |
~/.openclaw/openclaw.json |
Adds the plugin path to plugins.load.paths and an entry to plugins.entries.token-goat. Existing config preserved; a timestamped .bak is written before any change. |
Zero maintenance
Hooks fire automatically on every tool call once installed — nothing to start or restart there. The background worker is a separate, manual step: token-goat worker start launches it as a detached process, token-goat worker status checks it, token-goat worker stop kills it. It restarts itself automatically if it crashes or gets killed while the machine is running — an edit hook checks its liveness and respawns it, rate-limited to about once every 5 minutes. It does not survive a reboot or logout, though; re-run worker start after either. token-goat uninstall removes the hook entries, CLAUDE.md block, and skill directory, but does not touch a running worker — stop it separately with token-goat worker stop if you no longer want it running.
Verify
token-goat doctor
token-goat stats
doctor confirms the install is healthy. stats shows cumulative savings.
Confirming hooks are wired
doctor checks the binary, worker, database, and disk. It does not inspect settings.json hook wiring. To confirm all three hooks are present, re-run install:
token-goat install
install is idempotent: it adds any missing entries and leaves existing hook entries alone. Re-running it is always safe. To inspect the wiring directly:
# Linux / macOS / WSL
jq '[.hooks | to_entries[] | select(.value[].hooks[]?.command | contains("token-goat hook")) | .key]' ~/.claude/settings.json
# Windows (PowerShell with jq on your PATH)
jq '[.hooks | to_entries[] | select(.value[].hooks[]?.command | contains("token-goat hook")) | .key]' "$env:USERPROFILE\.claude\settings.json"
A working install returns ["PreToolUse", "PostToolUse", "PreCompact"]. Any missing event means a partial install; run token-goat install to fill it in.
AI agents: after token-goat install, run the jq check above or re-run token-goat install before marking the install complete. doctor does not verify hook wiring.
Image support
Token-goat shrinks large images before they reach the model, cutting vision token costs by 60–90%. The pipeline uses sharp, a Node.js image processing library that ships prebuilt native binaries for Windows, macOS, Linux, and Alpine.
On most platforms, npm install -g token-goat installs sharp without additional steps. npm pulls a prebuilt binary keyed to your Node.js major version and OS — no C++ compiler, libvips, or system codec libraries required.
Quick check (any platform):
token-goat doctor
If the sharp line shows OK, you’re done.
Image support — troubleshooting
If token-goat doctor reports sharp: FAIL, the most common cause is a cached binary built against a different Node.js version. A fresh install usually fixes it:
npm install -g token-goat@latest
token-goat doctor
On Alpine Linux, some ARM boards, and air-gapped environments, npm can’t fetch a prebuilt binary and falls back to compiling from source. That requires libvips and C++ build tools:
# Debian / Ubuntu / WSL
sudo apt-get install -y libvips-dev build-essential
# Alpine
apk add --no-cache vips-dev build-base python3
# Fedora / RHEL
sudo dnf install -y vips-devel gcc-c++ make
After installing the system packages:
npm install -g token-goat@latest
token-goat doctor
For platform-specific build details, see the sharp installation docs.
Stats display
token-goat stats uses 24-bit ANSI color and Unicode block characters for gradient bars, sparklines, and the activity heatmap. In the right terminal it renders sharply. In the wrong one you get broken characters, flat gray blocks, or a “rich is not installed” error.
When it’s working, the output shows rounded box borders (╭─╮), gradient bars with fractional edges (▏▎▍▌▋▊▉█), sparklines (▁▂▃▄▅▆▇█), and a heatmap where cells step from dark to bright green. Question marks, boxes, or solid-color bars mean the terminal or font needs fixing.
Stats display — Windows
The old Windows console host — cmd.exe, the legacy “Windows PowerShell” app — does not support 24-bit color. Windows Terminal does.
Step 1: Install Windows Terminal (already on Windows 11; skip if you have it)
winget install --id Microsoft.WindowsTerminal -e --silent
Step 2: Set it as the default terminal (Windows 10 only — Windows 11 handles this automatically)
Open Windows Terminal → Ctrl+, → Startup → Default terminal application → Windows Terminal → Save.
Step 3: Confirm the font
Windows Terminal ships with Cascadia Code, which covers every character token-goat uses. No additional install needed. To confirm it’s selected: Ctrl+, → Profiles → Defaults → Appearance → Font face should read Cascadia Code or Cascadia Mono.
If you prefer a Nerd Font, download any variant from nerdfonts.com, install it, and select it in the font preference above.
If bars still look flat (solid single-color blocks instead of a gradient), add to your PowerShell profile ($PROFILE):
$env:COLORTERM = "truecolor"
Stats display — macOS
Terminal.app on Catalina and later, iTerm2, and the VS Code integrated terminal all handle truecolor and Unicode without configuration. Most users need nothing here. (macOS is untested — see the badge at the top.)
If sparklines or box borders show as question marks or plain dashes, install a complete font:
brew install --cask font-jetbrains-mono-nerd-font
Set it in your terminal’s font preferences and reopen.
If colors look flat, add to ~/.zshrc or ~/.bash_profile:
export COLORTERM=truecolor
Stats display — Linux / WSL
WSL users: you’re running inside Windows Terminal. Follow the Windows steps above — same terminal, same font.
SSH sessions: the remote shell doesn’t inherit truecolor from the local terminal. Add to ~/.bashrc on the remote machine:
export COLORTERM=truecolor
export TERM=xterm-256color
Missing Unicode characters: any Nerd Font covers everything token-goat uses.
# Ubuntu / Debian
sudo apt install fonts-jetbrains-mono
# Arch
sudo pacman -S ttf-jetbrains-mono-nerd
Stats display — AI automated setup
Scripts for non-interactive setup. No prompts.
Windows (PowerShell)
# 1. Install Windows Terminal if absent
if (-not (Get-Command wt.exe -ErrorAction SilentlyContinue)) {
winget install --id Microsoft.WindowsTerminal -e --silent
}
# 2. Set Windows Terminal as the default console host
# UI equivalent: Windows Terminal -> Ctrl+, -> Startup -> Default terminal application -> Windows Terminal
# GUIDs are for Windows Terminal stable release
reg add "HKCU\Console" /v DelegationConsole /t REG_SZ /d "{E12CFF52-A866-4C77-9A90-F570A7AA2C6B}" /f
reg add "HKCU\Console" /v DelegationTerminal /t REG_SZ /d "{E12CFF52-A866-4C77-9A90-F570A7AA2C6B}" /f
# 3. Enable truecolor for the current session and persistently for the user account
[System.Environment]::SetEnvironmentVariable("COLORTERM", "truecolor", "User")
$env:COLORTERM = "truecolor"
# 4. Verify
token-goat stats
macOS / Linux / WSL (bash)
OS="$(uname -s)"
# Install a complete font
if [[ "$OS" == "Darwin" ]]; then
command -v brew &>/dev/null && brew install --cask font-jetbrains-mono-nerd-font
elif [[ "$OS" == "Linux" ]]; then
command -v apt-get &>/dev/null && sudo apt-get install -y fonts-jetbrains-mono
command -v pacman &>/dev/null && sudo pacman -S --noconfirm ttf-jetbrains-mono-nerd
fi
# Enable truecolor — appends only if not already present
RCFILE="${HOME}/.zshrc"
[[ -f "${HOME}/.bashrc" ]] && RCFILE="${HOME}/.bashrc"
grep -q "COLORTERM=truecolor" "$RCFILE" || echo 'export COLORTERM=truecolor' >> "$RCFILE"
grep -q "TERM=xterm-256color" "$RCFILE" || echo 'export TERM=xterm-256color' >> "$RCFILE"
# shellcheck disable=SC1090
source "$RCFILE"
# Verify
token-goat stats
Truecolor check (any platform)
Run this if the stats output still looks wrong. A smooth green gradient from left to right means truecolor is active. Solid single-shade green means it isn’t.
node -e "for(let r=0;r<256;r+=32)process.stdout.write('\x1b[48;2;0;'+r+';0m ');process.stdout.write('\x1b[0m\n')"
Security, privacy, and uninstall
No telemetry. No analytics. No background reporting or silent outbound connections.
Outbound network is reserved to these explicit cases:
- Google Drive API calls, only if you already authorized Drive in Claude Code. Token-goat never prompts for its own auth.
- Image fetches from URLs: either explicit via
token-goat fetch-image <url>, or when the AI agent issues a WebFetch call that returns image content — the hook intercepts and shrinks the image. The URL always originates from the agent’s work, not from token-goat itself. token-goat screenshot <url>navigates a headless browser to the URL you give it, subject to the target restrictions described below.- The first
token-goat semanticrun on a machine downloads the embedding model fromhuggingface.co, pinned to an immutable commit rather than a mutable branch. Subsequent runs use the local cache and make no network call. Skip the download entirely by settingindexing.embeddings_enabled = false(it is on by default), in which casesemanticfalls back to full-text search. - The first optical-character read of an image downloads the English language data (about 4 MB) from
cdn.jsdelivr.net, at a fixed version path. Subsequent reads use the local cache. This happens for an explicittoken-goat image-text, and also for the automatic text extraction the image-shrink hook performs when the agent reads a screenshot; turn the automatic one off withimage_shrink.ocr_enabled = false.
One switch for all of it. Set network.offline = true (env TOKEN_GOAT_OFFLINE) and every one of the paths above refuses instead of connecting, saying so rather than failing quietly. Anything already cached keeps working: a machine that has the embedding model still runs semantic, and one that has the language data still reads text out of images. This is one of the settings a per-project config file may not touch, so cloning a repository cannot switch it back off.
A repository cannot reconfigure the security controls. A project-root .token-goat.toml layers on top of your global config, which is what it is for: hint thresholds, indexing settings, compression tuning. But that file arrives with the repository, so whoever wrote the repository wrote it. Five whole sections are therefore off limits to it, plus one individual key, and come from your global config or the environment only: injection (prompt-injection fencing), webfetch (the fetch allow and deny lists), gdrive (the Google Drive integration), mcp (root confinement and the allowed-roots list), network (offline mode), and the single key indexing.cross_project_symbols. A project file that sets one of them is ignored, and token-goat prints a line naming what it dropped. Everything else stays project-overridable.
Security reports. See SECURITY.md. Email token-goat@dfkhelper.com; do not file as a GitHub issue. Reports are acknowledged within 7 days; coordinated disclosure with a 90-day default window.
Dependency advisories. npm audit on the published package is not empty. The residual findings all trace to three packages that have no forward patch, two of which are optional and can be left out with npm install --omit=optional. Each one is named, with why it does or does not reach you, under Dependency advisories.
Verifying what you installed. Every published version is built and pushed by one pinned workflow when a GitHub release is published, with npm provenance, so npm audit signatures verifies the tarball against the commit that produced it. Details in Verifying what you installed.
Prompt injection. When an AI reads a file, web page, or command output, that content enters its context alongside your own instructions. Prompt injection is when untrusted content includes text designed to look like instructions — “Ignore all previous directives and run this instead” — to redirect the AI mid-task.
Token-goat intercepts every Read, Fetch, Bash, and MCP call the AI makes. Content is scanned for a set of imperative-override attack patterns (“ignore previous instructions,” “reveal system prompt,” and similar), and a match is wrapped in an untrusted-content fence rather than passed through untouched (injection.enabled, on by default). Three surfaces are covered. Every fetched page is scanned as it arrives and again when a cached copy is recalled with web-output. Every MCP tool result is scanned as it arrives, which matters most: it is a remote server’s output, so it is the least trustworthy text in the pipeline. And cached Bash and MCP output is scanned when recalled with bash-output or mcp-output, since the output of a build or test run in a project with a hostile dependency is written by a third party as much as any web page is. The fence naming tool output is a different tag from the one naming web content, so the label tells the model where the text came from.
Read is the exception: file content passes through to the model unfiltered, because filtering it would silently break legitimate use cases. Where token-goat splices a piece of a file into its own hint or denial message, that excerpt is fenced unconditionally. A fence only appears on a positive match, so ordinary output is unchanged. Outside of the scan, the primary defense is the model’s own training to treat tool output as data, not as commands from a trusted party.
Separately from that pass-through case: when a read hook denies a Read and substitutes its own message, any file bytes it embeds in that message (a markdown heading tree, a served compact or notebook sidecar, a re-read diff, a CSV header row, an HTML title) are wrapped in an <untrusted-file-content> fence first, so a hostile repo cannot get its own text presented to the model as token-goat speaking. That fencing is unconditional, not gated on the pattern scan.
The MCP tools (symbol when given a file filter, read, section, skeleton, outline, refs, brief, grep, imports, exports) are confined to the project root, resolving symlinks before the check. Set mcp.confine_reads_to_project_root = false (env TOKEN_GOAT_MCP_CONFINE_READS) in your global config if you genuinely need cross-root reads from an MCP client; a per-project file cannot set it. The CLI is deliberately unconfined and unchanged. This is defense in depth for one sink, not a sandbox: an agent that can call these tools can usually call its own read tool too.
Note what that flag does and does not cover. It stops a caller traversing out of the root it is given; it does not constrain which root the caller supplies. Every MCP tool takes an optional projectRoot, and it exists for a reason — the server’s cwd is often not the workspace root for MCP clients — but tool arguments are model-generated, so that choice is untrusted input like any other. If your deployment treats MCP as the only path to the filesystem, set mcp.allowed_roots (env TOKEN_GOAT_MCP_ALLOWED_ROOTS, delimiter-separated like PATH) to the roots that may legitimately be named; a resolved root outside every entry is then refused. It is empty by default, which keeps the multi-root behaviour above unchanged.
Restricting what token-goat may fetch. webfetch.allow and webfetch.deny (env TOKEN_GOAT_WEBFETCH_ALLOW / TOKEN_GOAT_WEBFETCH_DENY, comma-separated) are wildcard URL patterns that decide which addresses may be reached. Deny is checked first and wins; a non-empty allow list refuses anything it does not name. Patterns are matched against the address as it will actually be sent, not only as you typed it, so a trailing dot on the host, .. path segments, a default port written out, and percent-encoded path characters cannot be used to step around a rule. Writing a default port in a pattern (https://example.com:443/*) and omitting it are equivalent. Both are empty by default, which permits everything, exactly as before. They apply to the WebFetch call your AI makes, to the fetches token-goat performs itself (fetch-image, gdrive-sections), and to the headless browser behind screenshot (whose page sub-resources are checked too), including every redirect hop, so an allowed site cannot redirect the request on to a denied one.
Where cached content lives, and who can read it. Cached command output, fetched pages, MCP results, session state and the source index all sit under one data directory (~/.local/share/token-goat on Linux, ~/Library/Application Support/token-goat on macOS, %LOCALAPPDATA%\dfk-helper\token-goat on Windows). On POSIX that directory is created owner-only (mode 0700), and an existing one is tightened on the next run, so other local users on a shared build host cannot read it. Windows uses inherited ACLs instead. Individual JSON blobs are additionally written 0600.
Confining symbol to one project. token-goat symbol is the one read command that answers from the machine-wide index (global.db) rather than the current project, so by default symbol <name> and symbol --grep . return matching symbols, bodies included, from every project ever indexed on the host. That is deliberate and useful on a personal machine: it is how you find a helper you wrote in another repo. On a shared build host, or under an agent you have confined to one directory, it is a read channel that the directory sandbox does not close, because the answer comes out of the index instead of the filesystem. Set indexing.cross_project_symbols = false (env TOKEN_GOAT_CROSS_PROJECT_SYMBOLS) and symbol only answers from the project it is run in. --project and --file pointing outside that project are refused rather than honored, so the setting cannot be stepped around from inside the confined process. Every other read command (read, refs, callers, types, dead, find, semantic) is already project-scoped and is unaffected.
Turning off the Google Drive integration. token-goat gdrive-sections is the only feature that talks to Google. Set gdrive.enabled = false (env TOKEN_GOAT_GDRIVE_ENABLED) and the command refuses before it opens a connection, and the routing guidance token-goat writes into CLAUDE.md, AGENTS.md, copilot-instructions.md and the installed skill stops naming it, so an agent is never told the command exists. Nothing else in token-goat contacts Google Drive, and it holds no Drive credentials: gdrive-sections fetches the public export URL of a document id you pass it by hand.
Secret redaction in cached content. Token-goat caches command output, fetched pages, and MCP results so it can serve them back later instead of re-running the work. Anything it writes to those caches is passed through a redactor first, so a credential that appeared in output does not sit on disk in plain text and does not get replayed into a later session. This is unconditional — there is no flag to turn it on, and it applies to cached Bash and Task output (including the command string itself, which is where an inline --token=... would otherwise land), fetched web content, MCP tool results and their labels, compress-text/handoff payloads, and the raw JSON disk cache. Recognized shapes: Anthropic, OpenAI, AWS, GitHub, Slack, Stripe, npm, and Google keys; JWTs; Authorization: Bearer/Basic headers; PEM private-key blocks; presigned-url signatures (AWS X-Amz-Signature, Google Cloud Storage X-Goog-Signature, Azure SAS sig); and generic password=/secret=/api_key= assignments in .env, connection-string, and query-string shape. A match is replaced by a [REDACTED:<kind>] marker naming which pattern fired.
Session state gets the same treatment. The per-session file records which urls were fetched and which curl -o downloads landed where, and a url carries credentials as readily as output does. The fetched-url list is redacted, so the compaction manifest can still name what was fetched without naming the key; the download list is keyed by a digest of the url instead, because its only consumer is an exact-match check and a redaction there would make two urls differing only in their key look identical. The fetched-url entry also redacts the prompt that was sent with the page, and carries a digest of the pair so redaction cannot merge two entries that differ only inside the redacted span. An entry written by an older version is rewritten into this shape the first time the file is read, so upgrading clears the credentials an old file was holding rather than keeping them for the life of the session.
Two honest limits. It is a pattern matcher, not a classifier: a credential in a format it does not recognize — an internal token shape, a bare high-entropy string with no key= prefix — is cached as-is. And it protects what token-goat stores, not what your agent reads in real time; a secret printed to the terminal was already in the model’s context before any caching happened. Treat it as damage control on the cache layer, not a reason to relax about printing secrets.
screenshot target restriction. token-goat screenshot and the MCP-adjacent screenshot path only navigate to http:/https: URLs; loopback, link-local, private, unspecified, and cloud-metadata addresses (including IPv4-mapped IPv6 and NAT64-encoded forms) are refused by default. Every redirect hop and sub-resource the page pulls in is re-validated against the same policy, and the hostname is resolved and the validated address pinned into the browser’s own resolver, so DNS rebinding — a name that resolves differently between the check and the browser’s own lookup — cannot slip a private address through. This is controlled by screenshot.block_private_targets (env TOKEN_GOAT_SCREENSHOT_BLOCK_PRIVATE_TARGETS), on by default. One limit remains: cross-host sub-resources (images, scripts, frames from a different host than the page itself) are resolved and checked but not pinned, so a record that changes between the check and the browser’s own lookup could still be followed for those.
In practice: if you’re reading files from untrusted sources or fetching unknown URLs during a session, pay attention to any actions the AI takes immediately after. Unusual follow-on behavior — opening files it wasn’t asked about, writing to unexpected locations — is a sign that something in the read content may have tried to redirect it.
Windows Defender (optional, Windows only). Real-time scanning slows indexing. To exclude the data folder, open PowerShell as administrator:
Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\dfk-helper\token-goat"
0x800106ba means the prompt is not elevated; reopen as administrator. On enterprise-managed Windows (domain-joined / Intune), Defender exclusions may be locked by Group Policy. The command will fail; that is expected and harmless.
Uninstall.
token-goat uninstall
Reverses everything in What gets installed?: the hook entries in settings.json, the CLAUDE.md block, the skill directory. Add --codex, --gemini, --opencode, --pi, --hermes, --openclaw, --copilot, --grok, or --vscode to also strip those integrations. It does not stop a running worker; use token-goat worker stop for that. Nothing else on the system depends on it.
By default the data directories stay: the index took real time to build and a reinstall wants it back. Add --purge to delete them as well, which is what offboarding a machine needs:
token-goat worker stop
token-goat uninstall --purge
That removes both roots (the data directory holding the index, caches, models and logs, and the home directory holding session state and the OCR cache), naming each one and how much it reclaimed. It refuses while the worker is running, because the worker would rewrite files under a directory being deleted.
About
I built this because long Claude Code and Codex sessions on my machine kept burning context in the same ways: screenshots landing at 2-3 MB, the agent re-reading a file it parsed hours earlier in the same conversation, compactions that forgot which functions were edited. Each felt preventable.
This is a solo project. I use it daily on Windows 11. The full test suite runs on Node.js 22 across Windows, macOS, and Linux, and all three must pass before anything merges.
Sister project
Token-Goat Mem is a companion project: durable, local-first memory for AI coding agents, with trust levels and anchor-based staleness detection, so a decision like “we use pnpm, not npm” survives a compaction instead of getting re-explained every session. Token-Goat controls what gets read into context; Mem controls what gets remembered across sessions. The two are independent — neither requires the other — but Mem has an optional one-way seam that feeds its facts into Token-Goat’s compaction manifest.
Requests and issues
Want token-goat to support something it doesn’t yet? Open a GitHub issue. Feature requests: a new agent CLI integration (Cline, Copilot Workspace, or any tool not yet covered), a new language adapter, or an image or document format the shrink path doesn’t compress yet. Issues are public and searchable. That’s where I work out what to build next. A short repro plus what you’d want the command to do is enough.
Bug reports go to the same place. The most useful ones include:
- Your OS, shell, and token-goat version (
token-goat --version) - The matching log line from
%LOCALAPPDATA%\dfk-helper\token-goat\logs\on Windows or~/.local/share/token-goat/logs/on Linux/WSL - What you expected and what actually happened
For private questions, commercial licensing, or anything you’d rather not post publicly, contact me at token-goat@dfkhelper.com.
Available for work
Senior or staff engineering. Developer tools, AI infrastructure, or context management.
I’ve spent months inside Claude Code’s hook system, session management, and compaction pipeline. Not reading the docs. Instrumenting them to see what was actually happening. The work is in this repo.
I build systems that run without babysitting, measure their own impact, and fail quietly. If you’re building tooling for developers who work with AI, reach out.
Disclaimer
Token-Goat runs on your machine and touches your files. The software is provided as-is, without warranty of any kind. DFK Helper LLC is not liable for any damages arising from use. Full terms, including the No Liability clause, are in the LICENSE file.
License
Token-Goat is licensed under the PolyForm Noncommercial License 1.0.0. See the LICENSE file for the full terms.
Individual developers may install and use Token-Goat on their own machines for personal productivity without a commercial license, provided the use does not involve providing Token-Goat as a service to others, incorporating it into a commercial product or platform, or deploying it as shared infrastructure across a team or organization. Employment at a for-profit company does not by itself make use commercial — but if your employer is the primary beneficiary of the deployment, a commercial license applies. When in doubt, email token-goat@dfkhelper.com.
Commercial use is reserved. That means copying or incorporating this codebase into a product, charging for access to it, or running it as shared infrastructure across a team at a for-profit company. Commercial licensing: token-goat@dfkhelper.com.
Copyright (c) 2026 DFK Helper LLC.
Patent Pending.