token-goat

by DFKHelperVerified

Token burn reducer and focus keeper for Claude Code, Codex, Copilot, Gemini CLI, and more: surgical read hints, PDF/Office/CSV/markdown file interception, 160+ filter & interception rules, compact manifest injection, image shrinking, cache and compact skills, cache MCP calls, prompt injection protections, and much more.

104
Stars
14
Forks
TypeScript
Language
8/24/2026
Added
View on GitHubDownload ZIP

⚠️ Third-Party Software Notice

This skill is third-party open-source software developed and hosted independently on GitHub. SkillTip is an informational directory and does not control or maintain the underlying repository. Any security checks displayed are automated and limited in scope. Review the source code before installing.

Read the Terms of Service

Installation

Add to your Claude Code skills directory:

# Add to your Claude Code skills
git clone https://github.com/DFKHelper/token-goat

Getting Started

Guides for using skills like token-goat.

Security Report

Verified

Last scanned: —

{
  "status": "PASSED",
  "issues": []
}

README.md


title: "AI Token Optimizer — Cuts Costs, Sharpens Focus, Blocks Prompt Injection" description: "Cuts AI tool costs 40–80% and guards against prompt injection. Stops re-reads, extracts one function vs. whole file, shrinks screenshots 97%." image: /token-goat/assets/goat-social.png permalink: /

Token-Goat

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.

npm CI status PolyForm Noncommercial

Windows 10 | 11 Linux including WSL macOS (untested) requires Node.js

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


Side-by-side comparison: a bloated workflow sends whole files and grows context every round, while token-goat sends only the needed lines and stays lean
Same requirements, smarter input: fewer input tokens, shorter answers, and context that stops compounding across rounds

