← Back to the token-goat README
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.16 or later (all platforms)
npm install -g token-goat
token-goat install
token-goat doctor # confirms the hooks, index, and integrations are healthy
Three commands. Done. Hooks register and start working immediately; no terminal popups, no tray icon, no service to babysit.
WSL performance tip: Keep active repositories on WSL’s native ext4 filesystem (
~/projects/...) rather than Windows mounts (/mnt/c/...) to avoid 9P cross-OS filesystem translation overhead during initial indexing.
Agents choose the commands
People install token-goat. Agents use it. You do not need to memorize its commands or tell the agent which file type it has.
Installation adds a short routing guide to the agent’s instructions. When the agent tries to read a supported binary document, a hook identifies the extension and returns the right next step. The agent starts with an inventory, then reads only the relevant part.
| Task | Agent flow |
|---|---|
| Review a PDF | pdf-meta and pdf-outline, then pdf-locate to find the pages that mention a term and pdf-extract --pages only those |
| Review a Word document | docx-outline, then docx-text |
| Review a slide deck | pptx-outline, then pptx-slide or pptx-notes |
| Review a workbook | xlsx-sheets, then xlsx-head, xlsx-range, or xlsx-query |
Give the agent the file and the task: “Review manual.pdf for warranty exceptions.” It selects the bounded reader. If no routing rule fits, it can run token-goat commands instead of guessing.
The commands stay separate so every retrieval is visible, repeatable, and easy to narrow. The agent chooses the sequence; the developer can still inspect or run any step directly.
For bounded archive/document comparisons after setup, see the CLI comparison workflow.
Image shrinking needs nothing extra. The biggest single win (~39% smaller than JPEG, ~97% smaller than raw PNG) comes from WebP encoding, and the encoder is pure TypeScript inside the published bundle. There is no native image library to build and no platform where the image pipeline has to be installed separately, so a standard npm install -g token-goat already has it. See Image support in the README for which formats it converts.
Two things change how Claude Code sessions behave: hooks fire automatically (image shrink, re-read dedup, compact manifests), and a delimited routing block written to ~/.claude/CLAUDE.md plus a registered skill gate the agent’s reads — before any file read it must ask whether a token-goat read / symbol / section returns just what it needs, and the block explicitly subordinates the harness’s own Read/Grep tool-preference rules to the fallback choice once token-goat is ruled out. Install writes no permission entry: whether token-goat commands need a per-call approval prompt is left to your own settings.json, unchanged.
Keep that block where install put it. It’s plain markdown in a file you own, so moving it into a tidier reference file is tempting — but install and uninstall resolve one hardcoded path (~/.claude/CLAUDE.md). A relocated copy is never refreshed, so it freezes at whatever version was current when it moved, and the next install sees CLAUDE.md missing its block and appends a fresh one — leaving the guidance duplicated across two files with only one of them live. token-goat doctor warns when it finds a block outside CLAUDE.md, naming the file; install warns at write time and uninstall reports what it couldn’t remove. None of them edit a file token-goat doesn’t own, so cleanup stays your call. A pointer that merely mentions the markers in prose is fine — detection requires both markers on their own lines.
The background indexer is not started by install. Run token-goat worker start on any platform to launch it as a detached process; token-goat worker status / token-goat worker stop manage it from there.
Companion CLI tools (recommended — install these too)
token-goat covers the narrow-read half of cheap context: pulling one symbol, one section, one cached command output instead of a whole file. It does not cover the deterministic-transform half — searching wide, rewriting code structurally, converting data, running language tooling. Those belong in utilities, not in model output: an operation with a defined algorithm is reproducible, cheaper, and checkable against a spec rather than re-read for plausibility. Install these alongside token-goat so an agent has a real tool for each job instead of burning tokens simulating one.
Priority tier, the three that close actual gaps in a token-goat-only setup:
| Tool | Why it matters next to token-goat |
|---|---|
ast-grep |
The symbol-aware write half. token-goat reads by symbol; ast-grep matches the AST and rewrites it (--rewrite, YAML rule files). Repo-wide renames, call-shape changes, and codemods become a reviewable diff instead of a model regenerating files. Unlike rg/sd it ignores comments and strings. |
uv |
One Rust binary replacing pip, pyenv, virtualenv, and pipx. Every Python env probe and validation cycle gets an order-of-magnitude faster, so verification stops being the slow step agents skip. |
ruff |
Python lint + format in one binary. Agent environment probes commonly emit ruff check as the Python verify command; without it installed that path silently degrades to no check at all. |
Base stack, what a read or a search falls back to once the gate has ruled token-goat out. The guidance token-goat writes names no binary on purpose, because an instruction-file loader will harvest backticked names into a tool allowlist and then warn that every one of them is unknown. So this list is a recommendation, not something the installed block depends on:
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 event-shape translation. Tool names still need translating: Qwen Code’s payloads carry its own runtime tool ids (read_file, run_shell_command, grep_search, …), which token-goat maps to its internal tool vocabulary from Qwen Code’s own tool-name source. 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 tested 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.cjs 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 tested 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.
Antigravity CLI users
token-goat install --antigravity
This adds token-goat to Google’s Antigravity CLI (agy) as a plugin: a folder at ~/.gemini/config/plugins/token-goat/ holding a plugin.json, a hooks.json, and on Windows a one-line launcher, token-goat-hook.cmd. agy turns plugins on by default, so nothing else needs changing. token-goat does not edit ~/.gemini/config/hooks.json, which other tools write to, or ~/.gemini/config/config.json, so if you turned the plugin off there it stays off.
It wires agy’s PreToolUse and PostToolUse events. agy’s tool names (view_file, run_command, grep_search, find_by_name, replace_file_content, write_to_file and others) and their argument names are translated to token-goat’s own, so repeat-read hints, repeat-read denial, bash output handling and post-edit indexing see agy’s calls. A pass answers {}, not "allow", because in agy "allow" approves the tool call without asking you. agy has no session-start or pre-compaction event token-goat can use, so those are not wired.
The hooks were checked loading and running under agy 1.2.11 on Windows, and the payloads they read were captured from real agy tool calls. Whether agy acts on every answer (a hint, a rewritten command, a rewritten result) has not been checked in a live session yet, so if something seems not to take effect, check first that ~/.gemini/config/plugins/token-goat/hooks.json is there and that the plugin is not turned off in ~/.gemini/config/config.json. To remove: token-goat uninstall --antigravity, which deletes the plugin folder and any ~/.gemini folders the install created, if they are empty.
opencode users
token-goat install --opencode
The --opencode flag drops a TypeScript bridge plugin into opencode’s plugins directory. Image shrinking, post-edit indexing, compact assist, and rewritten tool results (prompt-injection fencing, secret redaction, and output compression replace the raw result, the same protection Claude Code sessions get) work. So do repeat-search denial for websearch, repeat-load denial for skill, and the subagent prompt briefing for task — all three tool ids and their argument keys were verified against opencode’s own source at the installed release’s tag. 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 (before_tool_call returns rewritten params whose path points at a materialized shrunk copy, the same mechanism the pi bridge uses), and post-edit indexing (all via before_tool_call/after_tool_call; OpenClaw’s read/edit/write tools send the file path under path, which the plugin now forwards to token-goat as file_path too — earlier versions of this bridge assumed the keys already matched, so these read/edit hooks silently never engaged). 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 tested 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; pi’s powershell tool, whose input schema is identical to its bash tool’s, is bridged the same way), 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, output caching and rewritten tool output (all three from tool_result: a compressed or redacted result is returned to pi as replacement content, with any image blocks in the result left in place), 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. -p/--project does the same as --local. Remove it with npx token-goat uninstall --pi --local, or 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, postToolUseFailure, preCompact, agentStop, subagentStart, subagentStop, and userPromptSubmitted, per Copilot’s own hooks reference) plus the shim script it points at, ~/.copilot/hooks/token-goat-shim.cjs. 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"), background-shell output compression (postToolUse returns modifiedResult), image shrinking (preToolUse on a view call returns modifiedArgs carrying the full original arguments with path swapped to a materialized shrunk copy — Copilot replaces the tool call’s arguments wholesale with modifiedArgs, so the rewrite must carry them all), post-edit indexing (a postToolUse side effect; it needs no response channel), and stop-hallucination logging (agentStop/subagentStop map a token-goat deny onto decision: "block", everything else onto decision: "allow"). A subagent’s session briefing goes to the subagent itself: subagentStart answers with it, so it is no longer added to the parent’s task call and left in the parent conversation. A long subagent report is compacted on subagentStop (long code blocks trimmed to their ends, repeated blocks folded) with a pointer to the full copy, which token-goat mcp-output <id> --full prints. After upgrading, run token-goat install --copilot again to add the subagentStart hook; until then the briefing keeps arriving the old way. Prompt hints and the compaction manifest reach the model through userPromptSubmitted: Copilot CLI 1.0.88 appends the returned additionalContext to the prompt inside a <system_reminder> block. preCompact itself ignores a returned additionalContext, so token-goat saves the manifest when compaction starts and hands it back with your next prompt. Copilot’s built-in tool names are remapped onto token-goat’s internal names where a clear match exists (view→Read, edit→Edit, create→Write, bash/powershell→Bash, read_bash/read_powershell→BashOutput, web_fetch→WebFetch, grep→Grep, glob→Glob). MCP-server tool calls, which Copilot names <server>-<tool> rather than mcp__<server>__<tool>, are translated too, but only when the name matches Copilot’s own cached tool list exactly — never guessed from the name’s shape, because a server name can itself contain a hyphen and a wrong guess would make the read-only MCP dedup path deny an ordinary built-in call. With no cache to match against, nothing is translated. memory, ask_user, write_bash/write_powershell (which send keystrokes to a running shell, not commands), and stop_bash/list_bash pass through unmapped and simply no-op. task, Copilot’s subagent tool, is not remapped either, but it is handled under its own name: a task spawn gets the same prompt briefing, duplicate-spawn advisory, and recall pointer on a long report that a Claude Code Agent spawn gets. The once-per-session unrestricted-spawn advisory is the one exception: it is suppressed under Copilot, because its subagent_type advice describes Claude Code’s Task schema, which Copilot’s task tool does not use.
Why the background-shell compression matters most on Copilot. Copilot runs shell commands in the background: a build or a test suite is started once, and the model then checks on it repeatedly while it runs. Each check hands back everything the command has printed since it started, from the first line. So the second check re-sends the whole first check, the third re-sends the first two, and a check ten minutes into a slow build re-sends the same output for the tenth time. The model has already read all of it and pays again for every word, every time. Token-goat sends the first check through untouched, then returns only the new part on each later check, with one line saying that is what it is; a check that found nothing new comes back as a single short line instead of the whole output again. Measured through the installed hook: a second check of 5,200 characters came back as about 1,250, and a third check that added nothing came back as 60 — roughly a quarter of the cost for the second look and about one percent for the third, improving the longer the command runs. Nothing is lost, because what is cut is what was already sent. It only shortens a check when the new output genuinely continues the last one seen; anything else passes straight through, so the worst case is a saving that does not happen rather than a wrong answer.
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/, the routing block to $COPILOT_HOME/copilot-instructions.md and the MCP server entry to $COPILOT_HOME/mcp-config.json, matching where Copilot CLI actually reads them. The MCP entry (mcpServers.token-goat, in the shape copilot mcp add writes) registers token-goat’s MCP server with Copilot, so copilot mcp list shows it; every other server in that file is kept as it was. A project install (--local) does not touch it, because Copilot reads that file only at user scope. To install for one project instead of user scope: token-goat install --copilot --local, or -p/--project in place of --local (writes .github/hooks/token-goat.json and .github/copilot-instructions.md in the current project). .github/hooks/token-goat.json holds absolute paths to node and token-goat on your machine, so do not commit it or the token-goat-shim.cjs and token-goat-shim.js next to it: list them in .git/info/exclude (just for you) or .gitignore. Install prints this reminder. 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. The same happens when something stops the native hook client tg-hook from starting after it was installed, such as an application-control policy (Smart App Control, App Control for Business, AppLocker) or an antivirus quarantining the copy: doctor then reports that the client fails its self-test, or that it no longer exists when the antivirus removed it. token-goat install --copilot copies the client again and tests it; one that still cannot run is left out and the entries are written as plain Node commands, after which Copilot needs the same full restart. If the client passes that test and is blocked again later, set TOKEN_GOAT_NATIVE_HOOKS=0 (or native = "off" under [hooks] in the global config) before running install, which writes the Node commands whatever the client does.
Status line. Copilot CLI runs a status line command the same way Claude Code does, and token-goat statusline works as one. Install does not set it, because ~/.copilot/settings.json holds only one status line and yours may already be in use. To use it, add this to ~/.copilot/settings.json (or $COPILOT_HOME/settings.json), merged with what is already there:
{ "statusLine": { "type": "command", "command": "token-goat statusline" } }
It shows the project, the model, the context percentage Copilot’s own footer shows, whether the index is up to date, and the tokens token-goat saved today.
VS Code (Copilot agent) users
token-goat install --vscode
This installs into the current project: the MCP entry goes to .vscode/mcp.json (a timestamped .bak is written before any change to an existing file), the agent hooks to .github/hooks/ (token-goat.json plus the token-goat-shim.cjs it runs), and the routing guidance to .github/copilot-instructions.md. Run it once in each project you want token-goat in. VS Code’s Copilot agent reads hooks from that folder, so token-goat sees the agent’s built-in tool calls: read_file, view_image, list_dir, grep_search, file_search, create_file, replace_string_in_file, insert_edit_into_file, edit_notebook_file, run_in_terminal, multi_replace_string_in_file, apply_patch, fetch_webpage, runSubagent and get_terminal_output. The hooks file also registers SubagentStart, so a subagent’s session briefing reaches the subagent itself rather than the parent’s history (run token-goat install --vscode again after upgrading to add it). Both .vscode/mcp.json and .github/hooks/token-goat.json hold absolute paths to node and token-goat on your machine, so do not commit them or the token-goat-shim.js next to them: list them in .git/info/exclude (just for you) or .gitignore. Install prints this reminder.
--vscode is the one integration that installs into the project rather than user scope by default, and the reason is a limitation of VS Code itself. VS Code resolves an agent hook’s working directory from the hook file’s own location: the folder of the workspace that contains it, or the workspace’s first folder when no workspace contains it. A user-scope hooks file lives in ~/.copilot/hooks/, which is inside no workspace folder, so it always runs with the first folder of a multi-root workspace as its working directory — and since token-goat confines every pre-approval hook to that directory, read hints, image shrinking and edit interception silently do nothing for every other folder. A project-scope hooks file is inside its own folder, so each folder gets its own.
Add --user for the old behaviour — one install covering every project, at %APPDATA%\Code\User\mcp.json (or the platform equivalent), ~/.copilot/hooks/, and ~/.copilot/instructions/token-goat.instructions.md — with the multi-root limitation above. -p/--project is still accepted and selects what is now the default. Running token-goat install --vscode on a machine that has the old user-scope install moves it into the project and prints a note saying so, because VS Code runs every hooks file it finds in both scopes and leaving both in place would fire every hook twice. token-goat doctor reports a user-scope install that is still around.
What works: a repeated read of a large file is denied, with a pointer to what the agent already has; hints ride along with reads and edits; a large image inside the workspace is shrunk before view_image loads it; edited files are queued for reindexing; and the session-start reminder tells the agent token-goat exists. The hook runs before VS Code asks you to approve a call, so token-goat does not look at any file or folder on a network share or outside the workspace until then. What does not: VS Code gives hooks no way to change what a tool returns, so token-goat cannot fold or trim what read_file returns the way it does in Claude Code. Terminal output is not compressed either: VS Code does not tell the hook which shell will run a run_in_terminal command, so rewriting it safely is not possible, and token-goat leaves the command as it is. VS Code also never fires a pre-compaction hook, so the compaction manifest has no route there.
The hooks folder and files are the same ones token-goat install --copilot uses, and one shim serves both: it tells a VS Code payload from a Copilot CLI one and answers each in its own format. token-goat records which install owns the files (token-goat.owners next to them), so token-goat uninstall --vscode leaves the hooks in place while --copilot still needs them, and the other way round. To remove: token-goat uninstall --vscode (add --user for a user-scope install).
If VS Code’s chat.useClaudeHooks setting is on, VS Code also runs the Claude Code hooks in ~/.claude/settings.json, so each token-goat hook fires twice. token-goat install --vscode prints a note when it sees that setting, and token-goat doctor reports it. Turn the setting off to keep only the VS Code hooks. token-goat never edits your VS Code settings.
Visual Studio users
token-goat install --visualstudio
This is for the full Visual Studio IDE with GitHub Copilot agent mode: Visual Studio 2022 17.14 or later, or Visual Studio 2026. It registers token-goat’s MCP server under the servers key of %USERPROFILE%\.mcp.json, the user-level file Visual Studio reads (Microsoft’s MCP servers page), and adds a routing block to %USERPROFILE%\copilot-instructions.md, which Visual Studio 2026 reads as user-level custom instructions (Microsoft’s chat context page). A user install never writes into the folder you run it from. Add -p/--project to install for the solution in the current folder instead: the entry goes to .mcp.json and the block to .github/copilot-instructions.md, which Visual Studio 2022 reads too. .mcp.json then holds absolute paths to node and token-goat on your machine, so do not commit it.
In Visual Studio, token-goat works through its MCP tools and instructions only. Visual Studio has no documented agent hooks (GitHub’s hooks page lists only Copilot cloud agent and Copilot CLI), so --visualstudio writes no hooks file, and there is no read dedup, no hints, no image shrink, and no output folding. The agent gets narrow reads only when it calls the token-goat tools.
Two steps in Visual Studio after installing:
- Tools > Options: turn on “Enable custom instructions to be loaded from .github/copilot-instructions.md files and added to requests”. Without it, Visual Studio ignores the routing block.
- In Copilot Chat agent mode, open the Tools picker and tick the token-goat tools. New MCP tools start disabled. Visual Studio 18.7 and later also asks you to trust the server when its command or arguments change, for example after a reinstall.
Both .mcp.json files are also read by Claude Code, under a different key (mcpServers), and Claude Code stops with an error on a .mcp.json that has no mcpServers key. So token-goat writes its entry under servers and, when the file has no mcpServers key yet, adds an empty one. It registers nothing for Claude Code and leaves your own mcpServers entries as they were. A timestamped .bak is written before any change to an existing .mcp.json. Visual Studio also reads .vscode/mcp.json, so if token-goat install --vscode (project scope, the default) put token-goat there too, Visual Studio lists the server twice. The install prints a note when that happens, and token-goat doctor warns about it; keep one of the two. If --vscode (project scope) or --copilot --local (or -p) already put a token-goat block in .github/copilot-instructions.md, the Visual Studio block shrinks to a short addendum rather than repeating it, and grows back to the full text when that other block is removed.
token-goat doctor reports the entry and warns when its node or token-goat path no longer exists. To remove: token-goat uninstall --visualstudio (add -p for the project install). It removes only token-goat’s entry and block.
Zed users
token-goat install --zed
Zed’s first-party agent has no agent-hooks API at all (zed-industries/zed#52688 is still open), so --zed does not install hooks: it registers token-goat as an MCP context server instead, the only integration surface Zed offers. This is user scope only — there is no -p/--project option, since Zed has no documented project-local equivalent of VS Code’s .vscode/mcp.json.
--zed writes two files: a small generated shim script (token-goat-mcp.cmd on Windows, token-goat-mcp.sh elsewhere) that launches token-goat mcp-serve, and an entry in Zed’s settings.json (%APPDATA%\Zed\settings.json on Windows, ~/.config/zed/settings.json elsewhere) pointing context_servers.token-goat at that shim. Any other content already in your settings.json — comments, your theme, other context servers — is left exactly as it was, and a timestamped .bak is written before any change to an existing file. To use it, open Zed’s Agent panel and enable the token-goat server under its MCP tools list; new servers start disabled the same way VS Code’s do.
Like Visual Studio, this is MCP tools only: no read dedup, no hints, no image shrink, no output folding, since there is no hook to fire them from. The agent gets narrow reads only when it calls the token-goat tools directly.
token-goat doctor reports whether the entry is present. To remove: token-goat uninstall --zed, which deletes the shim and the context_servers.token-goat entry (and the whole settings.json, but only if token-goat created it and nothing else was ever added to it).
Cursor users
token-goat install --cursor
--cursor registers token-goat as an MCP server in ~/.cursor/mcp.json (root mcpServers, confirmed against the installed Cursor 3.19.7 bundle’s own JSON schema for that file). Add -p/--project to write <project>/.cursor/mcp.json instead — Cursor reads both. Cursor’s schema rejects unknown properties on a server entry (additionalProperties: false) and has no type field, unlike VS Code’s and Visual Studio’s servers entries, so the entry token-goat writes is command + args only. A timestamped .bak is written before any change to an existing mcp.json.
--cursor never writes ~/.cursor/hooks.json (or .cursor/hooks.json), on purpose. Cursor’s own shipped code loads ~/.claude/settings.json by default (thirdPartyExtensibilityEnabled defaults to on) and translates Claude Code’s hook step names into its own before deduping against anything already in hooks.json by exact command string. So a plain token-goat install for Claude Code already makes those same hooks fire once inside Cursor, automatically, with no separate Cursor hooks file to install or keep in sync. Writing a second copy into hooks.json would only add a way for the two copies to drift and fire twice, and ~/.cursor/hooks.json may already be a real file some other tool manages — token-goat never touches it. If you have not run a plain token-goat install yet, Cursor’s MCP tools still work, but no hooks fire there until you do.
token-goat doctor reports the MCP entry and whether Claude Code hooks are installed for Cursor to pick up. To remove: token-goat uninstall --cursor (add -p for the project install). It removes only token-goat’s MCP entry.
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.cjs. 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. That normalization maps every tool id registered in the grok 0.2.93 binary itself — both shell-tool spellings (run_terminal_command and run_terminal_cmd), web_fetch, web_search, glob, and the hashline_*/*_concise read/edit/grep variants — onto token-goat’s internal tool names, so hooks fire regardless of which id a given Grok build sends.
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 — this passive terminal filter is separate from the --cursor MCP bridge in Cursor users above; it needs no install step), 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.
Windsurf gets terminal-output compression only — not the read/index hook integration Claude Code, Codex, Copilot CLI, Gemini, Qwen, Kimi, Antigravity, VS Code, Visual Studio and Grok get above. Windsurf’s Cascade agent hooks (cascadeHooksJson) are configured on Windsurf’s own servers, per team, not from a file on your machine, so there is no local hook config for token-goat to install into. There is no --windsurf flag, and none is planned unless that changes.
JetBrains IDEs (WebStorm, IntelliJ, PyCharm, Rider, PhpStorm) users
There is no --junie or --jetbrains flag, and no JetBrains integration of any kind today: no hooks, no MCP registration, no terminal-output filter (JetBrains IDEs have no CLI binary for token-goat to detect on your PATH). Junie, the JetBrains ACP agent, reads guidelines from .junie/AGENTS.md, MCP servers from ~/.junie/mcp/mcp.json, and — per JetBrains’ own docs, not a live-verified source — hooks from ~/.junie/config.json, but no ~/.junie directory exists without Junie itself creating one, and no live Junie CLI was available to confirm the on-disk shape of any of those files. Writing one on documentation alone risks shipping a config Junie ignores or rejects, so token-goat writes nothing there.
Separately, GitHub’s Copilot-for-JetBrains plugin documents (as of its March 2026 changelog) previewing agent hooks placed in .github/hooks/ — the same folder --copilot and --vscode already write for their own hook integrations. If that holds, a project that has already run token-goat install --copilot or --vscode may get some Copilot-for-JetBrains hook pickup for free, with no JetBrains-specific code in token-goat at all. This is unconfirmed: the real Copilot-for-JetBrains agent plugin is not installed on the machine this was checked from (only its theme jar is present), so the claim rests on GitHub’s changelog, not a live run, and is not something token-goat currently relies on or tests for.
Updating
There is no auto-update mechanism — token-goat never schedules or runs anything on its own. Updating is always a manual npm install -g token-goat@latest.
| When | Command |
|---|---|
| Update now | npm install -g token-goat@latest |
| Reinstall from scratch (broken or partial install) | 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.cjs" pre_tool_use "<entry>").
Both install and uninstall recognize every command spelling token-goat has ever written as its own: the current shim and native tg-hook forms, and the older token_goat, tokenwise, token-goat-hook and pre-shim token-goat hook spellings, 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. An entry counts as the current native client only when it has the shape install writes today (a tg-hook binary, its --harness and --event flags, and the Node command after --); any other tg-hook spelling is treated as an old entry and replaced.
To upgrade cleanly:
- Remove the Python package:
pip uninstall token-goat - Install and register the npm package:
npm install -g token-goat && token-goat install - Verify: re-run
token-goat install, which should now report hooks already up to date, or use thejqcheck under Verify.
AI agents handling an upgrade: complete all three steps in sequence. After step 3, confirm no token_goat entries remain in settings.json before reporting the upgrade done.
Command auto-approval and permissions
By default, token-goat install configures hook triggers only. It never writes permission entries into your environment. If your setup requires confirmation for shell commands, an approval prompt can appear whenever file names, line numbers, or arguments change.
You can add wildcard rules to avoid repeated prompts for routine queries:
Claude Code (~/.claude/settings.json)
To allow all token-goat commands without prompting:
{
"permissions": {
"allow": [
"Bash(token-goat *)",
"Bash(token-goat:*)"
]
}
}
To limit auto-approval strictly to read-only queries:
{
"permissions": {
"allow": [
"Bash(token-goat read *)",
"Bash(token-goat symbol *)",
"Bash(token-goat section *)",
"Bash(token-goat outline *)",
"Bash(token-goat skeleton *)",
"Bash(token-goat brief *)",
"Bash(token-goat json-query *)",
"Bash(token-goat yaml-query *)",
"Bash(token-goat xml-query *)",
"Bash(token-goat map *)",
"Bash(token-goat refs *)",
"Bash(token-goat deps *)"
]
}
}
Codex CLI (~/.codex/config.toml)
Running token-goat install --codex automatically computes and writes trusted_hash into [hooks.state] so Codex trusts the hooks. To allow automatic command execution, set approval_policy in ~/.codex/config.toml:
approval_policy = "never"
Copilot and VS Code MCP
For VS Code using the token-goat MCP server (token-goat install --vscode), enable MCP auto-approval in your VS Code settings.json:
{
"chat.mcp.autoApprove": true
}
Supported languages
One table in src/language_specs.ts drives every per-language list token-goat uses, so this is the whole set. Symbols means symbol, read "file::Name", skeleton and outline return named declarations from the file. Structure only means the file is indexed for headings, keys or sections rather than code symbols.
Symbols: ABAP (.abap), Apache (httpd.conf, apache2.conf, .htaccess, .apache, .apache2), Apex (.cls, .trigger), Assembly (.s, .asm, .nasm), Bash and compatible shells (.sh, .bash, .zsh, .ksh, .bats), C (.c, .h), C# (.cs), C++ (.cpp, .cc, .cxx, .hpp, .hxx), Caddyfile (Caddyfile, .caddy), Clojure (.clj, .cljs, .cljc), CMake (.cmake, CMakeLists.txt), COBOL (.cbl, .cob, .cobol, .cpy), Common Lisp (.lisp, .lsp, .cl), Dart (.dart), Elixir (.ex, .exs), Emacs Lisp (.el), Erlang (.erl, .hrl), F# (.fs, .fsi, .fsx), Fortran (.f, .for, .f77, .f90, .f95, .f03, .f08), GLSL (.glsl, .vert, .frag, .comp, .geom, .tesc, .tese), Go (.go), GraphQL (.graphql, .gql), Groovy (.groovy, .gvy, .gradle, Jenkinsfile), Haskell (.hs), HLSL (.hlsl, .hlsli), Java (.java), JavaScript (.js, .jsx, .mjs, .cjs), JCL (.jcl), Kotlin (.kt, .kts), Lua (.lua), MATLAB, Metal (.metal), Natural (.nsp, .nsn, .nss, .nsa, .nsl, .nsg, .nsc, .nsh), Nginx (nginx.conf, .nginx), Nix (.nix), Objective-C (.mm, and a .h that declares an @interface or @protocol), OCaml (.ml, .mli), OpenEdge ABL, Pascal (.pas, .dpr, .dpk, .lpr, .dfm), Perl (.pl, .pm), PHP (.php), PL/I (.pli, .pl1), PowerShell (.ps1, .psm1), Protocol Buffers (.proto), Python and Starlark (.py, .pyi, .bzl, .star, BUILD, WORKSPACE, MODULE.bazel), R (.r), Racket (.rkt, .rktl), RPG (.rpgle, .sqlrpgle), Ruby (.rb, .ruby, .rake, Gemfile, Rakefile, and the other extensionless Ruby DSL files), Rust (.rs), Salesforce markup (.cmp, .app, .evt, .intf, .design, .auradoc, .tokens, .page, .component, .email), Salesforce metadata, SAS (.sas), Scala (.scala, .sc), Scheme (.scm, .ss), Solidity (.sol), SQL and PL/SQL (.sql, .pks, .pkb, .pls, .plsql, .pck, .prc, .fnc, .trg, .tps, .tpb; including DuckDB CREATE MACRO and CREATE SECRET statements), Swift (.swift), Terraform (.tf, .tfvars, .hcl), Thrift (.thrift), TypeScript (.ts, .tsx, .mts, .cts), VHDL (.vhd, .vhdl), Visual Basic (.vb, .bas, .vbs, .frm), WGSL (.wgsl), Windows batch (.bat, .cmd), Zig (.zig).
Structure only: Astro (.astro), CSS and its preprocessors (.css, .scss, .sass, .less), Dockerfile, environment files (.env, .envrc), HTML (.html, .htm), INI (.ini, .cfg, .conf), JSON including JSON with comments and Avro schemas (.json, .jsonc, .avsc), Jupyter notebooks (.ipynb), Makefiles (.mk, Makefile), Markdown (.md, .markdown, .mdx), Svelte (.svelte), TOML (.toml), Vue (.vue), YAML (.yaml, .yml), and seven template-engine dialects read as HTML: Liquid (.liquid), Jinja2 (.j2, .jinja, .jinja2), Handlebars (.hbs, .handlebars), ERB (.erb), EJS (.ejs), Nunjucks (.njk), Twig (.twig).
MATLAB, OpenEdge ABL and Salesforce metadata share their file extensions with other languages, so token-goat picks them by looking at the file’s contents rather than its name.
Troubleshooting
tree-sitter unavailable
token-goat doctor warns when the optional tree-sitter package does not load. Without it, TypeScript, JavaScript, Python, Go, Rust, Ruby, Java, C and C++ files are read by a rougher scan that finds fewer symbols and no references, and the skeleton fold is off. Every other language indexes as usual. The doctor line says which of three causes applies:
- Not installed. The install left out optional dependencies. Run
npm install -g token-goat --include=optional. - No native build for this platform.
tree-sitterand each grammar ship prebuilt binaries inside the npm package for Windows, Linux and macOS on x64 and arm64, and pick the matching one when they load, so no compiler runs and nothing is downloaded when one matches. Their install script (node-gyp-build) compiles a binary only when none matches, which needs a C++ toolchain. npm 12 skips dependency install scripts unless you allow them, so reinstall withnpm install -g token-goat --allow-scripts=tree-sitter. - Binary will not load. The binary was built for a different Node version or CPU. Reinstall with the same Node you run token-goat with:
npm install -g token-goat.
A file type shows no symbols
outline, skeleton and read "file::Name" say when token-goat has no symbol extractor for a file type. The supported languages list above says which file types are indexed; anything outside it, and any extension token-goat does not recognize, has no extractor. Grep and plain reads still work on those files. To ask for support for another file type, open an issue or email token-goat@dfkhelper.com.
What gets installed?
token-goat install writes the following on your machine — nothing else, anywhere. Every entry is reversed by token-goat uninstall. Integrations for other harnesses are additive on the way out as well as in, so a plain uninstall does not touch one you installed with --codex, --copilot, or a sibling flag: rather than undo something you did not ask about, it names each one still present and the flag that removes it. Run token-goat doctor at any time to see which of these are currently present.
A bare token-goat install (no other flag) installs the Claude Code integration below. Passing a harness flag (--vscode, --codex, --gemini, and so on) installs only that harness’s own files, listed in its own section further down: it never also touches ~/.claude/ on the side. To have the Claude Code integration as well, run a bare token-goat install too. Several harness flags in one command install each of those harnesses. The one exception is --hermes, which delegates to claude -p and so genuinely needs the Claude Code hooks below; it installs them the same way a bare install does.
Claude Code integration (~/.claude/; written by a bare install, or by --hermes)
| Path | What |
|---|---|
~/.claude/settings.json |
Hook entries for SessionStart, PreToolUse (Read/Grep/Bash, Drive/WebFetch), PostToolUse (Edit/Write/MultiEdit, Read/Grep/Glob, Bash, WebFetch, Skill), and PreCompact. Hook entries only: install writes nothing under permissions, so it never grants the agent unprompted execution of anything. Existing hooks are preserved; a timestamped .bak is written before any change.The PreToolUse and PostToolUse matchers are narrowed to exactly the tools token-goat handles (plus ^mcp__), generated from the live hook registry rather than a fixed list, so they can’t fall out of date as handlers change. Claude Code starts a new process per matcher hit and most of that cost is process startup, so a catch-all matcher would make every unrelated tool call — TodoWrite, TaskUpdate, and friends — pay for a hook that has nothing to do. |
~/.claude/hooks/token-goat-shim.cjs |
The hook script those settings.json commands invoke ("<node>" "<shim>" <event> "<entry>"). It imports the hook library in-process instead of spawning a second process, and naming the node binary directly skips the npm bin wrapper — on Windows a cmd.exe layer every hook would otherwise pay for. Measured 480 ms → 324 ms per hook call. Regenerated on every install run. Always written here even for a --project install, since the command bakes in machine-specific absolute paths; a project-scope settings.json just points at this one. The .cjs extension keeps Node from loading it as an ES module when a package.json above it says "type": "module". A small token-goat-shim.js beside it hands off to the .cjs file, for sessions started before the rename that still run the old path. |
~/.claude/CLAUDE.md |
A delimited block (<!-- token-goat-begin --> … <!-- token-goat-end -->) telling the agent to prefer token-goat read / symbol / section over Read / Grep. Any existing content is preserved; a timestamped .bak is written before any change. |
~/.claude/skills/token-goat/SKILL.md |
The token-goat skill — the same routing guidance in skill form. A timestamped .bak is written before any change. |
With -p/--project, the hooks go to <project>/.claude/settings.json, the routing block to <project>/CLAUDE.md, and the skill to <project>/.claude/skills/token-goat/SKILL.md, so nothing under ~/.claude/ changes except the shared hook script above, which both scopes use. token-goat uninstall -p removes exactly those project files (a CLAUDE.md that install created and that now holds nothing else is deleted; your own content in it stays) and leaves the user-wide block and skill alone. Run it from the project root: the project scope is the directory you run the command in.
Background worker. token-goat does not register any persistent OS-level autostart entry — no Windows registry Run key, no systemd user unit, no XDG .desktop entry, and no macOS launchd .plist. The worker that drains the reindex queue is started manually as a detached child process: token-goat worker start launches node <npm-prefix>/lib/node_modules/token-goat/dist/token-goat.mjs --worker-daemon and returns immediately, and the child keeps running independent of the parent shell. token-goat worker status reports whether it’s running; token-goat worker stop kills it. If it crashes or is killed while the machine stays up, the next edit hook detects it’s gone and respawns it automatically (checked on every edit, rate-limited to roughly once every 5 minutes). It does not survive a reboot or logout, though — re-run token-goat worker start after either.
Hook server. The first hook call also starts up to three hook servers in the background: plain detached node processes, not OS services, that answer later hook calls and read-only commands so each call skips starting Node. A read-only command a server has taken waits for its answer however long it runs, rather than starting a second copy beside it. Each listens on a named pipe (Windows) or a Unix socket with mode 0600 (elsewhere), never on a network port, and answers only a caller that proves it holds the random key in hook-server.key in the data directory, a file readable only by you. hook-server.spawn-<slot>, hook-server.disabled and hook-server.failed beside it are small marker files: a start rate limit, a record that the config turned the server off, and the reason the last start failed. A server exits after 30 minutes idle, after an upgrade replaces the build it loaded, when server = false under [hooks] in the global config or TOKEN_GOAT_HOOK_SERVER=0 turns it off, and within two seconds of its key file being deleted. uninstall and uninstall --purge stop every running server before touching anything else. token-goat capabilities lists it under “Listens for other processes on this machine”.
Native hook client. On Windows and Linux (x64 and arm64), install puts a small native program, tg-hook, in front of each hook command it writes, for every harness it wires. tg-hook hands the call to the hook server and prints its answer, which saves starting Node on every call. When the server cannot answer (it is off, not running yet, or busy), tg-hook runs the ordinary Node hook command it wraps, with the same input, so a hook never fails because the server is away. The command after -- in each entry is exactly the Node command install writes without it. On Windows the hook entries name a copy of the binary kept in the data directory under native\<id>\tg-hook.exe, not the one inside the npm package, so an upgrade can replace the package while a hook call is still running. install refreshes that copy, and moves a running one aside to be removed by a later install. uninstall removes the hook entries but leaves that copy in place with the rest of token-goat’s data, because a project-scope install in another repository may point at the same copy and uninstall cannot see it; delete the native folder under the data directory once no install uses it. install writes the native form only when the binary for this platform is present and passes its own self-test (tg-hook --selftest); otherwise it writes the Node command alone, which is always the case on macOS, where no binary ships. A Windows binary ships only when the release signed it with Authenticode; a release built without the signing certificate carries none, Windows then gets the Node command as macOS does, and doctor says this build ships no native hook client for the platform. Linux release binaries carry build provenance attestations; how to check both is under Verifying what you installed. To turn the native client off, set native = "off" under [hooks] in the global config, or TOKEN_GOAT_NATIVE_HOOKS=0, and run install again; the variable wins over the config, and a project’s .token-goat.toml cannot set this key. token-goat doctor prints one Native hooks line per harness and scope, for example Native hooks (Codex): native, <path>, self-test passed; last 7 days: 1 served natively, 0 fell back, 0 through Node: the form wired (native, Node, or mixed), the binary and its self-test result, and over the last seven days how many hook calls the server answered, how many fell back to the Node command and why, and how many ran the Node command directly. An entry written by an older build is flagged as outdated with the install command that rewrites it, and an entry whose binary no longer exists is reported as broken, since those events fail rather than fall back. The fallback to Node runs inside tg-hook, so it cannot help when tg-hook itself is stopped from starting after install, by an antivirus quarantining it or an application-control policy (Smart App Control, App Control for Business, AppLocker) refusing it. Most harnesses then carry on without token-goat’s hooks, and Copilot CLI denies every tool call. doctor reports either case as a failure; run install again, which writes the Node command for a client that still cannot run, and turn the native client off as above if the block comes back.
There is no auto-update mechanism. Updating token-goat is always a manual npm install -g token-goat@latest.
Data directory (created on first run)
| Platform | Path |
|---|---|
| Windows | %LOCALAPPDATA%\dfk-helper\token-goat\ |
| Linux / WSL | ~/.local/share/token-goat/ |
| macOS | ~/Library/Application Support/dfk-helper/token-goat/ |
Contains the symbol index (global.db, per-project .db files), session cache, shrunken-image cache, cached skill bodies (5 MB cap, LRU-evicted), logs, locks, and the dirty-file queue. Nothing outside this directory and ~/.claude/ is written.
What the index actually holds, in plain terms. The point of a surgical read is returning a function body without the file around it, which means the database stores those bodies. symbols.body holds the source text of every indexed symbol, symbols.docstring its doc comment, refs.context the line around each reference, and chunks.text the passages that semantic search embeds. There is also a full-text index over the bodies and docstrings. So the database is not a list of names and line numbers: it is a substantial copy of your source, sitting in a plain unencrypted SQLite file outside the repository, at the path in the table above.
Three things follow, and they are worth knowing before you decide. It never leaves the machine: token-goat sends no telemetry of any kind, and the only outbound requests it makes at all are the ones listed in the security section, none of which carry index content. It is not protected by your repository’s access controls any more, so anything on the machine that can read your home directory can read it, and on Linux and macOS that directory sits under a home that backup and sync tools routinely copy. And it outlives an uninstall unless you say otherwise: token-goat uninstall --purge deletes both roots and tells you how much it reclaimed.
With --codex (Codex CLI integration)
| Path | What |
|---|---|
~/.codex/config.toml |
Hooks block with Codex-specific matchers (view_image|Bash, apply_patch, web_search) plus PreCompact/UserPromptSubmit/SubagentStop global hooks. Existing hooks preserved. |
~/.codex/AGENTS.md |
A delimited block (<!-- token-goat-codex-begin --> … <!-- token-goat-codex-end -->) with the same routing guidance, adapted for Codex tool names. |
~/.codex/hooks/token-goat-shim.cjs |
The hook script config.toml’s hook commands invoke (node "<path>" <event>). Strips internal _tg_* keys and injects hookSpecificOutput.hookEventName to satisfy Codex’s strict schemas. Regenerated on every install --codex run. The .cjs extension keeps Node from loading it as an ES module when a package.json above it says "type": "module". A small token-goat-shim.js beside it hands off to the .cjs file, for sessions started before the rename that still run the old path. |
With --gemini (Gemini CLI integration)
| Path | What |
|---|---|
~/.gemini/settings.json |
Hook entries under Gemini’s BeforeTool, AfterTool, and PreCompress events, using Gemini’s own snake_case tool-name matchers (run_shell_command, read_file, grep_search, etc.). Existing hooks preserved; a timestamped .bak is written before any change. |
With --qwen (Qwen Code integration)
| Path | What |
|---|---|
~/.qwen/settings.json |
Hook entries under Qwen Code’s PreToolUse, PostToolUse, PreCompact, UserPromptSubmit, and SubagentStop events (Claude-Code-native names and payload shape, not Gemini’s), using a catch-all matcher per event. Existing hooks preserved; a timestamped .bak is written before any change. |
With --kimi (Kimi Code integration)
| Path | What |
|---|---|
~/.kimi-code/config.toml |
[[hooks]] entries for Kimi Code’s PreToolUse, PostToolUse, PreCompact, UserPromptSubmit, SubagentStop, and SessionStart events. Each entry carries only event and command, the keys Kimi Code’s strict schema accepts. Existing hooks and other config keys preserved; a timestamped .bak is written before any change. |
~/.kimi-code/hooks/token-goat-shim.cjs |
The hook script those commands invoke. Rewrites a token-goat block into hookSpecificOutput.permissionDecision and a hint into a top-level message, and writes empty stdout for a no-op. Regenerated on every install --kimi run. The .cjs extension keeps Node from loading it as an ES module when a package.json above it says "type": "module". A small token-goat-shim.js beside it hands off to the .cjs file, for sessions started before the rename that still run the old path. |
~/.kimi-code/AGENTS.md |
A delimited block (<!-- token-goat-kimi-begin --> … <!-- token-goat-kimi-end -->) with the routing guidance, adapted for Kimi Code tool names. |
~/.kimi-code/skills/token-goat/SKILL.md |
The same guidance as a Kimi Code skill. |
With --antigravity (Antigravity CLI integration)
| Path | What |
|---|---|
~/.gemini/config/plugins/token-goat/plugin.json |
{"name": "token-goat"}, the marker agy needs before it treats the folder as a plugin. Written only if missing. |
~/.gemini/config/plugins/token-goat/hooks.json |
A token-goat entry with PreToolUse and PostToolUse hooks, catch-all matcher, 30-second timeout. Other entries in the file are kept. |
~/.gemini/config/plugins/token-goat/token-goat-hook.cmd |
Windows only. A one-line launcher the hooks call as .\token-goat-hook.cmd <event>: agy runs plugin hooks from the plugin folder, and on Windows it neither finds a bare name there nor passes a quoted path through intact. Rewritten on each install --antigravity run. |
With --opencode (opencode plugin)
| Path | What |
|---|---|
~/.config/opencode/plugins/token-goat.ts on every platform, Windows included ($XDG_CONFIG_HOME/opencode/plugins/token-goat.ts when that is set) |
TypeScript bridge plugin. Fires on tool.execute.before, tool.execute.after, and experimental.session.compacting. Covers image shrinking, post-edit indexing, and compact assist. |
With --pi (pi extension)
| Path | What |
|---|---|
~/.pi/agent/extensions/token-goat.ts |
TypeScript extension (default-exported ExtensionAPI factory). Subscribes to session_start, tool_call, tool_result, session_before_compact, and session_compact. Covers bash compression, re-read denial, pressure-scaled surgical-read redirects for oversized first reads, image shrinking, post-edit indexing, output caching, and the compaction manifest. A project-local install writes <project>/.pi/extensions/token-goat.ts instead. |
With --copilot (Copilot CLI hook bridge)
| Path | What |
|---|---|
~/.copilot/hooks/token-goat.json |
Hook config ({ version, hooks }) registering preToolUse, postToolUse, preCompact, agentStop, and subagentStop, each pointing at the shim script below. Existing files elsewhere in the hooks directory are untouched. |
~/.copilot/hooks/token-goat-shim.cjs |
The shim token-goat.json’s hook commands invoke (node "<path>"). Translates Copilot’s event names and response schema (permissionDecision/modifiedArgs, additionalContext) to/from token-goat’s internal hook protocol. Regenerated on every install --copilot run. A project-local install (--copilot --local or --copilot -p) writes <project>/.github/hooks/token-goat.json and <project>/.github/hooks/token-goat-shim.cjs instead. The .cjs extension keeps Node from loading it as an ES module in a repository whose package.json says "type": "module". A two-line token-goat-shim.js beside it hands off to the .cjs file, for Copilot sessions started before the rename that still run the old path. |
~/.copilot/mcp-config.json |
The mcpServers.token-goat entry ({ tools, type: "local", command, args }, the shape copilot mcp add writes) that registers token-goat mcp-serve with Copilot CLI. Other servers in the file are kept. User scope only; follows COPILOT_HOME. |
~/.copilot/copilot-instructions.md |
A delimited block (<!-- token-goat-begin --> … <!-- token-goat-end -->) with the same routing gate written to ~/.claude/CLAUDE.md and ~/.codex/AGENTS.md, naming Copilot CLI’s own view/grep/glob tools in the conflict-resolution clause. Merged idempotently — everything outside the markers is preserved byte-for-byte. A project-local install (--copilot --local or --copilot -p) writes <project>/.github/copilot-instructions.md instead. |
With --grok (Grok CLI / xAI Grok Build hook bridge)
| Path | What |
|---|---|
~/.grok/hooks/token-goat.json |
Hook config ({ hooks }) registering PreToolUse, PostToolUse, PreCompact, UserPromptSubmit, and SubagentStop with an empty (match-everything) matcher, each pointing at the shim script below. Existing files elsewhere in the hooks directory are untouched; global scope only (Grok’s project-scoped .grok/hooks/ requires a separate manual /hooks-trust grant). |
~/.grok/hooks/token-goat-shim.cjs |
The shim token-goat.json’s hook commands invoke. Translates PreToolUse’s deny shape only ({"decision":"block",...} → Grok’s documented {"decision":"deny",...}, plus exit code 2); every other event’s response is forwarded unmodified. Regenerated on every install --grok run. The .cjs extension keeps Node from loading it as an ES module when a package.json above it says "type": "module". A small token-goat-shim.js beside it hands off to the .cjs file, for sessions started before the rename that still run the old path. |
With --vscode (VS Code MCP configuration; project scope by default — the only integration that inverts the usual default, because VS Code pins a user-scope hook to the first folder of a multi-root workspace — with --user for the old every-project install)
| Path | What |
|---|---|
<project>/.vscode/mcp.json — or %APPDATA%\Code\User\mcp.json (Windows) / ~/Library/Application Support/Code/User/mcp.json (macOS) / ~/.config/Code/User/mcp.json (Linux) with --user |
Merges the token-goat stdio entry under VS Code’s servers root key, preserving unrelated servers and settings. --user refuses to write when the project scope already has a token-goat-managed entry, to avoid a duplicate registration; the reverse direction is a migration instead, and removes the user-scope install. |
<project>/.github/copilot-instructions.md (~/.copilot/instructions/token-goat.instructions.md with --user) |
A delimited VS Code routing block that documents supported MCP selection and what the agent hooks can and cannot do with built-in file reads. The user-scope file is a personal instructions file with applyTo: '**', so VS Code applies it in every workspace; install creates it with that frontmatter if it is missing and otherwise merges the block in, and uninstall deletes it again when nothing else is left in it. A user-scope install never writes into the folder you run it from. |
<project>/.github/hooks/token-goat.json, token-goat-shim.cjs, token-goat-shim.js, token-goat.owners (~/.copilot/hooks/ with --user) |
VS Code agent hooks, shared with --copilot; the owners file records which of the two installs still uses them. |
With --visualstudio (Visual Studio MCP configuration; user scope by default, -p/--project for the solution folder)
| Path | What |
|---|---|
%USERPROFILE%\.mcp.json (<project>/.mcp.json with -p) |
Merges the token-goat stdio entry under the servers root key, preserving other servers, comments, and Claude Code’s mcpServers key. Refuses to write if the other scope already has a token-goat-managed entry. Uninstall removes the entry, and deletes the file if nothing else is left in it. |
%USERPROFILE%\copilot-instructions.md (<project>/.github/copilot-instructions.md with -p) |
A delimited Visual Studio routing block (<!-- token-goat-visualstudio-begin --> … <!-- token-goat-visualstudio-end -->). Everything outside the markers is preserved. |
With --zed (Zed MCP context server; user scope only, no project option)
| Path | What |
|---|---|
%APPDATA%\Zed\settings.json (Windows) / ~/.config/zed/settings.json (macOS/Linux) |
Merges the token-goat entry (command, timeout only) under Zed’s context_servers root key, preserving unrelated keys, comments, and other context servers. Refuses to write if a non-token-goat token-goat entry is already there. Uninstall removes the entry, and deletes the file if nothing else is left in it. |
%APPDATA%\Zed\token-goat-mcp.cmd (Windows) / ~/.config/zed/token-goat-mcp.sh (macOS/Linux) |
The shim script the settings.json entry’s command points at; runs node <bundle path> mcp-serve. Regenerated on every install --zed run. |
With --cursor (Cursor MCP server; user scope by default, -p/--project for <project>/.cursor/mcp.json)
| Path | What |
|---|---|
~/.cursor/mcp.json (<project>/.cursor/mcp.json with -p) |
Merges the token-goat entry (command, args only – no type key) under Cursor’s mcpServers root key, preserving unrelated keys, comments, and other servers. Refuses to write if a non-token-goat token-goat entry is already there. Uninstall removes the entry, and deletes the file if nothing else is left in it. |
Nothing is ever written to ~/.cursor/hooks.json by --cursor – see Cursor users above.
With --hermes (Hermes Agent integration)
| Path | What |
|---|---|
~/.claude/settings.json |
No new entries beyond the base Claude Code install. Hermes delegates tasks to Claude Code via claude -p '<task>', which loads hooks from this file normally. token-goat install --hermes verifies the hooks are present and reports the result. To remove the Hermes detection: token-goat uninstall --hermes (removes no files — Hermes shares the Claude Code hook entries). |
With --openclaw (OpenClaw plugin)
| Path | What |
|---|---|
~/.openclaw/plugins/token-goat.ts |
TypeScript bridge plugin (definePluginEntry registration). Subscribes to session_start, session_end, before_tool_call, after_tool_call, and before_compaction. Covers bash compression, re-read denial, pressure-scaled surgical-read redirects for oversized first reads, image shrinking, and post-edit indexing. Not validated against a live OpenClaw instance — see README’s “openclaw users” section. |
~/.openclaw/openclaw.json |
Adds the plugin path to plugins.load.paths and an entry to plugins.entries.token-goat. Existing config preserved; a timestamped .bak is written before any change. |