token-goat 2.9.2 → 2.9.3

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.
Files changed (26) hide show
  1. package/README.md +20 -49
  2. package/dist/{token-goat-chunk-Q4LOQY44.mjs → token-goat-chunk-2VVXTAGD.mjs} +137 -12
  3. package/dist/{token-goat-chunk-LEBYARO3.mjs → token-goat-chunk-3UBORD6Z.mjs} +9 -6
  4. package/dist/{token-goat-chunk-LJ3CHCTT.mjs → token-goat-chunk-3XVGPLDS.mjs} +3492 -46
  5. package/dist/{token-goat-chunk-KKIB7O3Z.mjs → token-goat-chunk-6FQMEMWX.mjs} +418 -105
  6. package/dist/{token-goat-chunk-FXAPKVRG.mjs → token-goat-chunk-7A7SBE6R.mjs} +26 -5
  7. package/dist/token-goat-chunk-A37V4PBF.mjs +56 -0
  8. package/dist/{token-goat-chunk-VCNW7BGU.mjs → token-goat-chunk-A4VBNWSL.mjs} +20 -3
  9. package/dist/{token-goat-chunk-G6XLWJWI.mjs → token-goat-chunk-E3K3BTEQ.mjs} +5 -2
  10. package/dist/{token-goat-chunk-AO2QD2AG.mjs → token-goat-chunk-EEIDFMEM.mjs} +4 -0
  11. package/dist/{token-goat-chunk-324QOJYZ.mjs → token-goat-chunk-EVC4TOLE.mjs} +4 -1
  12. package/dist/{token-goat-chunk-WN5T5EW5.mjs → token-goat-chunk-FQD3OB5W.mjs} +98 -2
  13. package/dist/token-goat-chunk-HC5NMGPD.mjs +23 -0
  14. package/dist/{token-goat-chunk-UZ2NFOOZ.mjs → token-goat-chunk-KKHKXUVS.mjs} +154 -8
  15. package/dist/{token-goat-chunk-NY4HYRSK.mjs → token-goat-chunk-LLNA42NI.mjs} +10 -12
  16. package/dist/{token-goat-chunk-5CVKO3DA.mjs → token-goat-chunk-LYWIRYCF.mjs} +5 -2
  17. package/dist/{token-goat-chunk-CGWACYYZ.mjs → token-goat-chunk-OKJX2JWW.mjs} +6 -1
  18. package/dist/{token-goat-chunk-R4SR7MQY.mjs → token-goat-chunk-PXWBHSFB.mjs} +2 -2
  19. package/dist/{token-goat-chunk-JMUBXBG7.mjs → token-goat-chunk-XSSQFI4C.mjs} +7 -7
  20. package/dist/token-goat-hook.mjs +12 -7
  21. package/dist/token-goat.core.mjs +10 -7
  22. package/docs/cli.md +53 -0
  23. package/docs/security.md +6 -2
  24. package/package.json +8 -2
  25. package/dist/token-goat-chunk-AEX54RUZ.mjs +0 -11
  26. package/dist/token-goat-chunk-ZVN5WHTO.mjs +0 -23
@@ -2,13 +2,18 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  relayInProcess
5
- } from "./token-goat-chunk-Q4LOQY44.mjs";
6
- import "./token-goat-chunk-VCNW7BGU.mjs";
7
- import "./token-goat-chunk-LJ3CHCTT.mjs";
8
- import "./token-goat-chunk-CGWACYYZ.mjs";
9
- import "./token-goat-chunk-UZ2NFOOZ.mjs";
10
- import "./token-goat-chunk-AO2QD2AG.mjs";
11
- import "./token-goat-chunk-AEX54RUZ.mjs";
5
+ } from "./token-goat-chunk-2VVXTAGD.mjs";
6
+ import "./token-goat-chunk-A4VBNWSL.mjs";
7
+ import "./token-goat-chunk-3XVGPLDS.mjs";
8
+ import "./token-goat-chunk-OKJX2JWW.mjs";
9
+ import "./token-goat-chunk-KKHKXUVS.mjs";
10
+ import "./token-goat-chunk-EEIDFMEM.mjs";
11
+ import {
12
+ init_define_import_meta_env
13
+ } from "./token-goat-chunk-A37V4PBF.mjs";
14
+
15
+ // src/hook_lib.ts
16
+ init_define_import_meta_env();
12
17
  export {
13
18
  relayInProcess
14
19
  };