token-goat stats display
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-GoatWith Token-Goat
3.3 MB screenshot lands in model context84 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-sessionUnified 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 editedStructured session manifest injected before compact
Same files re-read from scratch after /compactRecovery 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 sessionPreToolUse 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 --> presentFirst 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 sessionRe-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 BashPre-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 filePre-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 patternPre-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>.outputPre-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., `
pnpm/yarn/bun install or build dumps full outputpnpm, 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 dumpCapped at ~25k tokens; marker names the truncation ratio and narrowing command (symbolfile::Class.method; section → sub-heading; cached → --grep/--tail)
Full file read for one function or sectiontoken-goat read file::symbol, about 85% smaller
pytest dumps 150 PASSED lines + dots + tracebacksFailures-first view, 80 to 97% smaller
npm install floods deprecation warnings + spinnerErrors kept; warnings collapsed by package, ~90% smaller
docker build emits sha256 digests + transfer progressStep headers + errors kept; noise dropped, ~75% smaller
ruff / eslint / mypy repeat the same rule 50 timesGrouped by rule with first 3 examples, ~80% smaller
Same pytest / cargo / git log re-run mid-sessionSmall 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 matchesPre-Grep dedup hint quotes the prior match count
Grep in content mode repeats the same file path on every match linePost-Grep hook folds matches under one path header per file (lossless, path/line survive verbatim)
Same docs URL fetched twiceRe-fetch denied at warm+ context pressure (redirects to token-goat web-output <id>); advisory hint at cool
token-goat section pyproject.toml::tool.ruffOne TOML table extracted instead of the whole config; same for .yaml/.yml/.json/.ini/.cfg/.env/Dockerfile
Typoed token-goat symbol getUserrsymbol 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 linesFile-level summary: top 20 files by match count; full result cached, ~80% smaller
Same "already read" hint fires on every re-readSuppressed after first injection; SHA-256 fingerprinting prevents the same nag twice per session
A file already read in another session is requested unchangedWith 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 matchIt 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 sessionEscalating warning: "ran 2×" on repeat, "WARNING: ran N×" by the third; output always cached
Agent starts cold with no git context in a dirty repoBranch, change counts, and 5 recent commits injected at startup (~50 tokens)
Re-read hint shows only the line rangeHint includes previously-accessed symbol names: [symbols: login, refresh, …]
Manifest too large or unstructured after compactionManifest 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 changedPre-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 sessionPre-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 ReadFull 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 ReadFull 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 ReadFull read denied; redirects to pandoc for text extraction (no dedicated reader for these formats yet)
Large CSV or TSV file (≥10 KB) read in fullColumn 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 HTMLHTML-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 fullDuration, 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 fullLine 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 windowpre_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 whytoken-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 passedpre_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-shrinkInline 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 hintsA 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 onAgent 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 resultDeterministic 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 objectGitHub 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 entryBrowser 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 headersVerbose lines stripped; request line, HTTP status, content-type, and body kept — typically 70–90% smaller
jest --verbose / vitest --verbose emits one line per passing testConsecutive 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 testPASS 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 tracebackIntermediate frame pairs collapsed to a count; outermost frame, exception type, and message kept
tsc --noEmit emits hundreds of type errors across many filesErrors grouped by file, up to 3 examples per file shown, rest counted; ~70–90% smaller
make/cmake/ninja emits hundreds of [N%] Building … progress linesProgress lines collapsed to a count; warnings, errors, and Built target lines kept, ~85% smaller on clean builds
Command writes JUnit XML and prints the pathXML 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 fileMatching line truncated to 200 chars; filename and line number preserved
Claude Code writes async-task output to a temp filepre_read intercepts the path and redirects to token-goat bash-output <id> with --head/--tail/--grep support
Re-read hints fire immediately after conversation compactionGrace 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 sessiontoken-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 ignoreWhen 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 sessionsHint fingerprint includes file path; unchanged-file short-circuit skips re-read pre-check entirely
Bash dedup hints conflict with other compressiontoken-goat compress can be called as dedup-vs-hint filter; one-call access to cached output
Large manifest sections with no useful signalDrop empty sections, strip project name from paths (cleaner relative paths in manifest)
Manifest git-history section loses signal on clean mainInline git diffs + skip git log when on clean main branch; session-awareness improves manifest hygiene
Skill body lost after compaction but recovery too verboseRecovery hint deduped skills by content_sha (same skill loaded twice = one entry); inline skill checklist
Recovery hints omit critical paths when space is tightSkip bash snippet when recall available
AVIF format not supported despite better compressionAVIF image-shrink via sharp (when libvips is built with libaom); WebP fallback; codec auto-detection in docker
Token-savings invisible until you run statsToken-savings benchmark (slow-marked test suite) locks in measured wins; token-goat stats reports net-positive impact
Hook crash leaves agent waiting for responseEvery 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-sessionSession CAS + mtime-based retry prevent lost edits in manifest
Dirty queue appends corrupt on concurrent writesEach 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 crashMtime staleness check (>60s) auto-recovers zombie claim files
Compaction hook subprocess ~190 ms coldLazy imports of heavy modules in hooks_session and compact; compaction path ~110 ms cold (~42% faster)
Pre-compact subprocess runs on every sessionCompact-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 dirsgit 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 pluginsProvider 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 blockNoise attributes (id, arn, timeouts, tags) stripped per resource block; high-signal fields kept with a suppression note
kubectl events lists raw repetitive eventsEvents grouped by REASON with a per-group count; field-selector hint added to narrow scope
kubectl describe floods labels and annotationsLabels/annotations blocks collapsed to line counts; Conditions table kept in full; container resource fields preserved
npm install verbose output with sill/http/verb/spinner linesVerbose timing, sill, http, verb lines suppressed; warn lines beyond first 3 collapsed; braille spinner reify lines dropped
Fetched web content lands raw in model contextScanned 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 timestoken-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 versiontoken-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.

SourceImprovementMeasured impactWhere
Image shrinkWebP encoder beats JPEG on screenshot-shaped images~39% smaller than the same image at JPEG quality 85src/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 locationsDenser overview for the same byte budgetsrc/baseline.ts (buildProjectMap, token-goat map --compact)
DB reindexBatched 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-startLazy import of heavy modules; unknown events short-circuit86 ms → 30 ms (~65% faster); unknown-event dispatch <1 mssrc/hooks_cli.ts
Symbol start_lineTypeScript decorators captured in symbol spanOne token-goat read returns the decorator + signature + body; no re-readsrc/parser.ts (TypeScript adapter)
Section extractionSetext headings, h5/h6, anchor IDs, and __frontmatter__token-goat section resolves more headings without falling back to a full file readsrc/parser.ts (Markdown adapter)
Image cacheRepeat Read of an unchanged image serves the stored re-encode, keyed on path + size + mtime, instead of running sharp againSkips the re-encode entirely on a hit; the same bytes reach the model, so the reported saving is identical either waysrc/image_shrink.ts (findCachedShrink)
Monorepo defaultsReindex batch 500 → 2000; compact min_events 5 → 3Fewer worker wakeups; compact manifests fire on shorter sessionssrc/config.ts defaults
Miss suggestionsread / section print "Did you mean…?" on a miss; section also auto-redirects on an unambiguous heading-prefix matchKeeps agents on the surgical-read path instead of falling back to full-file Readsrc/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.

TaskAgent flow
Review a PDFpdf-meta and pdf-outline, then pdf-extract --pages
Review a Word documentdocx-outline, then docx-text
Review a slide deckpptx-outline, then pptx-slide or pptx-notes
Review a workbookxlsx-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:

ToolWhy it matters next to token-goat
ast-grepThe 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.
uvOne 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.
ruffPython 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 pipx
pipx install uv && pipx ensurepath     # pipx puts uv in ~/.local/bin
export PATH="$HOME/.local/bin:$PATH"   # this shell; ensurepath covers later ones
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.

WhenCommand
Update nownpm 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:

  1. Remove the Python package: pip uninstall token-goat
  2. Install and register the npm package: npm install -g token-goat && token-goat install
  3. Verify: re-run token-goat install, which should now report hooks already up to date, or use the jq check 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.

CommandWhat 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 (getUserrgetUser) — 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 deadSurface 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-gapsFind 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}
token-goat semantic "<query>"Find code by meaning, not by filename: embedding-vector similarity search over indexed file chunks and full-text search (BM25) over symbol names/bodies both run on every query and are fused by Reciprocal Rank Fusion (score = sum of 1/(60 + rank) per list), so an exact keyword match can outrank a weak vector hit instead of being shadowed by the vector branch. Covers extracted text from PDF/DOCX/PPTX/XLSX files alongside source code, so a query can surface a spec PDF, design doc, deck, or spreadsheet, not just code. Results are re-ranked with a path-priority multiplier so live source wins ties/near-ties against stale or archival prose (archive/, archived/, old/, deprecated/, plans/, drafts/, CHANGELOG*, *.bak, *.orig) and, more mildly, general docs (docs/**, *.md) — a nudge, not a hard filter, so a genuinely much better archival match still surfaces. Configure with token-goat config set semantic.archive_weight <0-1> / token-goat config set semantic.docs_weight <0-1> (both default <1; set to 1 to disable that penalty entirely, e.g. for a project with a genuinely live plans/ directory). --limit <n> caps result count; --json for structured output: {source, items, truncated, totalCount}, where source is "hybrid" when both the embedding and BM25 branches contributed at least one raw hit, "embeddings" when only the embedding branch did (e.g. no vector index exists yet: optional embedding deps unavailable, or indexing.embeddings_enabled is off), or "fts" when only BM25 did, and every item carries the same keys (filePath, name, kind, startLine, endLine, distance, preview), with null for whichever of name/kind/distance don't apply to that item's source, and filePath rendered root-relative when a project root resolves, absolute when none does, matching plain-text output. On an embeddings hit, name/kind are resolved to the innermost indexed symbol whose line range contains the hit's start line (null/null when the hit falls outside any symbol, e.g. a top-of-file imports chunk); text output appends the same as a — inside <name> (<kind>) suffix. --grep <pattern> only shows hits whose FILE PATH matches this regex (literal substring if it is not valid regex), tested against the path exactly as rendered (so an anchored --grep "^src/" matches what you see, not the stored absolute path) — the high-value case is dropping test/vendored noise from a project-wide semantic hit list. Applied before the --limit slice in both the embeddings and full-text-fallback branches, so it selects from the whole hit set, not an already-capped page; when it matches nothing among hits that do exist, the output names the active filter (and --json sets grepFilteredToEmpty: true) instead of reading like the search found nothing. --exclude-tests hides hits whose file is a test file (opt-in — omitted, output is unchanged), covering the case --grep structurally cannot: --grep can only ever select a path, and the negative-lookahead pattern that would express "not a test" silently degrades to a literal substring match whenever the regex-compile fallback fires. Applied before the --limit slice in both branches and composable with --grep (a hit must satisfy both); when it hides every hit there was, the output names how many were hidden (and --json sets excludeTestsFilteredToEmpty: true) and exits 0, instead of the exit-1 "no matches" a genuinely empty search returns. With both filters set and both emptying the view, the --grep notice takes priority.
token-goat mapGet a compact orientation of the repo. Add --compact to fit a fixed 2000-token budget. --json emits the project map as JSON instead of text.
token-goat deps "file"One-level import listing for a single file: resolves relative imports to project files (internal, root-relative paths) and groups everything else as external. --json for structured output. --grep <pattern> only shows dependencies whose MODULE SPECIFIER (the resolved internal path or the external package name) matches this regex (literal substring if it is not valid regex), applied before output is built; when it matches nothing among real dependencies, the output names the active filter instead of reading like the file has no imports at all. Complemented by token-goat arch for the project-wide graph.
token-goat archProject-wide import graph summary: hub modules (most imported), entry points (nothing imports them), and circular chains. Complements token-goat deps <file> for per-file depth.
token-goat index [path]Parse all git-tracked files and (re)build the symbol index from scratch. Runs automatically on install and incrementally via the background worker after edits — use this to force a full rebuild (e.g. after a config change that narrows what gets indexed). --walk indexes a bounded directory walk instead when path isn't a git repo. --force-walk does the same non-git walk and raises its 20,000-file refusal to 500,000 for a folder you know is genuinely that large (slow, and produces a large index — check token-goat doctor afterwards); it never lifts the separate refusal to walk a filesystem root or your home directory. On a real terminal (not a pipe/CI), prints a live progress line to stderr (files done/total, current phase, elapsed time) so a large r

Frequently Asked Questions

What is token-goat?

token-goat is an open-source mcp servers skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by DFKHelper. Token burn reducer and focus keeper for Claude Code, Codex, Copilot, Gemini CLI, and more: surgical read hints, PDF/Office/CSV/markdown file interception, 160+ filter & interception rules, compact manifest injection, image shrinking, cache and compact skills, cache MCP calls, prompt injection protections, and much more. It has 104 GitHub stars.

Is token-goat safe to use?

Yes. token-goat passed SkillsLLM's automated security scan — a dependency vulnerability audit plus prompt-injection heuristics — with no high-severity issues. You can read the full report in the Security Report section on this page.

How do I install token-goat?

Clone the repository with "git clone https://github.com/DFKHelper/token-goat" and add it to your Claude Code skills directory (see the Installation section above).

What programming language is token-goat written in?

token-goat is primarily written in TypeScript. It is open-source under DFKHelper on GitHub, so you can review or fork the full source.

Are there alternatives to token-goat?

Yes. SkillsLLM lists many other MCP Servers skills you can browse and compare side by side. Open the MCP Servers category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh token-goat against similar tools.

Comments (0)

No comments yet. Be the first to share your thoughts!

n8n

by n8n-io

12

Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.

201,88160,308TypeScript
MCP Serversapisai-tools
View details

Scrapling

by D4Vinci

🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!

75,9137,581Python
MCP Servers
View details

TrendRadar

by sansan0

⭐AI-driven public opinion & trend monitor with multi-platform aggregation, RSS, and smart alerts.🎯 告别信息过载,你的 AI 舆情监控助手与热点筛选工具!聚合多平台热点 + RSS 订阅,支持关键词精准筛选。AI 智能筛选新闻 + AI 翻译 + AI 分析简报直推手机,也支持接入 MCP 架构,赋能 AI 自然语言对话分析、情感洞察与趋势预测等。支持 Docker ,数据本地/云端自持。集成微信/飞书/钉钉/Telegram/邮件/ntfy/bark/slack 等渠道智能推送。

61,65224,883Python
MCP Servers
View details

context7

by upstash

Context7 Platform -- Up-to-date code documentation for LLMs and AI code editors

61,0602,938TypeScript
MCP Servers
View details

High-performance code intelligence MCP server. Indexes codebases into a persistent knowledge graph — average repo in milliseconds. 158 languages, sub-ms queries, 99% fewer tokens. Single static binary, zero dependencies.

39,9393,219C
MCP Servers
View details

Developers Also Liked

Based on votes and bookmarks from developers who liked this skill

ECC

by affaan-m

10

The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.

242,21936,702JavaScript
AI Agentsai-agentsanthropicclaude-code
View details
15

An agentic skills framework & software development methodology that works.

234,96620,863Shell
AI Agentsai-agentsbrainstorming
View details

n8n

by n8n-io

12

Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.

201,88160,308TypeScript
MCP Serversapisai-tools
View details

The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.

185,94028,768JavaScript
AI Agentsai-agentsanthropicclaude-code
View details

cc-switch

by farion1231

3

A cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build & Hermes Agent. Only official website: ccswitch.io

128,8688,826Rust
AI Agentsclaude-codeai-tools
View details