token-goat 2.8.3 → 2.8.4
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 +14 -12
- package/dist/{token-goat-chunk-PWVXXPCC.mjs → token-goat-chunk-4OTIB7SB.mjs} +450 -138
- package/dist/{token-goat-chunk-EFF2XCLB.mjs → token-goat-chunk-CZALRRGN.mjs} +175 -56
- package/dist/{token-goat-chunk-6ODZ6PZK.mjs → token-goat-chunk-E76UNTVK.mjs} +189 -69
- package/dist/{token-goat-chunk-4OM2Q2SX.mjs → token-goat-chunk-HNODKNWO.mjs} +5 -5
- package/dist/{token-goat-chunk-4HIMCBYK.mjs → token-goat-chunk-IZRXU64B.mjs} +51 -4
- package/dist/{token-goat-chunk-DP3NNGGV.mjs → token-goat-chunk-L6YP6FKQ.mjs} +5 -5
- package/dist/{token-goat-chunk-C5IL6MJH.mjs → token-goat-chunk-LZOAPGWR.mjs} +6 -5
- package/dist/{token-goat-chunk-VAGPOZHO.mjs → token-goat-chunk-NZFTWBGV.mjs} +2 -2
- package/dist/{token-goat-chunk-TX4JFJTD.mjs → token-goat-chunk-RE7S7H26.mjs} +110 -50
- package/dist/{token-goat-chunk-222VPFP2.mjs → token-goat-chunk-U4UI5F3S.mjs} +953 -188
- package/dist/{token-goat-chunk-NKNCHJ4H.mjs → token-goat-chunk-ZQ3PUOP3.mjs} +310 -109
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +3 -1
|
@@ -86,7 +86,7 @@ import {
|
|
|
86
86
|
runZipRead,
|
|
87
87
|
symbolNamesInFile,
|
|
88
88
|
upsertNote
|
|
89
|
-
} from "./token-goat-chunk-
|
|
89
|
+
} from "./token-goat-chunk-RE7S7H26.mjs";
|
|
90
90
|
import {
|
|
91
91
|
BASH_OUTPUT_SUBDIR,
|
|
92
92
|
GEMINI_TOOL_NAME_MAP,
|
|
@@ -113,13 +113,15 @@ import {
|
|
|
113
113
|
searchRecall,
|
|
114
114
|
storeWebOutput,
|
|
115
115
|
summarizeResidentContext
|
|
116
|
-
} from "./token-goat-chunk-
|
|
116
|
+
} from "./token-goat-chunk-IZRXU64B.mjs";
|
|
117
117
|
import {
|
|
118
118
|
AGENT_SALT_MARKER,
|
|
119
119
|
CONTEXT_AUTOCOMPACT_TOKENS,
|
|
120
120
|
DEFAULT_MAX_AGE_MS,
|
|
121
121
|
DEFAULT_MAX_COUNT,
|
|
122
|
+
MATERIALIZE_SHRUNK_IMAGE_JS,
|
|
122
123
|
MAX_FILES_SCANNED,
|
|
124
|
+
PARSER_FINGERPRINT,
|
|
123
125
|
SESSIONS_SUBDIR,
|
|
124
126
|
SKILLS_OUTPUT_SUBDIR,
|
|
125
127
|
SKIP_DIRS,
|
|
@@ -159,6 +161,7 @@ import {
|
|
|
159
161
|
extractCompactFromMarker,
|
|
160
162
|
extractNamedSection,
|
|
161
163
|
fenceUntrustedContent,
|
|
164
|
+
fenceUntrustedOcrText,
|
|
162
165
|
findClaudeMdFiles,
|
|
163
166
|
findContentDuplicates,
|
|
164
167
|
findLatestSessionId,
|
|
@@ -257,7 +260,7 @@ import {
|
|
|
257
260
|
vscodeDecoderConfigured,
|
|
258
261
|
walkProject,
|
|
259
262
|
writeCompact
|
|
260
|
-
} from "./token-goat-chunk-
|
|
263
|
+
} from "./token-goat-chunk-4OTIB7SB.mjs";
|
|
261
264
|
import {
|
|
262
265
|
C,
|
|
263
266
|
CONFIG_KEY_ENV_OVERRIDES,
|
|
@@ -324,6 +327,7 @@ import {
|
|
|
324
327
|
runGit,
|
|
325
328
|
safeSlice,
|
|
326
329
|
saveConfig,
|
|
330
|
+
savedTokensFromBytes,
|
|
327
331
|
sleepSync,
|
|
328
332
|
stripAnsi,
|
|
329
333
|
stripOwnHooksFromMap,
|
|
@@ -339,7 +343,7 @@ import {
|
|
|
339
343
|
withFileLock,
|
|
340
344
|
withRetryOnLock,
|
|
341
345
|
writeJsonSettings
|
|
342
|
-
} from "./token-goat-chunk-
|
|
346
|
+
} from "./token-goat-chunk-E76UNTVK.mjs";
|
|
343
347
|
import {
|
|
344
348
|
__export
|
|
345
349
|
} from "./token-goat-chunk-AEX54RUZ.mjs";
|
|
@@ -3458,9 +3462,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3458
3462
|
* @param {string} [path]
|
|
3459
3463
|
* @return {(string|null|Command)}
|
|
3460
3464
|
*/
|
|
3461
|
-
executableDir(
|
|
3462
|
-
if (
|
|
3463
|
-
this._executableDir =
|
|
3465
|
+
executableDir(path29) {
|
|
3466
|
+
if (path29 === void 0) return this._executableDir;
|
|
3467
|
+
this._executableDir = path29;
|
|
3464
3468
|
return this;
|
|
3465
3469
|
}
|
|
3466
3470
|
/**
|
|
@@ -3759,9 +3763,9 @@ function attemptedCommandName(argv) {
|
|
|
3759
3763
|
}
|
|
3760
3764
|
|
|
3761
3765
|
// src/cli.ts
|
|
3762
|
-
import * as
|
|
3763
|
-
import * as
|
|
3764
|
-
import { homedir as
|
|
3766
|
+
import * as fs28 from "fs";
|
|
3767
|
+
import * as path28 from "path";
|
|
3768
|
+
import { homedir as homedir13 } from "os";
|
|
3765
3769
|
|
|
3766
3770
|
// src/walk_index.ts
|
|
3767
3771
|
import * as fs2 from "node:fs";
|
|
@@ -4095,10 +4099,11 @@ import * as os from "node:os";
|
|
|
4095
4099
|
import * as path from "node:path";
|
|
4096
4100
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4097
4101
|
|
|
4098
|
-
// pi built-in tool names -> token-goat internal tool names
|
|
4102
|
+
// pi built-in tool names -> token-goat internal tool names. powershell is pi's Windows shell twin of bash -- same input schema (PowerShellToolInput = BashToolInput, i.e. command/timeout) and registered under its own name (badlogic/pi-mono packages/coding-agent/src/core/tools/powershell.ts + tools/index.ts's ToolName union). Unmapped, every shell command in a powershell-tool pi session bypassed the Bash hooks entirely, the same shape as the Copilot shim's bash/powershell pairing.
|
|
4099
4103
|
const TOOL_TO_TG: Record<string, string> = {
|
|
4100
4104
|
read: "Read",
|
|
4101
4105
|
bash: "Bash",
|
|
4106
|
+
powershell: "Bash",
|
|
4102
4107
|
edit: "Edit",
|
|
4103
4108
|
write: "Write",
|
|
4104
4109
|
grep: "Grep",
|
|
@@ -4109,15 +4114,14 @@ const TOOL_TO_TG: Record<string, string> = {
|
|
|
4109
4114
|
const ARGS_TO_TG: Record<string, Record<string, string>> = {
|
|
4110
4115
|
read: { path: "file_path", offset: "offset", limit: "limit" },
|
|
4111
4116
|
bash: { command: "command", timeout: "timeout" },
|
|
4117
|
+
powershell: { command: "command", timeout: "timeout" },
|
|
4112
4118
|
edit: { path: "file_path" },
|
|
4113
4119
|
write: { path: "file_path" },
|
|
4114
4120
|
grep: { pattern: "pattern", path: "path" },
|
|
4115
4121
|
find: { pattern: "pattern", path: "path" },
|
|
4116
4122
|
};
|
|
4117
4123
|
|
|
4118
|
-
// Tools
|
|
4119
|
-
// pre_tool_use handler in token-goat (only Read/Grep/Bash/WebFetch do), so
|
|
4120
|
-
// it's excluded here rather than spawning a hook call that always no-ops.
|
|
4124
|
+
// Tools with a pre-hook whose output shape this extension can act on: deny ({block}), updatedInput (in-place arg rewrite), or the Read image-shrink materialization. Glob's pre handler (preGlobDedupHandler in hooks_glob.ts -- the old claim here that Glob has no pre handler was stale) emits only an advisory contextOutput hint, which pi's tool_call contract has no channel for (see the module docblock), so calling it would cost a hook call per find only to drop the answer; it stays excluded for that reason.
|
|
4121
4125
|
const PRE_HOOK_TOOLS = new Set(["Read", "Grep", "Bash", "WebFetch"]);
|
|
4122
4126
|
|
|
4123
4127
|
// resolveEntryPath reads a sidecar JSON file (token-goat-entry.json, written by
|
|
@@ -4269,6 +4273,30 @@ function extractUpdatedInput(resp: Record<string, unknown>): Record<string, unkn
|
|
|
4269
4273
|
// since pi's tool_call handler has no context-injection channel -- only
|
|
4270
4274
|
// in-place arg mutation. Returns undefined (leaving the read path untouched)
|
|
4271
4275
|
// if the context isn't a shrink payload or anything goes wrong writing it.
|
|
4276
|
+
// Best-effort sweep of previously materialized shrunk copies in the OS temp dir: typed twin of pruneMaterializedShrinks in shrink_block.ts (MATERIALIZE_SHRUNK_IMAGE_JS), which documents why pi keeps its own copy. The temp file only needs to outlive the single tool call whose path was rewritten to it, so anything older than an hour is finished with; the "token-goat-shrink-" prefix check confines the sweep to this mechanism's own files.
|
|
4277
|
+
const MATERIALIZED_SHRINK_MAX_AGE_MS = 60 * 60 * 1000;
|
|
4278
|
+
let lastMaterializedShrinkSweepAtMs = 0;
|
|
4279
|
+
function pruneMaterializedShrinks(): void {
|
|
4280
|
+
const now = Date.now();
|
|
4281
|
+
if (now - lastMaterializedShrinkSweepAtMs < MATERIALIZED_SHRINK_MAX_AGE_MS) return;
|
|
4282
|
+
lastMaterializedShrinkSweepAtMs = now;
|
|
4283
|
+
try {
|
|
4284
|
+
const dir = os.tmpdir();
|
|
4285
|
+
for (const file of fs.readdirSync(dir)) {
|
|
4286
|
+
if (!file.startsWith("token-goat-shrink-")) continue;
|
|
4287
|
+
const full = path.join(dir, file);
|
|
4288
|
+
try {
|
|
4289
|
+
const st = fs.statSync(full);
|
|
4290
|
+
if (st.isFile() && now - st.mtimeMs > MATERIALIZED_SHRINK_MAX_AGE_MS) fs.unlinkSync(full);
|
|
4291
|
+
} catch {
|
|
4292
|
+
// Best-effort per-file cleanup; one bad stat/unlink must not abort the sweep.
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
} catch {
|
|
4296
|
+
// Best-effort; a readdir failure must never break the materialization below.
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4272
4300
|
function materializeShrunkImage(context: string | undefined): string | undefined {
|
|
4273
4301
|
if (typeof context !== "string") return undefined;
|
|
4274
4302
|
const idx = context.indexOf("data:image/");
|
|
@@ -4276,6 +4304,7 @@ function materializeShrunkImage(context: string | undefined): string | undefined
|
|
|
4276
4304
|
const match = /^data:image\\/([a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)$/.exec(context.slice(idx).trim());
|
|
4277
4305
|
if (!match) return undefined;
|
|
4278
4306
|
try {
|
|
4307
|
+
pruneMaterializedShrinks();
|
|
4279
4308
|
const buf = Buffer.from(match[2], "base64");
|
|
4280
4309
|
const name = \`token-goat-shrink-\${process.pid}-\${Date.now()}-\${Math.random().toString(36).slice(2)}.\${match[1]}\`;
|
|
4281
4310
|
const file = path.join(os.tmpdir(), name);
|
|
@@ -4361,13 +4390,35 @@ export default function (pi: ExtensionAPI) {
|
|
|
4361
4390
|
.filter((c: { type?: string }) => c && c.type === "text")
|
|
4362
4391
|
.map((c: { text?: string }) => c.text ?? "")
|
|
4363
4392
|
.join("\\n");
|
|
4364
|
-
await callHook("post_tool_use", {
|
|
4393
|
+
const resp = await callHook("post_tool_use", {
|
|
4365
4394
|
session_id: sessionId,
|
|
4366
4395
|
tool_name: tg,
|
|
4367
4396
|
tool_input: toToolInput(event.toolName, (event.input ?? {}) as Record<string, unknown>),
|
|
4368
4397
|
cwd,
|
|
4369
4398
|
tool_response: { output },
|
|
4370
4399
|
});
|
|
4400
|
+
if (!resp) return;
|
|
4401
|
+
|
|
4402
|
+
// rewriteOutput: replace the tool result the model sees. pi's tool_result handler CAN modify the result: its own types declare "Fired after a tool executes. Can modify result." over ToolResultEvent and a ToolResultEventResult of { content, details, isError, usage }, and the shipped runtime applies it (dist/core/extensions/runner.js emitToolResult assigns handlerResult.content onto the event, and dist/core/agent-session.js afterToolCall returns hookResult.content as the tool result content). Without this, injection fencing, secret redaction and output compression were computed and thrown away on every pi session, the same whitelist-shaped drop fixed for opencode.
|
|
4403
|
+
const hso = resp["hookSpecificOutput"] as Record<string, unknown> | undefined;
|
|
4404
|
+
const updatedToolOutput = hso && typeof hso["updatedToolOutput"] === "string" ? (hso["updatedToolOutput"] as string) : undefined;
|
|
4405
|
+
if (updatedToolOutput === undefined) return;
|
|
4406
|
+
|
|
4407
|
+
// Replace only the text blocks, in place, and keep every non-text block (images) untouched: emitToolResult overwrites content wholesale, so anything not carried here is dropped from what the model sees. details/isError/usage are deliberately not returned, since runner.js only overwrites the fields a handler actually provides.
|
|
4408
|
+
const rewritten: typeof contentBlocks = [];
|
|
4409
|
+
let replaced = false;
|
|
4410
|
+
for (const block of contentBlocks) {
|
|
4411
|
+
const isText = Boolean(block) && (block as { type?: string }).type === "text";
|
|
4412
|
+
if (!isText) {
|
|
4413
|
+
rewritten.push(block);
|
|
4414
|
+
continue;
|
|
4415
|
+
}
|
|
4416
|
+
if (replaced) continue;
|
|
4417
|
+
replaced = true;
|
|
4418
|
+
rewritten.push({ type: "text", text: updatedToolOutput });
|
|
4419
|
+
}
|
|
4420
|
+
if (!replaced) rewritten.push({ type: "text", text: updatedToolOutput });
|
|
4421
|
+
return { content: rewritten };
|
|
4371
4422
|
});
|
|
4372
4423
|
|
|
4373
4424
|
// Compaction: pi's session_before_compact REPLACES the summary rather than
|
|
@@ -4554,7 +4605,7 @@ import os from "node:os"
|
|
|
4554
4605
|
import path from "node:path"
|
|
4555
4606
|
import { fileURLToPath, pathToFileURL } from "node:url"
|
|
4556
4607
|
|
|
4557
|
-
// opencode built-in tool id -> token-goat canonical tool name.
|
|
4608
|
+
// opencode built-in tool id -> token-goat canonical tool name. websearch/skill/task were re-verified against opencode's own source at the tag matching the installed release (anomalyco/opencode v1.18.16 packages/opencode/src/tool/): WebSearchTool registers as "websearch" (websearch.ts), SkillTool as "skill" (skill.ts), TaskTool as "task" (task.ts, params prompt/subagent_type/description -- the exact keys token-goat's hooks_agent_spawn.ts reads, which registers a lowercase 'task' handler). Unmapped, all three were dead mechanisms here: no WebSearch repeat-search deny/compression, no repeat-skill-load deny, no agent-spawn briefing or report compaction. apply_patch (patchText only, no per-file path) and lsp/plan/question/todo have no token-goat equivalent and stay unmapped.
|
|
4558
4609
|
const TOOL_TO_TG = {
|
|
4559
4610
|
read: "Read",
|
|
4560
4611
|
bash: "Bash",
|
|
@@ -4563,15 +4614,15 @@ const TOOL_TO_TG = {
|
|
|
4563
4614
|
grep: "Grep",
|
|
4564
4615
|
glob: "Glob",
|
|
4565
4616
|
webfetch: "WebFetch",
|
|
4617
|
+
websearch: "WebSearch",
|
|
4618
|
+
skill: "Skill",
|
|
4619
|
+
task: "task",
|
|
4566
4620
|
}
|
|
4567
4621
|
|
|
4568
|
-
// Tools with a
|
|
4569
|
-
|
|
4570
|
-
// in token-goat at all, so skip the subprocess call for them entirely. Glob
|
|
4571
|
-
// has none either, so it's excluded too rather than spawning a no-op call.
|
|
4572
|
-
const PRE_HOOK_TOOLS = new Set(["read", "bash", "grep", "webfetch"])
|
|
4622
|
+
// Tools with a pre_tool_use handler server-side whose OUTPUT SHAPE this hook can act on: deny (throw), updatedInput (args rewrite), or the Read image-shrink materialization. websearch (repeat-search deny within the dedup TTL, hooks_websearch.ts), skill (repeat-load deny, hooks_skill.ts) and task (briefing updatedInput rewrite, hooks_agent_spawn.ts) all qualify. Edit/Write have no pre-hook at all. Glob DOES have one (preGlobDedupHandler, hooks_glob.ts) -- the old claim here that it has none was stale -- but its only output is an advisory contextOutput hint, and tool.execute.before has no context channel (see the module docblock), so calling it would spawn a subprocess per glob only to drop the answer; it stays excluded for that reason, not the old one.
|
|
4623
|
+
const PRE_HOOK_TOOLS = new Set(["read", "bash", "grep", "webfetch", "websearch", "skill", "task"])
|
|
4573
4624
|
|
|
4574
|
-
// opencode tool args (camelCase) -> token-goat snake_case tool_input keys.
|
|
4625
|
+
// opencode tool args (camelCase) -> token-goat snake_case tool_input keys. websearch's query (websearch.ts Parameters), skill's name (skill.ts Parameters; hooks_skill.ts reads tool_input['skill']) and task's prompt/subagent_type/description (task.ts BaseParameterFields) are all from opencode's own schemas at v1.18.16.
|
|
4575
4626
|
const ARGS_TO_TG = {
|
|
4576
4627
|
read: { filePath: "file_path", offset: "offset", limit: "limit" },
|
|
4577
4628
|
bash: { command: "command", timeout: "timeout" },
|
|
@@ -4580,6 +4631,9 @@ const ARGS_TO_TG = {
|
|
|
4580
4631
|
grep: { pattern: "pattern", path: "path" },
|
|
4581
4632
|
glob: { pattern: "pattern", path: "path" },
|
|
4582
4633
|
webfetch: { url: "url" },
|
|
4634
|
+
websearch: { query: "query" },
|
|
4635
|
+
skill: { name: "skill" },
|
|
4636
|
+
task: { prompt: "prompt", subagent_type: "subagent_type", description: "description" },
|
|
4583
4637
|
}
|
|
4584
4638
|
|
|
4585
4639
|
function reverseArgMap(tool) {
|
|
@@ -4615,28 +4669,7 @@ function extractUpdatedInput(resp) {
|
|
|
4615
4669
|
return hso && typeof hso === "object" ? hso.updatedInput : undefined
|
|
4616
4670
|
}
|
|
4617
4671
|
|
|
4618
|
-
|
|
4619
|
-
// ("<summary>\\ndata:image/<fmt>;base64,<data>") into a real file on disk,
|
|
4620
|
-
// since tool.execute.before has no context-injection channel to hand the
|
|
4621
|
-
// shrunk image to the model directly -- only output.args mutation. Returns
|
|
4622
|
-
// undefined (leaving output.args untouched) if the context isn't a shrink
|
|
4623
|
-
// payload or anything goes wrong writing it.
|
|
4624
|
-
function materializeShrunkImage(context) {
|
|
4625
|
-
if (typeof context !== "string") return undefined
|
|
4626
|
-
const idx = context.indexOf("data:image/")
|
|
4627
|
-
if (idx === -1) return undefined
|
|
4628
|
-
const match = /^data:image\\/([a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)$/.exec(context.slice(idx).trim())
|
|
4629
|
-
if (!match) return undefined
|
|
4630
|
-
try {
|
|
4631
|
-
const buf = Buffer.from(match[2], "base64")
|
|
4632
|
-
const name = \`token-goat-shrink-\${process.pid}-\${Date.now()}-\${Math.random().toString(36).slice(2)}.\${match[1]}\`
|
|
4633
|
-
const file = path.join(os.tmpdir(), name)
|
|
4634
|
-
fs.writeFileSync(file, buf)
|
|
4635
|
-
return file
|
|
4636
|
-
} catch {
|
|
4637
|
-
return undefined
|
|
4638
|
-
}
|
|
4639
|
-
}
|
|
4672
|
+
${MATERIALIZE_SHRUNK_IMAGE_JS}
|
|
4640
4673
|
|
|
4641
4674
|
export const TokenGoatPlugin = async ({ directory }) => {
|
|
4642
4675
|
return {
|
|
@@ -4686,6 +4719,14 @@ export const TokenGoatPlugin = async ({ directory }) => {
|
|
|
4686
4719
|
})
|
|
4687
4720
|
if (!resp) return
|
|
4688
4721
|
|
|
4722
|
+
// rewriteOutput: replace the tool result wholesale. This is the shape every post_tool_use rewrite producer emits (WebFetch injection fencing and secret redaction, websearch redaction, bash/grep output compression, agent-report compaction), and it went unapplied here for the same whitelist reason the response-contract docblock above used to omit it -- the plugin only handled the shapes its author listed, so opencode sessions got the appended hint channel but never a fenced, redacted or compressed result. Mutating output.output is the exact mechanism the context append below already relies on, so this claims no capability the append did not.
|
|
4723
|
+
const hso = resp.hookSpecificOutput
|
|
4724
|
+
const updatedToolOutput = hso && typeof hso.updatedToolOutput === "string" ? hso.updatedToolOutput : undefined
|
|
4725
|
+
if (updatedToolOutput !== undefined) {
|
|
4726
|
+
output.output = updatedToolOutput
|
|
4727
|
+
return
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4689
4730
|
const context = extractContext(resp)
|
|
4690
4731
|
if (context && typeof output.output === "string") {
|
|
4691
4732
|
output.output += \`\\n\\n[token-goat] \${context}\`
|
|
@@ -4740,15 +4781,11 @@ var OPENCLAW_PLUGIN_SCRIPT = `// token-goat bridge plugin for OpenClaw
|
|
|
4740
4781
|
// Bridges OpenClaw's tool-call and session hooks to token-goat's subprocess
|
|
4741
4782
|
// hook protocol. https://github.com/DFKHelper/token-goat
|
|
4742
4783
|
//
|
|
4743
|
-
// NOTE ON PARAMETER SHAPES: OpenClaw's tool-
|
|
4744
|
-
// token-goat's tool_input unchanged (no key remapping) -- verified against
|
|
4745
|
-
// OpenClaw's own event-type source that these keys are already snake_case
|
|
4746
|
-
// (file_path, command, etc.), matching token-goat's own convention. The
|
|
4747
|
-
// built-in tool NAME list below is broader than any single source confirms,
|
|
4748
|
-
// since an unmatched name is a harmless no-op here, not a wrong rewrite.
|
|
4784
|
+
// NOTE ON PARAMETER SHAPES: OpenClaw's bash tool really does use token-goat's own key names (command/timeout), but the path-carrying tools (read/edit/write) send the file path under "path", not "file_path" -- read out of OpenClaw's own tool input schemas (src/agents/sessions/tools/read-tool-contract.ts, edit.ts, bash.ts in openclaw/openclaw). toToolInput below ADDS the canonical file_path alongside the original path so token-goat's getFilePath()-based handlers can see it, without renaming anything. The built-in tool NAME list below is broader than any single source confirms, since an unmatched name is a harmless no-op here, not a wrong rewrite.
|
|
4749
4785
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
4750
4786
|
import { spawnSync } from "node:child_process";
|
|
4751
4787
|
import * as fs from "node:fs";
|
|
4788
|
+
import * as os from "node:os";
|
|
4752
4789
|
import * as path from "node:path";
|
|
4753
4790
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4754
4791
|
|
|
@@ -4759,20 +4796,43 @@ const TOOL_TO_TG = {
|
|
|
4759
4796
|
edit: "Edit",
|
|
4760
4797
|
apply_patch: "Edit",
|
|
4761
4798
|
exec: "Bash",
|
|
4799
|
+
bash: "Bash",
|
|
4762
4800
|
grep: "Grep",
|
|
4763
4801
|
glob: "Glob",
|
|
4802
|
+
find: "Glob",
|
|
4764
4803
|
webfetch: "WebFetch",
|
|
4765
4804
|
web_search: "WebFetch",
|
|
4766
4805
|
web_fetch: "WebFetch",
|
|
4767
4806
|
};
|
|
4768
4807
|
|
|
4769
|
-
// Tools with a
|
|
4770
|
-
// none; Glob has none either -- see pi.ts/opencode.ts's identical
|
|
4771
|
-
// PRE_HOOK_TOOLS note -- so it's excluded to avoid a wasted no-op spawn).
|
|
4808
|
+
// Tools with a pre_tool_use handler whose output shape this hook can act on: deny (block/blockReason), updatedInput (params rewrite), or the Read image-shrink materialization. Edit/Write have no pre handler at all. Glob (OpenClaw's find) DOES have one -- preGlobDedupHandler in hooks_glob.ts; the old claim here that it has none was stale -- but its only output is an advisory contextOutput hint and before_tool_call has no context channel, so calling it would spawn a hook subprocess per find only to drop the answer; it stays excluded for that reason (matching pi.ts/opencode.ts's corrected note).
|
|
4772
4809
|
const PRE_HOOK_TOOLS = new Set(["Read", "Grep", "Bash", "WebFetch"]);
|
|
4773
4810
|
|
|
4811
|
+
// OpenClaw tools whose file path travels under "path" (see the parameter-shapes note above). apply_patch is deliberately absent: its input is a patch document, not a single path.
|
|
4812
|
+
const PATH_ARG_TOOLS = new Set(["read", "write", "edit"]);
|
|
4813
|
+
|
|
4814
|
+
// Build token-goat's tool_input from OpenClaw's params: same object, plus the canonical file_path added alongside the original path for the tools that carry one. Never renames or drops a key.
|
|
4815
|
+
function toToolInput(toolName, params) {
|
|
4816
|
+
const p = params || {};
|
|
4817
|
+
if (PATH_ARG_TOOLS.has(toolName) && typeof p.path === "string" && p.file_path === undefined) {
|
|
4818
|
+
return { ...p, file_path: p.path };
|
|
4819
|
+
}
|
|
4820
|
+
return p;
|
|
4821
|
+
}
|
|
4822
|
+
|
|
4823
|
+
// Pull the additionalContext / systemMessage string out of a hook response, whichever shape it came back as (see the response-contract note in this module's header comment).
|
|
4824
|
+
function extractContext(resp) {
|
|
4825
|
+
if (!resp) return undefined;
|
|
4826
|
+
const hso = resp["hookSpecificOutput"];
|
|
4827
|
+
if (hso && typeof hso["additionalContext"] === "string") return hso["additionalContext"];
|
|
4828
|
+
if (typeof resp["systemMessage"] === "string") return resp["systemMessage"];
|
|
4829
|
+
return undefined;
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4774
4832
|
${BRIDGE_RELAY_JS}
|
|
4775
4833
|
|
|
4834
|
+
${MATERIALIZE_SHRUNK_IMAGE_JS}
|
|
4835
|
+
|
|
4776
4836
|
export default definePluginEntry({
|
|
4777
4837
|
id: "token-goat",
|
|
4778
4838
|
name: "token-goat",
|
|
@@ -4808,7 +4868,7 @@ export default definePluginEntry({
|
|
|
4808
4868
|
const resp = await callHook("pre_tool_use", {
|
|
4809
4869
|
session_id: sid,
|
|
4810
4870
|
tool_name: tg,
|
|
4811
|
-
tool_input: event.params
|
|
4871
|
+
tool_input: toToolInput(event.toolName, event.params),
|
|
4812
4872
|
cwd: process.cwd(),
|
|
4813
4873
|
});
|
|
4814
4874
|
if (!resp) return {};
|
|
@@ -4827,6 +4887,12 @@ export default definePluginEntry({
|
|
|
4827
4887
|
return { params: { ...(event.params || {}), ...updated } };
|
|
4828
4888
|
}
|
|
4829
4889
|
|
|
4890
|
+
// Image shrink has no context channel here -- translate it into a rewritten path pointing at a materialized shrunk copy instead, via the same params-rewrite channel the updatedInput merge above already relies on. OpenClaw's read tool takes the path under "path" (see the parameter-shapes note above), and the rest of the original params are preserved.
|
|
4891
|
+
if (tg === "Read") {
|
|
4892
|
+
const shrunkPath = materializeShrunkImage(extractContext(resp));
|
|
4893
|
+
if (shrunkPath) return { params: { ...(event.params || {}), path: shrunkPath } };
|
|
4894
|
+
}
|
|
4895
|
+
|
|
4830
4896
|
return {};
|
|
4831
4897
|
});
|
|
4832
4898
|
|
|
@@ -4846,7 +4912,7 @@ export default definePluginEntry({
|
|
|
4846
4912
|
await callHook("post_tool_use", {
|
|
4847
4913
|
session_id: sid,
|
|
4848
4914
|
tool_name: tg,
|
|
4849
|
-
tool_input: event.params
|
|
4915
|
+
tool_input: toToolInput(event.toolName, event.params),
|
|
4850
4916
|
cwd: process.cwd(),
|
|
4851
4917
|
tool_response: toolResponse,
|
|
4852
4918
|
});
|
|
@@ -5695,7 +5761,7 @@ function renderTopSessionFilesFromDisk(topN = 5, overrideSessionsDir) {
|
|
|
5695
5761
|
var METHODOLOGY = {
|
|
5696
5762
|
estimate_scope: "Local estimate of content avoided or reduced by token-goat.",
|
|
5697
5763
|
billing: "They are not GitHub Copilot usage, provider-reported token consumption, or billing data.",
|
|
5698
|
-
byte_derived_formula: "Most read, hook, and command entries
|
|
5764
|
+
byte_derived_formula: "Most read, hook, and command entries go through savedTokensFromBytes in src/stats.ts, which is Math.round(bytes_saved / 4).",
|
|
5699
5765
|
filter_estimates: "Output compressors record their filter-calculated delta; image entries use the byte-derived approximation unless the source provides a narrower estimate.",
|
|
5700
5766
|
advisory_events: "Zero-byte, zero-token advisory events show that guidance fired, not that an agent followed it.",
|
|
5701
5767
|
audit: "Use stats --full or stats --json for source and command breakdowns; reconcile billing with provider-exported usage data."
|
|
@@ -5718,6 +5784,21 @@ function renderMethodology(json = false) {
|
|
|
5718
5784
|
""
|
|
5719
5785
|
].join("\n"));
|
|
5720
5786
|
}
|
|
5787
|
+
function statsJsonPayload(summary) {
|
|
5788
|
+
return {
|
|
5789
|
+
total_events: summary.total_events,
|
|
5790
|
+
total_bytes_saved: summary.total_bytes_saved,
|
|
5791
|
+
total_tokens_saved: summary.total_tokens_saved,
|
|
5792
|
+
by_kind: summary.by_kind,
|
|
5793
|
+
by_day: summary.by_day,
|
|
5794
|
+
by_project: summary.by_project,
|
|
5795
|
+
by_command: summary.by_command,
|
|
5796
|
+
by_source: summary.by_source,
|
|
5797
|
+
by_harness: summary.by_harness,
|
|
5798
|
+
counts: summary.counts,
|
|
5799
|
+
window_days: summary.window_days
|
|
5800
|
+
};
|
|
5801
|
+
}
|
|
5721
5802
|
function runStats(opts = {}) {
|
|
5722
5803
|
if (opts.methodology === true) {
|
|
5723
5804
|
renderMethodology(opts.json === true);
|
|
@@ -5726,19 +5807,7 @@ function runStats(opts = {}) {
|
|
|
5726
5807
|
const window = opts.windowDays ?? 30;
|
|
5727
5808
|
const summary = summarize(window, void 0, opts.homeDir);
|
|
5728
5809
|
if (opts.json === true) {
|
|
5729
|
-
|
|
5730
|
-
total_events: summary.total_events,
|
|
5731
|
-
total_bytes_saved: summary.total_bytes_saved,
|
|
5732
|
-
total_tokens_saved: summary.total_tokens_saved,
|
|
5733
|
-
by_kind: summary.by_kind,
|
|
5734
|
-
by_day: summary.by_day,
|
|
5735
|
-
by_project: summary.by_project,
|
|
5736
|
-
by_command: summary.by_command,
|
|
5737
|
-
by_source: summary.by_source,
|
|
5738
|
-
by_harness: summary.by_harness,
|
|
5739
|
-
window_days: summary.window_days
|
|
5740
|
-
};
|
|
5741
|
-
process.stdout.write(JSON.stringify(out2) + "\n");
|
|
5810
|
+
process.stdout.write(JSON.stringify(statsJsonPayload(summary)) + "\n");
|
|
5742
5811
|
return;
|
|
5743
5812
|
}
|
|
5744
5813
|
const renderOpts = { windowDays: window };
|
|
@@ -6287,7 +6356,9 @@ function checkUnmappedTools(dbPath) {
|
|
|
6287
6356
|
if (rows.length === 0) {
|
|
6288
6357
|
return { name, status: "ok", message: "every tool name seen so far reached a handler that wanted it" };
|
|
6289
6358
|
}
|
|
6290
|
-
const nearMisses = rows.filter(
|
|
6359
|
+
const nearMisses = rows.filter(
|
|
6360
|
+
(r) => r.near_miss !== null && r.near_miss !== void 0 && r.near_miss !== r.tool_name
|
|
6361
|
+
);
|
|
6291
6362
|
if (nearMisses.length > 0) {
|
|
6292
6363
|
const shown2 = nearMisses.slice(0, UNMAPPED_TOOL_SAMPLE).map((r) => `${r.harness} sent "${r.tool_name}" where "${r.near_miss}" is handled (${r.event_name}, ${r.hits}x)`);
|
|
6293
6364
|
const more2 = nearMisses.length > UNMAPPED_TOOL_SAMPLE ? ` (+${nearMisses.length - UNMAPPED_TOOL_SAMPLE} more)` : "";
|
|
@@ -9189,12 +9260,12 @@ function formatBudgetText(result, contextK) {
|
|
|
9189
9260
|
lines.push(` ${e.rel_path.padEnd(colW, " ")} ${String(e.lines).padStart(6, " ")} ${String(e.tokens).padStart(8, " ")}`);
|
|
9190
9261
|
}
|
|
9191
9262
|
lines.push(` ${"-".repeat(colW)} ${"-".repeat(6)} ${"-".repeat(8)}`);
|
|
9192
|
-
let
|
|
9263
|
+
let pct2 = "";
|
|
9193
9264
|
if (contextK) {
|
|
9194
|
-
|
|
9265
|
+
pct2 = ` (${Math.round(result.total_tokens / (contextK * 1e3) * 100)}% of ${contextK}K)`;
|
|
9195
9266
|
}
|
|
9196
9267
|
lines.push(
|
|
9197
|
-
` ${"Total".padEnd(colW, " ")} ${String(result.total_lines).padStart(6, " ")} ${String(result.total_tokens).padStart(8, " ")}${
|
|
9268
|
+
` ${"Total".padEnd(colW, " ")} ${String(result.total_lines).padStart(6, " ")} ${String(result.total_tokens).padStart(8, " ")}${pct2}`
|
|
9198
9269
|
);
|
|
9199
9270
|
if (result.skipped.length > 0) {
|
|
9200
9271
|
const skipped = result.skipped.slice(0, 5).join(", ");
|
|
@@ -11087,7 +11158,7 @@ function guardDeclarationRows(rows, budgetTokens) {
|
|
|
11087
11158
|
function recordDepDocsStat(fullSourceBytes, emittedText, packageName) {
|
|
11088
11159
|
const emittedBytes = Buffer.byteLength(emittedText, "utf8");
|
|
11089
11160
|
const bytesSaved = Math.max(1, fullSourceBytes - emittedBytes);
|
|
11090
|
-
recordStat("dep_docs", bytesSaved,
|
|
11161
|
+
recordStat("dep_docs", bytesSaved, savedTokensFromBytes(bytesSaved), void 0, packageName);
|
|
11091
11162
|
}
|
|
11092
11163
|
function runDepDocs(opts) {
|
|
11093
11164
|
const root = resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() });
|
|
@@ -11504,15 +11575,15 @@ function cmdCompactHint(opts) {
|
|
|
11504
11575
|
const pressure = getContextPressure(cache ?? void 0);
|
|
11505
11576
|
const [manifest, eventCount] = sessionId !== null ? buildManifestWithCount(sessionId) : ["", 0];
|
|
11506
11577
|
const manifestTokens = estimateTokens(manifest);
|
|
11507
|
-
const
|
|
11578
|
+
const pct2 = (pressure.fillFraction * 100).toFixed(1);
|
|
11508
11579
|
if (opts.json === true) {
|
|
11509
|
-
const out2 = { tier: pressure.tier, fillFraction: pressure.fillFraction, pct: Number(
|
|
11580
|
+
const out2 = { tier: pressure.tier, fillFraction: pressure.fillFraction, pct: Number(pct2), manifestTokens, eventCount };
|
|
11510
11581
|
if (sessionId !== null) out2["sessionId"] = sessionId;
|
|
11511
11582
|
if (opts.trigger !== void 0) out2["trigger"] = opts.trigger;
|
|
11512
11583
|
process.stdout.write(JSON.stringify(out2, null, 2) + "\n");
|
|
11513
11584
|
return;
|
|
11514
11585
|
}
|
|
11515
|
-
process.stdout.write(`Compact hint \u2014 context: ${pressure.tier} (${
|
|
11586
|
+
process.stdout.write(`Compact hint \u2014 context: ${pressure.tier} (${pct2}% full)
|
|
11516
11587
|
`);
|
|
11517
11588
|
if (sessionId !== null) process.stdout.write(`Session: ${sessionId}
|
|
11518
11589
|
`);
|
|
@@ -11612,6 +11683,19 @@ function indexSizeBytes(dbPath) {
|
|
|
11612
11683
|
}
|
|
11613
11684
|
return total;
|
|
11614
11685
|
}
|
|
11686
|
+
function comparableSizes(result) {
|
|
11687
|
+
const walInFlight = result.checkpointBusy && result.afterBytes > result.beforeBytes;
|
|
11688
|
+
const before = walInFlight ? result.beforeDbBytes : result.beforeBytes;
|
|
11689
|
+
const after = walInFlight ? result.afterDbBytes : result.afterBytes;
|
|
11690
|
+
return { before, after, freed: before - after };
|
|
11691
|
+
}
|
|
11692
|
+
function dbFileBytes(dbPath) {
|
|
11693
|
+
try {
|
|
11694
|
+
return fs18.statSync(dbPath).size;
|
|
11695
|
+
} catch {
|
|
11696
|
+
return 0;
|
|
11697
|
+
}
|
|
11698
|
+
}
|
|
11615
11699
|
function tableExists(db, table) {
|
|
11616
11700
|
const row = db.prepare(`SELECT 1 AS present FROM sqlite_master WHERE type IN ('table','view') AND name = ?`).get(table);
|
|
11617
11701
|
return row?.present === 1;
|
|
@@ -11619,6 +11703,7 @@ function tableExists(db, table) {
|
|
|
11619
11703
|
function reclaimIndex(dbPath, opts = {}) {
|
|
11620
11704
|
const rebuild = opts.rebuild === true;
|
|
11621
11705
|
const beforeBytes = indexSizeBytes(dbPath);
|
|
11706
|
+
const beforeDbBytes = dbFileBytes(dbPath);
|
|
11622
11707
|
const db = getDb(dbPath);
|
|
11623
11708
|
const dropped = {};
|
|
11624
11709
|
if (rebuild) {
|
|
@@ -11643,6 +11728,8 @@ function reclaimIndex(dbPath, opts = {}) {
|
|
|
11643
11728
|
return {
|
|
11644
11729
|
beforeBytes,
|
|
11645
11730
|
afterBytes: indexSizeBytes(dbPath),
|
|
11731
|
+
beforeDbBytes,
|
|
11732
|
+
afterDbBytes: dbFileBytes(dbPath),
|
|
11646
11733
|
dropped,
|
|
11647
11734
|
rebuilt: rebuild,
|
|
11648
11735
|
checkpointBusy: checkpointBusy || finalCheckpointBusy,
|
|
@@ -11678,13 +11765,12 @@ function cmdReclaimIndex(opts) {
|
|
|
11678
11765
|
process.stdout.write(JSON.stringify({ dbPath, ...result }, null, 2) + "\n");
|
|
11679
11766
|
return;
|
|
11680
11767
|
}
|
|
11681
|
-
const freed = result
|
|
11768
|
+
const { before, after, freed } = comparableSizes(result);
|
|
11769
|
+
const delta = freed >= 0 ? `freed ${mb(freed)}` : `grew ${mb(-freed)}`;
|
|
11682
11770
|
process.stdout.write(`reclaim-index: ${dbPath}
|
|
11683
11771
|
`);
|
|
11684
|
-
process.stdout.write(
|
|
11685
|
-
|
|
11686
|
-
`
|
|
11687
|
-
);
|
|
11772
|
+
process.stdout.write(` ${mb(before)} -> ${mb(after)} (${delta})
|
|
11773
|
+
`);
|
|
11688
11774
|
if (result.rebuilt) {
|
|
11689
11775
|
for (const [table, n] of Object.entries(result.dropped)) {
|
|
11690
11776
|
process.stdout.write(` dropped ${n} row(s) from ${table}
|
|
@@ -11703,7 +11789,7 @@ function cmdReclaimIndex(opts) {
|
|
|
11703
11789
|
}
|
|
11704
11790
|
if (result.checkpointBusy) {
|
|
11705
11791
|
process.stdout.write(
|
|
11706
|
-
` note: a concurrent reader blocked WAL truncation, so some space
|
|
11792
|
+
` note: a concurrent reader blocked WAL truncation, so some space is still held in ${path18.basename(dbPath)}-wal and the figures above count the main file only. The next checkpoint releases it
|
|
11707
11793
|
`
|
|
11708
11794
|
);
|
|
11709
11795
|
}
|
|
@@ -12183,7 +12269,7 @@ function cmdProject(opts) {
|
|
|
12183
12269
|
}
|
|
12184
12270
|
function recordCompactDocStat(fullSourceBytes, emittedText, detail) {
|
|
12185
12271
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emittedText, "utf8"));
|
|
12186
|
-
recordStat("compact_doc", bytesSaved,
|
|
12272
|
+
recordStat("compact_doc", bytesSaved, savedTokensFromBytes(bytesSaved), void 0, detail);
|
|
12187
12273
|
}
|
|
12188
12274
|
function cmdCompactDoc(opts) {
|
|
12189
12275
|
const resolved = path19.resolve(opts.filePath);
|
|
@@ -13108,10 +13194,10 @@ function printCopilotReport(report) {
|
|
|
13108
13194
|
w(` Conversation: ${conversationTokens.toLocaleString()} tok
|
|
13109
13195
|
`);
|
|
13110
13196
|
if (total > 0) {
|
|
13111
|
-
const
|
|
13197
|
+
const pct2 = (fixed / total * 100).toFixed(1);
|
|
13112
13198
|
w(` ${fixed.toLocaleString()} tok of system prompt and tool definitions ships with every
|
|
13113
13199
|
`);
|
|
13114
|
-
w(` request; at shutdown that was ${
|
|
13200
|
+
w(` request; at shutdown that was ${pct2}% of the context. No hook can reach it: Copilot
|
|
13115
13201
|
`);
|
|
13116
13202
|
w(" assembles both natively, with nothing between assembly and send. The levers are all\n");
|
|
13117
13203
|
w(" config: fewer MCP servers and custom tools, and --excluded-tools / --available-tools /\n");
|
|
@@ -13171,9 +13257,9 @@ function printCopilotReport(report) {
|
|
|
13171
13257
|
`);
|
|
13172
13258
|
} else {
|
|
13173
13259
|
for (const block of report.blocks) {
|
|
13174
|
-
const
|
|
13260
|
+
const pct2 = block.bytes > 0 ? (block.repeatBytes / block.bytes * 100).toFixed(1) : "0.0";
|
|
13175
13261
|
w(` ${block.kind}: ${countNoun(block.count, "injection")}, ${formatBytes(block.bytes)}`);
|
|
13176
|
-
w(block.repeatBytes > 0 ? `, ${formatBytes(block.repeatBytes)} re-sent verbatim (${
|
|
13262
|
+
w(block.repeatBytes > 0 ? `, ${formatBytes(block.repeatBytes)} re-sent verbatim (${pct2}%)
|
|
13177
13263
|
` : "\n");
|
|
13178
13264
|
}
|
|
13179
13265
|
}
|
|
@@ -13503,14 +13589,650 @@ function formatSessionSlice(turns) {
|
|
|
13503
13589
|
return parts.join("\n").trimEnd();
|
|
13504
13590
|
}
|
|
13505
13591
|
|
|
13506
|
-
// src/
|
|
13592
|
+
// src/session_audit.ts
|
|
13507
13593
|
import * as fs26 from "node:fs";
|
|
13508
13594
|
import * as os12 from "node:os";
|
|
13509
13595
|
import * as path26 from "node:path";
|
|
13596
|
+
import * as readline3 from "node:readline";
|
|
13597
|
+
function emptyMeasured() {
|
|
13598
|
+
return { apiCalls: 0, inputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0, outputTokens: 0 };
|
|
13599
|
+
}
|
|
13600
|
+
function emptyCategory() {
|
|
13601
|
+
return { count: 0, bytes: 0, estTokens: 0 };
|
|
13602
|
+
}
|
|
13603
|
+
function addCategory(cat, bytes) {
|
|
13604
|
+
cat.count += 1;
|
|
13605
|
+
cat.bytes += bytes;
|
|
13606
|
+
cat.estTokens += estimateTokensFromLength(bytes);
|
|
13607
|
+
}
|
|
13608
|
+
var LOCAL_ONLY_TYPES = /* @__PURE__ */ new Set([
|
|
13609
|
+
"file-history-snapshot",
|
|
13610
|
+
"queue-operation",
|
|
13611
|
+
"mode",
|
|
13612
|
+
"permission-mode",
|
|
13613
|
+
"last-prompt",
|
|
13614
|
+
"bridge-session",
|
|
13615
|
+
"ai-title"
|
|
13616
|
+
]);
|
|
13617
|
+
var ATTACHMENT_VISIBLE_FIELDS = {
|
|
13618
|
+
hook_success: ["content"],
|
|
13619
|
+
hook_additional_context: ["content"],
|
|
13620
|
+
task_reminder: ["content"],
|
|
13621
|
+
skill_listing: ["content"],
|
|
13622
|
+
agent_listing_delta: ["addedLines"],
|
|
13623
|
+
file: ["content"],
|
|
13624
|
+
queued_command: ["prompt"],
|
|
13625
|
+
plan_file_reference: ["planContent"],
|
|
13626
|
+
edited_text_file: ["snippet"],
|
|
13627
|
+
read_truncation_notice: ["banner"],
|
|
13628
|
+
total_tokens_reminder: ["text"],
|
|
13629
|
+
nested_memory: ["content"],
|
|
13630
|
+
mcp_instructions_delta: ["addedBlocks"],
|
|
13631
|
+
invoked_skills: ["skills"],
|
|
13632
|
+
date_change: ["newDate"],
|
|
13633
|
+
goal_status: ["condition"],
|
|
13634
|
+
task_status: ["deltaSummary", "description"],
|
|
13635
|
+
command_permissions: ["allowedTools"]
|
|
13636
|
+
};
|
|
13637
|
+
var CACHE_WRITE_MULTIPLIER = 1.25;
|
|
13638
|
+
var CACHE_READ_MULTIPLIER = 0.1;
|
|
13639
|
+
var READ_DIVERT_MARKER_RE = /(?:was already read this session|Already read |You've already read|Use `token-goat (?:section|read|bash-output|config-get|skeleton)|token-goat bash-output --file)/;
|
|
13640
|
+
var READ_DIVERT_MAX_BYTES = 2500;
|
|
13641
|
+
var READ_FULL_SERVE_MIN_BYTES = 10240;
|
|
13642
|
+
var BASH_FILTER_MARKER_RE = /\[token-goat[:\]]/;
|
|
13643
|
+
var BASH_SMALL_RESULT_MAX_BYTES = 1024;
|
|
13644
|
+
function commandHead(raw) {
|
|
13645
|
+
let s = raw.trim();
|
|
13646
|
+
for (let guard = 0; guard < 20; guard++) {
|
|
13647
|
+
const stripped = s.replace(/^[(\s]+/, "");
|
|
13648
|
+
const env = /^[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S*)\s+/.exec(stripped);
|
|
13649
|
+
if (env !== null) {
|
|
13650
|
+
s = stripped.slice(env[0].length);
|
|
13651
|
+
continue;
|
|
13652
|
+
}
|
|
13653
|
+
const cd = /^cd\s+(?:"[^"]*"|'[^']*'|[^\s;&]+)[^\S\n]*(?:&&|;|\n)\s*/.exec(stripped);
|
|
13654
|
+
if (cd !== null) {
|
|
13655
|
+
s = stripped.slice(cd[0].length);
|
|
13656
|
+
continue;
|
|
13657
|
+
}
|
|
13658
|
+
if (stripped === s) break;
|
|
13659
|
+
s = stripped;
|
|
13660
|
+
}
|
|
13661
|
+
const m = /^"([^"]+)"|^'([^']+)'|^(\S+)/.exec(s);
|
|
13662
|
+
const tok = m === null ? "" : m[1] ?? m[2] ?? m[3] ?? "";
|
|
13663
|
+
const base = tok.split(/[\\/]/).pop() ?? "";
|
|
13664
|
+
const head = base.replace(/\.exe$/i, "").toLowerCase();
|
|
13665
|
+
return head === "" ? "(none)" : head;
|
|
13666
|
+
}
|
|
13667
|
+
function normalizeReadPath(p) {
|
|
13668
|
+
return p.replace(/\\/g, "/").toLowerCase();
|
|
13669
|
+
}
|
|
13670
|
+
function deepStringBytes(value) {
|
|
13671
|
+
if (typeof value === "string") return Buffer.byteLength(value, "utf8");
|
|
13672
|
+
if (Array.isArray(value)) return value.reduce((acc, v) => acc + deepStringBytes(v), 0);
|
|
13673
|
+
if (value !== null && typeof value === "object") {
|
|
13674
|
+
return Object.values(value).reduce((acc, v) => acc + deepStringBytes(v), 0);
|
|
13675
|
+
}
|
|
13676
|
+
return 0;
|
|
13677
|
+
}
|
|
13678
|
+
var OMISSION_MARKER_RE = /(?:--- (\d+) lines omitted ---|\[token-goat: \+?(\d+) more [a-z ]*lines omitted\]|--- patch: (\d+) lines omitted by token-goat ---|\.\.\. (\d+) lines omitted by token-goat \.\.\.)/g;
|
|
13679
|
+
function toolResultText(content) {
|
|
13680
|
+
if (typeof content === "string") return content;
|
|
13681
|
+
if (Array.isArray(content)) {
|
|
13682
|
+
let text = "";
|
|
13683
|
+
for (const block of content) {
|
|
13684
|
+
if (block !== null && typeof block === "object" && typeof block.text === "string") {
|
|
13685
|
+
text += block.text;
|
|
13686
|
+
}
|
|
13687
|
+
}
|
|
13688
|
+
return text;
|
|
13689
|
+
}
|
|
13690
|
+
return "";
|
|
13691
|
+
}
|
|
13692
|
+
function listCorpusTranscripts(corpusDir) {
|
|
13693
|
+
const found = [];
|
|
13694
|
+
const walk = (dir, entries) => {
|
|
13695
|
+
for (const entry of entries) {
|
|
13696
|
+
const full = path26.join(dir, entry.name);
|
|
13697
|
+
if (entry.name.endsWith(".jsonl")) {
|
|
13698
|
+
found.push(full);
|
|
13699
|
+
} else if (entry.isDirectory()) {
|
|
13700
|
+
let inner;
|
|
13701
|
+
try {
|
|
13702
|
+
inner = fs26.readdirSync(full, { withFileTypes: true });
|
|
13703
|
+
} catch {
|
|
13704
|
+
continue;
|
|
13705
|
+
}
|
|
13706
|
+
walk(full, inner);
|
|
13707
|
+
}
|
|
13708
|
+
}
|
|
13709
|
+
};
|
|
13710
|
+
walk(corpusDir, fs26.readdirSync(corpusDir, { withFileTypes: true }));
|
|
13711
|
+
return found.sort();
|
|
13712
|
+
}
|
|
13713
|
+
function defaultCorpusDir() {
|
|
13714
|
+
return path26.join(os12.homedir(), ".claude", "projects");
|
|
13715
|
+
}
|
|
13716
|
+
async function auditOneFile(filePath, s, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap) {
|
|
13717
|
+
const isLane = filePath.split(/[\\/]/).includes("subagents");
|
|
13718
|
+
let laneFirstPrefix = null;
|
|
13719
|
+
let laneBriefBytes = -1;
|
|
13720
|
+
const toolNameById = /* @__PURE__ */ new Map();
|
|
13721
|
+
const bashHeadById = /* @__PURE__ */ new Map();
|
|
13722
|
+
let pendingBashReread = [];
|
|
13723
|
+
const readCallById = /* @__PURE__ */ new Map();
|
|
13724
|
+
const readPathEpoch = /* @__PURE__ */ new Map();
|
|
13725
|
+
let compactEpoch = 0;
|
|
13726
|
+
let sawTokenGoatHook = false;
|
|
13727
|
+
let fileRepeats = 0;
|
|
13728
|
+
let fileRepeatsFullNoRange = 0;
|
|
13729
|
+
let fileRepeatsFullNoRangeAfterCompaction = 0;
|
|
13730
|
+
const usageSeenIds = /* @__PURE__ */ new Set();
|
|
13731
|
+
const perCall = [];
|
|
13732
|
+
const laneCalls = [0, 0];
|
|
13733
|
+
let pendingReread = [];
|
|
13734
|
+
const lastVisibleByKind = /* @__PURE__ */ new Map();
|
|
13735
|
+
const flushLane = (lane) => {
|
|
13736
|
+
const kept = [];
|
|
13737
|
+
for (const p of pendingReread) {
|
|
13738
|
+
if (p.lane !== lane) {
|
|
13739
|
+
kept.push(p);
|
|
13740
|
+
continue;
|
|
13741
|
+
}
|
|
13742
|
+
attachmentRollup(attachmentMap, p.kind).rereadTokens += p.tokens * Math.max(0, laneCalls[lane] - p.atCall);
|
|
13743
|
+
}
|
|
13744
|
+
pendingReread = kept;
|
|
13745
|
+
const keptBash = [];
|
|
13746
|
+
for (const p of pendingBashReread) {
|
|
13747
|
+
if (p.lane !== lane) {
|
|
13748
|
+
keptBash.push(p);
|
|
13749
|
+
continue;
|
|
13750
|
+
}
|
|
13751
|
+
s.bashInterception.untouchedRereadTokens += p.tokens * Math.max(0, laneCalls[lane] - p.atCall);
|
|
13752
|
+
}
|
|
13753
|
+
pendingBashReread = keptBash;
|
|
13754
|
+
};
|
|
13755
|
+
const stream = fs26.createReadStream(filePath);
|
|
13756
|
+
const rl = readline3.createInterface({ input: stream, crlfDelay: Infinity });
|
|
13757
|
+
const streamFailure = new Promise((_, reject) => stream.on("error", reject));
|
|
13758
|
+
const consume = (async () => {
|
|
13759
|
+
for await (const line of rl) {
|
|
13760
|
+
if (line.length === 0) continue;
|
|
13761
|
+
s.lines += 1;
|
|
13762
|
+
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
13763
|
+
s.totalBytes += lineBytes;
|
|
13764
|
+
let obj;
|
|
13765
|
+
try {
|
|
13766
|
+
obj = JSON.parse(line);
|
|
13767
|
+
} catch {
|
|
13768
|
+
s.parseFailedLines += 1;
|
|
13769
|
+
addCategory(s.estimated.otherLocal, lineBytes);
|
|
13770
|
+
continue;
|
|
13771
|
+
}
|
|
13772
|
+
const type = typeof obj["type"] === "string" ? obj["type"] : "(untyped)";
|
|
13773
|
+
const census = s.lineTypes[type] ??= { lines: 0, bytes: 0 };
|
|
13774
|
+
census.lines += 1;
|
|
13775
|
+
census.bytes += lineBytes;
|
|
13776
|
+
if (LOCAL_ONLY_TYPES.has(type)) {
|
|
13777
|
+
addCategory(s.estimated.otherLocal, lineBytes);
|
|
13778
|
+
continue;
|
|
13779
|
+
}
|
|
13780
|
+
const message = obj["message"];
|
|
13781
|
+
if (type === "assistant" && message !== void 0) {
|
|
13782
|
+
const usage = message.usage;
|
|
13783
|
+
if (usage !== void 0 && typeof message.id === "string" && !usageSeenIds.has(message.id)) {
|
|
13784
|
+
usageSeenIds.add(message.id);
|
|
13785
|
+
const num = (k) => typeof usage[k] === "number" ? usage[k] : 0;
|
|
13786
|
+
const input = num("input_tokens");
|
|
13787
|
+
const cacheWrite = num("cache_creation_input_tokens");
|
|
13788
|
+
const cacheRead = num("cache_read_input_tokens");
|
|
13789
|
+
const output = num("output_tokens");
|
|
13790
|
+
s.measured.apiCalls += 1;
|
|
13791
|
+
s.measured.inputTokens += input;
|
|
13792
|
+
s.measured.cacheCreationTokens += cacheWrite;
|
|
13793
|
+
s.measured.cacheReadTokens += cacheRead;
|
|
13794
|
+
s.measured.outputTokens += output;
|
|
13795
|
+
if (obj["isSidechain"] === true) {
|
|
13796
|
+
s.measuredSidechain.apiCalls += 1;
|
|
13797
|
+
s.measuredSidechain.inputTokens += input;
|
|
13798
|
+
s.measuredSidechain.cacheCreationTokens += cacheWrite;
|
|
13799
|
+
s.measuredSidechain.cacheReadTokens += cacheRead;
|
|
13800
|
+
s.measuredSidechain.outputTokens += output;
|
|
13801
|
+
}
|
|
13802
|
+
if (isLane && laneFirstPrefix === null) laneFirstPrefix = input + cacheWrite + cacheRead;
|
|
13803
|
+
perCall.push({ inputTotal: input + cacheWrite + cacheRead, cacheRead, output });
|
|
13804
|
+
const callLane = obj["isSidechain"] === true ? 1 : 0;
|
|
13805
|
+
laneCalls[callLane] = laneCalls[callLane] + 1;
|
|
13806
|
+
}
|
|
13807
|
+
const blocks = Array.isArray(message.content) ? message.content : [];
|
|
13808
|
+
for (const block of blocks) {
|
|
13809
|
+
if (block === null || typeof block !== "object") continue;
|
|
13810
|
+
if (block["type"] === "text" && typeof block["text"] === "string") {
|
|
13811
|
+
addCategory(s.estimated.assistantText, Buffer.byteLength(block["text"], "utf8"));
|
|
13812
|
+
} else if (block["type"] === "thinking" && typeof block["thinking"] === "string") {
|
|
13813
|
+
addCategory(s.estimated.assistantThinking, Buffer.byteLength(block["thinking"], "utf8"));
|
|
13814
|
+
} else if (block["type"] === "tool_use" && typeof block["id"] === "string") {
|
|
13815
|
+
const name = typeof block["name"] === "string" ? block["name"] : "(unnamed)";
|
|
13816
|
+
if (!toolNameById.has(block["id"])) {
|
|
13817
|
+
toolNameById.set(block["id"], name);
|
|
13818
|
+
toolRollup(toolMap, name).calls += 1;
|
|
13819
|
+
const input = block["input"] !== null && typeof block["input"] === "object" ? block["input"] : {};
|
|
13820
|
+
if (name === "Bash" && typeof input["command"] === "string") {
|
|
13821
|
+
bashHeadById.set(block["id"], commandHead(input["command"]));
|
|
13822
|
+
} else if (name === "Read" && typeof input["file_path"] === "string") {
|
|
13823
|
+
const norm = normalizeReadPath(input["file_path"]);
|
|
13824
|
+
const priorEpoch = readPathEpoch.get(norm);
|
|
13825
|
+
readCallById.set(block["id"], { wasSeenBefore: priorEpoch !== void 0, hasRange: input["offset"] !== void 0 || input["limit"] !== void 0, afterCompaction: priorEpoch !== void 0 && compactEpoch > priorEpoch });
|
|
13826
|
+
readPathEpoch.set(norm, compactEpoch);
|
|
13827
|
+
}
|
|
13828
|
+
}
|
|
13829
|
+
let inputBytes;
|
|
13830
|
+
try {
|
|
13831
|
+
inputBytes = Buffer.byteLength(JSON.stringify(block["input"] ?? null), "utf8");
|
|
13832
|
+
} catch {
|
|
13833
|
+
inputBytes = 0;
|
|
13834
|
+
}
|
|
13835
|
+
addCategory(s.estimated.toolUseInputs, inputBytes);
|
|
13836
|
+
}
|
|
13837
|
+
}
|
|
13838
|
+
continue;
|
|
13839
|
+
}
|
|
13840
|
+
if (type === "user" && message !== void 0) {
|
|
13841
|
+
const blocks = Array.isArray(message.content) ? message.content : [];
|
|
13842
|
+
let sawToolResult = false;
|
|
13843
|
+
for (const block of blocks) {
|
|
13844
|
+
if (block === null || typeof block !== "object" || block["type"] !== "tool_result") continue;
|
|
13845
|
+
sawToolResult = true;
|
|
13846
|
+
const text = toolResultText(block["content"]);
|
|
13847
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
13848
|
+
const id = typeof block["tool_use_id"] === "string" ? block["tool_use_id"] : "";
|
|
13849
|
+
const name = toolNameById.get(id) ?? "(unknown)";
|
|
13850
|
+
addCategory(s.estimated.toolResults, bytes);
|
|
13851
|
+
const roll = toolRollup(toolMap, name);
|
|
13852
|
+
if (!toolNameById.has(id)) roll.calls += 1;
|
|
13853
|
+
roll.resultBytes += bytes;
|
|
13854
|
+
roll.resultEstTokens += estimateTokensFromLength(bytes);
|
|
13855
|
+
if (name === "Read") {
|
|
13856
|
+
s.readInterception.readResults += 1;
|
|
13857
|
+
if (bytes < READ_DIVERT_MAX_BYTES && READ_DIVERT_MARKER_RE.test(text)) {
|
|
13858
|
+
s.readInterception.divertedByMarker += 1;
|
|
13859
|
+
s.readInterception.divertedBytes += bytes;
|
|
13860
|
+
} else if (bytes >= READ_FULL_SERVE_MIN_BYTES) {
|
|
13861
|
+
s.readInterception.fullServesOver10k += 1;
|
|
13862
|
+
s.readInterception.fullServeBytesOver10k += bytes;
|
|
13863
|
+
const call = readCallById.get(id);
|
|
13864
|
+
if (call === void 0) {
|
|
13865
|
+
s.readInterception.fullServesPathUnknown += 1;
|
|
13866
|
+
} else if (call.wasSeenBefore) {
|
|
13867
|
+
s.readInterception.fullServesRepeat += 1;
|
|
13868
|
+
s.readInterception.repeatBytes += bytes;
|
|
13869
|
+
fileRepeats += 1;
|
|
13870
|
+
if (call.hasRange) {
|
|
13871
|
+
s.readInterception.repeatWithRange += 1;
|
|
13872
|
+
} else {
|
|
13873
|
+
s.readInterception.repeatFullNoRange += 1;
|
|
13874
|
+
fileRepeatsFullNoRange += 1;
|
|
13875
|
+
if (call.afterCompaction) fileRepeatsFullNoRangeAfterCompaction += 1;
|
|
13876
|
+
}
|
|
13877
|
+
} else {
|
|
13878
|
+
s.readInterception.fullServesFirstRead += 1;
|
|
13879
|
+
}
|
|
13880
|
+
}
|
|
13881
|
+
} else if (name === "Bash") {
|
|
13882
|
+
const b = s.bashInterception;
|
|
13883
|
+
b.bashResults += 1;
|
|
13884
|
+
if (BASH_FILTER_MARKER_RE.test(text)) {
|
|
13885
|
+
b.markedByFilter += 1;
|
|
13886
|
+
b.markedBytes += bytes;
|
|
13887
|
+
} else if (bytes < BASH_SMALL_RESULT_MAX_BYTES) {
|
|
13888
|
+
b.smallUntouched += 1;
|
|
13889
|
+
b.smallUntouchedBytes += bytes;
|
|
13890
|
+
} else {
|
|
13891
|
+
b.untouched += 1;
|
|
13892
|
+
b.untouchedBytes += bytes;
|
|
13893
|
+
const tokens = estimateTokensFromLength(bytes);
|
|
13894
|
+
b.untouchedEstTokens += tokens;
|
|
13895
|
+
const lane = obj["isSidechain"] === true ? 1 : 0;
|
|
13896
|
+
pendingBashReread.push({ tokens, atCall: laneCalls[lane], lane });
|
|
13897
|
+
const head = bashHeadById.get(id) ?? "(unknown)";
|
|
13898
|
+
let headRoll = bashHeadMap.get(head);
|
|
13899
|
+
if (headRoll === void 0) {
|
|
13900
|
+
headRoll = { head, results: 0, bytes: 0 };
|
|
13901
|
+
bashHeadMap.set(head, headRoll);
|
|
13902
|
+
}
|
|
13903
|
+
headRoll.results += 1;
|
|
13904
|
+
headRoll.bytes += bytes;
|
|
13905
|
+
}
|
|
13906
|
+
}
|
|
13907
|
+
OMISSION_MARKER_RE.lastIndex = 0;
|
|
13908
|
+
for (const m of text.matchAll(OMISSION_MARKER_RE)) {
|
|
13909
|
+
s.omissionMarkers.fires += 1;
|
|
13910
|
+
s.omissionMarkers.linesOmitted += Number(m[1] ?? m[2] ?? m[3] ?? m[4] ?? 0);
|
|
13911
|
+
}
|
|
13912
|
+
}
|
|
13913
|
+
if (!sawToolResult) {
|
|
13914
|
+
const target = obj["isMeta"] === true ? s.estimated.harnessMeta : s.estimated.userTurns;
|
|
13915
|
+
let contentBytes = 0;
|
|
13916
|
+
if (typeof message.content === "string") {
|
|
13917
|
+
contentBytes = Buffer.byteLength(message.content, "utf8");
|
|
13918
|
+
} else {
|
|
13919
|
+
for (const block of blocks) {
|
|
13920
|
+
if (block !== null && typeof block === "object" && typeof block["text"] === "string") {
|
|
13921
|
+
contentBytes += Buffer.byteLength(block["text"], "utf8");
|
|
13922
|
+
}
|
|
13923
|
+
}
|
|
13924
|
+
}
|
|
13925
|
+
addCategory(target, contentBytes);
|
|
13926
|
+
if (isLane && laneBriefBytes < 0 && obj["isMeta"] !== true) laneBriefBytes = contentBytes;
|
|
13927
|
+
}
|
|
13928
|
+
continue;
|
|
13929
|
+
}
|
|
13930
|
+
if (type === "attachment") {
|
|
13931
|
+
addCategory(s.estimated.attachments, lineBytes);
|
|
13932
|
+
const att = obj["attachment"] !== null && typeof obj["attachment"] === "object" ? obj["attachment"] : {};
|
|
13933
|
+
const kind = typeof att["type"] === "string" ? att["type"] : "(untyped)";
|
|
13934
|
+
const lane = obj["isSidechain"] === true ? 1 : 0;
|
|
13935
|
+
const roll = attachmentRollup(attachmentMap, kind);
|
|
13936
|
+
roll.injections += 1;
|
|
13937
|
+
roll.lineBytes += lineBytes;
|
|
13938
|
+
const fields = ATTACHMENT_VISIBLE_FIELDS[kind];
|
|
13939
|
+
if (fields !== void 0) {
|
|
13940
|
+
const visibleBytes = fields.reduce((acc, f) => acc + deepStringBytes(att[f]), 0);
|
|
13941
|
+
roll.visibleBytes += visibleBytes;
|
|
13942
|
+
const tokens = visibleBytes > 0 ? estimateTokensFromLength(visibleBytes) : 0;
|
|
13943
|
+
roll.estTokens += tokens;
|
|
13944
|
+
if (tokens > 0) pendingReread.push({ kind, tokens, atCall: laneCalls[lane], lane });
|
|
13945
|
+
let serialized;
|
|
13946
|
+
try {
|
|
13947
|
+
serialized = JSON.stringify(fields.map((f) => att[f]));
|
|
13948
|
+
} catch {
|
|
13949
|
+
serialized = "";
|
|
13950
|
+
}
|
|
13951
|
+
const previous = lastVisibleByKind.get(kind);
|
|
13952
|
+
if (previous !== void 0) {
|
|
13953
|
+
if (previous === serialized) roll.repeatedIdentical += 1;
|
|
13954
|
+
else roll.repeatedChanged += 1;
|
|
13955
|
+
}
|
|
13956
|
+
lastVisibleByKind.set(kind, serialized);
|
|
13957
|
+
}
|
|
13958
|
+
if (kind === "hook_success") {
|
|
13959
|
+
const command = typeof att["command"] === "string" ? att["command"] : "";
|
|
13960
|
+
const origin = command.includes("token-goat") || command.includes("token_goat") ? "token-goat" : "other";
|
|
13961
|
+
if (origin === "token-goat") sawTokenGoatHook = true;
|
|
13962
|
+
const event = typeof att["hookEvent"] === "string" ? att["hookEvent"] : "(none)";
|
|
13963
|
+
const hook = hookRollup(hookMap, origin, event);
|
|
13964
|
+
hook.fires += 1;
|
|
13965
|
+
hook.stdoutBytes += typeof att["stdout"] === "string" ? Buffer.byteLength(att["stdout"], "utf8") : 0;
|
|
13966
|
+
hook.contextBytes += typeof att["content"] === "string" ? Buffer.byteLength(att["content"], "utf8") : 0;
|
|
13967
|
+
}
|
|
13968
|
+
continue;
|
|
13969
|
+
}
|
|
13970
|
+
if (type === "system" || type === "summary") {
|
|
13971
|
+
if (obj["subtype"] === "compact_boundary") {
|
|
13972
|
+
flushLane(obj["isSidechain"] === true ? 1 : 0);
|
|
13973
|
+
compactEpoch += 1;
|
|
13974
|
+
}
|
|
13975
|
+
addCategory(s.estimated.system, lineBytes);
|
|
13976
|
+
continue;
|
|
13977
|
+
}
|
|
13978
|
+
addCategory(s.estimated.otherLocal, lineBytes);
|
|
13979
|
+
}
|
|
13980
|
+
})();
|
|
13981
|
+
void consume.catch(() => {
|
|
13982
|
+
});
|
|
13983
|
+
void streamFailure.catch(() => {
|
|
13984
|
+
});
|
|
13985
|
+
try {
|
|
13986
|
+
await Promise.race([consume, streamFailure]);
|
|
13987
|
+
} finally {
|
|
13988
|
+
rl.close();
|
|
13989
|
+
stream.destroy();
|
|
13990
|
+
}
|
|
13991
|
+
flushLane(0);
|
|
13992
|
+
flushLane(1);
|
|
13993
|
+
if (sawTokenGoatHook) {
|
|
13994
|
+
s.readInterception.repeatInHookedSessions += fileRepeats;
|
|
13995
|
+
s.readInterception.repeatFullNoRangeInHookedSessions += fileRepeatsFullNoRange;
|
|
13996
|
+
s.readInterception.repeatFullNoRangeHookedAfterCompaction += fileRepeatsFullNoRangeAfterCompaction;
|
|
13997
|
+
}
|
|
13998
|
+
if (isLane) {
|
|
13999
|
+
let agentType = "(none)";
|
|
14000
|
+
try {
|
|
14001
|
+
const meta = JSON.parse(fs26.readFileSync(filePath.replace(/\.jsonl$/i, ".meta.json"), "utf8"));
|
|
14002
|
+
if (typeof meta["agentType"] === "string" && meta["agentType"] !== "") agentType = meta["agentType"];
|
|
14003
|
+
} catch {
|
|
14004
|
+
}
|
|
14005
|
+
laneObservations.push({ firstPrefixTokens: laneFirstPrefix, calls: usageSeenIds.size, briefBytes: Math.max(0, laneBriefBytes), agentType });
|
|
14006
|
+
}
|
|
14007
|
+
const callCount = perCall.length;
|
|
14008
|
+
for (let i = 0; i < callCount; i++) {
|
|
14009
|
+
const call = perCall[i];
|
|
14010
|
+
const d = s.positionDeciles[Math.min(9, Math.floor(i * 10 / callCount))];
|
|
14011
|
+
d.apiCalls += 1;
|
|
14012
|
+
d.inputTokens += call.inputTotal;
|
|
14013
|
+
d.cacheReadTokens += call.cacheRead;
|
|
14014
|
+
d.outputTokens += call.output;
|
|
14015
|
+
}
|
|
14016
|
+
}
|
|
14017
|
+
function toolRollup(toolMap, name) {
|
|
14018
|
+
let roll = toolMap.get(name);
|
|
14019
|
+
if (roll === void 0) {
|
|
14020
|
+
roll = { name, calls: 0, resultBytes: 0, resultEstTokens: 0 };
|
|
14021
|
+
toolMap.set(name, roll);
|
|
14022
|
+
}
|
|
14023
|
+
return roll;
|
|
14024
|
+
}
|
|
14025
|
+
function attachmentRollup(attachmentMap, kind) {
|
|
14026
|
+
let roll = attachmentMap.get(kind);
|
|
14027
|
+
if (roll === void 0) {
|
|
14028
|
+
roll = { kind, injections: 0, lineBytes: 0, visibleBytes: 0, estTokens: 0, rereadTokens: 0, billedEquivTokens: 0, repeatedIdentical: 0, repeatedChanged: 0 };
|
|
14029
|
+
attachmentMap.set(kind, roll);
|
|
14030
|
+
}
|
|
14031
|
+
return roll;
|
|
14032
|
+
}
|
|
14033
|
+
function hookRollup(hookMap, origin, event) {
|
|
14034
|
+
const key = `${origin}|${event}`;
|
|
14035
|
+
let roll = hookMap.get(key);
|
|
14036
|
+
if (roll === void 0) {
|
|
14037
|
+
roll = { origin, event, fires: 0, stdoutBytes: 0, contextBytes: 0 };
|
|
14038
|
+
hookMap.set(key, roll);
|
|
14039
|
+
}
|
|
14040
|
+
return roll;
|
|
14041
|
+
}
|
|
14042
|
+
async function auditSessionCorpus(opts = {}) {
|
|
14043
|
+
const corpusDir = path26.resolve(opts.dir ?? defaultCorpusDir());
|
|
14044
|
+
if (!fs26.existsSync(corpusDir)) {
|
|
14045
|
+
throw new Error(`session corpus directory not found: ${corpusDir}`);
|
|
14046
|
+
}
|
|
14047
|
+
const files = listCorpusTranscripts(corpusDir);
|
|
14048
|
+
if (files.length === 0) {
|
|
14049
|
+
throw new Error(`no .jsonl session transcripts found under ${corpusDir}`);
|
|
14050
|
+
}
|
|
14051
|
+
const started = Date.now();
|
|
14052
|
+
const summary = {
|
|
14053
|
+
corpusDir,
|
|
14054
|
+
filesScanned: 0,
|
|
14055
|
+
filesFailed: 0,
|
|
14056
|
+
lines: 0,
|
|
14057
|
+
parseFailedLines: 0,
|
|
14058
|
+
totalBytes: 0,
|
|
14059
|
+
runtimeMs: 0,
|
|
14060
|
+
measured: emptyMeasured(),
|
|
14061
|
+
measuredSidechain: emptyMeasured(),
|
|
14062
|
+
estimated: {
|
|
14063
|
+
userTurns: emptyCategory(),
|
|
14064
|
+
toolResults: emptyCategory(),
|
|
14065
|
+
assistantText: emptyCategory(),
|
|
14066
|
+
assistantThinking: emptyCategory(),
|
|
14067
|
+
toolUseInputs: emptyCategory(),
|
|
14068
|
+
attachments: emptyCategory(),
|
|
14069
|
+
harnessMeta: emptyCategory(),
|
|
14070
|
+
system: emptyCategory(),
|
|
14071
|
+
otherLocal: emptyCategory()
|
|
14072
|
+
},
|
|
14073
|
+
tools: [],
|
|
14074
|
+
attachmentKinds: [],
|
|
14075
|
+
hookOutputs: [],
|
|
14076
|
+
positionDeciles: Array.from({ length: 10 }, (_, i) => ({ decile: i + 1, apiCalls: 0, inputTokens: 0, cacheReadTokens: 0, outputTokens: 0 })),
|
|
14077
|
+
sidechainLanes: { laneFiles: 0, lanesWithUsage: 0, meanFirstCallPrefixTokens: 0, medianFirstCallPrefixTokens: 0, p90FirstCallPrefixTokens: 0, meanCallsPerLane: 0, meanBriefBytes: 0, prefixBilledEquivTokens: 0 },
|
|
14078
|
+
laneAgentTypes: [],
|
|
14079
|
+
readInterception: { readResults: 0, divertedByMarker: 0, divertedBytes: 0, fullServesOver10k: 0, fullServeBytesOver10k: 0, fullServesFirstRead: 0, fullServesRepeat: 0, repeatBytes: 0, repeatWithRange: 0, repeatFullNoRange: 0, repeatInHookedSessions: 0, repeatFullNoRangeInHookedSessions: 0, repeatFullNoRangeHookedAfterCompaction: 0, fullServesPathUnknown: 0 },
|
|
14080
|
+
bashInterception: { bashResults: 0, markedByFilter: 0, markedBytes: 0, smallUntouched: 0, smallUntouchedBytes: 0, untouched: 0, untouchedBytes: 0, untouchedEstTokens: 0, untouchedRereadTokens: 0, untouchedBilledEquivTokens: 0, untouchedHeads: [] },
|
|
14081
|
+
lineTypes: {},
|
|
14082
|
+
omissionMarkers: { fires: 0, linesOmitted: 0 }
|
|
14083
|
+
};
|
|
14084
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
14085
|
+
const attachmentMap = /* @__PURE__ */ new Map();
|
|
14086
|
+
const hookMap = /* @__PURE__ */ new Map();
|
|
14087
|
+
const laneObservations = [];
|
|
14088
|
+
const bashHeadMap = /* @__PURE__ */ new Map();
|
|
14089
|
+
for (const file of files) {
|
|
14090
|
+
try {
|
|
14091
|
+
await auditOneFile(file, summary, toolMap, attachmentMap, hookMap, laneObservations, bashHeadMap);
|
|
14092
|
+
summary.filesScanned += 1;
|
|
14093
|
+
} catch {
|
|
14094
|
+
summary.filesFailed += 1;
|
|
14095
|
+
}
|
|
14096
|
+
}
|
|
14097
|
+
const withUsage = laneObservations.filter((l) => l.firstPrefixTokens !== null);
|
|
14098
|
+
const prefixes = withUsage.map((l) => l.firstPrefixTokens).sort((a, b) => a - b);
|
|
14099
|
+
const mean = (arr) => arr.length === 0 ? 0 : Math.round(arr.reduce((a, b) => a + b, 0) / arr.length);
|
|
14100
|
+
summary.sidechainLanes = {
|
|
14101
|
+
laneFiles: laneObservations.length,
|
|
14102
|
+
lanesWithUsage: withUsage.length,
|
|
14103
|
+
meanFirstCallPrefixTokens: mean(prefixes),
|
|
14104
|
+
medianFirstCallPrefixTokens: prefixes.length === 0 ? 0 : prefixes[Math.floor(prefixes.length / 2)],
|
|
14105
|
+
p90FirstCallPrefixTokens: prefixes.length === 0 ? 0 : prefixes[Math.min(prefixes.length - 1, Math.floor(prefixes.length * 0.9))],
|
|
14106
|
+
meanCallsPerLane: mean(withUsage.map((l) => l.calls)),
|
|
14107
|
+
meanBriefBytes: mean(withUsage.map((l) => l.briefBytes)),
|
|
14108
|
+
prefixBilledEquivTokens: Math.round(withUsage.reduce((acc, l) => acc + l.firstPrefixTokens * (CACHE_WRITE_MULTIPLIER + CACHE_READ_MULTIPLIER * Math.max(0, l.calls - 1)), 0))
|
|
14109
|
+
};
|
|
14110
|
+
const byType = /* @__PURE__ */ new Map();
|
|
14111
|
+
for (const l of laneObservations) {
|
|
14112
|
+
const bucket = byType.get(l.agentType);
|
|
14113
|
+
if (bucket === void 0) byType.set(l.agentType, [l]);
|
|
14114
|
+
else bucket.push(l);
|
|
14115
|
+
}
|
|
14116
|
+
summary.laneAgentTypes = [...byType.entries()].map(([agentType, obs]) => {
|
|
14117
|
+
const typed = obs.filter((l) => l.firstPrefixTokens !== null).map((l) => l.firstPrefixTokens).sort((a, b) => a - b);
|
|
14118
|
+
return { agentType, lanes: obs.length, lanesWithUsage: typed.length, meanFirstCallPrefixTokens: mean(typed), medianFirstCallPrefixTokens: typed.length === 0 ? 0 : typed[Math.floor(typed.length / 2)] };
|
|
14119
|
+
}).sort((a, b) => b.lanes - a.lanes || a.agentType.localeCompare(b.agentType));
|
|
14120
|
+
summary.bashInterception.untouchedBilledEquivTokens = Math.round(CACHE_WRITE_MULTIPLIER * summary.bashInterception.untouchedEstTokens + CACHE_READ_MULTIPLIER * summary.bashInterception.untouchedRereadTokens);
|
|
14121
|
+
summary.bashInterception.untouchedHeads = [...bashHeadMap.values()].sort((a, b) => b.bytes - a.bytes || b.results - a.results || a.head.localeCompare(b.head));
|
|
14122
|
+
summary.tools = [...toolMap.values()].sort((a, b) => b.resultBytes - a.resultBytes || a.name.localeCompare(b.name));
|
|
14123
|
+
for (const roll of attachmentMap.values()) {
|
|
14124
|
+
roll.billedEquivTokens = Math.round(CACHE_WRITE_MULTIPLIER * roll.estTokens + CACHE_READ_MULTIPLIER * roll.rereadTokens);
|
|
14125
|
+
}
|
|
14126
|
+
summary.attachmentKinds = [...attachmentMap.values()].sort((a, b) => b.billedEquivTokens - a.billedEquivTokens || b.injections - a.injections || a.kind.localeCompare(b.kind));
|
|
14127
|
+
summary.hookOutputs = [...hookMap.values()].sort((a, b) => b.contextBytes - a.contextBytes || b.fires - a.fires || a.origin.localeCompare(b.origin) || a.event.localeCompare(b.event));
|
|
14128
|
+
summary.runtimeMs = Date.now() - started;
|
|
14129
|
+
return summary;
|
|
14130
|
+
}
|
|
14131
|
+
function fmt(n) {
|
|
14132
|
+
return n.toLocaleString("en-US");
|
|
14133
|
+
}
|
|
14134
|
+
function pct(part, whole) {
|
|
14135
|
+
return whole === 0 ? "0.0%" : `${(part / whole * 100).toFixed(1)}%`;
|
|
14136
|
+
}
|
|
14137
|
+
function formatSessionAudit(s) {
|
|
14138
|
+
const lines = [];
|
|
14139
|
+
lines.push("# Session corpus audit");
|
|
14140
|
+
lines.push(`Corpus: ${s.corpusDir}`);
|
|
14141
|
+
lines.push(`Files: ${fmt(s.filesScanned)} scanned, ${fmt(s.filesFailed)} unreadable`);
|
|
14142
|
+
lines.push(`Lines: ${fmt(s.lines)} (${fmt(s.parseFailedLines)} unparseable), bytes: ${fmt(s.totalBytes)}`);
|
|
14143
|
+
lines.push(`Runtime: ${(s.runtimeMs / 1e3).toFixed(1)}s`);
|
|
14144
|
+
lines.push("");
|
|
14145
|
+
lines.push("## Measured billed tokens (assistant message.usage, one count per API response)");
|
|
14146
|
+
const m = s.measured;
|
|
14147
|
+
lines.push(`API calls: ${fmt(m.apiCalls)} (sidechain: ${fmt(s.measuredSidechain.apiCalls)})`);
|
|
14148
|
+
lines.push(`Output tokens: ${fmt(m.outputTokens)}`);
|
|
14149
|
+
lines.push(`Input, uncached: ${fmt(m.inputTokens)}`);
|
|
14150
|
+
lines.push(`Input, cache-write: ${fmt(m.cacheCreationTokens)}`);
|
|
14151
|
+
lines.push(`Input, cache-read: ${fmt(m.cacheReadTokens)}`);
|
|
14152
|
+
const totalInput = m.inputTokens + m.cacheCreationTokens + m.cacheReadTokens;
|
|
14153
|
+
lines.push(`Cache-read share of input: ${pct(m.cacheReadTokens, totalInput)}`);
|
|
14154
|
+
lines.push("");
|
|
14155
|
+
lines.push("## Estimated content attribution (chars/3 heuristic; NOT billed units)");
|
|
14156
|
+
const e = s.estimated;
|
|
14157
|
+
const rows = [
|
|
14158
|
+
["tool results", e.toolResults],
|
|
14159
|
+
["assistant text", e.assistantText],
|
|
14160
|
+
["assistant thinking", e.assistantThinking],
|
|
14161
|
+
["tool call inputs", e.toolUseInputs],
|
|
14162
|
+
["attachments (harness)", e.attachments],
|
|
14163
|
+
["user turns", e.userTurns],
|
|
14164
|
+
["meta user lines", e.harnessMeta],
|
|
14165
|
+
["system lines", e.system],
|
|
14166
|
+
["local bookkeeping (never sent)", e.otherLocal]
|
|
14167
|
+
];
|
|
14168
|
+
const modelVisibleBytes = rows.slice(0, 8).reduce((acc, [, c]) => acc + c.bytes, 0);
|
|
14169
|
+
for (const [label, cat] of rows.sort((a, b) => b[1].bytes - a[1].bytes)) {
|
|
14170
|
+
lines.push(`${label.padEnd(31)} count ${fmt(cat.count).padStart(11)} bytes ${fmt(cat.bytes).padStart(15)} est-tokens ${fmt(cat.estTokens).padStart(13)} ${label === "local bookkeeping (never sent)" ? "(excluded from share)" : pct(cat.bytes, modelVisibleBytes)}`);
|
|
14171
|
+
}
|
|
14172
|
+
lines.push("");
|
|
14173
|
+
lines.push("## Tool results by tool (estimated content size; calls = tool_use invocations)");
|
|
14174
|
+
for (const t of s.tools.slice(0, 25)) {
|
|
14175
|
+
lines.push(`${t.name.padEnd(42)} calls ${fmt(t.calls).padStart(9)} bytes ${fmt(t.resultBytes).padStart(15)} est-tokens ${fmt(t.resultEstTokens).padStart(12)}`);
|
|
14176
|
+
}
|
|
14177
|
+
if (s.tools.length > 25) lines.push(`(${s.tools.length - 25} smaller tools omitted from this table; --json has all)`);
|
|
14178
|
+
lines.push("");
|
|
14179
|
+
lines.push("## Attachment kinds by modeled billed cost (model-visible fields only; NOT billed units)");
|
|
14180
|
+
lines.push("Model: est-tokens x 1.25 cache-write + reread-tokens x 0.1 cache-read; an injection stays in context until the next compact boundary on its lane, or end of transcript.");
|
|
14181
|
+
for (const a of s.attachmentKinds.slice(0, 15)) {
|
|
14182
|
+
lines.push(`${a.kind.padEnd(28)} inj ${fmt(a.injections).padStart(9)} visible-bytes ${fmt(a.visibleBytes).padStart(13)} est-tok ${fmt(a.estTokens).padStart(12)} reread-tok ${fmt(a.rereadTokens).padStart(14)} billed-equiv ${fmt(a.billedEquivTokens).padStart(12)} identical-reinject ${fmt(a.repeatedIdentical).padStart(8)}`);
|
|
14183
|
+
}
|
|
14184
|
+
if (s.attachmentKinds.length > 15) lines.push(`(${s.attachmentKinds.length - 15} smaller kinds omitted from this table; --json has all)`);
|
|
14185
|
+
lines.push("");
|
|
14186
|
+
lines.push("## Hook stdout channel (hook_success attachments; context-bytes is the model-visible share)");
|
|
14187
|
+
for (const h of s.hookOutputs) {
|
|
14188
|
+
lines.push(`${h.origin.padEnd(11)} ${h.event.padEnd(18)} fires ${fmt(h.fires).padStart(9)} stdout-bytes ${fmt(h.stdoutBytes).padStart(13)} context-bytes ${fmt(h.contextBytes).padStart(13)}`);
|
|
14189
|
+
}
|
|
14190
|
+
lines.push("");
|
|
14191
|
+
lines.push("## Measured billed tokens by session position (deciles of each session's API calls)");
|
|
14192
|
+
for (const d of s.positionDeciles) {
|
|
14193
|
+
lines.push(`decile ${d.decile.toString().padStart(2)} calls ${fmt(d.apiCalls).padStart(9)} input ${fmt(d.inputTokens).padStart(15)} cache-read ${fmt(d.cacheReadTokens).padStart(15)} output ${fmt(d.outputTokens).padStart(11)}`);
|
|
14194
|
+
}
|
|
14195
|
+
lines.push("");
|
|
14196
|
+
lines.push("## Subagent lanes (spawn-prefix carriage; same residency model as the attachment census; NOT billed units)");
|
|
14197
|
+
const sl = s.sidechainLanes;
|
|
14198
|
+
lines.push(`Lane files: ${fmt(sl.laneFiles)} (${fmt(sl.lanesWithUsage)} with usage)`);
|
|
14199
|
+
lines.push(`First-call prefix tokens: mean ${fmt(sl.meanFirstCallPrefixTokens)}, median ${fmt(sl.medianFirstCallPrefixTokens)}, p90 ${fmt(sl.p90FirstCallPrefixTokens)}`);
|
|
14200
|
+
lines.push(`Calls per lane: mean ${fmt(sl.meanCallsPerLane)}; task-brief bytes: mean ${fmt(sl.meanBriefBytes)}`);
|
|
14201
|
+
lines.push(`Prefix billed-equiv tokens: ${fmt(sl.prefixBilledEquivTokens)} (write x 1.25, then x 0.1 per later call in the lane)`);
|
|
14202
|
+
for (const t of s.laneAgentTypes.slice(0, 12)) {
|
|
14203
|
+
lines.push(` type ${t.agentType.padEnd(24)} lanes ${fmt(t.lanes).padStart(7)} (${fmt(t.lanesWithUsage)} with usage) prefix mean ${fmt(t.meanFirstCallPrefixTokens).padStart(9)}, median ${fmt(t.medianFirstCallPrefixTokens).padStart(9)}`);
|
|
14204
|
+
}
|
|
14205
|
+
if (s.laneAgentTypes.length > 12) lines.push(` (${s.laneAgentTypes.length - 12} smaller agent types omitted from this table; --json has all)`);
|
|
14206
|
+
lines.push("");
|
|
14207
|
+
lines.push("## Read interception (token-goat divert markers inside Read tool results)");
|
|
14208
|
+
const ri = s.readInterception;
|
|
14209
|
+
lines.push(`Read results: ${fmt(ri.readResults)}; diverted by marker: ${fmt(ri.divertedByMarker)} (${fmt(ri.divertedBytes)} bytes); full serves >=10 KiB: ${fmt(ri.fullServesOver10k)} (${fmt(ri.fullServeBytesOver10k)} bytes)`);
|
|
14210
|
+
lines.push(`Full-serve split (same transcript file; a session's lanes are separate files, so repeats UNDER-count): first read ${fmt(ri.fullServesFirstRead)}, repeat ${fmt(ri.fullServesRepeat)} (${fmt(ri.repeatBytes)} bytes), path unknown ${fmt(ri.fullServesPathUnknown)}`);
|
|
14211
|
+
lines.push(`Repeats: with offset/limit ${fmt(ri.repeatWithRange)} (deliberate paging), whole-file ${fmt(ri.repeatFullNoRange)} (divert-miss candidates), in sessions with a token-goat hook fire ${fmt(ri.repeatInHookedSessions)} (whole-file among them: ${fmt(ri.repeatFullNoRangeInHookedSessions)}, of which post-compaction and so correct by design: ${fmt(ri.repeatFullNoRangeHookedAfterCompaction)})`);
|
|
14212
|
+
lines.push("");
|
|
14213
|
+
lines.push("## Bash filter fire-rate (token-goat in-band markers inside Bash tool results)");
|
|
14214
|
+
const bi = s.bashInterception;
|
|
14215
|
+
lines.push(`Bash results: ${fmt(bi.bashResults)}; marked by a filter: ${fmt(bi.markedByFilter)} (${fmt(bi.markedBytes)} bytes); small unmarked <${fmt(BASH_SMALL_RESULT_MAX_BYTES)} B: ${fmt(bi.smallUntouched)} (${fmt(bi.smallUntouchedBytes)} bytes)`);
|
|
14216
|
+
lines.push(`Untouched >=${fmt(BASH_SMALL_RESULT_MAX_BYTES)} B: ${fmt(bi.untouched)} (${fmt(bi.untouchedBytes)} bytes, est-tokens ${fmt(bi.untouchedEstTokens)}, billed-equiv ${fmt(bi.untouchedBilledEquivTokens)})`);
|
|
14217
|
+
lines.push("A filter that matched but fell under the 100-byte net-savings floor leaves no transcript trace and counts as untouched here.");
|
|
14218
|
+
for (const h of bi.untouchedHeads.slice(0, 15)) {
|
|
14219
|
+
lines.push(` ${h.head.padEnd(28)} results ${fmt(h.results).padStart(9)} bytes ${fmt(h.bytes).padStart(15)}`);
|
|
14220
|
+
}
|
|
14221
|
+
if (bi.untouchedHeads.length > 15) lines.push(` (${bi.untouchedHeads.length - 15} smaller command heads omitted from this table; --json has all)`);
|
|
14222
|
+
lines.push("");
|
|
14223
|
+
lines.push("## Mid-trim omission markers inside tool results");
|
|
14224
|
+
lines.push(`fires: ${fmt(s.omissionMarkers.fires)}, lines discarded: ${fmt(s.omissionMarkers.linesOmitted)}`);
|
|
14225
|
+
return lines.join("\n");
|
|
14226
|
+
}
|
|
14227
|
+
|
|
14228
|
+
// src/cli_mcp_audit.ts
|
|
14229
|
+
import * as fs27 from "node:fs";
|
|
14230
|
+
import * as os13 from "node:os";
|
|
14231
|
+
import * as path27 from "node:path";
|
|
13510
14232
|
function readMcpJsonFile(configPath2) {
|
|
13511
14233
|
try {
|
|
13512
|
-
if (!
|
|
13513
|
-
const content =
|
|
14234
|
+
if (!fs27.existsSync(configPath2)) return null;
|
|
14235
|
+
const content = fs27.readFileSync(configPath2, "utf-8");
|
|
13514
14236
|
const parsed = JSON.parse(content);
|
|
13515
14237
|
const servers = parsed && typeof parsed === "object" ? parsed.mcpServers ?? parsed : null;
|
|
13516
14238
|
return servers && typeof servers === "object" ? servers : null;
|
|
@@ -13526,8 +14248,8 @@ function driveLetterCaseVariants(p) {
|
|
|
13526
14248
|
}
|
|
13527
14249
|
function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
|
|
13528
14250
|
try {
|
|
13529
|
-
if (!
|
|
13530
|
-
const parsed = JSON.parse(
|
|
14251
|
+
if (!fs27.existsSync(claudeJsonPath)) return null;
|
|
14252
|
+
const parsed = JSON.parse(fs27.readFileSync(claudeJsonPath, "utf-8"));
|
|
13531
14253
|
const projects = parsed && typeof parsed === "object" ? parsed.projects : null;
|
|
13532
14254
|
if (!projects || typeof projects !== "object") return null;
|
|
13533
14255
|
const slashForms = [projectRoot, projectRoot.replace(/\\/g, "/"), projectRoot.replace(/\//g, "\\")];
|
|
@@ -13545,8 +14267,8 @@ function readClaudeJsonConfig(claudeJsonPath, projectRoot) {
|
|
|
13545
14267
|
}
|
|
13546
14268
|
}
|
|
13547
14269
|
function discoverMcpConfig(projectRoot, home) {
|
|
13548
|
-
const mcpJsonPath =
|
|
13549
|
-
const claudeJsonPath =
|
|
14270
|
+
const mcpJsonPath = path27.join(projectRoot, ".mcp.json");
|
|
14271
|
+
const claudeJsonPath = path27.join(home, ".claude.json");
|
|
13550
14272
|
const sourcesChecked = [mcpJsonPath, claudeJsonPath];
|
|
13551
14273
|
const fromMcpJson = readMcpJsonFile(mcpJsonPath);
|
|
13552
14274
|
if (fromMcpJson !== null) return { servers: fromMcpJson, sourcePath: mcpJsonPath, sourcesChecked };
|
|
@@ -13575,7 +14297,7 @@ function analyzeMcpCache() {
|
|
|
13575
14297
|
}
|
|
13576
14298
|
return serverMetrics;
|
|
13577
14299
|
}
|
|
13578
|
-
function buildMcpAuditReport(projectRoot, home =
|
|
14300
|
+
function buildMcpAuditReport(projectRoot, home = os13.homedir()) {
|
|
13579
14301
|
const discovery = discoverMcpConfig(projectRoot, home);
|
|
13580
14302
|
const config = discovery.servers;
|
|
13581
14303
|
const cacheMetrics = analyzeMcpCache();
|
|
@@ -13728,9 +14450,9 @@ function printSummary(rows) {
|
|
|
13728
14450
|
};
|
|
13729
14451
|
w(pad("category", 22) + pad("emitted", 9) + pad("acted-on", 10) + pad("efficacy", 10) + pad("suppressed", 17) + pad("manual+", 9) + pad("manual-", 9) + "spent\n");
|
|
13730
14452
|
for (const row of rows) {
|
|
13731
|
-
const
|
|
14453
|
+
const pct2 = row.efficacyPct === null ? "n/a" : `${row.efficacyPct}%`;
|
|
13732
14454
|
w(
|
|
13733
|
-
pad(row.category, 22) + pad(String(row.emitted), 9) + pad(String(row.actedOn), 10) + pad(
|
|
14455
|
+
pad(row.category, 22) + pad(String(row.emitted), 9) + pad(String(row.actedOn), 10) + pad(pct2, 10) + pad(suppressedCell(row), 17) + pad(String(row.manualEffective), 9) + pad(String(row.manualIneffective), 9) + formatSpentCell(row) + "\n"
|
|
13734
14456
|
);
|
|
13735
14457
|
}
|
|
13736
14458
|
}
|
|
@@ -13875,10 +14597,10 @@ function readBoundedText(text, file) {
|
|
|
13875
14597
|
if (text === void 0 && file === void 0) throw new CliError("provide text or --file");
|
|
13876
14598
|
let value;
|
|
13877
14599
|
if (file !== void 0) {
|
|
13878
|
-
if (
|
|
14600
|
+
if (fs28.statSync(file).size > CONTENT_MAX_INPUT_CHARS) {
|
|
13879
14601
|
throw new CliError(`file exceeds the ${CONTENT_MAX_INPUT_CHARS}-byte safety limit`);
|
|
13880
14602
|
}
|
|
13881
|
-
value =
|
|
14603
|
+
value = fs28.readFileSync(file, "utf8");
|
|
13882
14604
|
} else {
|
|
13883
14605
|
if (text === void 0) throw new CliError("provide text or --file");
|
|
13884
14606
|
value = text;
|
|
@@ -14015,10 +14737,10 @@ async function cmdIndex(pathArg, opts = {}) {
|
|
|
14015
14737
|
continue;
|
|
14016
14738
|
}
|
|
14017
14739
|
const sha = fingerprintFile(key);
|
|
14018
|
-
if (sha === null && !
|
|
14740
|
+
if (sha === null && !fs28.existsSync(key)) continue;
|
|
14019
14741
|
const entry = sha !== null ? getFileEntry(key, dbPath) : null;
|
|
14020
14742
|
const spellingStale = entry !== null && indexedPathSpellingIsStale(entry.filePath, key);
|
|
14021
|
-
const parseUnchanged = !force && !spellingStale && sha !== null && entry?.sha === sha;
|
|
14743
|
+
const parseUnchanged = !force && !spellingStale && sha !== null && entry?.sha === sha && entry.parserSha === PARSER_FINGERPRINT;
|
|
14022
14744
|
const embeddingsEnabled = loadConfig().indexing?.embeddings_enabled ?? true;
|
|
14023
14745
|
const depsAvailable = embeddingsEnabled && embeddingsDepsAvailable(getDb(dbPath));
|
|
14024
14746
|
const embedUnchanged = !force && parseUnchanged && sha !== null && isEmbedFresh(entry?.embedSha, sha, embeddingsEnabled, depsAvailable);
|
|
@@ -14041,7 +14763,7 @@ async function cmdIndex(pathArg, opts = {}) {
|
|
|
14041
14763
|
}
|
|
14042
14764
|
continue;
|
|
14043
14765
|
}
|
|
14044
|
-
if (!
|
|
14766
|
+
if (!fs28.existsSync(key)) continue;
|
|
14045
14767
|
}
|
|
14046
14768
|
if (!embedUnchanged) {
|
|
14047
14769
|
paintProgress("embedding");
|
|
@@ -14074,7 +14796,7 @@ function cmdMap(opts) {
|
|
|
14074
14796
|
out(text);
|
|
14075
14797
|
}
|
|
14076
14798
|
const bytesSaved = mapLookupBytesSaved(map, text);
|
|
14077
|
-
recordStat("map_lookup", bytesSaved,
|
|
14799
|
+
recordStat("map_lookup", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14078
14800
|
}
|
|
14079
14801
|
function cmdBridgesStatus(opts) {
|
|
14080
14802
|
if (opts.json === true) {
|
|
@@ -14101,7 +14823,7 @@ async function cmdMcpServe() {
|
|
|
14101
14823
|
let StdioServerTransport;
|
|
14102
14824
|
try {
|
|
14103
14825
|
;
|
|
14104
|
-
({ createMcpServer } = await import("./token-goat-chunk-
|
|
14826
|
+
({ createMcpServer } = await import("./token-goat-chunk-LZOAPGWR.mjs"));
|
|
14105
14827
|
({ StdioServerTransport } = await import("./token-goat-chunk-324QOJYZ.mjs"));
|
|
14106
14828
|
} catch (err2) {
|
|
14107
14829
|
process.stderr.write(
|
|
@@ -14114,8 +14836,8 @@ async function cmdMcpServe() {
|
|
|
14114
14836
|
const server = await createMcpServer();
|
|
14115
14837
|
const transport = new StdioServerTransport();
|
|
14116
14838
|
await server.connect(transport);
|
|
14117
|
-
await new Promise((
|
|
14118
|
-
server.onclose =
|
|
14839
|
+
await new Promise((resolve13) => {
|
|
14840
|
+
server.onclose = resolve13;
|
|
14119
14841
|
});
|
|
14120
14842
|
}
|
|
14121
14843
|
function printBridgeVerificationNotice(harness) {
|
|
@@ -14126,7 +14848,7 @@ async function cmdHook(event, opts) {
|
|
|
14126
14848
|
if (typeof opts.harness === "string" && opts.harness.length > 0) {
|
|
14127
14849
|
process.env[ENV_KEYS.HARNESS_OVERRIDE] = opts.harness;
|
|
14128
14850
|
}
|
|
14129
|
-
const { relay } = await import("./token-goat-chunk-
|
|
14851
|
+
const { relay } = await import("./token-goat-chunk-L6YP6FKQ.mjs");
|
|
14130
14852
|
await relay(event);
|
|
14131
14853
|
}
|
|
14132
14854
|
async function cmdInstall(opts) {
|
|
@@ -14239,16 +14961,16 @@ async function cmdInstall(opts) {
|
|
|
14239
14961
|
);
|
|
14240
14962
|
}
|
|
14241
14963
|
try {
|
|
14242
|
-
const skillDir =
|
|
14243
|
-
if (
|
|
14244
|
-
const entries =
|
|
14964
|
+
const skillDir = path28.join(homedir13(), ".claude", "skills");
|
|
14965
|
+
if (fs28.existsSync(skillDir)) {
|
|
14966
|
+
const entries = fs28.readdirSync(skillDir, { withFileTypes: true });
|
|
14245
14967
|
const skillNames = [];
|
|
14246
14968
|
const sessionId = getSessionId();
|
|
14247
14969
|
for (const entry of entries) {
|
|
14248
14970
|
if (!entry.isDirectory()) continue;
|
|
14249
|
-
const skillFile =
|
|
14250
|
-
if (
|
|
14251
|
-
const body =
|
|
14971
|
+
const skillFile = path28.join(skillDir, entry.name, "SKILL.md");
|
|
14972
|
+
if (fs28.existsSync(skillFile)) {
|
|
14973
|
+
const body = fs28.readFileSync(skillFile, "utf-8");
|
|
14252
14974
|
const compact = extractCompactFromMarker(body);
|
|
14253
14975
|
if (compact === null) continue;
|
|
14254
14976
|
const sourceSha = contentHash(body);
|
|
@@ -14258,10 +14980,10 @@ async function cmdInstall(opts) {
|
|
|
14258
14980
|
}
|
|
14259
14981
|
if (skillNames.length > 0) {
|
|
14260
14982
|
const dir = skillOutputsDir();
|
|
14261
|
-
await
|
|
14262
|
-
const pregenPath =
|
|
14983
|
+
await fs28.promises.mkdir(dir, { recursive: true });
|
|
14984
|
+
const pregenPath = path28.join(dir, "pregen.json");
|
|
14263
14985
|
const pregenData = { ts: Date.now(), names: skillNames };
|
|
14264
|
-
await
|
|
14986
|
+
await fs28.promises.writeFile(pregenPath, JSON.stringify(pregenData, null, 2));
|
|
14265
14987
|
out(`Pre-generated ${skillNames.length} skill compacts.`);
|
|
14266
14988
|
}
|
|
14267
14989
|
}
|
|
@@ -14439,6 +15161,15 @@ function cmdWaste(opts = {}) {
|
|
|
14439
15161
|
...opts.copilot === true ? { copilot: true } : {}
|
|
14440
15162
|
});
|
|
14441
15163
|
}
|
|
15164
|
+
async function cmdSessionAudit(opts = {}) {
|
|
15165
|
+
let summary;
|
|
15166
|
+
try {
|
|
15167
|
+
summary = await auditSessionCorpus({ ...opts.dir !== void 0 ? { dir: opts.dir } : {} });
|
|
15168
|
+
} catch (err2) {
|
|
15169
|
+
throw new CliError(err2 instanceof Error ? err2.message : String(err2));
|
|
15170
|
+
}
|
|
15171
|
+
out(opts.json === true ? JSON.stringify(summary) : formatSessionAudit(summary));
|
|
15172
|
+
}
|
|
14442
15173
|
async function cmdSessionOutline(sessionIdOrPath, opts = {}) {
|
|
14443
15174
|
const transcriptPath = resolveSessionTranscript(sessionIdOrPath, opts.project !== void 0 ? { project: opts.project } : {});
|
|
14444
15175
|
if (transcriptPath === null) {
|
|
@@ -14452,11 +15183,11 @@ ${formatSessionOutline(turns)}`;
|
|
|
14452
15183
|
out(text);
|
|
14453
15184
|
const fullSourceBytes = sessionTranscriptSize(transcriptPath);
|
|
14454
15185
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
|
|
14455
|
-
recordStat("session_outline", bytesSaved,
|
|
15186
|
+
recordStat("session_outline", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14456
15187
|
}
|
|
14457
15188
|
function sessionTranscriptSize(transcriptPath) {
|
|
14458
15189
|
try {
|
|
14459
|
-
return
|
|
15190
|
+
return fs28.statSync(transcriptPath).size;
|
|
14460
15191
|
} catch {
|
|
14461
15192
|
return 0;
|
|
14462
15193
|
}
|
|
@@ -14474,7 +15205,7 @@ async function cmdSessionSlice(sessionIdOrPath, opts) {
|
|
|
14474
15205
|
out(text);
|
|
14475
15206
|
const fullSourceBytes = sessionTranscriptSize(transcriptPath);
|
|
14476
15207
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
|
|
14477
|
-
recordStat("session_slice", bytesSaved,
|
|
15208
|
+
recordStat("session_slice", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14478
15209
|
}
|
|
14479
15210
|
function cmdMcpAudit(opts = {}) {
|
|
14480
15211
|
return runMcpAuditCommand({
|
|
@@ -14593,7 +15324,7 @@ function fenceFileTextIfMatched(text) {
|
|
|
14593
15324
|
}
|
|
14594
15325
|
function fileSizeOrZero(filePath) {
|
|
14595
15326
|
try {
|
|
14596
|
-
return
|
|
15327
|
+
return fs28.statSync(filePath).size;
|
|
14597
15328
|
} catch {
|
|
14598
15329
|
return 0;
|
|
14599
15330
|
}
|
|
@@ -14608,11 +15339,11 @@ function cmdBashOutput(id, opts) {
|
|
|
14608
15339
|
}
|
|
14609
15340
|
let content;
|
|
14610
15341
|
try {
|
|
14611
|
-
const st =
|
|
15342
|
+
const st = fs28.statSync(opts.file);
|
|
14612
15343
|
if (st.isFIFO() || st.isSocket()) {
|
|
14613
15344
|
throw new CliError(`--file '${opts.file}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
|
|
14614
15345
|
}
|
|
14615
|
-
content = redactIfDotenv(opts.file, decodeSource(
|
|
15346
|
+
content = redactIfDotenv(opts.file, decodeSource(fs28.readFileSync(opts.file)));
|
|
14616
15347
|
} catch (e) {
|
|
14617
15348
|
if (e instanceof CliError) throw e;
|
|
14618
15349
|
throw new CliError(`cannot read file: ${opts.file}`);
|
|
@@ -14657,7 +15388,7 @@ async function cmdPdfExtract(file, opts) {
|
|
|
14657
15388
|
const printed = _applyFiltersAndPrint(text, opts, true, UNTRUSTED_FILE_TAG);
|
|
14658
15389
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14659
15390
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
|
|
14660
|
-
recordStat("pdf_extract", bytesSaved,
|
|
15391
|
+
recordStat("pdf_extract", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14661
15392
|
}
|
|
14662
15393
|
async function cmdPdfLocate(file, pattern, opts) {
|
|
14663
15394
|
const locateOpts = {
|
|
@@ -14685,7 +15416,7 @@ ${countNoun(fencedMatches.length, "match", "matches")} across ${countNoun(pages.
|
|
|
14685
15416
|
}
|
|
14686
15417
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14687
15418
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(printed, "utf8"));
|
|
14688
|
-
recordStat("pdf_locate", bytesSaved,
|
|
15419
|
+
recordStat("pdf_locate", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14689
15420
|
}
|
|
14690
15421
|
async function cmdPdfOutline(file, opts) {
|
|
14691
15422
|
const entries = await runPdfOutline(file);
|
|
@@ -14702,7 +15433,7 @@ async function cmdPdfOutline(file, opts) {
|
|
|
14702
15433
|
out(text);
|
|
14703
15434
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14704
15435
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
|
|
14705
|
-
recordStat("pdf_outline", bytesSaved,
|
|
15436
|
+
recordStat("pdf_outline", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14706
15437
|
}
|
|
14707
15438
|
async function cmdPdfMeta(file, opts = {}) {
|
|
14708
15439
|
const meta = await runPdfMeta(file);
|
|
@@ -14716,7 +15447,7 @@ async function cmdPdfMeta(file, opts = {}) {
|
|
|
14716
15447
|
out(text);
|
|
14717
15448
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14718
15449
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
|
|
14719
|
-
recordStat("pdf_meta", bytesSaved,
|
|
15450
|
+
recordStat("pdf_meta", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14720
15451
|
}
|
|
14721
15452
|
async function cmdImageMeta(file, opts = {}) {
|
|
14722
15453
|
const meta = await runImageMeta(file);
|
|
@@ -14738,7 +15469,17 @@ ${msg}`;
|
|
|
14738
15469
|
out(text);
|
|
14739
15470
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14740
15471
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
|
|
14741
|
-
recordStat("image_meta", bytesSaved,
|
|
15472
|
+
recordStat("image_meta", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15473
|
+
}
|
|
15474
|
+
function fenceOcrText(text) {
|
|
15475
|
+
try {
|
|
15476
|
+
if (loadConfig().injection.enabled) {
|
|
15477
|
+
const matches2 = scanForInjectionPatterns(text);
|
|
15478
|
+
if (matches2.length > 0) recordStat("injection_detected", 0, 0, void 0, matches2.join(","));
|
|
15479
|
+
}
|
|
15480
|
+
} catch {
|
|
15481
|
+
}
|
|
15482
|
+
return fenceUntrustedOcrText(text);
|
|
14742
15483
|
}
|
|
14743
15484
|
async function cmdImageText(file, opts = {}) {
|
|
14744
15485
|
const result = await runImageText(file);
|
|
@@ -14751,7 +15492,7 @@ async function cmdImageText(file, opts = {}) {
|
|
|
14751
15492
|
let text;
|
|
14752
15493
|
if (opts.json === true) {
|
|
14753
15494
|
text = JSON.stringify(
|
|
14754
|
-
result.text === null ? result : { ...result, text:
|
|
15495
|
+
result.text === null ? result : { ...result, text: fenceOcrText(result.text) },
|
|
14755
15496
|
null,
|
|
14756
15497
|
2
|
|
14757
15498
|
);
|
|
@@ -14759,13 +15500,13 @@ async function cmdImageText(file, opts = {}) {
|
|
|
14759
15500
|
const lines = [`Confidence: ${Math.round(result.confidence)}%`, `Characters: ${result.chars}`];
|
|
14760
15501
|
text = result.textHeavy && result.text !== null ? `${lines.join("\n")}
|
|
14761
15502
|
|
|
14762
|
-
${
|
|
15503
|
+
${fenceOcrText(result.text)}` : `${lines.join("\n")}
|
|
14763
15504
|
(below usefulness threshold; text likely noise, not shown)`;
|
|
14764
15505
|
}
|
|
14765
15506
|
out(text);
|
|
14766
15507
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14767
15508
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
|
|
14768
|
-
recordStat("image_text", bytesSaved,
|
|
15509
|
+
recordStat("image_text", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14769
15510
|
}
|
|
14770
15511
|
function cmdVideoChapters(file) {
|
|
14771
15512
|
const { chapters, subtitleStreams } = extractVideoChapters(file);
|
|
@@ -14791,7 +15532,7 @@ function cmdVideoChapters(file) {
|
|
|
14791
15532
|
out(text);
|
|
14792
15533
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14793
15534
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(text, "utf8"));
|
|
14794
|
-
recordStat("video_chapters", bytesSaved,
|
|
15535
|
+
recordStat("video_chapters", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14795
15536
|
}
|
|
14796
15537
|
function formatVideoTimestamp(totalSeconds) {
|
|
14797
15538
|
const hours = Math.floor(totalSeconds / 3600);
|
|
@@ -14818,7 +15559,7 @@ function cmdSharepointResolve(url) {
|
|
|
14818
15559
|
function recordXlsxStat(kind, file, emitted) {
|
|
14819
15560
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14820
15561
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emitted, "utf8"));
|
|
14821
|
-
recordStat(kind, bytesSaved,
|
|
15562
|
+
recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14822
15563
|
}
|
|
14823
15564
|
async function cmdXlsxSheets(file, opts = {}) {
|
|
14824
15565
|
const sheets = await listSheets(file);
|
|
@@ -14847,14 +15588,14 @@ async function cmdXlsxQuery(file, opts) {
|
|
|
14847
15588
|
...wheres !== void 0 ? { wheres } : {},
|
|
14848
15589
|
...opts.head !== void 0 ? { head: requireNonNegativeInt("--head", opts.head) } : {}
|
|
14849
15590
|
});
|
|
14850
|
-
const text = fenceFileTextIfMatched(formatCsvTable(result));
|
|
15591
|
+
const text = fenceFileTextIfMatched(formatCsvTable(result, (opts.where ?? []).map((w) => `--where ${w}`)));
|
|
14851
15592
|
out(text);
|
|
14852
15593
|
recordXlsxStat("xlsx_query", file, text);
|
|
14853
15594
|
}
|
|
14854
15595
|
function recordDocStat(kind, file, emitted) {
|
|
14855
15596
|
const fullSourceBytes = fileSizeOrZero(file);
|
|
14856
15597
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(emitted, "utf8"));
|
|
14857
|
-
recordStat(kind, bytesSaved,
|
|
15598
|
+
recordStat(kind, bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
14858
15599
|
}
|
|
14859
15600
|
async function cmdPptxOutline(file, opts) {
|
|
14860
15601
|
const slides = await pptxOutline(file);
|
|
@@ -15041,7 +15782,7 @@ function emitExtraFileArgsNote(command, first, extras, opts = {}) {
|
|
|
15041
15782
|
}
|
|
15042
15783
|
async function cmdCompress(opts) {
|
|
15043
15784
|
try {
|
|
15044
|
-
const bashRunner = await import("./token-goat-chunk-
|
|
15785
|
+
const bashRunner = await import("./token-goat-chunk-NZFTWBGV.mjs");
|
|
15045
15786
|
if (opts.compress === false) {
|
|
15046
15787
|
process.exitCode = bashRunner.runRaw(opts.cmd, parseTimeout(opts.timeout, bashRunner.DEFAULT_TIMEOUT_SECONDS));
|
|
15047
15788
|
return;
|
|
@@ -15067,9 +15808,12 @@ async function cmdSkillBody(name, opts) {
|
|
|
15067
15808
|
if (filePath === null) {
|
|
15068
15809
|
throw new CliError(`skill '${name}' not found`);
|
|
15069
15810
|
}
|
|
15070
|
-
const body = decodeSource(
|
|
15811
|
+
const body = decodeSource(fs28.readFileSync(filePath));
|
|
15071
15812
|
if (opts.compact === true) {
|
|
15072
|
-
|
|
15813
|
+
const emitted = extractCompactFromMarker(body) ?? body;
|
|
15814
|
+
out(emitted);
|
|
15815
|
+
const bytesSaved = Buffer.byteLength(body, "utf8") - Buffer.byteLength(emitted, "utf8");
|
|
15816
|
+
if (bytesSaved > 0) recordStat("skill_body:compact", bytesSaved, savedTokensFromBytes(bytesSaved), void 0, name);
|
|
15073
15817
|
} else {
|
|
15074
15818
|
out(body);
|
|
15075
15819
|
}
|
|
@@ -15081,12 +15825,26 @@ async function cmdSkillCompact(name, opts) {
|
|
|
15081
15825
|
const skills = await listSkills(sessionId);
|
|
15082
15826
|
let regenerated = 0;
|
|
15083
15827
|
let skipped = 0;
|
|
15828
|
+
let noMarker = 0;
|
|
15829
|
+
let unresolvable = 0;
|
|
15084
15830
|
for (const skill of skills) {
|
|
15085
15831
|
const filePath = await getSkillFilePath(skill.name);
|
|
15086
|
-
if (!filePath)
|
|
15087
|
-
|
|
15832
|
+
if (!filePath) {
|
|
15833
|
+
unresolvable++;
|
|
15834
|
+
continue;
|
|
15835
|
+
}
|
|
15836
|
+
let body2;
|
|
15837
|
+
try {
|
|
15838
|
+
body2 = decodeSource(fs28.readFileSync(filePath));
|
|
15839
|
+
} catch {
|
|
15840
|
+
unresolvable++;
|
|
15841
|
+
continue;
|
|
15842
|
+
}
|
|
15088
15843
|
const compact2 = extractCompactFromMarker(body2);
|
|
15089
|
-
if (compact2 === null)
|
|
15844
|
+
if (compact2 === null) {
|
|
15845
|
+
noMarker++;
|
|
15846
|
+
continue;
|
|
15847
|
+
}
|
|
15090
15848
|
const sourceSha2 = contentHash(body2);
|
|
15091
15849
|
if (skill.compactStale === false) {
|
|
15092
15850
|
skipped++;
|
|
@@ -15095,7 +15853,13 @@ async function cmdSkillCompact(name, opts) {
|
|
|
15095
15853
|
regenerated++;
|
|
15096
15854
|
}
|
|
15097
15855
|
}
|
|
15098
|
-
|
|
15856
|
+
const parts = [`Regenerated ${regenerated}, skipped ${skipped} (fresh)`];
|
|
15857
|
+
if (noMarker > 0) parts.push(`no marker ${noMarker}`);
|
|
15858
|
+
if (unresolvable > 0) parts.push(`unresolvable ${unresolvable}`);
|
|
15859
|
+
out(`${parts.join(", ")}, total ${skills.length}.`);
|
|
15860
|
+
if (noMarker > 0) {
|
|
15861
|
+
out(`${noMarker} cached skill${noMarker === 1 ? " has" : "s have"} no COMPACT_END marker and cannot be compacted; run \`token-goat skill-size\` for per-skill marker recommendations.`);
|
|
15862
|
+
}
|
|
15099
15863
|
return;
|
|
15100
15864
|
}
|
|
15101
15865
|
let body;
|
|
@@ -15105,19 +15869,19 @@ async function cmdSkillCompact(name, opts) {
|
|
|
15105
15869
|
if (!opts.path.trim()) {
|
|
15106
15870
|
throw new CliError("--path cannot be empty");
|
|
15107
15871
|
}
|
|
15108
|
-
if (!
|
|
15872
|
+
if (!fs28.existsSync(opts.path)) {
|
|
15109
15873
|
throw new CliError(`skill file not found: ${opts.path}`);
|
|
15110
15874
|
}
|
|
15111
15875
|
try {
|
|
15112
|
-
body =
|
|
15876
|
+
body = fs28.readFileSync(opts.path, "utf-8");
|
|
15113
15877
|
} catch (e) {
|
|
15114
15878
|
if (e.code === "ENOENT") {
|
|
15115
15879
|
throw new CliError(`skill file not found: ${opts.path}`);
|
|
15116
15880
|
}
|
|
15117
15881
|
throw new CliError(`failed to read skill file '${opts.path}': ${extractErrorMessage(e)}`);
|
|
15118
15882
|
}
|
|
15119
|
-
cacheName = name ??
|
|
15120
|
-
sourcePath =
|
|
15883
|
+
cacheName = name ?? path28.basename(path28.dirname(path28.resolve(opts.path)));
|
|
15884
|
+
sourcePath = path28.resolve(opts.path);
|
|
15121
15885
|
} else {
|
|
15122
15886
|
if (name === void 0 || !name.trim()) {
|
|
15123
15887
|
throw new CliError("skill-compact requires a <name> or --path <file>");
|
|
@@ -15126,7 +15890,7 @@ async function cmdSkillCompact(name, opts) {
|
|
|
15126
15890
|
if (filePath === null) {
|
|
15127
15891
|
throw new CliError(`skill '${name}' not found`);
|
|
15128
15892
|
}
|
|
15129
|
-
body =
|
|
15893
|
+
body = fs28.readFileSync(filePath, "utf-8");
|
|
15130
15894
|
cacheName = name;
|
|
15131
15895
|
sourcePath = filePath;
|
|
15132
15896
|
}
|
|
@@ -15258,8 +16022,8 @@ async function cmdSkillDiff(name) {
|
|
|
15258
16022
|
}
|
|
15259
16023
|
const newer = versions[0];
|
|
15260
16024
|
const older = versions[1];
|
|
15261
|
-
const newerBody = await
|
|
15262
|
-
const olderBody = await
|
|
16025
|
+
const newerBody = await fs28.promises.readFile(path28.resolve(dir, `${newer.outputId}.txt`), "utf-8").catch(() => null);
|
|
16026
|
+
const olderBody = await fs28.promises.readFile(path28.resolve(dir, `${older.outputId}.txt`), "utf-8").catch(() => null);
|
|
15263
16027
|
if (newerBody === null || olderBody === null) {
|
|
15264
16028
|
out(`a cached version of '${name}' was evicted while diffing -- try again`);
|
|
15265
16029
|
return;
|
|
@@ -15288,7 +16052,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
|
|
|
15288
16052
|
if (!filePath) {
|
|
15289
16053
|
throw new CliError(`skill '${skillName}' not found`);
|
|
15290
16054
|
}
|
|
15291
|
-
const body = decodeSource(
|
|
16055
|
+
const body = decodeSource(fs28.readFileSync(filePath));
|
|
15292
16056
|
const extracted = extractNamedSection(body, heading);
|
|
15293
16057
|
if (!extracted) {
|
|
15294
16058
|
const messages = [`Section '${heading}' not found in skill '${skillName}'`];
|
|
@@ -15303,7 +16067,7 @@ async function cmdSkillSection(nameHeading, headingArg) {
|
|
|
15303
16067
|
}
|
|
15304
16068
|
function atomicWriteBuffer(dest, data) {
|
|
15305
16069
|
try {
|
|
15306
|
-
if (
|
|
16070
|
+
if (fs28.statSync(dest).isDirectory()) {
|
|
15307
16071
|
const e = Object.assign(new Error(`EISDIR: illegal operation on a directory, open '${dest}'`), { code: "EISDIR", path: dest });
|
|
15308
16072
|
throw e;
|
|
15309
16073
|
}
|
|
@@ -15311,23 +16075,23 @@ function atomicWriteBuffer(dest, data) {
|
|
|
15311
16075
|
if (e.code !== "ENOENT") throw e;
|
|
15312
16076
|
}
|
|
15313
16077
|
const rnd = randomBytes(4).toString("hex");
|
|
15314
|
-
const tmp =
|
|
16078
|
+
const tmp = path28.join(path28.dirname(path28.resolve(dest)), `.tmp.${process.pid}.${rnd}`);
|
|
15315
16079
|
try {
|
|
15316
|
-
|
|
16080
|
+
fs28.writeFileSync(tmp, data, { mode: 384 });
|
|
15317
16081
|
try {
|
|
15318
|
-
const destMode =
|
|
15319
|
-
|
|
16082
|
+
const destMode = fs28.statSync(dest).mode;
|
|
16083
|
+
fs28.chmodSync(tmp, destMode);
|
|
15320
16084
|
} catch (e) {
|
|
15321
16085
|
if (e.code !== "ENOENT") throw e;
|
|
15322
16086
|
}
|
|
15323
16087
|
withRetryOnLock(() => {
|
|
15324
16088
|
try {
|
|
15325
|
-
|
|
16089
|
+
fs28.renameSync(tmp, dest);
|
|
15326
16090
|
} catch (e) {
|
|
15327
16091
|
if (e.code === "EXDEV") {
|
|
15328
|
-
|
|
16092
|
+
fs28.copyFileSync(tmp, dest);
|
|
15329
16093
|
try {
|
|
15330
|
-
|
|
16094
|
+
fs28.unlinkSync(tmp);
|
|
15331
16095
|
} catch (ue) {
|
|
15332
16096
|
process.stderr.write(`token-goat write-file: warning: could not remove temp file ${tmp}: ${ue.message}
|
|
15333
16097
|
`);
|
|
@@ -15339,7 +16103,7 @@ function atomicWriteBuffer(dest, data) {
|
|
|
15339
16103
|
});
|
|
15340
16104
|
} catch (e) {
|
|
15341
16105
|
try {
|
|
15342
|
-
|
|
16106
|
+
fs28.unlinkSync(tmp);
|
|
15343
16107
|
} catch {
|
|
15344
16108
|
}
|
|
15345
16109
|
throw e;
|
|
@@ -15349,9 +16113,9 @@ function mapFsError(e, src, dest, srcLabel = "source") {
|
|
|
15349
16113
|
const fe = e;
|
|
15350
16114
|
if (fe.code === "ENOENT") {
|
|
15351
16115
|
const errPath = fe.path ?? "";
|
|
15352
|
-
const isSource = src !== void 0 &&
|
|
16116
|
+
const isSource = src !== void 0 && path28.resolve(errPath) === path28.resolve(src);
|
|
15353
16117
|
if (isSource) throw new CliError(`${/\bfile$/i.test(srcLabel) ? srcLabel : `${srcLabel} file`} not found: ${src}`);
|
|
15354
|
-
const destDir = dest ?
|
|
16118
|
+
const destDir = dest ? path28.dirname(path28.resolve(dest)) : path28.dirname(path28.resolve(errPath || "."));
|
|
15355
16119
|
throw new CliError(`destination directory does not exist: ${destDir}`);
|
|
15356
16120
|
}
|
|
15357
16121
|
if (fe.code === "ENOTDIR") {
|
|
@@ -15362,7 +16126,7 @@ function mapFsError(e, src, dest, srcLabel = "source") {
|
|
|
15362
16126
|
}
|
|
15363
16127
|
if (fe.code === "EISDIR") {
|
|
15364
16128
|
const errPath = fe.path ?? "";
|
|
15365
|
-
const isSource = src !== void 0 && (errPath === "" ||
|
|
16129
|
+
const isSource = src !== void 0 && (errPath === "" || path28.resolve(errPath) === path28.resolve(src));
|
|
15366
16130
|
if (isSource) throw new CliError(`source is a directory, not a file: ${src}`);
|
|
15367
16131
|
throw new CliError(`destination is a directory, not a file: ${dest ?? (errPath || "(unknown)")}`);
|
|
15368
16132
|
}
|
|
@@ -15431,7 +16195,7 @@ function validateWritablePath(dest, label) {
|
|
|
15431
16195
|
throw new CliError(`${label} path contains a null byte`);
|
|
15432
16196
|
}
|
|
15433
16197
|
if (isWindows()) {
|
|
15434
|
-
const base =
|
|
16198
|
+
const base = path28.basename(dest);
|
|
15435
16199
|
const stem = base.replace(/\.[^.]*$/, "").toUpperCase();
|
|
15436
16200
|
if (WIN_RESERVED.has(stem)) {
|
|
15437
16201
|
throw new CliError(`${label} '${base}' is a reserved Windows device name`);
|
|
@@ -15461,7 +16225,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
|
|
|
15461
16225
|
throw new CliError(`${label} ${filePath} requires piped input; use ${altLabel} for interactive use`);
|
|
15462
16226
|
}
|
|
15463
16227
|
try {
|
|
15464
|
-
const st =
|
|
16228
|
+
const st = fs28.statSync(filePath);
|
|
15465
16229
|
if (st.isFIFO() || st.isSocket()) {
|
|
15466
16230
|
throw new CliError(`${label} '${filePath}' is a special file (FIFO or socket) \u2014 only regular files are supported`);
|
|
15467
16231
|
}
|
|
@@ -15469,7 +16233,7 @@ function readFileBoundedRaw(filePath, label, allowStdIn = false) {
|
|
|
15469
16233
|
if (st.size > maxBytes) {
|
|
15470
16234
|
throw new CliError(`${label} '${filePath}' exceeds size limit (${Math.round(st.size / 1024 / 1024)} MB); set TOKEN_GOAT_MAX_STDIN_MB to override`);
|
|
15471
16235
|
}
|
|
15472
|
-
return
|
|
16236
|
+
return fs28.readFileSync(filePath);
|
|
15473
16237
|
} catch (e) {
|
|
15474
16238
|
if (e instanceof CliError) throw e;
|
|
15475
16239
|
mapFsError(e, filePath, void 0, label);
|
|
@@ -15513,7 +16277,7 @@ function cmdNoteAdd(file, opts) {
|
|
|
15513
16277
|
throw new CliError("note content must be valid UTF-8 text");
|
|
15514
16278
|
}
|
|
15515
16279
|
const resolvedPath = resolveIndexPath(file);
|
|
15516
|
-
if (!
|
|
16280
|
+
if (!fs28.existsSync(resolvedPath)) {
|
|
15517
16281
|
throw new CliError(`File not found: '${resolvedPath}'`);
|
|
15518
16282
|
}
|
|
15519
16283
|
healStaleIndex(resolvedPath);
|
|
@@ -15572,7 +16336,7 @@ function cmdWriteFile(dest, opts) {
|
|
|
15572
16336
|
throw new CliError(`TOKEN_GOAT_MAX_STDIN_MB must be a positive integer; got '${process.env["TOKEN_GOAT_MAX_STDIN_MB"] ?? ""}'`);
|
|
15573
16337
|
}
|
|
15574
16338
|
const maxBytes = maxMB * 1024 * 1024;
|
|
15575
|
-
return new Promise((
|
|
16339
|
+
return new Promise((resolve13, reject) => {
|
|
15576
16340
|
const chunks = [];
|
|
15577
16341
|
let totalBytes = 0;
|
|
15578
16342
|
let settled = false;
|
|
@@ -15596,7 +16360,7 @@ function cmdWriteFile(dest, opts) {
|
|
|
15596
16360
|
try {
|
|
15597
16361
|
atomicWriteBuffer(dest, Buffer.concat(chunks));
|
|
15598
16362
|
enqueueDirtyPathSafe(dest);
|
|
15599
|
-
|
|
16363
|
+
resolve13();
|
|
15600
16364
|
} catch (e) {
|
|
15601
16365
|
try {
|
|
15602
16366
|
mapFsError(e, void 0, dest);
|
|
@@ -15727,14 +16491,14 @@ function writeReplacedBuffer(file, replacedBuf, preWriteStat) {
|
|
|
15727
16491
|
const until = process.env["TOKEN_GOAT_TEST_REPLACE_DELAY_UNTIL"];
|
|
15728
16492
|
const deadline = Date.now() + testDelayMs;
|
|
15729
16493
|
if (until !== void 0 && until !== "") {
|
|
15730
|
-
while (Date.now() < deadline && !
|
|
16494
|
+
while (Date.now() < deadline && !fs28.existsSync(until)) sleepSync(5);
|
|
15731
16495
|
} else {
|
|
15732
16496
|
sleepSync(testDelayMs);
|
|
15733
16497
|
}
|
|
15734
16498
|
}
|
|
15735
16499
|
let preRenameStat;
|
|
15736
16500
|
try {
|
|
15737
|
-
preRenameStat =
|
|
16501
|
+
preRenameStat = fs28.statSync(file);
|
|
15738
16502
|
} catch {
|
|
15739
16503
|
}
|
|
15740
16504
|
if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
|
|
@@ -15775,7 +16539,7 @@ function cmdReplace(file, opts) {
|
|
|
15775
16539
|
const targetBuf = readFileBoundedRaw(file, "target file", true);
|
|
15776
16540
|
let preWriteStat;
|
|
15777
16541
|
try {
|
|
15778
|
-
preWriteStat =
|
|
16542
|
+
preWriteStat = fs28.statSync(file);
|
|
15779
16543
|
} catch {
|
|
15780
16544
|
}
|
|
15781
16545
|
const usingFrom = opts.oldFrom !== void 0 || opts.newFrom !== void 0;
|
|
@@ -15869,7 +16633,7 @@ function cmdInsertSection(file, opts) {
|
|
|
15869
16633
|
}
|
|
15870
16634
|
let preWriteStat;
|
|
15871
16635
|
try {
|
|
15872
|
-
preWriteStat =
|
|
16636
|
+
preWriteStat = fs28.statSync(file);
|
|
15873
16637
|
} catch {
|
|
15874
16638
|
}
|
|
15875
16639
|
const result = readSection(file, opts.after);
|
|
@@ -15895,7 +16659,7 @@ function cmdInsertSection(file, opts) {
|
|
|
15895
16659
|
}
|
|
15896
16660
|
let rawBytes;
|
|
15897
16661
|
try {
|
|
15898
|
-
rawBytes =
|
|
16662
|
+
rawBytes = fs28.readFileSync(file);
|
|
15899
16663
|
} catch (e) {
|
|
15900
16664
|
mapFsError(e, void 0, file);
|
|
15901
16665
|
}
|
|
@@ -15911,7 +16675,7 @@ function cmdInsertSection(file, opts) {
|
|
|
15911
16675
|
if (preWriteStat !== void 0) {
|
|
15912
16676
|
let preRenameStat;
|
|
15913
16677
|
try {
|
|
15914
|
-
preRenameStat =
|
|
16678
|
+
preRenameStat = fs28.statSync(file);
|
|
15915
16679
|
} catch {
|
|
15916
16680
|
}
|
|
15917
16681
|
if (preRenameStat !== void 0 && (preRenameStat.mtimeMs !== preWriteStat.mtimeMs || preRenameStat.size !== preWriteStat.size)) {
|
|
@@ -15959,29 +16723,29 @@ ${content}`;
|
|
|
15959
16723
|
}
|
|
15960
16724
|
out(toEmit);
|
|
15961
16725
|
const bytesSaved = Math.max(1, fullSourceBytes - Buffer.byteLength(toEmit, "utf8"));
|
|
15962
|
-
recordStat("gdrive_sections", bytesSaved,
|
|
16726
|
+
recordStat("gdrive_sections", bytesSaved, savedTokensFromBytes(bytesSaved));
|
|
15963
16727
|
}
|
|
15964
16728
|
function expandGlobs(root, patterns, globFnOverride) {
|
|
15965
16729
|
const out2 = [];
|
|
15966
|
-
const globFn = globFnOverride ??
|
|
16730
|
+
const globFn = globFnOverride ?? fs28["globSync"];
|
|
15967
16731
|
for (const p of patterns) {
|
|
15968
16732
|
if (globFn !== void 0 && (p.includes("*") || p.includes("?") || p.includes("{"))) {
|
|
15969
16733
|
try {
|
|
15970
16734
|
const hits = globFn(p, { cwd: root });
|
|
15971
|
-
for (const h of hits) out2.push(
|
|
16735
|
+
for (const h of hits) out2.push(path28.isAbsolute(h) ? h : path28.join(root, h));
|
|
15972
16736
|
continue;
|
|
15973
16737
|
} catch {
|
|
15974
16738
|
}
|
|
15975
16739
|
}
|
|
15976
|
-
out2.push(
|
|
16740
|
+
out2.push(path28.isAbsolute(p) ? p : path28.join(root, p));
|
|
15977
16741
|
}
|
|
15978
16742
|
return out2;
|
|
15979
16743
|
}
|
|
15980
16744
|
function readIgnoreFile(root) {
|
|
15981
|
-
const ignorePath =
|
|
16745
|
+
const ignorePath = path28.join(root, ".tokengoatignore");
|
|
15982
16746
|
let raw;
|
|
15983
16747
|
try {
|
|
15984
|
-
raw =
|
|
16748
|
+
raw = fs28.readFileSync(ignorePath, "utf8");
|
|
15985
16749
|
} catch {
|
|
15986
16750
|
return void 0;
|
|
15987
16751
|
}
|
|
@@ -16022,14 +16786,14 @@ function cmdPack(patterns, opts) {
|
|
|
16022
16786
|
}
|
|
16023
16787
|
let instruction;
|
|
16024
16788
|
if (opts.instructionFile !== void 0) {
|
|
16025
|
-
instruction =
|
|
16789
|
+
instruction = fs28.readFileSync(opts.instructionFile, "utf8");
|
|
16026
16790
|
}
|
|
16027
16791
|
const formatted = formatPack(result, style, {
|
|
16028
16792
|
...opts.lineNumbers === true ? { line_numbers: true } : {},
|
|
16029
16793
|
...instruction !== void 0 ? { instruction } : {}
|
|
16030
16794
|
});
|
|
16031
16795
|
if (opts.output !== void 0) {
|
|
16032
|
-
|
|
16796
|
+
fs28.writeFileSync(opts.output, formatted, "utf8");
|
|
16033
16797
|
} else {
|
|
16034
16798
|
out(formatted);
|
|
16035
16799
|
}
|
|
@@ -16047,17 +16811,17 @@ function cmdTokens(patterns, opts) {
|
|
|
16047
16811
|
if (opts.tree === true) {
|
|
16048
16812
|
const dirs = /* @__PURE__ */ new Map();
|
|
16049
16813
|
for (const e of entries) {
|
|
16050
|
-
const dir =
|
|
16814
|
+
const dir = path28.dirname(e.rel_path);
|
|
16051
16815
|
if (!dirs.has(dir)) dirs.set(dir, []);
|
|
16052
16816
|
dirs.get(dir).push(e);
|
|
16053
16817
|
}
|
|
16054
16818
|
const lines2 = [];
|
|
16055
16819
|
for (const [dir, dirEntries] of dirs) {
|
|
16056
16820
|
const dirTokens = dirEntries.reduce((s, e) => s + e.tokens, 0);
|
|
16057
|
-
const
|
|
16058
|
-
lines2.push(`${dir}/ (${dirTokens} tokens, ${
|
|
16821
|
+
const pct2 = result.total_tokens > 0 ? Math.round(dirTokens / result.total_tokens * 100) : 0;
|
|
16822
|
+
lines2.push(`${dir}/ (${dirTokens} tokens, ${pct2}%)`);
|
|
16059
16823
|
for (const e of dirEntries) {
|
|
16060
|
-
lines2.push(` ${
|
|
16824
|
+
lines2.push(` ${path28.basename(e.rel_path).padEnd(30)} ${String(e.tokens).padStart(8)} tokens`);
|
|
16061
16825
|
}
|
|
16062
16826
|
}
|
|
16063
16827
|
out(lines2.join("\n"));
|
|
@@ -16088,7 +16852,7 @@ function cmdBudget(patterns, opts) {
|
|
|
16088
16852
|
}
|
|
16089
16853
|
}
|
|
16090
16854
|
function cmdFailures(src, opts) {
|
|
16091
|
-
const text = src !== void 0 ?
|
|
16855
|
+
const text = src !== void 0 ? fs28.readFileSync(src, "utf8") : fs28.readFileSync(0, "utf8");
|
|
16092
16856
|
const result = extractFailures(text, opts.runner !== void 0 ? { runner: opts.runner } : {});
|
|
16093
16857
|
if (opts.delta !== true) {
|
|
16094
16858
|
out(opts.json === true ? formatFailuresJson(result) : formatFailuresText(result));
|
|
@@ -16290,6 +17054,7 @@ function buildProgram() {
|
|
|
16290
17054
|
program2.command("waste").description("session spend-ledger: token cost per tool/file from the current Claude Code session transcript, plus waste signals").option("--project <path>", "project root to analyze").option("--transcript <path>", "explicit transcript JSONL path (default: most-recently-modified transcript for this project)").option("--top <n>", "number of top expensive tool calls to show (default: 10)").option("--copilot", "analyze a Copilot CLI session event log instead, reporting Copilot's own token split").option("--json", "output JSON").action(guard(cmdWaste));
|
|
16291
17055
|
program2.command("session-outline [session-id-or-path]").description("turn-by-turn structure (role, preview, tool calls, approx size) of a Claude Code session JSONL transcript, instead of a raw Read; defaults to the current project's most recent session").option("--project <path>", "project root to resolve the session transcript against").option("--json", "output JSON").action(guard(cmdSessionOutline));
|
|
16292
17056
|
program2.command("session-slice [session-id-or-path]").description("full content of one turn range from a Claude Code session JSONL transcript (see session-outline for turn numbers), instead of a raw Read").requiredOption("--range <spec>", "turn range, e.g. 5-9 or 12 (see session-outline for turn numbers)").option("--project <path>", "project root to resolve the session transcript against").option("--json", "output JSON").action(guard(cmdSessionSlice));
|
|
17057
|
+
program2.command("session-audit").description("corpus-wide token attribution across every local Claude Code session transcript: measured billed usage, estimated content size by source and by tool, and billed cost by session position (aggregate counts only, never transcript content)").option("--dir <path>", "transcript corpus root to scan (default: ~/.claude/projects)").option("--json", "output JSON").action(guard(cmdSessionAudit));
|
|
16293
17058
|
program2.command("mcp-audit").description("MCP server schema cost-vs-usage report: estimate per-server token cost from cached tool calls").option("--project <path>", "project root to analyze").option("--json", "output JSON").action(guard(cmdMcpAudit));
|
|
16294
17059
|
program2.command("recall [query]").description("search across every cached bash-output, web-output, and mcp-output entry (full-text); with no query, list them newest-first").option("--type <type>", "filter to one cache type: bash, web, or mcp").option("--limit <n>", "max results to return (default: 10)").option("--json", "output JSON").action(guard(cmdRecall));
|
|
16295
17060
|
program2.command("hint-stats").description("per-category efficacy report for token-goat's discretionary hint hooks (emitted/acted-on/suppression)").option("--json", "output JSON").option("--reset", "clear all tracked emissions and manual marks").option("--mark-effective <category>", 'record a manual "effective" vote for a hint category').option("--mark-ineffective <category>", 'record a manual "ineffective" vote for a hint category').action(guard(cmdHintStats));
|
|
@@ -16654,7 +17419,7 @@ function buildProgram() {
|
|
|
16654
17419
|
)
|
|
16655
17420
|
);
|
|
16656
17421
|
program2.command("gdrive-sections <file-id>").description("fetch and list sections from a public Google Doc").option("--heading <name>", "get content of one named section").option("--fresh", "skip the on-disk cache and force a live fetch").action(guard(cmdGdriveSections));
|
|
16657
|
-
program2.command("compress").alias("bash").alias("run").description("run a shell command and emit a compressed view of its output").requiredOption("-c, --cmd <command>", "the shell command to run, as one string").option("-f, --filter <name>", "filter name (auto-detected from the command when omitted)").option("--timeout <seconds>", "wall-clock timeout in seconds (0 = built-in default)").option("--no-compress", "stream output raw without compression (debug the wrapper)").option("--profile <name>", "compression profile: aggressive | balanced | minimal").option("--max-tokens <n>", "post-compress token cap (0 = no cap)").action(cmdCompress);
|
|
17422
|
+
program2.command("compress").alias("bash").alias("run").description("run a shell command (under a POSIX shell / bash) and emit a compressed view of its output").requiredOption("-c, --cmd <command>", "the shell command to run, as one string (use / for paths across platforms)").option("-f, --filter <name>", "filter name (auto-detected from the command when omitted)").option("--timeout <seconds>", "wall-clock timeout in seconds (0 = built-in default)").option("--no-compress", "stream output raw without compression (debug the wrapper)").option("--profile <name>", "compression profile: aggressive | balanced | minimal").option("--max-tokens <n>", "post-compress token cap (0 = no cap)").action(cmdCompress);
|
|
16658
17423
|
program2.command("version").description("print the token-goat version").action(
|
|
16659
17424
|
guard(() => {
|
|
16660
17425
|
out(VERSION);
|