token-goat 2.9.29 → 2.9.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -42
- package/SECURITY.md +37 -11
- package/THIRD_PARTY_NOTICES.md +41 -4
- package/dist/native/linux-arm64/tg-hook +0 -0
- package/dist/native/linux-x64/tg-hook +0 -0
- package/dist/native/win32-arm64/tg-hook.exe +0 -0
- package/dist/native/win32-x64/tg-hook.exe +0 -0
- package/dist/ort-wasm-simd-threaded.mjs +59 -0
- package/dist/{token-goat-chunk-SE6E4BKJ.mjs → token-goat-chunk-25TRAWET.mjs} +23 -6
- package/dist/token-goat-chunk-2KXO6V2B.mjs +203 -0
- package/dist/{token-goat-chunk-UXSXQQIY.mjs → token-goat-chunk-2M7E4PF7.mjs} +43 -14
- package/dist/token-goat-chunk-2UPZGKKY.mjs +188 -0
- package/dist/token-goat-chunk-2WLPWKHV.mjs +135 -0
- package/dist/token-goat-chunk-2XBMMDBD.mjs +126 -0
- package/dist/token-goat-chunk-32ZOY4N5.mjs +23 -0
- package/dist/token-goat-chunk-3EZRNKTK.mjs +188 -0
- package/dist/token-goat-chunk-4ADZHI7J.mjs +63 -0
- package/dist/token-goat-chunk-4GBVPH3D.mjs +242 -0
- package/dist/{token-goat-chunk-2WOUYWLM.mjs → token-goat-chunk-4GILLLWY.mjs} +34 -7
- package/dist/token-goat-chunk-4NAPX7BA.mjs +90 -0
- package/dist/{token-goat-chunk-NMBWLRHW.mjs → token-goat-chunk-4SC7UJYV.mjs} +9981 -11563
- package/dist/token-goat-chunk-4SMRO4TO.mjs +366 -0
- package/dist/{token-goat-chunk-O6I2XU4P.mjs → token-goat-chunk-4ZBFWLXF.mjs} +310 -441
- package/dist/token-goat-chunk-57BIAXJL.mjs +286 -0
- package/dist/token-goat-chunk-5AC66HNP.mjs +115 -0
- package/dist/token-goat-chunk-5IMLKSOP.mjs +145 -0
- package/dist/token-goat-chunk-5RG5SBL6.mjs +221 -0
- package/dist/{token-goat-chunk-RRNZMM3A.mjs → token-goat-chunk-5Y6ZYL6U.mjs} +21 -2
- package/dist/token-goat-chunk-646E6U44.mjs +398 -0
- package/dist/token-goat-chunk-65N4RHP4.mjs +33 -0
- package/dist/{token-goat-chunk-6AK46ZT7.mjs → token-goat-chunk-6DG6HWP3.mjs} +13 -3
- package/dist/token-goat-chunk-6ENQSNRZ.mjs +573 -0
- package/dist/token-goat-chunk-6ZY4GNF2.mjs +91 -0
- package/dist/{token-goat-chunk-E5TK5DNO.mjs → token-goat-chunk-76FGDLRO.mjs} +574 -340
- package/dist/{token-goat-chunk-TGOXNAHW.mjs → token-goat-chunk-7EZTZOMC.mjs} +780 -767
- package/dist/token-goat-chunk-7MAOA6X4.mjs +3647 -0
- package/dist/token-goat-chunk-7R2ZVVAX.mjs +218 -0
- package/dist/{token-goat-chunk-TCQANXYC.mjs → token-goat-chunk-AM5V6GSX.mjs} +18 -2
- package/dist/token-goat-chunk-AUNGYYQX.mjs +184 -0
- package/dist/token-goat-chunk-AUQYRMMH.mjs +2985 -0
- package/dist/token-goat-chunk-B3PEKBYG.mjs +128 -0
- package/dist/{token-goat-chunk-KOPIR3KZ.mjs → token-goat-chunk-BA4HI6BW.mjs} +1910 -165
- package/dist/{token-goat-chunk-52NSDWYY.mjs → token-goat-chunk-BFCWY73P.mjs} +58 -20
- package/dist/token-goat-chunk-BPGCUMR7.mjs +30 -0
- package/dist/token-goat-chunk-BWGFUZZE.mjs +25 -0
- package/dist/token-goat-chunk-C75NA7PL.mjs +322 -0
- package/dist/token-goat-chunk-CBIQ7WTD.mjs +87 -0
- package/dist/token-goat-chunk-CETM74KC.mjs +307 -0
- package/dist/token-goat-chunk-CIMQ2BB3.mjs +233 -0
- package/dist/{token-goat-chunk-DNSPXQXQ.mjs → token-goat-chunk-CJLS3CDL.mjs} +70 -18
- package/dist/{token-goat-chunk-MELOKFDJ.mjs → token-goat-chunk-CO3UYX6G.mjs} +7 -1
- package/dist/token-goat-chunk-COPNXRJM.mjs +154 -0
- package/dist/token-goat-chunk-CSJ5L3NL.mjs +479 -0
- package/dist/token-goat-chunk-CVCEVUGK.mjs +315 -0
- package/dist/token-goat-chunk-CXHOFN7R.mjs +371 -0
- package/dist/token-goat-chunk-D735C4QM.mjs +603 -0
- package/dist/{token-goat-chunk-E4ZOJIZK.mjs → token-goat-chunk-DGS2LVNA.mjs} +70 -160
- package/dist/{token-goat-chunk-L7H3U27L.mjs → token-goat-chunk-DOWRSMWO.mjs} +1 -1
- package/dist/token-goat-chunk-E45INMY3.mjs +71 -0
- package/dist/token-goat-chunk-EC5F37F4.mjs +244 -0
- package/dist/{token-goat-chunk-XTQAOTSO.mjs → token-goat-chunk-EDBDRVH5.mjs} +4 -1
- package/dist/token-goat-chunk-EELTBKDM.mjs +613 -0
- package/dist/token-goat-chunk-EVMZJ27Z.mjs +147 -0
- package/dist/token-goat-chunk-EXKGNHEG.mjs +2096 -0
- package/dist/{token-goat-chunk-YOOVFPTP.mjs → token-goat-chunk-F4HYL64R.mjs} +12 -2
- package/dist/token-goat-chunk-F6PAUIEK.mjs +624 -0
- package/dist/token-goat-chunk-FDSREWLX.mjs +692 -0
- package/dist/{token-goat-chunk-NMTKNYGF.mjs → token-goat-chunk-FNFBKLRD.mjs} +74 -9
- package/dist/token-goat-chunk-G5PJ7AGM.mjs +36 -0
- package/dist/token-goat-chunk-GGGG7RWB.mjs +236 -0
- package/dist/{token-goat-chunk-TDUT2CL6.mjs → token-goat-chunk-GL3ETKSC.mjs} +276 -19
- package/dist/{token-goat-chunk-7CPMDFGB.mjs → token-goat-chunk-GVB2MUST.mjs} +265 -1567
- package/dist/token-goat-chunk-GWFLAUMQ.mjs +307 -0
- package/dist/token-goat-chunk-HAVAOB3S.mjs +87 -0
- package/dist/token-goat-chunk-HLPW63A7.mjs +48 -0
- package/dist/token-goat-chunk-HU4TORC5.mjs +1377 -0
- package/dist/token-goat-chunk-I7VKZZAW.mjs +35 -0
- package/dist/token-goat-chunk-IA6XSPE3.mjs +218 -0
- package/dist/token-goat-chunk-IAGXMHIK.mjs +863 -0
- package/dist/{token-goat-chunk-435UQTKD.mjs → token-goat-chunk-IK2DRMFX.mjs} +129 -74
- package/dist/{token-goat-chunk-ZER6EAXZ.mjs → token-goat-chunk-INNPTRUX.mjs} +2954 -2783
- package/dist/token-goat-chunk-JJ5BX4F4.mjs +129 -0
- package/dist/token-goat-chunk-JVFSHVFJ.mjs +129 -0
- package/dist/token-goat-chunk-KGEFFSHD.mjs +50 -0
- package/dist/token-goat-chunk-KKUFXAPD.mjs +25 -0
- package/dist/token-goat-chunk-KQBLRPBN.mjs +433 -0
- package/dist/token-goat-chunk-LCNQ2CNN.mjs +497 -0
- package/dist/token-goat-chunk-M3LFJDPU.mjs +58 -0
- package/dist/token-goat-chunk-M52KPT3C.mjs +87 -0
- package/dist/token-goat-chunk-MBEG42H7.mjs +150 -0
- package/dist/{token-goat-chunk-EVC4TOLE.mjs → token-goat-chunk-METECDOH.mjs} +3 -12
- package/dist/{token-goat-chunk-2AWQ2R5X.mjs → token-goat-chunk-MHX7XCC3.mjs} +3 -2
- package/dist/token-goat-chunk-MNWKPRAL.mjs +13 -0
- package/dist/token-goat-chunk-N5LSOVSU.mjs +137 -0
- package/dist/{token-goat-chunk-DPNLUJWB.mjs → token-goat-chunk-N5MMR2IW.mjs} +30 -8
- package/dist/token-goat-chunk-N7B3JQ2C.mjs +160 -0
- package/dist/{token-goat-chunk-VMFWNHGR.mjs → token-goat-chunk-NISNMD7T.mjs} +2545 -2953
- package/dist/token-goat-chunk-NMD42SFQ.mjs +434 -0
- package/dist/token-goat-chunk-NOSS5VWY.mjs +174 -0
- package/dist/token-goat-chunk-NSNSPPBG.mjs +69 -0
- package/dist/token-goat-chunk-OBOFIHKN.mjs +38 -0
- package/dist/token-goat-chunk-OBRFJ5O5.mjs +76 -0
- package/dist/token-goat-chunk-ODBT2USM.mjs +800 -0
- package/dist/token-goat-chunk-OS5EM5PP.mjs +107 -0
- package/dist/token-goat-chunk-OUKDTVNU.mjs +1178 -0
- package/dist/token-goat-chunk-OXFMP75N.mjs +566 -0
- package/dist/{token-goat-chunk-ASLWOQ3I.mjs → token-goat-chunk-OYIYKDDT.mjs} +2 -1
- package/dist/{token-goat-chunk-67BNIKKO.mjs → token-goat-chunk-P6HQSWQ3.mjs} +55 -47
- package/dist/token-goat-chunk-P6VGNTQT.mjs +71 -0
- package/dist/token-goat-chunk-PFWIMH2E.mjs +261 -0
- package/dist/token-goat-chunk-PJZXIS3G.mjs +39 -0
- package/dist/token-goat-chunk-PUO5INXP.mjs +606 -0
- package/dist/token-goat-chunk-PX5PQWTY.mjs +462 -0
- package/dist/token-goat-chunk-Q5QDBC6O.mjs +111 -0
- package/dist/token-goat-chunk-QFNQXSDT.mjs +40 -0
- package/dist/token-goat-chunk-QH7YVYOY.mjs +3957 -0
- package/dist/token-goat-chunk-QLMUHEPB.mjs +43 -0
- package/dist/{token-goat-chunk-PYSUADX4.mjs → token-goat-chunk-QQOJIHL3.mjs} +50 -14
- package/dist/token-goat-chunk-QQTWJY2O.mjs +85 -0
- package/dist/token-goat-chunk-QYHO5CPH.mjs +93 -0
- package/dist/token-goat-chunk-RELAVWAD.mjs +27 -0
- package/dist/token-goat-chunk-RSLPOBKQ.mjs +339 -0
- package/dist/token-goat-chunk-SAMFDIS2.mjs +712 -0
- package/dist/token-goat-chunk-SDELXLDM.mjs +48 -0
- package/dist/{token-goat-chunk-PGTH2U4C.mjs → token-goat-chunk-SOAFZNNZ.mjs} +164 -40
- package/dist/{token-goat-chunk-G23DWHV4.mjs → token-goat-chunk-SRTHT7KV.mjs} +33 -15
- package/dist/token-goat-chunk-T5P5ACNQ.mjs +212 -0
- package/dist/token-goat-chunk-T5YGG4V3.mjs +45 -0
- package/dist/token-goat-chunk-TAUZ4OFS.mjs +110 -0
- package/dist/token-goat-chunk-TM6MC754.mjs +40 -0
- package/dist/token-goat-chunk-TWUKBOMP.mjs +72 -0
- package/dist/token-goat-chunk-U2KSB4SG.mjs +56 -0
- package/dist/token-goat-chunk-U4WHMG6Y.mjs +898 -0
- package/dist/token-goat-chunk-UA5V6P3P.mjs +122 -0
- package/dist/token-goat-chunk-UGJHGR5A.mjs +52 -0
- package/dist/{token-goat-chunk-RQGGMPCR.mjs → token-goat-chunk-UOUDVA5U.mjs} +76 -16
- package/dist/{token-goat-chunk-HNFZZHFW.mjs → token-goat-chunk-URRJ33GR.mjs} +22 -4
- package/dist/token-goat-chunk-UZOROKIS.mjs +165 -0
- package/dist/token-goat-chunk-VIHGGGEI.mjs +4234 -0
- package/dist/token-goat-chunk-VTJLAA7P.mjs +21 -0
- package/dist/token-goat-chunk-VWDXLT4F.mjs +678 -0
- package/dist/token-goat-chunk-VWZA2H4P.mjs +368 -0
- package/dist/{token-goat-chunk-QO3LXVVR.mjs → token-goat-chunk-VXEXMJDL.mjs} +2964 -3153
- package/dist/token-goat-chunk-WBA5SKT4.mjs +547 -0
- package/dist/{token-goat-chunk-ONXH6NZ5.mjs → token-goat-chunk-WDH44VAU.mjs} +19 -22
- package/dist/token-goat-chunk-X2SUP3EI.mjs +107 -0
- package/dist/token-goat-chunk-X3BELZSJ.mjs +125 -0
- package/dist/{token-goat-chunk-RLP6J7WR.mjs → token-goat-chunk-XLRBNZCC.mjs} +40 -21
- package/dist/{token-goat-chunk-S4VSXURL.mjs → token-goat-chunk-XRKUX6CM.mjs} +4 -0
- package/dist/token-goat-chunk-XX6O25RN.mjs +82 -0
- package/dist/token-goat-chunk-XZSUHBVS.mjs +65 -0
- package/dist/token-goat-chunk-Y2ZTQOCL.mjs +35 -0
- package/dist/{token-goat-chunk-UA5BKWWQ.mjs → token-goat-chunk-YB4VMGK2.mjs} +15 -4
- package/dist/token-goat-chunk-YBIIY3HR.mjs +51 -0
- package/dist/{token-goat-chunk-PKRWMHVP.mjs → token-goat-chunk-YHVDKQBR.mjs} +389 -221
- package/dist/token-goat-chunk-YJB5XLWI.mjs +26 -0
- package/dist/{token-goat-chunk-PQ53BSCF.mjs → token-goat-chunk-YKB6AQG3.mjs} +53 -31
- package/dist/token-goat-chunk-YMTG2YA5.mjs +35 -0
- package/dist/token-goat-chunk-YPBZYQRV.mjs +40 -0
- package/dist/{token-goat-chunk-HUYUHODZ.mjs → token-goat-chunk-YY6AVUFL.mjs} +13246 -12247
- package/dist/token-goat-chunk-Z7N5OMMP.mjs +98 -0
- package/dist/{token-goat-chunk-BGHSSJUA.mjs → token-goat-chunk-ZFOBKUWH.mjs} +1 -1
- package/dist/token-goat-chunk-ZMBL6UVV.mjs +110 -0
- package/dist/token-goat-chunk-ZNMNUPAZ.mjs +938 -0
- package/dist/token-goat-hook-client.cjs +87 -23
- package/dist/token-goat-hook-client.mjs +3 -3
- package/dist/token-goat-hook.mjs +76 -31
- package/dist/token-goat.core.mjs +6 -6
- package/docs/C4_RUNTIME_ARCHITECTURE.md +2 -2
- package/docs/cli.md +113 -60
- package/docs/install.md +63 -20
- package/docs/security.md +10 -8
- package/package.json +18 -7
- package/scripts/verify-native-dist.mjs +386 -0
- package/dist/token-goat-chunk-2NNUA2LR.mjs +0 -143
- package/dist/token-goat-chunk-44BM7DDQ.mjs +0 -294
- package/dist/token-goat-chunk-BGYOZ6DO.mjs +0 -24
- package/dist/token-goat-chunk-C5YPNZKQ.mjs +0 -318
- package/dist/token-goat-chunk-CIQ2FTY3.mjs +0 -1990
- package/dist/token-goat-chunk-D4ETHJD6.mjs +0 -246
- package/dist/token-goat-chunk-DNRPXHYH.mjs +0 -1062
- package/dist/token-goat-chunk-EUSDXRXU.mjs +0 -1429
- package/dist/token-goat-chunk-FTP4U4QA.mjs +0 -64
- package/dist/token-goat-chunk-G7IWXCFF.mjs +0 -456
- package/dist/token-goat-chunk-GLXRTTLE.mjs +0 -36
- package/dist/token-goat-chunk-H5J3C56Q.mjs +0 -1511
- package/dist/token-goat-chunk-IVWI2OCK.mjs +0 -368
- package/dist/token-goat-chunk-JTKCQMQA.mjs +0 -418
- package/dist/token-goat-chunk-LZNBQTDK.mjs +0 -26
- package/dist/token-goat-chunk-ORNFMIER.mjs +0 -295
- package/dist/token-goat-chunk-RTMVLHWX.mjs +0 -7113
- package/dist/token-goat-chunk-VQ6F6T7X.mjs +0 -36
- package/dist/token-goat-chunk-VZQEWRVH.mjs +0 -312
- package/dist/token-goat-chunk-XYWPOSCN.mjs +0 -807
- package/dist/token-goat-chunk-YAGLESS3.mjs +0 -23
- package/dist/token-goat-chunk-YKSRAWUU.mjs +0 -33
- package/dist/token-goat-chunk-Z6KBMOBI.mjs +0 -53
package/docs/install.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Install guide"
|
|
3
|
-
description: "How to install, wire it into Claude Code, Codex, Gemini, Qwen, Kimi, opencode, OpenClaw, pi, Copilot, Grok, Cline or Cursor, and what lands on your machine."
|
|
3
|
+
description: "How to install, wire it into Claude Code, Codex, Gemini, Qwen, Kimi, opencode, OpenClaw, pi, Copilot, Grok, Antigravity, Cline or Cursor, and what lands on your machine."
|
|
4
4
|
image: /token-goat/assets/goat-social.png
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -130,13 +130,25 @@ token-goat install --kimi
|
|
|
130
130
|
|
|
131
131
|
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`.
|
|
132
132
|
|
|
133
|
+
### Antigravity CLI users
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
token-goat install --antigravity
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
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.
|
|
140
|
+
|
|
141
|
+
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.
|
|
142
|
+
|
|
143
|
+
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.
|
|
144
|
+
|
|
133
145
|
### opencode users
|
|
134
146
|
|
|
135
147
|
```
|
|
136
148
|
token-goat install --opencode
|
|
137
149
|
```
|
|
138
150
|
|
|
139
|
-
The `--opencode` flag drops a TypeScript bridge plugin into opencode's plugins directory.
|
|
151
|
+
The `--opencode` flag drops a TypeScript bridge plugin into opencode's plugins directory. 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. Neither does image shrinking: the only way to hand opencode a smaller image is to point its `read` at a copy, and opencode checks its `read` and outside-directory permissions against that copy's path instead of the image's, so a rule you set on the image would stop applying. Shell commands are not wrapped for compression either, since opencode's permission rules can come from an organization account or a well-known URL no hook reads; their output is still compressed after they run.
|
|
140
152
|
|
|
141
153
|
### openclaw users
|
|
142
154
|
|
|
@@ -166,7 +178,7 @@ What works: **bash output compression** (the bash command is rewritten in `tool_
|
|
|
166
178
|
npx token-goat install --pi --local
|
|
167
179
|
```
|
|
168
180
|
|
|
169
|
-
This writes `.pi/extensions/token-goat.ts` in the current project only. Remove it by deleting that file.
|
|
181
|
+
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.
|
|
170
182
|
|
|
171
183
|
### Copilot CLI users
|
|
172
184
|
|
|
@@ -174,15 +186,29 @@ This writes `.pi/extensions/token-goat.ts` in the current project only. Remove i
|
|
|
174
186
|
token-goat install --copilot
|
|
175
187
|
```
|
|
176
188
|
|
|
177
|
-
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](https://docs.github.com/en/copilot/reference/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.
|
|
189
|
+
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](https://docs.github.com/en/copilot/reference/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.
|
|
190
|
+
|
|
191
|
+
The shared `sessionStart` hook for Copilot CLI and VS Code reads one complete JSON object without waiting for stdin EOF, then runs the normal handler with a 10-second end-to-end budget and a final 1-second cleanup reserve. On Windows 10 / Windows Server 2016 and newer, the packaged `dist/native/win32-<arch>/tg-hook.exe` creates the canonical process atomically inside a strict Job Object. The owner's sole non-inherited job handle closes on owner death, terminating the owned descendants; neither breakaway flag is enabled on this job. A versioned acknowledgement confirms zero active job processes, root exit and bounded pipe drainage before the parent publishes output. A missing or incompatible owner, a job-association failure, or unverified cleanup fails open with `{}` and a stderr diagnostic, never an unowned Windows fallback. POSIX retains detached process-group cleanup. Input and canonical streams are bounded; the parent performs path arithmetic, not synchronous entry or native-binary filesystem probes. Reminder, notes, drift and receipt work remain enabled, and other events keep their existing behavior. When a start fails open because it missed the deadline, the first tool call of the session delivers it once, and `token-goat doctor` counts these on its "Deferred session starts" line. After upgrading, rerun `token-goat install --copilot` (and `token-goat install --vscode` for a VS Code project install) to refresh the generated shim; an upgrade does not rewrite already-installed hook files.
|
|
192
|
+
|
|
193
|
+
Native-wrapped startup commands also take this bounded path: the rebuilt binary routes startup before its ordinary stdin-to-EOF read or resident-server dispatch, including commands already present in hook manifests. One deadline starts at native entry and is carried through framing, owned relay launch, shim work and cleanup. The generated command shape is unchanged; existing manifests are not rewritten by a rebuild.
|
|
178
194
|
|
|
179
|
-
|
|
195
|
+
When startup detects index drift, it still queues the changed files. On Windows, worker activation is deferred until the outermost startup owner has verified tree cleanup, so the existing managed index daemon runs outside every startup job and continues draining after the hook returns. Direct Node startup performs the same activation from its top-level shim. Neither path waits for indexing or database locks before returning context, and a worker already running outside the startup jobs keeps its existing PID slot.
|
|
196
|
+
|
|
197
|
+
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), **re-read denial** (`preToolUse` returns `permissionDecision: "deny"`), **shell output compression** (`postToolUse` returns `modifiedResult` after the command has run; a command is never rewritten before it runs, because the `--allow-tool` and `--deny-tool` rules Copilot was started with are invisible to a hook), **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; only an image inside the working directory is shrunk, since the copy sits in the system temp folder Copilot reads without asking, and a hook cannot see which folders `/add-dir` added or whether Copilot checks the image's path or the copy's), **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.
|
|
180
198
|
|
|
181
199
|
**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.
|
|
182
200
|
|
|
183
|
-
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
|
|
201
|
+
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`.
|
|
202
|
+
|
|
203
|
+
**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.
|
|
184
204
|
|
|
185
|
-
**
|
|
205
|
+
**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:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{ "statusLine": { "type": "command", "command": "token-goat statusline" } }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
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.
|
|
186
212
|
|
|
187
213
|
### VS Code (Copilot agent) users
|
|
188
214
|
|
|
@@ -190,7 +216,7 @@ No ambient environment variable documents "this process is running under Copilot
|
|
|
190
216
|
token-goat install --vscode
|
|
191
217
|
```
|
|
192
218
|
|
|
193
|
-
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`, and `
|
|
219
|
+
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.
|
|
194
220
|
|
|
195
221
|
`--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.
|
|
196
222
|
|
|
@@ -200,7 +226,9 @@ What works: a repeated read of a large file is denied, with a pointer to what th
|
|
|
200
226
|
|
|
201
227
|
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).
|
|
202
228
|
|
|
203
|
-
|
|
229
|
+
The shared `sessionStart` handler has a 10-second end-to-end budget, including reading the complete input object, running the real handler in an isolated child, and cleaning up its owned process tree as described above. It does not wait for stdin EOF or skip reminder, notes, drift reporting, or receipt work. A timeout, malformed input, or failed cleanup returns `{}` with a stderr diagnostic. A start that missed the deadline is not lost: the first tool call of the session delivers it, once, and `token-goat doctor` counts these on its "Deferred session starts" line. This holds whenever the start message was received before the deadline, however late. On a heavily loaded PC the message can reach the hook after the 10-second deadline has passed, so the hook keeps listening for up to one more second past the deadline and still reads a message that lands in that window. A start whose message never arrives has no session identity and is not delivered, and the hook exits about 11 seconds after it started instead of 10. After upgrading, rerun `token-goat install --vscode` (or `token-goat install --vscode --user` for a user-scope install) to refresh the generated shim.
|
|
230
|
+
|
|
231
|
+
If VS Code's `chat.useClaudeHooks` setting is on and token-goat's Claude Code hooks are installed, VS Code also runs them from `~/.claude/settings.json`, so each token-goat hook fires twice. That setting is user-wide and covers every Claude hook in VS Code, not only token-goat's. A user-scope install (`token-goat install --vscode --user`) and `token-goat doctor --repair` turn it off in that case and say so, and `token-goat uninstall --vscode` turns it back on if it is still off. A project-scope install leaves it alone and prints a note instead; set `"chat.useClaudeHooks": false` in your VS Code user settings yourself if you see hooks run twice. `token-goat doctor` warns about the duplicate when a user-scope VS Code install is there for `--repair` to act on; when only a project-scope VS Code install exists it prints a plain note saying why the setting is left alone, which is not counted as a warning. An older or hand-trimmed install that wires only some events still counts as token-goat's hooks, since each wired event still fires. Without token-goat's Claude Code hooks there is no duplicate, and token-goat does not touch the setting.
|
|
204
232
|
|
|
205
233
|
### Visual Studio users
|
|
206
234
|
|
|
@@ -217,7 +245,7 @@ Two steps in Visual Studio after installing:
|
|
|
217
245
|
1. 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.
|
|
218
246
|
2. 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.
|
|
219
247
|
|
|
220
|
-
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` 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.
|
|
248
|
+
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.
|
|
221
249
|
|
|
222
250
|
`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.
|
|
223
251
|
|
|
@@ -249,7 +277,7 @@ token-goat install --cursor
|
|
|
249
277
|
|
|
250
278
|
### Grok CLI (xAI Grok Build) users
|
|
251
279
|
|
|
252
|
-
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](https://github.com/xai-org/grok-build/blob/main/crates/codegen/xai-grok-pager/docs/user-guide/10-hooks.md)), so `token-goat install` alone already gets most of the integration working — image shrinking, session hints, post-edit indexing, and
|
|
280
|
+
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](https://github.com/xai-org/grok-build/blob/main/crates/codegen/xai-grok-pager/docs/user-guide/10-hooks.md)), so `token-goat install` alone already gets most of the integration working — image shrinking, session hints, and post-edit indexing all fire. A shell command is not rewritten for compression, because Grok applies a rewrite before its own permission policy and prompt see the call. 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.
|
|
253
281
|
|
|
254
282
|
```
|
|
255
283
|
token-goat install --grok
|
|
@@ -265,7 +293,7 @@ No separate install step needed. Token-goat compresses the terminal output of th
|
|
|
265
293
|
|
|
266
294
|
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](#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.
|
|
267
295
|
|
|
268
|
-
Windsurf gets terminal-output compression only — not the read/index hook integration Claude Code, Codex, Copilot CLI, Gemini, Qwen, Kimi, 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.
|
|
296
|
+
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.
|
|
269
297
|
|
|
270
298
|
### JetBrains IDEs (WebStorm, IntelliJ, PyCharm, Rider, PhpStorm) users
|
|
271
299
|
|
|
@@ -286,7 +314,7 @@ There is no auto-update mechanism — token-goat never schedules or runs anythin
|
|
|
286
314
|
|
|
287
315
|
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>"`).
|
|
288
316
|
|
|
289
|
-
Both `install` and `uninstall` recognize the
|
|
317
|
+
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.
|
|
290
318
|
|
|
291
319
|
To upgrade cleanly:
|
|
292
320
|
|
|
@@ -306,6 +334,8 @@ You can add wildcard rules to avoid repeated prompts for routine queries:
|
|
|
306
334
|
|
|
307
335
|
To allow all `token-goat` commands without prompting:
|
|
308
336
|
|
|
337
|
+
The rule below also matches the `token-goat compress` wrapper token-goat puts around commands whose output it compresses, so token-goat stops compressing commands it cannot prove you already allow: otherwise the rule would run them unprompted. The read-only list below does not have that effect.
|
|
338
|
+
|
|
309
339
|
```json
|
|
310
340
|
{
|
|
311
341
|
"permissions": {
|
|
@@ -342,7 +372,7 @@ To limit auto-approval strictly to read-only queries:
|
|
|
342
372
|
|
|
343
373
|
#### Codex CLI (`~/.codex/config.toml`)
|
|
344
374
|
|
|
345
|
-
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`:
|
|
375
|
+
Running `token-goat install --codex` automatically computes and writes `trusted_hash` into `[hooks.state]` so Codex trusts the hooks. If you set `CODEX_HOME`, install follows it, as Codex does: every `~/.codex` path below is then under `$CODEX_HOME`. To allow automatic command execution, set `approval_policy` in `~/.codex/config.toml`:
|
|
346
376
|
|
|
347
377
|
```toml
|
|
348
378
|
approval_policy = "never"
|
|
@@ -397,9 +427,13 @@ A bare `token-goat install` (no other flag) installs the Claude Code integration
|
|
|
397
427
|
| `~/.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. |
|
|
398
428
|
| `~/.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. |
|
|
399
429
|
|
|
430
|
+
**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.
|
|
431
|
+
|
|
400
432
|
**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.
|
|
401
433
|
|
|
402
|
-
**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. 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".
|
|
434
|
+
**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".
|
|
435
|
+
|
|
436
|
+
**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. Before running it, `tg-hook` stops every handle it was given from passing to that command, so a hook server the command starts in the background does not keep the hook's output open; PowerShell, which Copilot CLI runs hook commands through on Windows, passes its own output on that way, and with the Node command alone the call that starts a server leaves it holding that output until the server exits. 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](../SECURITY.md#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.
|
|
403
437
|
|
|
404
438
|
There is no auto-update mechanism. Updating token-goat is always a manual `npm install -g token-goat@latest`.
|
|
405
439
|
|
|
@@ -421,7 +455,7 @@ Three things follow, and they are worth knowing before you decide. It never leav
|
|
|
421
455
|
|
|
422
456
|
| Path | What |
|
|
423
457
|
|------|------|
|
|
424
|
-
| `~/.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. |
|
|
458
|
+
| `~/.codex/config.toml` | Hooks block with Codex-specific matchers (`view_image|Bash`, `apply_patch`, `web_search`, `spawn_agent|multi_agent_v1wait_agent`) plus `PreCompact`/`UserPromptSubmit`/`Stop`/`SubagentStop` global hooks. Existing hooks preserved. |
|
|
425
459
|
| `~/.codex/AGENTS.md` | A delimited block (`<!-- token-goat-codex-begin -->` … `<!-- token-goat-codex-end -->`) with the same routing guidance, adapted for Codex tool names. |
|
|
426
460
|
| `~/.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. |
|
|
427
461
|
|
|
@@ -446,11 +480,19 @@ Three things follow, and they are worth knowing before you decide. It never leav
|
|
|
446
480
|
| `~/.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. |
|
|
447
481
|
| `~/.kimi-code/skills/token-goat/SKILL.md` | The same guidance as a Kimi Code skill. |
|
|
448
482
|
|
|
483
|
+
**With `--antigravity`** (Antigravity CLI integration)
|
|
484
|
+
|
|
485
|
+
| Path | What |
|
|
486
|
+
|------|------|
|
|
487
|
+
| `~/.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. |
|
|
488
|
+
| `~/.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. |
|
|
489
|
+
| `~/.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. |
|
|
490
|
+
|
|
449
491
|
**With `--opencode`** (opencode plugin)
|
|
450
492
|
|
|
451
493
|
| Path | What |
|
|
452
494
|
|------|------|
|
|
453
|
-
| `~/.config/opencode/plugins/token-goat.ts` (
|
|
495
|
+
| `~/.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 post-edit indexing, compact assist, and rewritten tool results. |
|
|
454
496
|
|
|
455
497
|
**With `--pi`** (pi extension)
|
|
456
498
|
|
|
@@ -463,8 +505,9 @@ Three things follow, and they are worth knowing before you decide. It never leav
|
|
|
463
505
|
| Path | What |
|
|
464
506
|
|------|------|
|
|
465
507
|
| `~/.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. |
|
|
466
|
-
| `~/.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`) 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. |
|
|
467
|
-
| `~/.copilot/
|
|
508
|
+
| `~/.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. |
|
|
509
|
+
| `~/.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`. |
|
|
510
|
+
| `~/.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. |
|
|
468
511
|
|
|
469
512
|
**With `--grok`** (Grok CLI / xAI Grok Build hook bridge)
|
|
470
513
|
|
|
@@ -477,7 +520,7 @@ Three things follow, and they are worth knowing before you decide. It never leav
|
|
|
477
520
|
|
|
478
521
|
| Path | What |
|
|
479
522
|
|------|------|
|
|
480
|
-
| `<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. |
|
|
523
|
+
| `<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, under `$XDG_CONFIG_HOME` when that is an absolute path) 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. |
|
|
481
524
|
| `<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. |
|
|
482
525
|
| `<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. |
|
|
483
526
|
|
package/docs/security.md
CHANGED
|
@@ -15,24 +15,26 @@ Outbound network is reserved to these explicit cases:
|
|
|
15
15
|
- Google Drive API calls, only if you already authorized Drive in Claude Code. Token-goat never prompts for its own auth.
|
|
16
16
|
- Image fetches from URLs: either explicit via `token-goat fetch-image <url>`, or when the AI agent issues a WebFetch call that returns image content — the hook intercepts and shrinks the image. The URL always originates from the agent's work, not from token-goat itself.
|
|
17
17
|
- `token-goat screenshot <url>` navigates a headless browser to the URL you give it, subject to the target restrictions described below.
|
|
18
|
-
- The first `token-goat semantic` run on a machine downloads the embedding model from `huggingface.co`, pinned to an immutable commit rather than a mutable branch and checked against a recorded SHA-256 and byte length before it is used,
|
|
19
|
-
- The first optical-character read of an image downloads the language data (about 4 MB; English `eng` by default, plus any additional opt-in languages configured via `image_shrink.ocr_lang`, `TOKEN_GOAT_OCR_LANG`, or `--lang` among 16 pinned models) from `cdn.jsdelivr.net`, at a fixed version path with precomputed SHA-256 integrity verification against the decompressed traineddata payload. Subsequent reads use the local cache. This happens for an explicit `token-goat image-text
|
|
18
|
+
- The first `token-goat semantic` run on a machine downloads the embedding model from `huggingface.co`, pinned to an immutable commit rather than a mutable branch and checked against a recorded SHA-256 and byte length before it is used. Unless `onnxruntime-node` is installed, the same first run also downloads the inference runtime: the `onnxruntime-web` 1.30.0 tarball from `registry.npmjs.org` (33 MB), itself checked against a recorded SHA-256 and byte length, from which only the 14 MB `ort-wasm-simd-threaded.wasm` is kept, checked against a digest of its own. The JavaScript that loads it ships inside token-goat and is checked against its recorded digest before it runs. Nothing is installed and no install script runs. Subsequent runs use the local cache, re-verify it, and make no network call. Setting `TOKEN_GOAT_MODEL_CACHE_DIR` adds one more place to look before the network: a directory you name that outlives any single data root, which is how a continuous-integration job stops refetching the weights once per worker. A file taken from there is checked against the same recorded digest as a downloaded one and dropped from the shared directory if it fails, and an entry that is not a plain file of the expected size is passed over without being read at all, so a tampered copy costs a download and cannot substitute different weights, stall the read, or fill the disk. Token-goat also writes there, and treats the directory as somebody else's: it creates its temporary file exclusively rather than through whatever is already sitting at that name, and removes only what it created. Point it at a directory you control all the same, since nothing token-goat does can stop another writer filling it with entries that will simply be rejected. It is an environment variable only, so no project config file can point it anywhere. Skip the download entirely by setting `indexing.embeddings_enabled = false` (it is on by default), in which case `semantic` falls back to full-text search.
|
|
19
|
+
- The first optical-character read of an image downloads the language data (about 4 MB; English `eng` by default, plus any additional opt-in languages configured via `image_shrink.ocr_lang`, `TOKEN_GOAT_OCR_LANG`, or `--lang` among 16 pinned models) from `cdn.jsdelivr.net`, at a fixed version path with precomputed SHA-256 integrity verification against the decompressed traineddata payload. Subsequent reads use the local cache. This happens only for an explicit `token-goat image-text`; the image-shrink hook runs no OCR and fetches no language data.
|
|
20
20
|
|
|
21
21
|
**One switch for all of it.** Set `network.offline = true` (env `TOKEN_GOAT_OFFLINE`) and every one of the paths above refuses instead of connecting, saying so rather than failing quietly. Anything already cached keeps working: a machine that has the embedding model still runs `semantic`, and one that has the language data still reads text out of images. This is one of the settings a per-project config file may not touch, so cloning a repository cannot switch it back off.
|
|
22
22
|
|
|
23
|
-
**A repository cannot reconfigure the security controls.** A project-root `.token-goat.toml` layers on top of your global config, which is what it is for: hint thresholds, indexing settings, compression tuning. But that file arrives with the repository, so whoever wrote the repository wrote it. Seven whole sections are therefore off limits to it, and come from your global config or the environment only: `injection` (prompt-injection fencing), `webfetch` (the fetch allow and deny lists), `gdrive` (the Google Drive integration), `mcp` (root confinement and the allowed-roots list), `network` (offline mode), `redaction` (the secret-redaction rules), and `screenshot` (the headless browser).
|
|
23
|
+
**A repository cannot reconfigure the security controls.** A project-root `.token-goat.toml` layers on top of your global config, which is what it is for: hint thresholds, indexing settings, compression tuning. But that file arrives with the repository, so whoever wrote the repository wrote it. Seven whole sections are therefore off limits to it, and come from your global config or the environment only: `injection` (prompt-injection fencing), `webfetch` (the fetch allow and deny lists), `gdrive` (the Google Drive integration), `mcp` (root confinement and the allowed-roots list), `network` (offline mode), `redaction` (the secret-redaction rules), and `screenshot` (the headless browser). Twenty-two individual keys inside otherwise-overridable sections are locked the same way. Seven decide what gets indexed at all: `indexing.cross_project_symbols`, `indexing.skip_dirs`, `indexing.skip_files`, `indexing.skip_minified`, `indexing.large_file_skip_kb`, `indexing.large_file_symbol_only_kb` and `indexing.max_chunks_per_file`, which matter because an unindexed file answers `symbol`, `read` and `semantic` in the same words a name that never existed does. Five more are the switches that decide whether a large file arrives folded rather than whole: `hints.fold_code_bodies`, `hints.fold_comment_blocks`, `hints.fold_prose_paragraphs`, `hints.outline_large_documents` and `hints.skeleton_large_sources`. Five more each guard one thing: `image_shrink.max_image_pixels`, the decompression-bomb cap; `worker.blocked_roots`, the folders you have kept out of the index; `compact_assist.summary_budget_chars`, the length target token-goat hands to whoever writes your compaction summary, which a repository must not be able to set low enough to erase the session; `semantic.max_distance`, the relevance floor for `semantic`, which a repository must not be able to set low enough that its own code stops matching while the command goes on answering from keyword search as though nothing were missing; and `semantic.weak_distance`, the line past which `semantic` warns that its closest match is weak, which a repository must not be able to raise until every poor hit reads as confident. Two more govern the shared database: `indexing.max_db_size_mb` and `indexing.auto_reclaim_embeddings`, which prevent a repository from manipulating global database disk caps or forcing vector purging across projects. Two more govern symbol-aware first-read thresholds and enforcement: `hints.first_read_symbol_bytes` and `hints.first_read_symbol_policy`. One more is `hooks.native`, which decides whether `install` writes the native hook client into the hook configs of every harness on the machine, so a project's file has no say in it. A project file that sets one of them is ignored, and token-goat prints a line naming what it dropped. Everything else stays project-overridable.
|
|
24
24
|
|
|
25
|
-
**The lock covers the config file, not the environment.** These settings still read a `TOKEN_GOAT_*` environment variable, and a repository has ways to set one: a `.envrc` for direnv, a `terminal.integrated.env.*` block in a committed `.vscode/settings.json`, a `containerEnv` entry in a devcontainer. Refusing environment overrides would break the operator who exports a variable in their own shell, which is the legitimate case and the common one, so token-goat reports instead of refusing: `token-goat doctor` prints a `Security config overrides` line naming every locked security setting the environment is currently deciding, and the variable to unset. It covers all twenty-
|
|
25
|
+
**The lock covers the config file, not the environment.** These settings still read a `TOKEN_GOAT_*` environment variable, and a repository has ways to set one: a `.envrc` for direnv, a `terminal.integrated.env.*` block in a committed `.vscode/settings.json`, a `containerEnv` entry in a devcontainer. Refusing environment overrides would break the operator who exports a variable in their own shell, which is the legitimate case and the common one, so token-goat reports instead of refusing: `token-goat doctor` prints a `Security config overrides` line naming every locked security setting the environment is currently deciding, and the variable to unset. It covers all twenty-five locked keys that read an environment variable, and it derives that set from the same two tables that define what a project config may not write, rather than from a list kept alongside them. Settings with a safe side (booleans) are reported when the environment holds them open; settings without one (lists, sizes) are reported whenever the environment supplies a value at all, since there is nothing to compare against. A default install, where nothing is set, prints a single ok line.
|
|
26
|
+
|
|
27
|
+
**token-goat's rewrites do not approve your commands.** To compress a command's output, token-goat rewrites the command to run under `token-goat compress`, and it can point a Read of a large image at a smaller copy. Claude Code checks your permission rules against the rewritten call, so token-goat reads your Claude Code settings first (managed policy, `~/.claude/settings.json`, and the `.claude` folders of your project) and leaves the call alone whenever a deny or ask rule could apply to it, or a settings file cannot be read. A path reached through a symlink, a Windows junction or an 8.3 short name is checked under its real spelling too, as Claude Code does. A rewritten call is approved on your behalf only when Claude Code would have run the original with no prompt anyway, for example a read-only command like `git status`, or a command one of your own allow rules covers; an allow in `.claude/settings.local.json` counts only when git reports that file untracked inside a repository, so one shipped in a downloaded folder with no repository never approves anything. Rules are read from the folder the session started in, its main git checkout when that folder is a worktree, and the current directory; once a command's directory leaves the project, token-goat leaves the command alone, since the rules that apply there are not ones it can see. It is never approved in plan mode; otherwise Claude Code asks you as usual. In auto mode and bypassPermissions mode token-goat never adds a prompt: it either compresses a call or leaves it alone. In auto mode it leaves every call alone, since an approval would skip the auto-mode classifier's review. In bypassPermissions mode it rewrites and approves a call only when no rule source a hook cannot read could apply, and otherwise leaves it alone; those sources are a host that answers permission prompts itself (the Agent SDK's `canUseTool`, an editor extension, anything but the terminal `claude` command), a command-line flag that adds or relays rules (`--disallowedTools`, `--settings`, or any flag token-goat does not know to be harmless), a skill, slash command, agent or nested `.claude` folder whose `disallowed-tools` names a tool by pattern, and a `PermissionRequest` hook in a settings file or an installed plugin. A call made inside a subagent is approved only when the subagent is one of Claude Code's own or one whose definition token-goat read, since a definition can remove tools by pattern too. Skill folders reached through a symlink or a Windows junction, and ones inside `node_modules`, are checked like any other; one in a folder git ignores is passed over only when the folder holding its `.claude` is ignored, which is the folder Claude Code asks git about. The folders Claude Code always reads are checked again on every call, so a skill written there counts from the next call. In every other mode where a call can be approved (default, acceptEdits and dontAsk), a call is also left alone while the claude command line carries a flag token-goat does not know to be harmless, or cannot be read, so a `--disallowedTools` rule now holds against a compressed command there too; a session launched with `--remote-control` is one of these, because that flag is not on the list of known-harmless flags, and `--setting-sources` is harmless only when it names user, project and local. Those modes also leave a call alone while a skill, command, agent or plugin could remove its tool by pattern or a PermissionRequest hook could answer for it, for Claude Code and for VS Code alike (the command line is asked only of the terminal command), and until that check has loaded no mode approves a call. Two unseen sources can still let a call be approved. The first is a deliberate cost trade: a `.claude` folder nested deeper in the project is searched again for each new session, working directory or project folder, and otherwise at most once a minute, which keeps the hook fast at the price of a skill written there going unseen for up to a minute, except that a change to the checkout's index or HEAD (a pull, a checkout, a `git add`), or to a folder within three levels of the project or on the path to a listed `.claude` at any depth, ends that minute at once; no clean answer is kept when a stamp is under 2 seconds old, cannot be read, or the folders to stamp reach 2,000. The one hole left is a `.claude` folder first created, untracked, more than three levels down in a folder that holds no file git lists, which keeps the minute. A skill file git tracks that is gone from the disk is read from git's index, so moving a skill away and back between two calls does not hide it; a gone file git never listed is no rule, as Claude Code reads skills from disk. Nothing about the claude process or the registry policy is kept in a file between hooks, because a file the same user can write is a file an agent can write, and a forged "no flags" would approve what `--disallowedTools` refuses; the resident hook server remembers the command line for the session in its own memory, and a one-shot hook reads it again. A command line that says nothing about its flags (a Node process that retitled itself, which leaves only its title in `/proc` on Linux) is read as unreadable, not as having none. The second is an accepted limit: rules a host passes in a way token-goat cannot see. When `CLAUDE_CODE_ENTRYPOINT` is set to something other than the terminal command (an editor extension, the desktop app, a remote session), outside bypassPermissions mode token-goat judges the call by your settings files alone, because it cannot tell how that host starts Claude Code; a missing or empty value takes the strict path instead, both before and after the check is loaded. This applies to Claude Code itself; VS Code runs the same settings walk with the mode treated as `default` and no command line rule, and Codex, Copilot CLI and every other harness never reach it. A project that can set `CLAUDE_PID` or `CLAUDE_CODE_ENTRYPOINT` through the `env` of its settings can already add `permissions.allow` rules, so forging either is no escalation. Put rules you rely on in a settings file. token-goat's own hints follow the same rules: a hint that answers a Read, a Grep or a `cat` by naming a token-goat command that reads the file itself (`token-goat read`, `json-query`, `skeleton`) is not given for a path a Read deny or ask rule could cover, since no Read rule applies to that command, and Claude Code then answers the call as it would without token-goat; a shell command whose `$`, `~` or wildcard names files a hook cannot resolve gets no such hint while any Read deny or ask rule exists. On Codex, a command is rewritten only when no rule in a Codex rules file (`rules/*.rules` in `~/.codex`, `CODEX_HOME`, the system Codex folder, and every `.codex` folder above the current directory) could match it, whatever the rule's decision, since an `allow` rule runs the command outside the sandbox and the wrapper would lose that; a rules file token-goat cannot read, or an administrator's `requirements.toml` or macOS managed preference that adds rules, stops every command rewrite. Requirements Codex fetches from the cloud (Agent Security in the Admin Console) are an accepted limit: no file on the machine holds them, so a hook cannot read them; where those apply, `TOKEN_GOAT_BASH_COMPRESS=0` turns compression off, though a plain `rg` or `grep` listing of one file's definitions is still answered from the index. On GitHub Copilot CLI, opencode and Grok, a command is never rewritten, a deliberate safety choice and not only a limit, since their shell permission rules cannot be seen and token-goat will not rewrite a command it cannot show those rules would allow: Copilot's `--allow-tool` and `--deny-tool` rules arrive on its command line, opencode's permission rules can come from an organization account or a well-known URL and its check for directories outside the project reads the paths a wrapped command would hide, and Grok applies a rewrite before its own policy and prompt see the call; none of that is visible to a hook. On Copilot CLI and opencode, a recognized command's output is still compressed after the command has run. On opencode, a Read of a large image is not pointed at a smaller copy either, because opencode checks its `read` permission against the copy's path instead of the image's. On Copilot CLI, only an image inside the working directory is pointed at a smaller copy: Copilot asks before reading outside the working directory, the system temp folder and any folder added with `/add-dir`, the copy sits in the temp folder, and a hook cannot tell whether Copilot checks the image's path or the copy's, nor which folders were added.
|
|
26
28
|
|
|
27
29
|
**Security reports.** See [SECURITY.md](../SECURITY.md). Email `token-goat@dfkhelper.com`; do not file as a GitHub issue. Reports are acknowledged within 7 days; coordinated disclosure with a 90-day default window.
|
|
28
30
|
|
|
29
|
-
**Dependency advisories.** `npm audit` on the published package is empty, for a default install as well as for `npm install --omit=optional`. That took removing the package the findings all came through: `@xenova/transformers`, which supplied the embedding half of `semantic` and carried a critical `protobufjs` advisory plus five more that had no forward patch. It is gone entirely now — the tokenizer and the ONNX runner are token-goat's own code
|
|
31
|
+
**Dependency advisories.** `npm audit` on the published package is empty, for a default install as well as for `npm install --omit=optional`. That took removing the package the findings all came through: `@xenova/transformers`, which supplied the embedding half of `semantic` and carried a critical `protobufjs` advisory plus five more that had no forward patch. It is gone entirely now — the tokenizer and the ONNX runner are token-goat's own code. By default they run on the WebAssembly build of ONNX Runtime, whose JavaScript is bundled into token-goat and whose binary is the pinned download described above, so a default install adds no package for it. If you install the native build, token-goat uses it instead:
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
|
-
npm install -g onnxruntime-node #
|
|
34
|
+
npm install -g onnxruntime-node # optional; about 288 MB installed. Drop -g if token-goat is a project dependency
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
That command is the one
|
|
37
|
+
That command is the one place an advisory can still reach: `onnxruntime-node` releases before 1.30.0 pull an `adm-zip` below 0.6.0, which carries [one high advisory](https://github.com/advisories/GHSA-xcpc-8h2w-3j85) that npm reports twice. It is reachable only from that package's own install script, unpacking the binary it just downloaded. The full accounting — including the override that clears it, why co-installing a fixed `adm-zip` does not, and what the old package cost — is under [Dependency advisories](../SECURITY.md#dependency-advisories).
|
|
36
38
|
|
|
37
39
|
**Verifying what you installed.** Every published version is built and pushed by one pinned workflow when a GitHub release is published, with npm provenance, so `npm audit signatures` verifies the tarball against the commit that produced it. Details in [Verifying what you installed](../SECURITY.md#verifying-what-you-installed).
|
|
38
40
|
|
|
@@ -66,7 +68,7 @@ Note what that flag does and does not cover. It stops a caller traversing *out o
|
|
|
66
68
|
|
|
67
69
|
**Where cached content lives, and who can read it.** Cached command output, fetched pages, MCP results, session state and the source index all sit under one data directory (`~/.local/share/token-goat` on Linux, `~/Library/Application Support/token-goat` on macOS, `%LOCALAPPDATA%\dfk-helper\token-goat` on Windows). On POSIX that directory is created owner-only (mode 0700), and an existing one is tightened on the next run, so other local users on a shared build host cannot read it. Windows uses inherited ACLs instead. Individual JSON blobs are additionally written 0600.
|
|
68
70
|
|
|
69
|
-
**Confining `symbol` to one project.** `token-goat symbol` is the one read command that answers from the machine-wide index (`global.db`) rather than the current project, so by default `symbol <name>` and `symbol --grep .` return matching symbols, bodies included, from every project ever indexed on the host. That is deliberate and useful on a personal machine: it is how you find a helper you wrote in another repo. On a shared build host, or under an agent you have confined to one directory, it is a read channel that the directory sandbox does not close, because the answer comes out of the index instead of the filesystem. Set `indexing.cross_project_symbols = false` (env `TOKEN_GOAT_CROSS_PROJECT_SYMBOLS`) and `symbol` only answers from the project it is run in. `--project` and `--file` pointing outside that project are refused rather than honored, so the setting cannot be stepped around from inside the confined process. Every other read command (`read`, `refs`, `callers`, `types`, `dead`, `find`, `semantic`)
|
|
71
|
+
**Confining `symbol` to one project.** `token-goat symbol` is the one read command that answers from the machine-wide index (`global.db`) rather than the current project, so by default `symbol <name>` and `symbol --grep .` return matching symbols, bodies included, from every project ever indexed on the host. That is deliberate and useful on a personal machine: it is how you find a helper you wrote in another repo. On a shared build host, or under an agent you have confined to one directory, it is a read channel that the directory sandbox does not close, because the answer comes out of the index instead of the filesystem. Set `indexing.cross_project_symbols = false` (env `TOKEN_GOAT_CROSS_PROJECT_SYMBOLS`) and `symbol` only answers from the project it is run in. `--project` and `--file` pointing outside that project are refused rather than honored, so the setting cannot be stepped around from inside the confined process. Every other read command (`read`, `refs`, `callers`, `types`, `dead`, `find`, `semantic`, `search`) answers only for the project it runs in, and a root named to read another project's indexed code, whether through `search --project` or the `projectRoot` of an MCP call, is refused the same way unless `mcp.allowed_roots` lists it. Because the permissive setting is the default and says nothing about itself, `token-goat doctor` prints a `Security symbol scope` line stating which way it is currently set, and names the switch when lookups are unconfined.
|
|
70
72
|
|
|
71
73
|
**Redacting credentials this build has never heard of.** Before anything is written to disk or handed back to the model, token-goat redacts the credential shapes it recognises — 19 patterns covering AWS, GitHub, Slack, Stripe, OpenAI, Anthropic, Google, npm and Azure keys, private key blocks, JWTs, bearer and basic auth headers, presigned URL signatures, and credentials embedded in a URL. Two settings cover what a fixed list cannot. `redaction.custom_patterns` (env `TOKEN_GOAT_REDACTION_CUSTOM_PATTERNS`, one pattern per line) is a list of your own regular expressions, redacted as `[REDACTED:custom]` — for an in-house token prefix, an employee number, an internal account id. A pattern that does not compile is skipped and named by `token-goat doctor` rather than failing silently, because a redaction rule you believe is running and which is not is worse than none at all. `redaction.strict` (env `TOKEN_GOAT_REDACTION_STRICT`, off by default) additionally redacts long high-entropy strings that match nothing known: the shape of a credential nobody wrote a rule for. It is a heuristic, and it says so — it needs three of the four character classes and genuine randomness, which spares git SHAs and hex digests, but it will sometimes redact a base64 blob that was not a secret. Both are settings a per-project config file may not touch, so a checked-in `.token-goat.toml` cannot weaken redaction; an environment variable still can, which is what the `Security config overrides` line above exists to surface.
|
|
72
74
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-goat",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.31",
|
|
4
4
|
"description": "Surgical token-reduction companion for Claude Code and other AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/token-goat.mjs",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "node esbuild.config.mjs",
|
|
21
|
+
"build:native": "node scripts/build-native.mjs",
|
|
21
22
|
"sbom": "npm sbom --sbom-format cyclonedx --omit=dev",
|
|
22
23
|
"notices": "node scripts/generate-third-party-notices.mjs",
|
|
23
24
|
"notices:check": "node scripts/generate-third-party-notices.mjs --check",
|
|
@@ -26,21 +27,29 @@
|
|
|
26
27
|
"typecheck:tests": "tsc -p tsconfig.tests.json --noEmit",
|
|
27
28
|
"test": "vitest run",
|
|
28
29
|
"model:warm": "tsx scripts/warm-model-cache.ts",
|
|
30
|
+
"eval:hints": "tsx scripts/eval-hints.ts",
|
|
31
|
+
"eval:retrieval": "tsx scripts/eval-retrieval.ts",
|
|
32
|
+
"eval:gate": "tsx scripts/eval-gate.ts",
|
|
29
33
|
"test:guards": "vitest run tests/guards",
|
|
30
34
|
"test:matrix": "vitest run tests/command_matrix_e2e",
|
|
31
35
|
"test:watch": "vitest",
|
|
32
36
|
"test:vscode-extension": "npm --prefix vscode-extension test",
|
|
33
|
-
"lint": "eslint src tests scripts vscode-extension/src vscode-extension/tests",
|
|
37
|
+
"lint": "eslint src tests scripts evals vscode-extension/src vscode-extension/tests",
|
|
34
38
|
"typecheck:vscode-extension": "npm --prefix vscode-extension run compile",
|
|
35
39
|
"typecheck:vscode-extension:tests": "npm --prefix vscode-extension run typecheck:tests",
|
|
36
40
|
"dev": "tsx src/main.ts",
|
|
41
|
+
"sync": "node scripts/post-merge.mjs",
|
|
37
42
|
"prepare": "node scripts/install-git-hooks.mjs",
|
|
43
|
+
"prepack": "node scripts/verify-native-dist.mjs --pack dist/native",
|
|
38
44
|
"parser:fingerprint": "node scripts/parser-fingerprint.mjs",
|
|
39
45
|
"parser:fingerprint:check": "node scripts/parser-fingerprint.mjs --check",
|
|
46
|
+
"skill:canonical": "node scripts/generate-canonical-skill.mjs",
|
|
47
|
+
"skill:canonical:check": "node scripts/generate-canonical-skill.mjs --check",
|
|
40
48
|
"schema:copilot": "node scripts/extract_harness_schema.mjs auto --harness copilot_cli --out schemas/copilot_cli.hooks.json",
|
|
41
49
|
"schema:copilot:check": "node scripts/extract_harness_schema.mjs auto --harness copilot_cli --out schemas/copilot_cli.hooks.json --check",
|
|
42
50
|
"docs:arch": "node scripts/sync-arch-docs.mjs",
|
|
43
|
-
"docs:arch:check": "node scripts/sync-arch-docs.mjs --check"
|
|
51
|
+
"docs:arch:check": "node scripts/sync-arch-docs.mjs --check",
|
|
52
|
+
"adoption": "node scripts/adoption-baseline.mjs"
|
|
44
53
|
},
|
|
45
54
|
"contributors": [
|
|
46
55
|
{
|
|
@@ -67,7 +76,8 @@
|
|
|
67
76
|
"docs/C4_RUNTIME_ARCHITECTURE.md",
|
|
68
77
|
"LICENSE",
|
|
69
78
|
"THIRD_PARTY_NOTICES.md",
|
|
70
|
-
"scripts/install-git-hooks.mjs"
|
|
79
|
+
"scripts/install-git-hooks.mjs",
|
|
80
|
+
"scripts/verify-native-dist.mjs"
|
|
71
81
|
],
|
|
72
82
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
73
83
|
"engines": {
|
|
@@ -80,7 +90,6 @@
|
|
|
80
90
|
"@eslint/js": "^10.0.1",
|
|
81
91
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
82
92
|
"@types/better-sqlite3": "^9.6.0",
|
|
83
|
-
"@types/jpeg-js": "^0.3.0",
|
|
84
93
|
"@types/node": "^26.2.0",
|
|
85
94
|
"@types/omggif": "^1.0.5",
|
|
86
95
|
"@xenova/transformers": "^2.17.2",
|
|
@@ -98,12 +107,14 @@
|
|
|
98
107
|
"lefthook": "^2.1.10",
|
|
99
108
|
"omggif": "^1.0.10",
|
|
100
109
|
"onnxruntime-node": "^1.27.0",
|
|
110
|
+
"onnxruntime-web": "1.30.0",
|
|
101
111
|
"sharp": "^0.35.3",
|
|
102
112
|
"smol-toml": "^1.8.0",
|
|
103
113
|
"tsx": "^4.23.12",
|
|
104
114
|
"typescript": "^6.0.3",
|
|
105
115
|
"typescript-eslint": "^8.67.0",
|
|
106
|
-
"
|
|
116
|
+
"vite": "^8.3.0",
|
|
117
|
+
"vitest": "^5.0.1",
|
|
107
118
|
"zod": "^4.4.3"
|
|
108
119
|
},
|
|
109
120
|
"optionalDependencies": {
|
|
@@ -130,7 +141,7 @@
|
|
|
130
141
|
"uuid": "^14.0.1",
|
|
131
142
|
"sharp": "$sharp",
|
|
132
143
|
"unzipper": "^0.12.5",
|
|
133
|
-
"adm-zip": "^0.6.
|
|
144
|
+
"adm-zip": "^0.6.1",
|
|
134
145
|
"fast-uri": "^3.1.6",
|
|
135
146
|
"qs": "^6.16.0",
|
|
136
147
|
"tesseract.js-core": "6.1.2"
|