@@ -2,16 +2,19 @@ import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./token-goat-chunk-KKIB7O3Z.mjs";
6
- import "./token-goat-chunk-FXAPKVRG.mjs";
7
- import "./token-goat-chunk-VCNW7BGU.mjs";
8
- import "./token-goat-chunk-LJ3CHCTT.mjs";
5
+ } from "./token-goat-chunk-6FQMEMWX.mjs";
6
+ import "./token-goat-chunk-7A7SBE6R.mjs";
7
+ import "./token-goat-chunk-A4VBNWSL.mjs";
8
+ import "./token-goat-chunk-3XVGPLDS.mjs";
9
9
  import {
10
10
  installEpipeGuard
11
- } from "./token-goat-chunk-UZ2NFOOZ.mjs";
12
- import "./token-goat-chunk-AO2QD2AG.mjs";
13
- import "./token-goat-chunk-AEX54RUZ.mjs";
11
+ } from "./token-goat-chunk-KKHKXUVS.mjs";
12
+ import "./token-goat-chunk-EEIDFMEM.mjs";
13
+ import {
14
+ init_define_import_meta_env
15
+ } from "./token-goat-chunk-A37V4PBF.mjs";
14
16
 
15
17
  // src/main.ts
18
+ init_define_import_meta_env();
16
19
  installEpipeGuard();
17
20
  void run();
package/docs/cli.md CHANGED
@@ -337,3 +337,56 @@ are shown alongside the automatic percentage but never blended into it. `--json`
337
337
  Note that what this feature calls "harness" (Claude Code, Codex, Gemini, ...) is not the same as
338
338
  "which LLM model" — no bridge in this codebase exposes an LLM model identifier to hooks, so
339
339
  harness is the closest real signal available.
