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 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 summarised 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 |
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 dogfooded 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`.
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 dogfooded 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`.
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 dogfooded 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`.
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 — labelled `--- compact form (N tokens) ---` so the model knows to request the full body only when it needs the detail.
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 labelled 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.
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 honouring 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.
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
- Token-goat shrinks large images before they reach the model, cutting vision token costs by 60–90%. 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.
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 behaviour above unchanged.
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-RMDQFTQD.mjs";
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-XEDQH5DA.mjs";
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-2G6RAB4G.mjs";
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-ELDJRLHZ.mjs";
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 homedir11 } from "os";
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 (.+):(\d+):(\d+):\s*$/;
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 readMcpConfig(projectRoot) {
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
- return parsed.mcpServers || parsed;
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 config = readMcpConfig(projectRoot);
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: config !== null,
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.configFound ? "yes" : "no"}
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.snippet}
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
- process.stdout.write(`${JSON.stringify(hits)}
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
- TOTAL saved=${totals.savedBytes} spent=${spent} net=${net}${legacyNote}
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-73MF6YWW.mjs"));
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-YUGNM3KL.mjs");
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(homedir11(), ".claude", "skills");
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 pages = matches2.map((m) => m.page);
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: matches2.length, pages, matches: matches2 }, null, 2);
14674
+ printed = JSON.stringify({ file, pattern, matchCount: fencedMatches.length, pages, matches: fencedMatches }, null, 2);
14600
14675
  out(printed);
14601
- } else if (matches2.length === 0) {
14676
+ } else if (fencedMatches.length === 0) {
14602
14677
  printed = "(no matches)";
14603
14678
  out(printed);
14604
14679
  } else {
14605
- const lines = matches2.map((m) => `p${m.page}: ${m.snippet}`);
14680
+ const lines = fencedMatches.map((m) => `p${m.page}: ${m.snippet}`);
14606
14681
  printed = `${lines.join("\n")}
14607
14682
 
14608
- ${countNoun(matches2.length, "match", "matches")} across ${countNoun(pages.length, "page")}`;
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 text = opts.json === true ? JSON.stringify(entries, null, 2) : entries.map((e) => `${" ".repeat(e.level)}${e.title}${e.page !== null ? ` (p.${e.page})` : ""}`).join("\n");
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(result, null, 2);
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 text = opts.json === true ? JSON.stringify(sheets.map((s) => ({ name: s.name, ref: s.ref, rows: s.rows, cols: s.cols })), null, 2) : sheets.map((s) => `${s.name} ${s.ref} (${s.rows} rows x ${s.cols} cols)`).join("\n");
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 text = opts.json === true ? JSON.stringify(slides, null, 2) : slides.map((s) => `${s.slide}. ${s.title || "(untitled)"} [${s.bodyChars} body chars${s.hasNotes ? ", has notes" : ""}]`).join("\n");
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 text = opts.json === true ? JSON.stringify(headings, null, 2) : headings.map((h) => `${" ".repeat(h.level - 1)}${h.text}`).join("\n");
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-I6TOUPLP.mjs");
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 = Math.random().toString(36).slice(2, 8);
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
- out(emitted);
15867
- const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emitted, "utf8"));
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-2G6RAB4G.mjs";
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-ELDJRLHZ.mjs";
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+(?:-\S+\s+)*(?:ls|list)\b|pip\s+(?:-\S+\s+)*(?:list|freeze)\b|uv\s+pip\s+(?:-\S+\s+)*(?:list|freeze)\b|pnpm\s+(?:-\S+\s+)*(?:list|ls)\b|yarn\s+(?:-\S+\s+)*(?:list)\b|cargo\s+(?:-\S+\s+)*tree\b|bundle\s+(?:-\S+\s+)*(?:list|show)\b|composer\s+(?:-\S+\s+)*show\b)/i,
408
- npmInstall: /^\s*npm\s+(?:-\S+\s+)*(?:install|ci)\b/i,
409
- npmAudit: /^\s*npm\s+(?:-\S+\s+)*audit\b(?!.*(?:--fix|fix)\b)/i,
410
- npmOutdated: /^\s*npm\s+(?:-\S+\s+)*outdated\b/i,
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-VBXBLGTO.mjs";
11
- import "./token-goat-chunk-RMDQFTQD.mjs";
12
- import "./token-goat-chunk-XEDQH5DA.mjs";
13
- import "./token-goat-chunk-2G6RAB4G.mjs";
14
- import "./token-goat-chunk-ELDJRLHZ.mjs";
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 {