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.
- package/README.md +20 -49
- package/dist/{token-goat-chunk-Q4LOQY44.mjs → token-goat-chunk-2VVXTAGD.mjs} +137 -12
- package/dist/{token-goat-chunk-LEBYARO3.mjs → token-goat-chunk-3UBORD6Z.mjs} +9 -6
- package/dist/{token-goat-chunk-LJ3CHCTT.mjs → token-goat-chunk-3XVGPLDS.mjs} +3492 -46
- package/dist/{token-goat-chunk-KKIB7O3Z.mjs → token-goat-chunk-6FQMEMWX.mjs} +418 -105
- package/dist/{token-goat-chunk-FXAPKVRG.mjs → token-goat-chunk-7A7SBE6R.mjs} +26 -5
- package/dist/token-goat-chunk-A37V4PBF.mjs +56 -0
- package/dist/{token-goat-chunk-VCNW7BGU.mjs → token-goat-chunk-A4VBNWSL.mjs} +20 -3
- package/dist/{token-goat-chunk-G6XLWJWI.mjs → token-goat-chunk-E3K3BTEQ.mjs} +5 -2
- package/dist/{token-goat-chunk-AO2QD2AG.mjs → token-goat-chunk-EEIDFMEM.mjs} +4 -0
- package/dist/{token-goat-chunk-324QOJYZ.mjs → token-goat-chunk-EVC4TOLE.mjs} +4 -1
- package/dist/{token-goat-chunk-WN5T5EW5.mjs → token-goat-chunk-FQD3OB5W.mjs} +98 -2
- package/dist/token-goat-chunk-HC5NMGPD.mjs +23 -0
- package/dist/{token-goat-chunk-UZ2NFOOZ.mjs → token-goat-chunk-KKHKXUVS.mjs} +154 -8
- package/dist/{token-goat-chunk-NY4HYRSK.mjs → token-goat-chunk-LLNA42NI.mjs} +10 -12
- package/dist/{token-goat-chunk-5CVKO3DA.mjs → token-goat-chunk-LYWIRYCF.mjs} +5 -2
- package/dist/{token-goat-chunk-CGWACYYZ.mjs → token-goat-chunk-OKJX2JWW.mjs} +6 -1
- package/dist/{token-goat-chunk-R4SR7MQY.mjs → token-goat-chunk-PXWBHSFB.mjs} +2 -2
- package/dist/{token-goat-chunk-JMUBXBG7.mjs → token-goat-chunk-XSSQFI4C.mjs} +7 -7
- package/dist/token-goat-hook.mjs +12 -7
- package/dist/token-goat.core.mjs +10 -7
- package/docs/cli.md +53 -0
- package/docs/security.md +6 -2
- package/package.json +8 -2
- package/dist/token-goat-chunk-AEX54RUZ.mjs +0 -11
- package/dist/token-goat-chunk-ZVN5WHTO.mjs +0 -23
package/dist/token-goat-hook.mjs
CHANGED
|
@@ -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-
|
|
6
|
-
import "./token-goat-chunk-
|
|
7
|
-
import "./token-goat-chunk-
|
|
8
|
-
import "./token-goat-chunk-
|
|
9
|
-
import "./token-goat-chunk-
|
|
10
|
-
import "./token-goat-chunk-
|
|
11
|
-
import
|
|
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
|
};
|
package/dist/token-goat.core.mjs
CHANGED
|
@@ -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-
|
|
6
|
-
import "./token-goat-chunk-
|
|
7
|
-
import "./token-goat-chunk-
|
|
8
|
-
import "./token-goat-chunk-
|
|
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-
|
|
12
|
-
import "./token-goat-chunk-
|
|
13
|
-
import
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
};
|