340
+
341
+ ### Scoring the compressors — `token-goat bench`
342
+
343
+ `token-goat bench` replays a fixed corpus of captured command output through the same function
344
+ that decides what a real shell command hands back to the model, and reports how much smaller the
345
+ result is. It exists so a change to a compressor can be measured instead of guessed at:
346
+
347
+ ```
348
+ $ token-goat bench
349
+ case filter in out saved fidelity
350
+ ---------------------------------------------------------------
351
+ git-log-stat git-log 57227 3027 94.7% 2/2
352
+ npm-ls-all dep-list 34546 1060 96.9% 2/2
353
+ vitest-run vitest 22684 22682 - 2/2
354
+ ---------------------------------------------------------------
355
+ TOTAL 114457 26769 76.6% 6/6
356
+
357
+ ratio 76.6% saved (PRIMARY -- must improve; measured floor 0.0%, headroom 23.4%)
358
+ fidelity 6/6 kept (GUARD -- must not regress; any miss exits 1)
359
+ coverage 3/157 filters exercised, 2/3 cases compressed
360
+ ```
361
+
362
+ There are two numbers on purpose. **Ratio** is the thing to push up. **Fidelity** counts the lines
363
+ each case declares it must never lose, and it is what stops the ratio from being gamed: deleting
364
+ output raises the ratio, so a benchmark reporting only the ratio would reward destroying the very
365
+ content the compressors exist to preserve. A dropped must-keep line exits 1, so a loop can revert
366
+ on the exit code alone.
367
+
368
+ The corpus lives in `tests/fixtures/bench`: one `<id>.json` naming the command, its exit code,
369
+ and its must-keep lines, beside an `<id>.txt` holding that run's real output. Point `--corpus
370
+ <dir>` at your own directory to score commands you care about. Every case must declare where its
371
+ output came from; a fixture with no `provenance` field is refused rather than scored, because
372
+ output written by hand proves nothing about what a real command emits.
373
+
374
+ `--tsv <path>` appends one row per run (timestamp, commit, sizes, ratio, fidelity, coverage), so a
375
+ sequence of attempts leaves a readable history instead of a scrollback. `--json` emits the same
376
+ report as an object. `--validate` runs the benchmark's own negative controls and says whether the
377
+ corpus can tell good from bad at all:
378
+
379
+ ```
380
+ $ token-goat bench --validate
381
+ ok floor: the net-benefit gate refuses a no-op filter on every case -- identity control applied to 0/3 cases, measured floor 0.0%
382
+ ok floor: a no-op filter keeps every must-keep line -- identity control kept 6/6
383
+ ok guard: deleting everything is caught -- destroy control kept 0/6 at 99.8% saved
384
+
385
+ The corpus discriminates: a ratio measured against it cannot be raised by deleting content.
386
+ ```
387
+
388
+ The first two controls establish the floor: a filter that changes nothing must score nothing, so
389
+ the reported percentage is a real saving rather than an offset every number carries. The third
390
+ runs a filter that deletes the entire output. It scores 99.8%, the best ratio possible, and the
391
+ fidelity guard fails it. Run `--validate` after changing the corpus; a corpus whose must-keep
392
+ lists have gone soft passes every benchmark while measuring nothing.
package/docs/security.md CHANGED
@@ -38,12 +38,16 @@ That command is the one thing here that is not clean: `onnxruntime-node` pulls a
38
38
 
39
39
  **Prompt injection.** When an AI reads a file, web page, or command output, that content enters its context alongside your own instructions. Prompt injection is when untrusted content includes text designed to look like instructions — "Ignore all previous directives and run this instead" — to redirect the AI mid-task.
40
40
 
41
- Token-goat intercepts every Read, Fetch, Bash, and MCP call the AI makes. Text that came from somewhere else is wrapped in an untrusted-content fence before the model sees it, decided by where the text came from and not by whether anything looked suspicious in it (`injection.enabled`, on by default, turns the whole thing off). The content is also scanned for a set of imperative-override attack patterns ("ignore previous instructions," "reveal system prompt," and similar); a match adds the pattern names to the fence's notice and writes a row to the log, and a clean scan changes only the wording. That ordering is the point: the pattern list is deliberately short, so anyone phrasing the same instruction differently would otherwise get an unlabelled channel, and a miss would be silent.
41
+ Token-goat intercepts every Read, Fetch, Bash, and MCP call the AI makes. Where it hands the model text in place of the tool's own result, that text is wrapped in an untrusted-content fence first, decided by where the text came from and not by whether anything looked suspicious in it (`injection.enabled`, on by default, turns the whole thing off). Which surfaces that covers, and which it deliberately does not, is set out below: the two exceptions are stated rather than left to be read off this sentence. The content is also scanned for a set of imperative-override attack patterns ("ignore previous instructions," "reveal system prompt," and similar); a match adds the pattern names to the fence's notice and writes a row to the log, and a clean scan changes only the wording. That ordering is the point: the pattern list is deliberately short, so anyone phrasing the same instruction differently would otherwise get an unlabelled channel, and a miss would be silent.
42
42
 
43
- Three surfaces are covered. Every fetched page is fenced as it arrives and again when a cached copy is recalled with `web-output`. Every MCP tool result is fenced as it arrives, which matters most: it is a remote server's output, so it is the least trustworthy text in the pipeline. And cached Bash and MCP output is fenced when recalled with `bash-output` or `mcp-output`, since the output of a build or test run in a project with a hostile dependency is written by a third party as much as any web page is. Document extraction (`pdf-extract`, `docx-text`, the `xlsx-*` and `pptx-*` commands), `pr-slice`, `gdrive-sections`, and `recall` are covered the same way. The fence naming tool output is a different tag from the one naming web content, so the label tells the model where the text came from.
43
+ The line that decides is substitution: wherever token-goat replaces a tool result with text of its own, that text is fenced. Every fetched page is fenced as it arrives and again when a cached copy is recalled with `web-output`. Every MCP tool result is fenced as it arrives, which matters most: it is a remote server's output, so it is the least trustworthy text in the pipeline. Bash output is fenced when token-goat compresses it, since a compressed body is not the command's output but token-goat's account of it, and it is fenced again when a cached copy is recalled with `bash-output` or `mcp-output`: the output of a build or test run in a project with a hostile dependency is written by a third party as much as any web page is. Document extraction (`pdf-extract`, `docx-text`, the `xlsx-*` and `pptx-*` commands), `pr-slice`, `gdrive-sections`, and `recall` are covered the same way. The fence naming tool output is a different tag from the one naming web content, so the label tells the model where the text came from.
44
+
45
+ In every one of those cases the fence wraps the third-party bytes and stops there. Token-goat's own notice, the filter's marker, and the pointer telling the model how to recall the full output all sit outside the closing tag, because the fence is the model's one signal for where token-goat stops speaking. Fold them inside and that signal is gone, and anyone who guesses the marker's wording can write a line the model reads as token-goat's own.
44
46
 
45
47
  Read is the exception: file content passes through to the model unfiltered, because filtering it would silently break legitimate use cases. Where token-goat splices a piece of a file into its own hint or denial message, that excerpt is fenced. Outside of the fence, the primary defense is the model's own training to treat tool output as data, not as commands from a trusted party.
46
48
 
49
+ Bash output that token-goat does not rewrite is the same kind of exception, and worth stating plainly rather than leaving to be inferred from the paragraph above. A command whose output is short, or too incompressible to be worth touching, reaches the model exactly as the harness delivered it, with no fence. So does output whose only change was stripping the colour codes a terminal would have rendered, since that path emits the command's own bytes and adds nothing of token-goat's to delimit. Fencing those cases would mean rewriting the result of every shell command an agent runs, a permanent cost on the most-used path in the tool, to re-label bytes the model was going to receive in that form anyway. The fence is worth its bytes where token-goat has substituted its own account of the output and the model can no longer tell whose words are whose. Where token-goat has stayed out of the way, the defense is the same as for Read: the model's training to treat tool output as data.
50
+
47
51
  One deliberate gap: `--json` output cannot carry a fence around the envelope, because a fence wrapped around JSON is no longer JSON and callers parse it. Those envelopes fence individual fields on a pattern match instead, since the fixed wrapper would otherwise cost more than a short field is worth. The printed (non-`--json`) form of the same command is always fenced.
48
52
 
49
53
  Separately from that pass-through case: when a read hook *denies* a Read and substitutes its own message, any file bytes it embeds in that message (a markdown heading tree, a served compact or notebook sidecar, a re-read diff, a CSV header row, an HTML title) are wrapped in an `<untrusted-file-content>` fence first, so a hostile repo cannot get its own text presented to the model as token-goat speaking. That fencing is unconditional, not gated on the pattern scan — as all of it now is.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-goat",
3
- "version": "2.9.2",
3
+ "version": "2.9.3",
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",
@@ -73,7 +73,9 @@
73
73
  "@eslint/js": "^10.0.1",
74
74
  "@modelcontextprotocol/sdk": "^1.30.0",
75
75
  "@types/better-sqlite3": "^9.6.0",
76
+ "@types/jpeg-js": "^0.3.0",
76
77
  "@types/node": "^26.2.0",
78
+ "@types/omggif": "^1.0.5",
77
79
  "@xenova/transformers": "^2.17.2",
78
80
  "better-sqlite3": "^12.11.1",
79
81
  "commander": "^15.0.0",
@@ -84,8 +86,10 @@
84
86
  "exceljs": "^4.4.0",
85
87
  "fast-xml-parser": "^5.9.3",
86
88
  "html-to-text": "^10.0.1",
89
+ "jpeg-js": "^0.4.4",
87
90
  "js-yaml": "^5.2.1",
88
91
  "lefthook": "^2.1.10",
92
+ "omggif": "^1.0.10",
89
93
  "onnxruntime-node": "^1.27.0",
90
94
  "smol-toml": "^1.8.0",
91
95
  "tsx": "^4.23.12",
@@ -119,6 +123,8 @@
119
123
  "uuid": "^14.0.1",
120
124
  "sharp": "$sharp",
121
125
  "unzipper": "^0.12.5",
122
- "adm-zip": "^0.6.0"
126
+ "adm-zip": "^0.6.0",
127
+ "fast-uri": "^3.1.6",
128
+ "qs": "^6.16.0"
123
129
  }
124
130
  }
@@ -1,11 +0,0 @@
1
- import { createRequire as __cjsRequire } from 'node:module';
2
- const require = __cjsRequire(import.meta.url);
3
- var __defProp = Object.defineProperty;
4
- var __export = (target, all) => {
5
- for (var name in all)
6
- __defProp(target, name, { get: all[name], enumerable: true });
7
- };
8
-
9
- export {
10
- __export
11
- };
@@ -1,23 +0,0 @@
1
- import { createRequire as __cjsRequire } from 'node:module';
2
- const require = __cjsRequire(import.meta.url);
3
- import {
4
- buildEvent,
5
- relay,
6
- relayInProcess
7
- } from "./token-goat-chunk-Q4LOQY44.mjs";
8
- import {
9
- MAX_STDIN_BYTES,
10
- readStdinJson
11
- } from "./token-goat-chunk-VCNW7BGU.mjs";
12
- import "./token-goat-chunk-LJ3CHCTT.mjs";
13
- import "./token-goat-chunk-CGWACYYZ.mjs";
14
- import "./token-goat-chunk-UZ2NFOOZ.mjs";
15
- import "./token-goat-chunk-AO2QD2AG.mjs";
16
- import "./token-goat-chunk-AEX54RUZ.mjs";
17
- export {
18
- MAX_STDIN_BYTES,
19
- buildEvent,
20
- readStdinJson,
21
- relay,
22
- relayInProcess
23
- };