token-goat 2.8.1 → 2.8.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 +15 -9
- package/dist/{token-goat-chunk-VBXBLGTO.mjs → token-goat-chunk-222VPFP2.mjs} +147 -53
- package/dist/{token-goat-chunk-XEDQH5DA.mjs → token-goat-chunk-4HIMCBYK.mjs} +6 -6
- package/dist/{token-goat-chunk-Y2SYNH3S.mjs → token-goat-chunk-4OM2Q2SX.mjs} +5 -5
- package/dist/{token-goat-chunk-ELDJRLHZ.mjs → token-goat-chunk-6ODZ6PZK.mjs} +219 -147
- package/dist/{token-goat-chunk-73MF6YWW.mjs → token-goat-chunk-C5IL6MJH.mjs} +3 -3
- package/dist/{token-goat-chunk-YUGNM3KL.mjs → token-goat-chunk-DP3NNGGV.mjs} +5 -5
- package/dist/{token-goat-chunk-7JDXDERZ.mjs → token-goat-chunk-EFF2XCLB.mjs} +31 -18
- package/dist/{token-goat-chunk-AOBZUFNJ.mjs → token-goat-chunk-NKNCHJ4H.mjs} +80 -49
- package/dist/{token-goat-chunk-2G6RAB4G.mjs → token-goat-chunk-PWVXXPCC.mjs} +136 -31
- package/dist/{token-goat-chunk-RMDQFTQD.mjs → token-goat-chunk-TX4JFJTD.mjs} +76 -19
- package/dist/{token-goat-chunk-I6TOUPLP.mjs → token-goat-chunk-VAGPOZHO.mjs} +2 -2
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ The fastest way to reduce AI token costs is fixing these five, not writing short
|
|
|
72
72
|
| Agent re-reads a file edited mid-session | Unified diff injected as a hint — full Read avoided when the diff covers the change. Docs and source/style/data files (`.md`/`.ts`/`.css`/`.json`/…) by default; set `serve_diff_on_reread = false` to disable source diffs |
|
|
73
73
|
| Compaction forgets which files were edited | Structured session manifest injected before compact |
|
|
74
74
|
| Same files re-read from scratch after `/compact` | Recovery hint at SessionStart lists cached snapshot + bash + WebFetch IDs |
|
|
75
|
-
| Loaded skill body
|
|
75
|
+
| Loaded skill body summarized away by compaction | `### Active Skills` manifest section + `**Skills**:` recovery block list every loaded skill; full body recoverable via `token-goat skill-body <name>` without re-invoking |
|
|
76
76
|
| Large skill bodies re-injected each turn (6 active skills = 65k+ tokens) | `<!-- COMPACT_END -->` marker: everything above the marker is the compact form; token-goat detects it on load, caches the compact slice, and injects only that — typically ~400 tokens vs. 10k+ |
|
|
77
77
|
| Model reads a skill SKILL.md file directly mid-session (burning the full 10k–65k tokens again) | Pre-Read hook intercepts `*/.claude/skills/<name>/SKILL.md` paths; if the skill is already cached this session it emits a `token-goat skill-body <name>` hint instead |
|
|
78
78
|
| Same large skill invoked twice in a session | PreToolUse hook blocks the reload; serves cached compact (~400 tokens) via `additionalContext` instead of the full 40–65k body. Allows the reload if compaction fired since the last load |
|
|
@@ -395,7 +395,7 @@ This writes hook entries into `~/.gemini/settings.json` using Gemini CLI's `Befo
|
|
|
395
395
|
token-goat install --qwen
|
|
396
396
|
```
|
|
397
397
|
|
|
398
|
-
This writes hook entries into `~/.qwen/settings.json`. Unlike Gemini CLI (its own ancestor, with a custom `BeforeTool`/`AfterTool`/`PreCompress` event/matcher scheme), Qwen Code's hooks system diverged and now mirrors Claude Code's own natively — `PreToolUse`/`PostToolUse`/`PreCompact`/`UserPromptSubmit`/`SubagentStop` event names and snake_case stdin JSON — so token-goat wires all five events with no wire-format translation needed. Qwen Code's own tool-name taxonomy is only partially documented, so token-goat uses a catch-all matcher per event rather than an incomplete per-tool list. Image shrinking, session hints, post-edit indexing, compact assist, and bash output compression all work. This bridge was built from QwenLM/qwen-code's published docs, not
|
|
398
|
+
This writes hook entries into `~/.qwen/settings.json`. Unlike Gemini CLI (its own ancestor, with a custom `BeforeTool`/`AfterTool`/`PreCompress` event/matcher scheme), Qwen Code's hooks system diverged and now mirrors Claude Code's own natively — `PreToolUse`/`PostToolUse`/`PreCompact`/`UserPromptSubmit`/`SubagentStop` event names and snake_case stdin JSON — so token-goat wires all five events with no wire-format translation needed. Qwen Code's own tool-name taxonomy is only partially documented, so token-goat uses a catch-all matcher per event rather than an incomplete per-tool list. Image shrinking, session hints, post-edit indexing, compact assist, and bash output compression all work. This bridge was built from QwenLM/qwen-code's published docs, not tested against a live Qwen Code install — if hooks aren't firing, `token-goat doctor` and the settings.json contents are the first things to check. To remove: `token-goat uninstall --qwen`.
|
|
399
399
|
|
|
400
400
|
### Kimi Code users
|
|
401
401
|
|
|
@@ -403,7 +403,7 @@ This writes hook entries into `~/.qwen/settings.json`. Unlike Gemini CLI (its ow
|
|
|
403
403
|
token-goat install --kimi
|
|
404
404
|
```
|
|
405
405
|
|
|
406
|
-
This writes `[[hooks]]` entries into `~/.kimi-code/config.toml` (or `$KIMI_CODE_HOME/config.toml`), covering Kimi Code's `PreToolUse`, `PostToolUse`, `PreCompact`, `UserPromptSubmit`, `SubagentStop`, and `SessionStart` events. Kimi Code sends a Claude-Code-shaped snake_case payload on stdin, but it reads a different response: only a top-level `message` and `hookSpecificOutput.permissionDecision` / `permissionDecisionReason`. So the install also writes a small shim at `~/.kimi-code/hooks/token-goat-shim.js` that translates token-goat's answer into that contract, turns a hint into `message`, and writes nothing at all for a no-op. Image shrinking, session hints, post-edit indexing, compact assist, and bash output compression all work. `Notification` and `Stop` are not wired, because token-goat has no handler for them. Input and output rewriting are not wired either: Kimi Code offers no channel to replace a tool's input or its result. This bridge was built from MoonshotAI/kimi-code's own source and docs, not
|
|
406
|
+
This writes `[[hooks]]` entries into `~/.kimi-code/config.toml` (or `$KIMI_CODE_HOME/config.toml`), covering Kimi Code's `PreToolUse`, `PostToolUse`, `PreCompact`, `UserPromptSubmit`, `SubagentStop`, and `SessionStart` events. Kimi Code sends a Claude-Code-shaped snake_case payload on stdin, but it reads a different response: only a top-level `message` and `hookSpecificOutput.permissionDecision` / `permissionDecisionReason`. So the install also writes a small shim at `~/.kimi-code/hooks/token-goat-shim.js` that translates token-goat's answer into that contract, turns a hint into `message`, and writes nothing at all for a no-op. Image shrinking, session hints, post-edit indexing, compact assist, and bash output compression all work. `Notification` and `Stop` are not wired, because token-goat has no handler for them. Input and output rewriting are not wired either: Kimi Code offers no channel to replace a tool's input or its result. This bridge was built from MoonshotAI/kimi-code's own source and docs, not 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`.
|
|
407
407
|
|
|
408
408
|
### opencode users
|
|
409
409
|
|
|
@@ -423,7 +423,7 @@ The `--openclaw` flag patches Claude Code and registers a TypeScript bridge plug
|
|
|
423
423
|
|
|
424
424
|
What works: **bash output compression**, **re-read denial** and **surgical-read redirects for oversized first reads**, **image shrinking**, and **post-edit indexing** (all via `before_tool_call`/`after_tool_call`). What doesn't: **session hints** — OpenClaw's tool-call hooks have no context-injection channel, only param rewriting — and the **compaction manifest** — OpenClaw's `before_compaction`/`after_compaction` are observation-only, with no return-value mechanism to inject a manifest into the next turn the way pi's compaction hooks do.
|
|
425
425
|
|
|
426
|
-
This bridge has not been validated against a live OpenClaw instance — it's built from OpenClaw's documented plugin SDK and hook event types, not
|
|
426
|
+
This bridge has not been validated against a live OpenClaw instance — it's built from OpenClaw's documented plugin SDK and hook event types, not tested against a real running gateway. If tool calls aren't being intercepted, the built-in tool name list in `openclaw.ts`'s `TOOL_TO_TG` map is the first thing to check. To remove: `token-goat uninstall --openclaw`.
|
|
427
427
|
|
|
428
428
|
### pi users
|
|
429
429
|
|
|
@@ -666,7 +666,7 @@ Missed lookups recover surgically: `read` and `section` print a "Did you mean…
|
|
|
666
666
|
|
|
667
667
|
### Skill efficiency — the `<!-- COMPACT_END -->` marker
|
|
668
668
|
|
|
669
|
-
When Claude Code invokes a skill, it re-injects the full skill body on every subsequent turn. A large skill file (e.g. a 10k-token `/improve` or `/ralph`) can cost 40–65k tokens per session across 6 active skills. The `<!-- COMPACT_END -->` marker solves this: place it in any skill file to split it into a compact form (above the marker, ~400 tokens) and a reference section (below). Token-goat detects the marker the first time the skill fires, caches only the compact slice, and injects that from then on —
|
|
669
|
+
When Claude Code invokes a skill, it re-injects the full skill body on every subsequent turn. A large skill file (e.g. a 10k-token `/improve` or `/ralph`) can cost 40–65k tokens per session across 6 active skills. The `<!-- COMPACT_END -->` marker solves this: place it in any skill file to split it into a compact form (above the marker, ~400 tokens) and a reference section (below). Token-goat detects the marker the first time the skill fires, caches only the compact slice, and injects that from then on — labeled `--- compact form (N tokens) ---` so the model knows to request the full body only when it needs the detail.
|
|
670
670
|
|
|
671
671
|
To add the marker to a skill, open the file and insert `<!-- COMPACT_END -->` on its own line where the "quick reference ends and the detail begins" — typically after the quick-start table and before step-by-step instructions. The full reference section is still reachable via `token-goat skill-section "<name>::<heading>"` or `token-goat skill-body <name>` when needed.
|
|
672
672
|
|
|
@@ -756,7 +756,7 @@ $ token-goat waste --copilot
|
|
|
756
756
|
Copilot counted 11,548 tok of tool definitions in total, so this is roughly 18.5% of it.
|
|
757
757
|
```
|
|
758
758
|
|
|
759
|
-
The fixed overhead is the largest number in a Copilot session and no hook can reach it: Copilot assembles the system prompt and the tool definitions natively, with nothing between assembly and send. Only configuration moves it. The per-server breakdown exists to make that configuration decision possible, since one aggregate says the tool definitions are expensive without saying which tools. It is read from Copilot's own MCP tool cache, counts only the fields a model is actually sent, and is
|
|
759
|
+
The fixed overhead is the largest number in a Copilot session and no hook can reach it: Copilot assembles the system prompt and the tool definitions natively, with nothing between assembly and send. Only configuration moves it. The per-server breakdown exists to make that configuration decision possible, since one aggregate says the tool definitions are expensive without saying which tools. It is read from Copilot's own MCP tool cache, counts only the fields a model is actually sent, and is labeled an estimate throughout: it comes from byte length rather than Copilot's tokeniser, and it deliberately does not add up to Copilot's total, because Copilot's own built-in tools are not cached there.
|
|
760
760
|
|
|
761
761
|
The "Assistant output" section is separate from the tool-call ledger above it: `generatedTokens` is what was actually paid, once, to produce the assistant's own text turns. `resendCeilingTokens` is a cache-unaware upper bound on how much re-sending those turns as conversation history on every later request could cost — not real spend, since Claude Code's prompt caching bills a repeated conversation prefix at cache-read rates, a fraction of full input price. Treat it as a ceiling on how bad unbounded verbosity could get, not as a dollar figure.
|
|
762
762
|
|
|
@@ -778,7 +778,7 @@ $ token-goat recall "eslint warnings"
|
|
|
778
778
|
|
|
779
779
|
Results are ranked by relevance (BM25 via SQLite FTS5, falling back to a plain substring scan if FTS5 is unavailable), newest indexed entries win ties. `--type bash|web|mcp` narrows to one cache type; `--limit <n>` caps the result count (default 10); `--json` emits `{ id, cacheType, label, snippet, storedAt }[]` instead. The index is built incrementally as entries are cached — there is no separate rebuild step.
|
|
780
780
|
|
|
781
|
-
Run `token-goat recall` with **no query** to browse instead of search: every cached entry across all three types, newest first, in the same format and
|
|
781
|
+
Run `token-goat recall` with **no query** to browse instead of search: every cached entry across all three types, newest first, in the same format and honoring the same `--type`/`--limit`/`--json` flags. This is the case where the index matters most — the ids have scrolled out of context and you have no term to search for, so the alternative is running `bash-history`, `web-history`, and `mcp-history` in turn.
|
|
782
782
|
|
|
783
783
|
### Hint efficacy tracking
|
|
784
784
|
|
|
@@ -1066,7 +1066,13 @@ A working install returns `["PreToolUse", "PostToolUse", "PreCompact"]`. Any mis
|
|
|
1066
1066
|
|
|
1067
1067
|
## Image support
|
|
1068
1068
|
|
|
1069
|
-
|
|
1069
|
+
Vision models bill by pixel dimensions, not file size. Anthropic charges one token per 28×28-pixel patch of a Claude image (`⌈width/28⌉ × ⌈height/28⌉` visual tokens, per the [Claude vision docs](https://platform.claude.com/docs/en/build-with-claude/vision#evaluate-image-size)), OpenAI's GPT-5.6 models tile in 32×32-pixel patches with a 1.2x multiplier on top, and Gemini charges a flat 258 tokens under 384×384 pixels and roughly 258 tokens per 768×768 tile above that. A heavily compressed screenshot can still decode to a large pixel count, so a small file on disk is no guarantee of a cheap read.
|
|
1070
|
+
|
|
1071
|
+
Token-goat shrinks an image before it reaches the model whenever either of two independent checks trips: the file is at or above 512 KB, or its longest edge exceeds 1568 pixels (`src/image_shrink.ts`). The byte check is a cheap pre-filter that skips decoding most images outright. The dimension check exists because the byte check alone misses a case: a flat-color screenshot can compress to a few hundred kilobytes on disk and still decode to a resolution well past 1568 pixels on its long edge, and vision models bill on that decoded resolution, not the compressed file. Either trigger routes the image through the same pipeline: downscale to a 1568px long edge and re-encode as WebP. The pipeline uses [`sharp`](https://sharp.pixelplumbing.com/), a Node.js image processing library that ships prebuilt native binaries for Windows, macOS, Linux, and Alpine.
|
|
1072
|
+
|
|
1073
|
+
How much this saves depends on the model, because Claude downscales an oversized image itself before billing and caps the cost rather than charging for every pixel it was sent. By the [published per-tier table](https://platform.claude.com/docs/en/build-with-claude/vision#evaluate-image-size), a 3840x2160 screenshot bills at 4784 visual tokens on Claude 4.7 and later, and 1560 on earlier models. Resizing it to a 1568px long edge first costs 1792 tokens: a 63% cut against the newer models, and nothing against the older ones, whose own cap is already tighter than what this pipeline produces. Other vendors cap differently, so treat 63% as the measured Claude figure rather than a universal rate.
|
|
1074
|
+
|
|
1075
|
+
Two cases are not about cost at all. Screenshots returned to the computer-use and browser-use toolsets are rejected outright when they exceed the model's limits, rather than downscaled, so resizing before the call is what lets it succeed. A request carrying more than 20 images also applies a stricter per-image dimension limit to every image in it, which a 1568px long edge already satisfies.
|
|
1070
1076
|
|
|
1071
1077
|
On most platforms, `npm install -g token-goat` installs sharp without additional steps. npm pulls a prebuilt binary keyed to your Node.js major version and OS — no C++ compiler, libvips, or system codec libraries required.
|
|
1072
1078
|
|
|
@@ -1278,7 +1284,7 @@ A third case needs no fence, because the danger is the line break rather than th
|
|
|
1278
1284
|
|
|
1279
1285
|
The MCP tools (`symbol` when given a `file` filter, `read`, `section`, `skeleton`, `outline`, `refs`, `brief`, `grep`, `imports`, `exports`) are confined to the project root, resolving symlinks before the check. Set `mcp.confine_reads_to_project_root = false` (env `TOKEN_GOAT_MCP_CONFINE_READS`) in your global config if you genuinely need cross-root reads from an MCP client; a per-project file cannot set it. The CLI is deliberately unconfined and unchanged. This is defense in depth for one sink, not a sandbox: an agent that can call these tools can usually call its own read tool too.
|
|
1280
1286
|
|
|
1281
|
-
Note what that flag does and does not cover. It stops a caller traversing *out of* the root it is given; it does not constrain *which* root the caller supplies. Every MCP tool takes an optional `projectRoot`, and it exists for a reason — the server's cwd is often not the workspace root for MCP clients — but tool arguments are model-generated, so that choice is untrusted input like any other. If your deployment treats MCP as the only path to the filesystem, set `mcp.allowed_roots` (env `TOKEN_GOAT_MCP_ALLOWED_ROOTS`, delimiter-separated like `PATH`) to the roots that may legitimately be named; a resolved root outside every entry is then refused. It is empty by default, which keeps the multi-root
|
|
1287
|
+
Note what that flag does and does not cover. It stops a caller traversing *out of* the root it is given; it does not constrain *which* root the caller supplies. Every MCP tool takes an optional `projectRoot`, and it exists for a reason — the server's cwd is often not the workspace root for MCP clients — but tool arguments are model-generated, so that choice is untrusted input like any other. If your deployment treats MCP as the only path to the filesystem, set `mcp.allowed_roots` (env `TOKEN_GOAT_MCP_ALLOWED_ROOTS`, delimiter-separated like `PATH`) to the roots that may legitimately be named; a resolved root outside every entry is then refused. It is empty by default, which keeps the multi-root behavior above unchanged.
|
|
1282
1288
|
|
|
1283
1289
|
**Restricting what token-goat may fetch.** `webfetch.allow` and `webfetch.deny` (env `TOKEN_GOAT_WEBFETCH_ALLOW` / `TOKEN_GOAT_WEBFETCH_DENY`, comma-separated) are wildcard URL patterns that decide which addresses may be reached. Deny is checked first and wins; a non-empty allow list refuses anything it does not name. Patterns are matched against the address as it will actually be sent, not only as you typed it, so a trailing dot on the host, `..` path segments, a default port written out, and percent-encoded path characters cannot be used to step around a rule. Writing a default port in a pattern (`https://example.com:443/*`) and omitting it are equivalent. Both are empty by default, which permits everything, exactly as before. They apply to the WebFetch call your AI makes, to the fetches token-goat performs itself (`fetch-image`, `gdrive-sections`), and to the headless browser behind `screenshot` (whose page sub-resources are checked too), including every redirect hop, so an allowed site cannot redirect the request on to a denied one.
|
|
1284
1290
|
|
|
@@ -86,7 +86,7 @@ import {
|
|
|
86
86
|
runZipRead,
|
|
87
87
|
symbolNamesInFile,
|
|
88
88
|
upsertNote
|
|
89
|
-
} from "./token-goat-chunk-
|
|
89
|
+
} from "./token-goat-chunk-TX4JFJTD.mjs";
|
|
90
90
|
import {
|
|
91
91
|
BASH_OUTPUT_SUBDIR,
|
|
92
92
|
GEMINI_TOOL_NAME_MAP,
|
|
@@ -113,7 +113,7 @@ import {
|
|
|
113
113
|
searchRecall,
|
|
114
114
|
storeWebOutput,
|
|
115
115
|
summarizeResidentContext
|
|
116
|
-
} from "./token-goat-chunk-
|
|
116
|
+
} from "./token-goat-chunk-4HIMCBYK.mjs";
|
|
117
117
|
import {
|
|
118
118
|
AGENT_SALT_MARKER,
|
|
119
119
|
CONTEXT_AUTOCOMPACT_TOKENS,
|
|
@@ -123,6 +123,7 @@ import {
|
|
|
123
123
|
SESSIONS_SUBDIR,
|
|
124
124
|
SKILLS_OUTPUT_SUBDIR,
|
|
125
125
|
SKIP_DIRS,
|
|
126
|
+
UNTRUSTED_FILE_TAG,
|
|
126
127
|
UNTRUSTED_TOOL_TAG,
|
|
127
128
|
UNTRUSTED_WEB_TAG,
|
|
128
129
|
WORKER_HEARTBEAT_STALE_MS,
|
|
@@ -256,7 +257,7 @@ import {
|
|
|
256
257
|
vscodeDecoderConfigured,
|
|
257
258
|
walkProject,
|
|
258
259
|
writeCompact
|
|
259
|
-
} from "./token-goat-chunk-
|
|
260
|
+
} from "./token-goat-chunk-PWVXXPCC.mjs";
|
|
260
261
|
import {
|
|
261
262
|
C,
|
|
262
263
|
CONFIG_KEY_ENV_OVERRIDES,
|
|
@@ -338,11 +339,14 @@ import {
|
|
|
338
339
|
withFileLock,
|
|
339
340
|
withRetryOnLock,
|
|
340
341
|
writeJsonSettings
|
|
341
|
-
} from "./token-goat-chunk-
|
|
342
|
+
} from "./token-goat-chunk-6ODZ6PZK.mjs";
|
|
342
343
|
import {
|
|
343
344
|
__export
|
|
344
345
|
} from "./token-goat-chunk-AEX54RUZ.mjs";
|
|
345
346
|
|
|
347
|
+
// src/cli.ts
|
|
348
|
+
import { randomBytes } from "node:crypto";
|
|
349
|
+
|
|
346
350
|
// node_modules/commander/lib/error.js
|
|
347
351
|
var CommanderError = class extends Error {
|
|
348
352
|
/**
|
|
@@ -3757,7 +3761,7 @@ function attemptedCommandName(argv) {
|
|
|
3757
3761
|
// src/cli.ts
|
|
3758
3762
|
import * as fs27 from "fs";
|
|
3759
3763
|
import * as path27 from "path";
|
|
3760
|
-
import { homedir as
|
|
3764
|
+
import { homedir as homedir12 } from "os";
|
|
3761
3765
|
|
|
3762
3766
|
// src/walk_index.ts
|
|
3763
3767
|
import * as fs2 from "node:fs";
|
|
@@ -4084,7 +4088,8 @@ var PI_EXTENSION_SCRIPT = `// token-goat bridge extension for pi (pi-coding-agen
|
|
|
4084
4088
|
// Bridges pi's extension events to token-goat's subprocess hook protocol.
|
|
4085
4089
|
// https://github.com/DFKHelper/token-goat
|
|
4086
4090
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4087
|
-
import { spawnSync } from "node:child_process"
|
|
4091
|
+
import { spawnSync } from "node:child_process"
|
|
4092
|
+
import crypto from "node:crypto";
|
|
4088
4093
|
import * as fs from "node:fs";
|
|
4089
4094
|
import * as os from "node:os";
|
|
4090
4095
|
import * as path from "node:path";
|
|
@@ -4543,6 +4548,7 @@ var OPENCODE_PLUGIN_SCRIPT = `// token-goat bridge plugin for opencode
|
|
|
4543
4548
|
// Bridges opencode's plugin hooks to token-goat's subprocess hook protocol.
|
|
4544
4549
|
// https://github.com/DFKHelper/token-goat
|
|
4545
4550
|
import { spawnSync } from "node:child_process"
|
|
4551
|
+
import crypto from "node:crypto"
|
|
4546
4552
|
import fs from "node:fs"
|
|
4547
4553
|
import os from "node:os"
|
|
4548
4554
|
import path from "node:path"
|
|
@@ -9976,7 +9982,7 @@ function parseNodeBlock(lines, start) {
|
|
|
9976
9982
|
}
|
|
9977
9983
|
return { block: { frames, exception: header }, nextIndex: i };
|
|
9978
9984
|
}
|
|
9979
|
-
var RUST_PANIC_HEADER_RE = /^thread '[^']*' panicked at (
|
|
9985
|
+
var RUST_PANIC_HEADER_RE = /^thread '[^']*' panicked at ([^:\r\n]+):(\d+):(\d+):\s*$/;
|
|
9980
9986
|
var RUST_BACKTRACE_NOTE_RE = /^note: run with `RUST_BACKTRACE=1`/;
|
|
9981
9987
|
var RUST_BACKTRACE_NUM_RE = /^\s*\d+:\s+(.+)$/;
|
|
9982
9988
|
var RUST_BACKTRACE_AT_RE = /^\s+at\s+(.+):(\d+):(\d+)\s*$/;
|
|
@@ -10022,7 +10028,7 @@ function parseRustBlock(lines, start) {
|
|
|
10022
10028
|
}
|
|
10023
10029
|
return { block: { frames: [panicFrame], exception }, nextIndex: i };
|
|
10024
10030
|
}
|
|
10025
|
-
var JVM_HEADER_RE = /^(?:Exception in thread "[^"]*" )?(?:Caused by: )?((?:[A-Za-z_$][\w$]*\.)+[A-Za-z_$][\w$]*): (.*)$/;
|
|
10031
|
+
var JVM_HEADER_RE = /^(?:Exception in thread "[^"\r\n]*" )?(?:Caused by: )?((?:[A-Za-z_$][\w$]*\.)+[A-Za-z_$][\w$]*): (.*)$/;
|
|
10026
10032
|
var JVM_FRAME_RE = /^\s+at\s+(\S+)\(([^)]*)\)\s*$/;
|
|
10027
10033
|
var JVM_MORE_RE = /^\s*\.\.\.\s+\d+\s+more\s*$/;
|
|
10028
10034
|
function parseJvmFrameLine(line) {
|
|
@@ -13499,20 +13505,55 @@ function formatSessionSlice(turns) {
|
|
|
13499
13505
|
|
|
13500
13506
|
// src/cli_mcp_audit.ts
|
|
13501
13507
|
import * as fs26 from "node:fs";
|
|
13508
|
+
import * as os12 from "node:os";
|
|
13502
13509
|
import * as path26 from "node:path";
|
|
13503
|
-
function
|
|
13504
|
-
const configPath2 = path26.join(projectRoot, ".mcp.json");
|
|
13510
|
+
function readMcpJsonFile(configPath2) {
|
|
13505
13511
|
try {
|
|
13506
|
-
if (!fs26.existsSync(configPath2))
|
|
13507
|
-
return null;
|
|
13508
|
-
}
|
|
13512
|
+
if (!fs26.existsSync(configPath2)) return null;
|
|
13509
13513
|
const content = fs26.readFileSync(configPath2, "utf-8");
|
|
13510
13514
|
const parsed = JSON.parse(content);
|
|
13511
|
-
|
|
13515
|
+
const servers = parsed && typeof parsed === "object" ? parsed.mcpServers ?? parsed : null;
|
|
13516
|
+
return servers && typeof servers === "object" ? servers : null;
|
|
13517
|
+
} catch {
|
|
13518
|
+
return null;
|
|
13519
|
+
}
|
|
13520
|
+
}
|
|
13521
|
+
function driveLetterCaseVariants(p) {
|
|
13522
|
+
const m = /^([a-zA-Z]:)(.*)$/s.exec(p);
|
|
13523
|
+
if (m === null) return [p];
|
|
13524
|
+
const [, drive, rest] = m;
|
|
13525
|
+
return [`${drive.toLowerCase()}${rest}`, `${drive.toUpperCase()}${rest}`];
|
|
13526
|
+
}
|
|
13527
|
+
function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
|
|
13528
|
+
try {
|
|
13529
|
+
if (!fs26.existsSync(claudeJsonPath)) return null;
|
|
13530
|
+
const parsed = JSON.parse(fs26.readFileSync(claudeJsonPath, "utf-8"));
|
|
13531
|
+
const projects = parsed && typeof parsed === "object" ? parsed.projects : null;
|
|
13532
|
+
if (!projects || typeof projects !== "object") return null;
|
|
13533
|
+
const slashForms = [projectRoot, projectRoot.replace(/\\/g, "/"), projectRoot.replace(/\//g, "\\")];
|
|
13534
|
+
const candidates = [...new Set(slashForms.flatMap(driveLetterCaseVariants))];
|
|
13535
|
+
for (const key of candidates) {
|
|
13536
|
+
const entry = projects[key];
|
|
13537
|
+
if (entry && typeof entry === "object") {
|
|
13538
|
+
const servers = entry["mcpServers"];
|
|
13539
|
+
if (servers && typeof servers === "object") return servers;
|
|
13540
|
+
}
|
|
13541
|
+
}
|
|
13542
|
+
return null;
|
|
13512
13543
|
} catch {
|
|
13513
13544
|
return null;
|
|
13514
13545
|
}
|
|
13515
13546
|
}
|
|
13547
|
+
function discoverMcpConfig(projectRoot, home) {
|
|
13548
|
+
const mcpJsonPath = path26.join(projectRoot, ".mcp.json");
|
|
13549
|
+
const claudeJsonPath = path26.join(home, ".claude.json");
|
|
13550
|
+
const sourcesChecked = [mcpJsonPath, claudeJsonPath];
|
|
13551
|
+
const fromMcpJson = readMcpJsonFile(mcpJsonPath);
|
|
13552
|
+
if (fromMcpJson !== null) return { servers: fromMcpJson, sourcePath: mcpJsonPath, sourcesChecked };
|
|
13553
|
+
const fromClaudeJson = readClaudeJsonConfig(claudeJsonPath, projectRoot);
|
|
13554
|
+
if (fromClaudeJson !== null) return { servers: fromClaudeJson, sourcePath: claudeJsonPath, sourcesChecked };
|
|
13555
|
+
return { servers: null, sourcePath: null, sourcesChecked };
|
|
13556
|
+
}
|
|
13516
13557
|
function analyzeMcpCache() {
|
|
13517
13558
|
const serverMetrics = /* @__PURE__ */ new Map();
|
|
13518
13559
|
const blobs = listBlobs(BASH_OUTPUT_SUBDIR).filter((b) => b.id.startsWith("mcp_"));
|
|
@@ -13534,8 +13575,9 @@ function analyzeMcpCache() {
|
|
|
13534
13575
|
}
|
|
13535
13576
|
return serverMetrics;
|
|
13536
13577
|
}
|
|
13537
|
-
function buildMcpAuditReport(projectRoot) {
|
|
13538
|
-
const
|
|
13578
|
+
function buildMcpAuditReport(projectRoot, home = os12.homedir()) {
|
|
13579
|
+
const discovery = discoverMcpConfig(projectRoot, home);
|
|
13580
|
+
const config = discovery.servers;
|
|
13539
13581
|
const cacheMetrics = analyzeMcpCache();
|
|
13540
13582
|
const servers = [];
|
|
13541
13583
|
let totalCost = 0;
|
|
@@ -13569,7 +13611,10 @@ function buildMcpAuditReport(projectRoot) {
|
|
|
13569
13611
|
servers.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
13570
13612
|
return {
|
|
13571
13613
|
projectRoot,
|
|
13572
|
-
configFound:
|
|
13614
|
+
configFound: discovery.sourcePath !== null,
|
|
13615
|
+
configSourcePath: discovery.sourcePath,
|
|
13616
|
+
configSourcesChecked: discovery.sourcesChecked,
|
|
13617
|
+
costKnown: discovery.sourcePath !== null || cacheMetrics.size > 0,
|
|
13573
13618
|
servers,
|
|
13574
13619
|
totalCost
|
|
13575
13620
|
};
|
|
@@ -13581,11 +13626,12 @@ function printReport3(report) {
|
|
|
13581
13626
|
w("\n# token-goat mcp-audit\n");
|
|
13582
13627
|
w(`Project: ${report.projectRoot}
|
|
13583
13628
|
`);
|
|
13584
|
-
w(`Config found: ${report.
|
|
13629
|
+
w(report.configSourcePath !== null ? `Config found: yes (${report.configSourcePath})
|
|
13630
|
+
` : `Config found: no (checked: ${report.configSourcesChecked.join(", ")})
|
|
13585
13631
|
`);
|
|
13586
13632
|
w("\n## MCP servers\n");
|
|
13587
13633
|
if (report.servers.length === 0) {
|
|
13588
|
-
w(" none\n");
|
|
13634
|
+
w(report.costKnown ? " none\n" : " none discovered from a readable config source\n");
|
|
13589
13635
|
} else {
|
|
13590
13636
|
w("| Server | Per-Call (tok) | Calls | Total (tok) |\n");
|
|
13591
13637
|
w("|--------|---|---|---|\n");
|
|
@@ -13594,9 +13640,10 @@ function printReport3(report) {
|
|
|
13594
13640
|
`);
|
|
13595
13641
|
}
|
|
13596
13642
|
}
|
|
13597
|
-
w(`
|
|
13643
|
+
w(report.costKnown ? `
|
|
13598
13644
|
Total cost: ${report.totalCost} tok
|
|
13599
|
-
`);
|
|
13645
|
+
` : "\nTotal cost: unknown -- no readable MCP config was found and no MCP calls have been recorded in this session's cache yet\n");
|
|
13646
|
+
w("\nNote: plugin-provided MCP servers have no on-disk config token-goat can read, so a live session may have MCP servers this audit cannot see or price.\n");
|
|
13600
13647
|
}
|
|
13601
13648
|
async function runMcpAuditCommand(opts = {}) {
|
|
13602
13649
|
const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
|
|
@@ -13615,6 +13662,20 @@ var RECALL_COMMAND = {
|
|
|
13615
13662
|
web: "web-output",
|
|
13616
13663
|
mcp: "mcp-output"
|
|
13617
13664
|
};
|
|
13665
|
+
function fenceTagForCacheType(cacheType) {
|
|
13666
|
+
return cacheType === "web" ? UNTRUSTED_WEB_TAG : UNTRUSTED_TOOL_TAG;
|
|
13667
|
+
}
|
|
13668
|
+
function fenceSnippetIfMatched(hit) {
|
|
13669
|
+
let matches2 = [];
|
|
13670
|
+
try {
|
|
13671
|
+
if (loadConfig().injection.enabled) matches2 = scanForInjectionPatterns(hit.snippet);
|
|
13672
|
+
} catch {
|
|
13673
|
+
matches2 = [];
|
|
13674
|
+
}
|
|
13675
|
+
if (matches2.length === 0) return hit.snippet;
|
|
13676
|
+
recordStat("injection_detected", 0, 0, void 0, matches2.join(","));
|
|
13677
|
+
return fenceUntrustedContent(hit.snippet, matches2, fenceTagForCacheType(hit.cacheType));
|
|
13678
|
+
}
|
|
13618
13679
|
function printHits(query, hits) {
|
|
13619
13680
|
const w = (text) => {
|
|
13620
13681
|
process.stdout.write(text);
|
|
@@ -13630,7 +13691,7 @@ function printHits(query, hits) {
|
|
|
13630
13691
|
`);
|
|
13631
13692
|
w(` ${label}
|
|
13632
13693
|
`);
|
|
13633
|
-
w(` ${hit
|
|
13694
|
+
w(` ${fenceSnippetIfMatched(hit)}
|
|
13634
13695
|
|
|
13635
13696
|
`);
|
|
13636
13697
|
}
|
|
@@ -13643,7 +13704,8 @@ function runRecallCommand(query, opts = {}) {
|
|
|
13643
13704
|
};
|
|
13644
13705
|
const hits = browse ? listRecentRecall(scope) : searchRecall(query, scope);
|
|
13645
13706
|
if (opts.json === true) {
|
|
13646
|
-
|
|
13707
|
+
const fenced = hits.map((hit) => ({ ...hit, snippet: fenceSnippetIfMatched(hit) }));
|
|
13708
|
+
process.stdout.write(`${JSON.stringify(fenced)}
|
|
13647
13709
|
`);
|
|
13648
13710
|
return;
|
|
13649
13711
|
}
|
|
@@ -13674,11 +13736,12 @@ function printSummary(rows) {
|
|
|
13674
13736
|
}
|
|
13675
13737
|
function printTotals(totals) {
|
|
13676
13738
|
const spent = totals.spentBytes === null ? "n/a" : String(totals.spentBytes);
|
|
13677
|
-
const net = totals.netBytes === null ? "n/a" : String(totals.netBytes);
|
|
13678
13739
|
const legacyNote = totals.legacyEmissions > 0 ? ` (excludes ${totals.legacyEmissions} legacy emission(s) recorded before spend tracking)` : "";
|
|
13679
|
-
process.stdout.write(
|
|
13680
|
-
|
|
13681
|
-
|
|
13740
|
+
process.stdout.write(
|
|
13741
|
+
`
|
|
13742
|
+
TOTAL saved=${totals.savedBytes} (all-time, every hint kind) spent=${spent} (hint_emissions ledger only)${legacyNote}
|
|
13743
|
+
`
|
|
13744
|
+
);
|
|
13682
13745
|
}
|
|
13683
13746
|
function runHintStatsCommand(opts = {}) {
|
|
13684
13747
|
if (opts.reset === true) {
|
|
@@ -14038,7 +14101,7 @@ async function cmdMcpServe() {
|
|
|
14038
14101
|
let StdioServerTransport;
|
|
14039
14102
|
try {
|
|
14040
14103
|
;
|
|
14041
|
-
({ createMcpServer } = await import("./token-goat-chunk-
|
|
14104
|
+
({ createMcpServer } = await import("./token-goat-chunk-C5IL6MJH.mjs"));
|
|
14042
14105
|
({ StdioServerTransport } = await import("./token-goat-chunk-324QOJYZ.mjs"));
|
|
14043
14106
|
} catch (err2) {
|
|
14044
14107
|
process.stderr.write(
|
|
@@ -14063,7 +14126,7 @@ async function cmdHook(event, opts) {
|
|
|
14063
14126
|
if (typeof opts.harness === "string" && opts.harness.length > 0) {
|
|
14064
14127
|
process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
|
|
14065
14128
|
}
|
|
14066
|
-
const { relay } = await import("./token-goat-chunk-
|
|
14129
|
+
const { relay } = await import("./token-goat-chunk-DP3NNGGV.mjs");
|
|
14067
14130
|
await relay(event);
|
|
14068
14131
|
}
|
|
14069
14132
|
async function cmdInstall(opts) {
|
|
@@ -14176,7 +14239,7 @@ async function cmdInstall(opts) {
|
|
|
14176
14239
|
);
|
|
14177
14240
|
}
|
|
14178
14241
|
try {
|
|
14179
|
-
const skillDir = path27.join(
|
|
14242
|
+
const skillDir = path27.join(homedir12(), ".claude", "skills");
|
|
14180
14243
|
if (fs27.existsSync(skillDir)) {
|
|
14181
14244
|
const entries = fs27.readdirSync(skillDir, { withFileTypes: true });
|
|
14182
14245
|
const skillNames = [];
|
|
@@ -14517,6 +14580,17 @@ function _applyFiltersAndPrint(content, opts, fenceUntrusted = false, fenceTag =
|
|
|
14517
14580
|
}
|
|
14518
14581
|
return emit2(result.join("\n"));
|
|
14519
14582
|
}
|
|
14583
|
+
function fenceFileTextIfMatched(text) {
|
|
14584
|
+
let matches2 = [];
|
|
14585
|
+
try {
|
|
14586
|
+
if (loadConfig().injection.enabled) matches2 = scanForInjectionPatterns(text);
|
|
14587
|
+
} catch {
|
|
14588
|
+
matches2 = [];
|
|
14589
|
+
}
|
|
14590
|
+
if (matches2.length === 0) return text;
|
|
14591
|
+
recordStat("injection_detected", 0, 0, void 0, matches2.join(","));
|
|
14592
|
+
return fenceUntrustedContent(text, matches2, UNTRUSTED_FILE_TAG);
|
|
14593
|
+
}
|
|
14520
14594
|
function fileSizeOrZero(filePath) {
|
|
14521
14595
|
try {
|
|
14522
14596
|
return fs27.statSync(filePath).size;
|
|
@@ -14580,7 +14654,7 @@ function cmdMcpOutput(id, opts) {
|
|
|
14580
14654
|
}
|
|
14581
14655
|
async function cmdPdfExtract(file, opts) {
|
|
14582
14656
|
const text = await runPdfExtractText(file, opts.pages, opts.layout === true);
|
|
14583
|
-
const printed = _applyFiltersAndPrint(text, opts);
|
|
14657
|
+
const printed = _applyFiltersAndPrint(text, opts, true, UNTRUSTED_FILE_TAG);
|
|
14584
14658
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14585
14659
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
|
|
14586
14660
|
recordStat("pdf_extract", bytesSaved, Math.round(bytesSaved / 4));
|
|
@@ -14593,19 +14667,20 @@ async function cmdPdfLocate(file, pattern, opts) {
|
|
|
14593
14667
|
if (opts.context !== void 0) locateOpts.context = requirePositiveInt("--context", opts.context);
|
|
14594
14668
|
if (opts.pages !== void 0) locateOpts.pages = opts.pages;
|
|
14595
14669
|
const matches2 = await runPdfLocate(file, pattern, locateOpts);
|
|
14596
|
-
const
|
|
14670
|
+
const fencedMatches = matches2.map((m) => ({ ...m, snippet: fenceFileTextIfMatched(m.snippet) }));
|
|
14671
|
+
const pages = fencedMatches.map((m) => m.page);
|
|
14597
14672
|
let printed;
|
|
14598
14673
|
if (opts.json === true) {
|
|
14599
|
-
printed = JSON.stringify({ file, pattern, matchCount:
|
|
14674
|
+
printed = JSON.stringify({ file, pattern, matchCount: fencedMatches.length, pages, matches: fencedMatches }, null, 2);
|
|
14600
14675
|
out(printed);
|
|
14601
|
-
} else if (
|
|
14676
|
+
} else if (fencedMatches.length === 0) {
|
|
14602
14677
|
printed = "(no matches)";
|
|
14603
14678
|
out(printed);
|
|
14604
14679
|
} else {
|
|
14605
|
-
const lines =
|
|
14680
|
+
const lines = fencedMatches.map((m) => `p${m.page}: ${m.snippet}`);
|
|
14606
14681
|
printed = `${lines.join("\n")}
|
|
14607
14682
|
|
|
14608
|
-
${countNoun(
|
|
14683
|
+
${countNoun(fencedMatches.length, "match", "matches")} across ${countNoun(pages.length, "page")}`;
|
|
14609
14684
|
out(printed);
|
|
14610
14685
|
}
|
|
14611
14686
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
@@ -14622,7 +14697,8 @@ async function cmdPdfOutline(file, opts) {
|
|
|
14622
14697
|
}
|
|
14623
14698
|
return;
|
|
14624
14699
|
}
|
|
14625
|
-
const
|
|
14700
|
+
const fencedEntries = entries.map((e) => ({ ...e, title: fenceFileTextIfMatched(e.title) }));
|
|
14701
|
+
const text = opts.json === true ? JSON.stringify(fencedEntries, null, 2) : fencedEntries.map((e) => `${" ".repeat(e.level)}${e.title}${e.page !== null ? ` (p.${e.page})` : ""}`).join("\n");
|
|
14626
14702
|
out(text);
|
|
14627
14703
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14628
14704
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
|
|
@@ -14674,12 +14750,16 @@ async function cmdImageText(file, opts = {}) {
|
|
|
14674
14750
|
}
|
|
14675
14751
|
let text;
|
|
14676
14752
|
if (opts.json === true) {
|
|
14677
|
-
text = JSON.stringify(
|
|
14753
|
+
text = JSON.stringify(
|
|
14754
|
+
result.text === null ? result : { ...result, text: fenceFileTextIfMatched(result.text) },
|
|
14755
|
+
null,
|
|
14756
|
+
2
|
|
14757
|
+
);
|
|
14678
14758
|
} else {
|
|
14679
14759
|
const lines = [`Confidence: ${Math.round(result.confidence)}%`, `Characters: ${result.chars}`];
|
|
14680
14760
|
text = result.textHeavy && result.text !== null ? `${lines.join("\n")}
|
|
14681
14761
|
|
|
14682
|
-
${result.text}` : `${lines.join("\n")}
|
|
14762
|
+
${fenceFileTextIfMatched(result.text)}` : `${lines.join("\n")}
|
|
14683
14763
|
(below usefulness threshold; text likely noise, not shown)`;
|
|
14684
14764
|
}
|
|
14685
14765
|
out(text);
|
|
@@ -14742,19 +14822,20 @@ function recordXlsxStat(kind, file, emitted) {
|
|
|
14742
14822
|
}
|
|
14743
14823
|
async function cmdXlsxSheets(file, opts = {}) {
|
|
14744
14824
|
const sheets = await listSheets(file);
|
|
14745
|
-
const
|
|
14825
|
+
const fencedSheets = sheets.map((s) => ({ ...s, name: fenceFileTextIfMatched(s.name) }));
|
|
14826
|
+
const text = opts.json === true ? JSON.stringify(fencedSheets.map((s) => ({ name: s.name, ref: s.ref, rows: s.rows, cols: s.cols })), null, 2) : fencedSheets.map((s) => `${s.name} ${s.ref} (${s.rows} rows x ${s.cols} cols)`).join("\n");
|
|
14746
14827
|
out(text);
|
|
14747
14828
|
recordXlsxStat("xlsx_sheets", file, text);
|
|
14748
14829
|
}
|
|
14749
14830
|
async function cmdXlsxHead(file, opts) {
|
|
14750
14831
|
const rows = opts.rows !== void 0 ? requireNonNegativeInt("--rows", opts.rows) : 20;
|
|
14751
|
-
const text = await headSheet(file, opts.sheet, rows);
|
|
14832
|
+
const text = fenceFileTextIfMatched(await headSheet(file, opts.sheet, rows));
|
|
14752
14833
|
out(text);
|
|
14753
14834
|
recordXlsxStat("xlsx_head", file, text);
|
|
14754
14835
|
}
|
|
14755
14836
|
async function cmdXlsxRange(file, opts) {
|
|
14756
14837
|
const result = await rangeSheet(file, opts.sheet, opts.range, opts.formulas === true);
|
|
14757
|
-
const text = formatXlsxRange(result);
|
|
14838
|
+
const text = fenceFileTextIfMatched(formatXlsxRange(result));
|
|
14758
14839
|
out(text);
|
|
14759
14840
|
recordXlsxStat("xlsx_range", file, text);
|
|
14760
14841
|
}
|
|
@@ -14766,7 +14847,7 @@ async function cmdXlsxQuery(file, opts) {
|
|
|
14766
14847
|
...wheres !== void 0 ? { wheres } : {},
|
|
14767
14848
|
...opts.head !== void 0 ? { head: requireNonNegativeInt("--head", opts.head) } : {}
|
|
14768
14849
|
});
|
|
14769
|
-
const text = formatCsvTable(result);
|
|
14850
|
+
const text = fenceFileTextIfMatched(formatCsvTable(result));
|
|
14770
14851
|
out(text);
|
|
14771
14852
|
recordXlsxStat("xlsx_query", file, text);
|
|
14772
14853
|
}
|
|
@@ -14777,20 +14858,21 @@ function recordDocStat(kind, file, emitted) {
|
|
|
14777
14858
|
}
|
|
14778
14859
|
async function cmdPptxOutline(file, opts) {
|
|
14779
14860
|
const slides = await pptxOutline(file);
|
|
14780
|
-
const
|
|
14861
|
+
const fencedSlides = slides.map((s) => ({ ...s, title: fenceFileTextIfMatched(s.title) }));
|
|
14862
|
+
const text = opts.json === true ? JSON.stringify(fencedSlides, null, 2) : fencedSlides.map((s) => `${s.slide}. ${s.title || "(untitled)"} [${s.bodyChars} body chars${s.hasNotes ? ", has notes" : ""}]`).join("\n");
|
|
14781
14863
|
out(text);
|
|
14782
14864
|
recordDocStat("pptx_outline", file, text);
|
|
14783
14865
|
}
|
|
14784
14866
|
async function cmdPptxSlide(file, opts) {
|
|
14785
14867
|
const n = requireNonNegativeInt("--slide", opts.slide);
|
|
14786
|
-
const text = await pptxSlideText(file, n, opts.notes === true);
|
|
14868
|
+
const text = fenceFileTextIfMatched(await pptxSlideText(file, n, opts.notes === true));
|
|
14787
14869
|
out(text);
|
|
14788
14870
|
recordDocStat("pptx_slide", file, text);
|
|
14789
14871
|
}
|
|
14790
14872
|
async function cmdPptxNotes(file, opts) {
|
|
14791
14873
|
const n = opts.slide !== void 0 ? requireNonNegativeInt("--slide", opts.slide) : void 0;
|
|
14792
14874
|
const text = await pptxNotesText(file, n);
|
|
14793
|
-
const printed = text.length > 0 ? text : "no speaker notes found";
|
|
14875
|
+
const printed = text.length > 0 ? fenceFileTextIfMatched(text) : "no speaker notes found";
|
|
14794
14876
|
out(printed);
|
|
14795
14877
|
recordDocStat("pptx_notes", file, printed);
|
|
14796
14878
|
}
|
|
@@ -14800,7 +14882,7 @@ async function cmdPptxText(file, opts) {
|
|
|
14800
14882
|
out("no matches");
|
|
14801
14883
|
return;
|
|
14802
14884
|
}
|
|
14803
|
-
const text = matches2.map((m) => `Slide ${m.slide}: ...${m.snippet}...`).join("\n");
|
|
14885
|
+
const text = fenceFileTextIfMatched(matches2.map((m) => `Slide ${m.slide}: ...${m.snippet}...`).join("\n"));
|
|
14804
14886
|
out(text);
|
|
14805
14887
|
recordDocStat("pptx_text", file, text);
|
|
14806
14888
|
}
|
|
@@ -14814,13 +14896,14 @@ async function cmdDocxOutline(file, opts) {
|
|
|
14814
14896
|
}
|
|
14815
14897
|
return;
|
|
14816
14898
|
}
|
|
14817
|
-
const
|
|
14899
|
+
const fencedHeadings = headings.map((h) => ({ ...h, text: fenceFileTextIfMatched(h.text) }));
|
|
14900
|
+
const text = opts.json === true ? JSON.stringify(fencedHeadings, null, 2) : fencedHeadings.map((h) => `${" ".repeat(h.level - 1)}${h.text}`).join("\n");
|
|
14818
14901
|
out(text);
|
|
14819
14902
|
recordDocStat("docx_outline", file, text);
|
|
14820
14903
|
}
|
|
14821
14904
|
async function cmdDocxText(file, opts) {
|
|
14822
14905
|
const text = await docxText(file);
|
|
14823
|
-
const printed = _applyFiltersAndPrint(text, opts);
|
|
14906
|
+
const printed = _applyFiltersAndPrint(text, opts, true, UNTRUSTED_FILE_TAG);
|
|
14824
14907
|
recordDocStat("docx_text", file, printed);
|
|
14825
14908
|
}
|
|
14826
14909
|
function cmdTranscriptOutline(file, opts) {
|
|
@@ -14958,7 +15041,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
|
|
|
14958
15041
|
}
|
|
14959
15042
|
async function cmdCompress(opts) {
|
|
14960
15043
|
try {
|
|
14961
|
-
const bashRunner = await import("./token-goat-chunk-
|
|
15044
|
+
const bashRunner = await import("./token-goat-chunk-VAGPOZHO.mjs");
|
|
14962
15045
|
if (opts.compress === false) {
|
|
14963
15046
|
process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
|
|
14964
15047
|
return;
|
|
@@ -15227,7 +15310,7 @@ function atomicWriteBuffer(dest, data) {
|
|
|
15227
15310
|
} catch (e) {
|
|
15228
15311
|
if (e.code !== "ENOENT") throw e;
|
|
15229
15312
|
}
|
|
15230
|
-
const rnd =
|
|
15313
|
+
const rnd = randomBytes(4).toString("hex");
|
|
15231
15314
|
const tmp = path27.join(path27.dirname(path27.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
|
|
15232
15315
|
try {
|
|
15233
15316
|
fs27.writeFileSync(tmp, data, { mode: 384 });
|
|
@@ -15863,8 +15946,19 @@ ${content}`;
|
|
|
15863
15946
|
const sections = await getDocSections(fileId, { fresh: false });
|
|
15864
15947
|
emitted = formatSections(sections);
|
|
15865
15948
|
}
|
|
15866
|
-
|
|
15867
|
-
|
|
15949
|
+
let toEmit = emitted;
|
|
15950
|
+
try {
|
|
15951
|
+
if (loadConfig().injection.enabled) {
|
|
15952
|
+
const matches2 = scanForInjectionPatterns(emitted);
|
|
15953
|
+
if (matches2.length > 0) {
|
|
15954
|
+
recordStat("injection_detected", 0, 0, void 0, matches2.join(","));
|
|
15955
|
+
toEmit = fenceUntrustedContent(emitted, matches2, UNTRUSTED_WEB_TAG);
|
|
15956
|
+
}
|
|
15957
|
+
}
|
|
15958
|
+
} catch {
|
|
15959
|
+
}
|
|
15960
|
+
out(toEmit);
|
|
15961
|
+
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(toEmit, "utf8"));
|
|
15868
15962
|
recordStat("gdrive_sections", bytesSaved, Math.round(bytesSaved / 4));
|
|
15869
15963
|
}
|
|
15870
15964
|
function expandGlobs(root, patterns, globFnOverride) {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
loadBlob,
|
|
9
9
|
sanitizeFtsQuery,
|
|
10
10
|
storeBlob
|
|
11
|
-
} from "./token-goat-chunk-
|
|
11
|
+
} from "./token-goat-chunk-PWVXXPCC.mjs";
|
|
12
12
|
import {
|
|
13
13
|
SYMBOL_BODY_CHAR_CAP,
|
|
14
14
|
extractErrorMessage,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
redactSecrets,
|
|
19
19
|
runGit,
|
|
20
20
|
shortFingerprint
|
|
21
|
-
} from "./token-goat-chunk-
|
|
21
|
+
} from "./token-goat-chunk-6ODZ6PZK.mjs";
|
|
22
22
|
import {
|
|
23
23
|
registerReset
|
|
24
24
|
} from "./token-goat-chunk-AO2QD2AG.mjs";
|
|
@@ -404,10 +404,10 @@ var COMMAND_PATTERNS = {
|
|
|
404
404
|
gitImmutable: /^\s*git\s+show\s+[0-9a-f]{40}\b/i,
|
|
405
405
|
gitDiffScoped: /\s--\s+\S/,
|
|
406
406
|
dirListing: /^\s*(?:ls|eza|exa|dir|Get-ChildItem|gci)\b/i,
|
|
407
|
-
depList: /^\s*(?:npm\s+(
|
|
408
|
-
npmInstall: /^\s*npm\s+(
|
|
409
|
-
npmAudit: /^\s*npm\s+(
|
|
410
|
-
npmOutdated: /^\s*npm\s+(
|
|
407
|
+
depList: /^\s*(?:npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:ls|list)\b|pip\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:list|freeze)\b|uv\s+pip\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:list|freeze)\b|pnpm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:list|ls)\b|yarn\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:list)\b|cargo\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*tree\b|bundle\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:list|show)\b|composer\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*show\b)/i,
|
|
408
|
+
npmInstall: /^\s*npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*(?:install|ci)\b/i,
|
|
409
|
+
npmAudit: /^\s*npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*audit\b(?!.*(?:--fix|fix)\b)/i,
|
|
410
|
+
npmOutdated: /^\s*npm\s+(?:--?[a-zA-Z0-9_-]+(?:\s+|=)?)*outdated\b/i,
|
|
411
411
|
envProbe: /^\s*(?:node\s+(?:-v|--version)|npm\s+(?:-v|--version)|python3?\s+(?:(?:-V)\b|--?version)|git\s+--version|uv\s+--version|go\s+version|rustc\s+--version|cargo\s+--version|java\s+--version|ruby\s+--version|gem\s+--version|php\s+--version|which\b|where\b)/i,
|
|
412
412
|
npx: /^\s*npx\s+(?:--?yes\s+)?(?!.*\b(?:install|add|remove|uninstall|i|rm|update|upgrade|set|get|publish|link|ci|audit|shrinkwrap|dedupe|prune|rebuild)\b)/i,
|
|
413
413
|
gitPush: /^\s*git\s+push\b/i,
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
expandGlobs,
|
|
8
8
|
leftoverIntegrations,
|
|
9
9
|
run
|
|
10
|
-
} from "./token-goat-chunk-
|
|
11
|
-
import "./token-goat-chunk-
|
|
12
|
-
import "./token-goat-chunk-
|
|
13
|
-
import "./token-goat-chunk-
|
|
14
|
-
import "./token-goat-chunk-
|
|
10
|
+
} from "./token-goat-chunk-222VPFP2.mjs";
|
|
11
|
+
import "./token-goat-chunk-TX4JFJTD.mjs";
|
|
12
|
+
import "./token-goat-chunk-4HIMCBYK.mjs";
|
|
13
|
+
import "./token-goat-chunk-PWVXXPCC.mjs";
|
|
14
|
+
import "./token-goat-chunk-6ODZ6PZK.mjs";
|
|
15
15
|
import "./token-goat-chunk-AO2QD2AG.mjs";
|
|
16
16
|
import "./token-goat-chunk-AEX54RUZ.mjs";
|
|
17
17
|
export {
|