token-goat 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +46 -124
  2. package/dist/token-goat.mjs +1481 -112
  3. package/package.json +1 -1
@@ -981,7 +981,7 @@ var require_command = __commonJS({
981
981
  init_define_import_meta_env();
982
982
  var EventEmitter = __require("node:events").EventEmitter;
983
983
  var childProcess = __require("node:child_process");
984
- var path12 = __require("node:path");
984
+ var path14 = __require("node:path");
985
985
  var fs14 = __require("node:fs");
986
986
  var process2 = __require("node:process");
987
987
  var { Argument: Argument2, humanReadableArgName } = require_argument();
@@ -1914,9 +1914,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
1914
1914
  let launchWithNode = false;
1915
1915
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1916
1916
  function findFile(baseDir, baseName) {
1917
- const localBin = path12.resolve(baseDir, baseName);
1917
+ const localBin = path14.resolve(baseDir, baseName);
1918
1918
  if (fs14.existsSync(localBin)) return localBin;
1919
- if (sourceExt.includes(path12.extname(baseName))) return void 0;
1919
+ if (sourceExt.includes(path14.extname(baseName))) return void 0;
1920
1920
  const foundExt = sourceExt.find(
1921
1921
  (ext) => fs14.existsSync(`${localBin}${ext}`)
1922
1922
  );
@@ -1934,17 +1934,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
1934
1934
  } catch (err2) {
1935
1935
  resolvedScriptPath = this._scriptPath;
1936
1936
  }
1937
- executableDir = path12.resolve(
1938
- path12.dirname(resolvedScriptPath),
1937
+ executableDir = path14.resolve(
1938
+ path14.dirname(resolvedScriptPath),
1939
1939
  executableDir
1940
1940
  );
1941
1941
  }
1942
1942
  if (executableDir) {
1943
1943
  let localFile = findFile(executableDir, executableFile);
1944
1944
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1945
- const legacyName = path12.basename(
1945
+ const legacyName = path14.basename(
1946
1946
  this._scriptPath,
1947
- path12.extname(this._scriptPath)
1947
+ path14.extname(this._scriptPath)
1948
1948
  );
1949
1949
  if (legacyName !== this._name) {
1950
1950
  localFile = findFile(
@@ -1955,7 +1955,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1955
1955
  }
1956
1956
  executableFile = localFile || executableFile;
1957
1957
  }
1958
- launchWithNode = sourceExt.includes(path12.extname(executableFile));
1958
+ launchWithNode = sourceExt.includes(path14.extname(executableFile));
1959
1959
  let proc;
1960
1960
  if (process2.platform !== "win32") {
1961
1961
  if (launchWithNode) {
@@ -2795,7 +2795,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2795
2795
  * @return {Command}
2796
2796
  */
2797
2797
  nameFromFilename(filename) {
2798
- this._name = path12.basename(filename, path12.extname(filename));
2798
+ this._name = path14.basename(filename, path14.extname(filename));
2799
2799
  return this;
2800
2800
  }
2801
2801
  /**
@@ -2809,9 +2809,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2809
2809
  * @param {string} [path]
2810
2810
  * @return {(string|null|Command)}
2811
2811
  */
2812
- executableDir(path13) {
2813
- if (path13 === void 0) return this._executableDir;
2814
- this._executableDir = path13;
2812
+ executableDir(path15) {
2813
+ if (path15 === void 0) return this._executableDir;
2814
+ this._executableDir = path15;
2815
2815
  return this;
2816
2816
  }
2817
2817
  /**
@@ -3162,16 +3162,16 @@ var require_filesystem = __commonJS({
3162
3162
  var LDD_PATH = "/usr/bin/ldd";
3163
3163
  var SELF_PATH = "/proc/self/exe";
3164
3164
  var MAX_LENGTH = 2048;
3165
- var readFileSync10 = (path12) => {
3166
- const fd = fs14.openSync(path12, "r");
3165
+ var readFileSync11 = (path14) => {
3166
+ const fd = fs14.openSync(path14, "r");
3167
3167
  const buffer = Buffer.alloc(MAX_LENGTH);
3168
3168
  const bytesRead = fs14.readSync(fd, buffer, 0, MAX_LENGTH, 0);
3169
3169
  fs14.close(fd, () => {
3170
3170
  });
3171
3171
  return buffer.subarray(0, bytesRead);
3172
3172
  };
3173
- var readFile2 = (path12) => new Promise((resolve4, reject) => {
3174
- fs14.open(path12, "r", (err2, fd) => {
3173
+ var readFile2 = (path14) => new Promise((resolve4, reject) => {
3174
+ fs14.open(path14, "r", (err2, fd) => {
3175
3175
  if (err2) {
3176
3176
  reject(err2);
3177
3177
  } else {
@@ -3187,7 +3187,7 @@ var require_filesystem = __commonJS({
3187
3187
  module.exports = {
3188
3188
  LDD_PATH,
3189
3189
  SELF_PATH,
3190
- readFileSync: readFileSync10,
3190
+ readFileSync: readFileSync11,
3191
3191
  readFile: readFile2
3192
3192
  };
3193
3193
  }
@@ -3238,7 +3238,7 @@ var require_detect_libc = __commonJS({
3238
3238
  init_define_import_meta_env();
3239
3239
  var childProcess = __require("child_process");
3240
3240
  var { isLinux, getReport } = require_process();
3241
- var { LDD_PATH, SELF_PATH, readFile: readFile2, readFileSync: readFileSync10 } = require_filesystem();
3241
+ var { LDD_PATH, SELF_PATH, readFile: readFile2, readFileSync: readFileSync11 } = require_filesystem();
3242
3242
  var { interpreterPath } = require_elf();
3243
3243
  var cachedFamilyInterpreter;
3244
3244
  var cachedFamilyFilesystem;
@@ -3292,11 +3292,11 @@ var require_detect_libc = __commonJS({
3292
3292
  }
3293
3293
  return null;
3294
3294
  };
3295
- var familyFromInterpreterPath = (path12) => {
3296
- if (path12) {
3297
- if (path12.includes("/ld-musl-")) {
3295
+ var familyFromInterpreterPath = (path14) => {
3296
+ if (path14) {
3297
+ if (path14.includes("/ld-musl-")) {
3298
3298
  return MUSL;
3299
- } else if (path12.includes("/ld-linux-")) {
3299
+ } else if (path14.includes("/ld-linux-")) {
3300
3300
  return GLIBC;
3301
3301
  }
3302
3302
  }
@@ -3330,7 +3330,7 @@ var require_detect_libc = __commonJS({
3330
3330
  }
3331
3331
  cachedFamilyFilesystem = null;
3332
3332
  try {
3333
- const lddContent = readFileSync10(LDD_PATH);
3333
+ const lddContent = readFileSync11(LDD_PATH);
3334
3334
  cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
3335
3335
  } catch (e) {
3336
3336
  }
@@ -3343,8 +3343,8 @@ var require_detect_libc = __commonJS({
3343
3343
  cachedFamilyInterpreter = null;
3344
3344
  try {
3345
3345
  const selfContent = await readFile2(SELF_PATH);
3346
- const path12 = interpreterPath(selfContent);
3347
- cachedFamilyInterpreter = familyFromInterpreterPath(path12);
3346
+ const path14 = interpreterPath(selfContent);
3347
+ cachedFamilyInterpreter = familyFromInterpreterPath(path14);
3348
3348
  } catch (e) {
3349
3349
  }
3350
3350
  return cachedFamilyInterpreter;
@@ -3355,9 +3355,9 @@ var require_detect_libc = __commonJS({
3355
3355
  }
3356
3356
  cachedFamilyInterpreter = null;
3357
3357
  try {
3358
- const selfContent = readFileSync10(SELF_PATH);
3359
- const path12 = interpreterPath(selfContent);
3360
- cachedFamilyInterpreter = familyFromInterpreterPath(path12);
3358
+ const selfContent = readFileSync11(SELF_PATH);
3359
+ const path14 = interpreterPath(selfContent);
3360
+ cachedFamilyInterpreter = familyFromInterpreterPath(path14);
3361
3361
  } catch (e) {
3362
3362
  }
3363
3363
  return cachedFamilyInterpreter;
@@ -3419,7 +3419,7 @@ var require_detect_libc = __commonJS({
3419
3419
  }
3420
3420
  cachedVersionFilesystem = null;
3421
3421
  try {
3422
- const lddContent = readFileSync10(LDD_PATH);
3422
+ const lddContent = readFileSync11(LDD_PATH);
3423
3423
  const versionMatch = lddContent.match(RE_GLIBC_VERSION);
3424
3424
  if (versionMatch) {
3425
3425
  cachedVersionFilesystem = versionMatch[1];
@@ -5138,9 +5138,9 @@ var require_sharp = __commonJS({
5138
5138
  ];
5139
5139
  var sharp;
5140
5140
  var errors = [];
5141
- for (const path12 of paths) {
5141
+ for (const path14 of paths) {
5142
5142
  try {
5143
- sharp = __require(path12);
5143
+ sharp = __require(path14);
5144
5144
  break;
5145
5145
  } catch (err2) {
5146
5146
  errors.push(err2);
@@ -6552,15 +6552,15 @@ var require_route = __commonJS({
6552
6552
  };
6553
6553
  }
6554
6554
  function wrapConversion(toModel, graph) {
6555
- const path12 = [graph[toModel].parent, toModel];
6555
+ const path14 = [graph[toModel].parent, toModel];
6556
6556
  let fn = conversions[graph[toModel].parent][toModel];
6557
6557
  let cur = graph[toModel].parent;
6558
6558
  while (graph[cur].parent) {
6559
- path12.unshift(graph[cur].parent);
6559
+ path14.unshift(graph[cur].parent);
6560
6560
  fn = link(conversions[graph[cur].parent][cur], fn);
6561
6561
  cur = graph[cur].parent;
6562
6562
  }
6563
- fn.conversion = path12;
6563
+ fn.conversion = path14;
6564
6564
  return fn;
6565
6565
  }
6566
6566
  module.exports = function(fromModel) {
@@ -8464,7 +8464,7 @@ var require_output = __commonJS({
8464
8464
  "node_modules/sharp/lib/output.js"(exports, module) {
8465
8465
  "use strict";
8466
8466
  init_define_import_meta_env();
8467
- var path12 = __require("node:path");
8467
+ var path14 = __require("node:path");
8468
8468
  var is = require_is();
8469
8469
  var sharp = require_sharp();
8470
8470
  var formats = /* @__PURE__ */ new Map([
@@ -8495,9 +8495,9 @@ var require_output = __commonJS({
8495
8495
  let err2;
8496
8496
  if (!is.string(fileOut)) {
8497
8497
  err2 = new Error("Missing output file path");
8498
- } else if (is.string(this.options.input.file) && path12.resolve(this.options.input.file) === path12.resolve(fileOut)) {
8498
+ } else if (is.string(this.options.input.file) && path14.resolve(this.options.input.file) === path14.resolve(fileOut)) {
8499
8499
  err2 = new Error("Cannot use same file for input and output");
8500
- } else if (jp2Regex.test(path12.extname(fileOut)) && !this.constructor.format.jp2k.output.file) {
8500
+ } else if (jp2Regex.test(path14.extname(fileOut)) && !this.constructor.format.jp2k.output.file) {
8501
8501
  err2 = errJp2Save();
8502
8502
  }
8503
8503
  if (err2) {
@@ -9492,7 +9492,7 @@ init_define_import_meta_env();
9492
9492
  import { createRequire } from "node:module";
9493
9493
  function resolveVersion() {
9494
9494
  if (true) {
9495
- return "2.0.0";
9495
+ return "2.0.1";
9496
9496
  }
9497
9497
  const require2 = createRequire(import.meta.url);
9498
9498
  const pkg = require2("../package.json");
@@ -10125,23 +10125,23 @@ function isNoisePath(inputPath) {
10125
10125
  }
10126
10126
  }
10127
10127
  const slashIdx = p.lastIndexOf("/");
10128
- const basename4 = slashIdx >= 0 ? p.slice(slashIdx + 1) : p;
10129
- if (NOISE_BASENAMES.has(basename4)) {
10128
+ const basename6 = slashIdx >= 0 ? p.slice(slashIdx + 1) : p;
10129
+ if (NOISE_BASENAMES.has(basename6)) {
10130
10130
  return true;
10131
10131
  }
10132
- if (basename4.startsWith(".improve-state-") || basename4.startsWith("improve_commit_msg_")) {
10132
+ if (basename6.startsWith(".improve-state-") || basename6.startsWith("improve_commit_msg_")) {
10133
10133
  return true;
10134
10134
  }
10135
- const dotIdx = basename4.lastIndexOf(".");
10135
+ const dotIdx = basename6.lastIndexOf(".");
10136
10136
  if (dotIdx >= 0) {
10137
- const ext = basename4.slice(dotIdx);
10137
+ const ext = basename6.slice(dotIdx);
10138
10138
  if (NOISE_EXTS.has(ext)) {
10139
10139
  return true;
10140
10140
  }
10141
10141
  }
10142
10142
  for (const ext of NOISE_EXTS) {
10143
10143
  if (ext.includes(".") && ext.split(".").length > 2) {
10144
- if (basename4.endsWith(ext)) {
10144
+ if (basename6.endsWith(ext)) {
10145
10145
  return true;
10146
10146
  }
10147
10147
  }
@@ -10297,6 +10297,9 @@ function wasFileReadThisSession(filePath) {
10297
10297
  function getSessionWebFetches() {
10298
10298
  return _webFetches;
10299
10299
  }
10300
+ function getBashOutputId(commandHash) {
10301
+ return _bashOutputs.get(commandHash) ?? null;
10302
+ }
10300
10303
  registerReset(() => {
10301
10304
  _files = /* @__PURE__ */ new Map();
10302
10305
  _hintsShown = /* @__PURE__ */ new Set();
@@ -10362,6 +10365,7 @@ var HOOK_EVENTS = [
10362
10365
  // src/hooks_read.ts
10363
10366
  init_define_import_meta_env();
10364
10367
  import * as fs4 from "node:fs";
10368
+ import * as path6 from "node:path";
10365
10369
 
10366
10370
  // src/hooks_common.ts
10367
10371
  init_define_import_meta_env();
@@ -10378,12 +10382,483 @@ function getFilePath(event) {
10378
10382
  function passOutput() {
10379
10383
  return { hookType: "pass" };
10380
10384
  }
10385
+ function denyOutput(message) {
10386
+ return { hookType: "deny", message };
10387
+ }
10381
10388
  function contextOutput(context) {
10382
10389
  return { hookType: "context", context };
10383
10390
  }
10384
10391
 
10392
+ // src/hints.ts
10393
+ init_define_import_meta_env();
10394
+ var HINT_PRIORITY_MEDIUM = 3;
10395
+ var STALE_READ_AGE_SECONDS = 30 * 60;
10396
+ function buildPackageManifestHint(options) {
10397
+ try {
10398
+ const hasOffset = options.offset !== null && options.offset !== void 0 && options.offset >= 0;
10399
+ const hasLimit = options.limit !== null && options.limit !== void 0 && options.limit > 0;
10400
+ if (hasOffset || hasLimit) {
10401
+ return null;
10402
+ }
10403
+ const fname = _sanitizeHintPath(options.file_path.split(/[/\\]/).pop() ?? "");
10404
+ const basenameLower = fname.toLowerCase();
10405
+ if (basenameLower === "package.json") {
10406
+ const text = `\`${fname}\` is a package manifest. Consider \`token-goat section package.json::dependencies\` or \`token-goat section package.json::devDependencies\` for focused reads.`;
10407
+ return {
10408
+ text,
10409
+ hint_priority: HINT_PRIORITY_MEDIUM
10410
+ };
10411
+ }
10412
+ if (basenameLower === "package-lock.json") {
10413
+ const text = `\`${fname}\` is a large lockfile. Consider \`npm ls\`, \`npm outdated\`, or \`npm audit\` instead for targeted info.`;
10414
+ return {
10415
+ text,
10416
+ hint_priority: HINT_PRIORITY_MEDIUM
10417
+ };
10418
+ }
10419
+ return null;
10420
+ } catch {
10421
+ return null;
10422
+ }
10423
+ }
10424
+ function _sanitizeHintPath(path14) {
10425
+ if (typeof path14 !== "string") {
10426
+ return "???";
10427
+ }
10428
+ return path14.replace(/[\x00]/g, "").slice(0, 200);
10429
+ }
10430
+
10431
+ // src/hints/lang_patterns.ts
10432
+ init_define_import_meta_env();
10433
+ var LOCK_FILE_NAMES = /* @__PURE__ */ new Set([
10434
+ "package-lock.json",
10435
+ "yarn.lock",
10436
+ "pnpm-lock.yaml",
10437
+ "cargo.lock",
10438
+ "poetry.lock",
10439
+ "pipfile.lock",
10440
+ "uv.lock",
10441
+ "gemfile.lock",
10442
+ "go.sum",
10443
+ "composer.lock",
10444
+ "mix.lock",
10445
+ "pubspec.lock",
10446
+ "package.resolved"
10447
+ ]);
10448
+ var MANIFEST_FILE_NAMES = /* @__PURE__ */ new Set([
10449
+ "package.json",
10450
+ "pyproject.toml",
10451
+ "cargo.toml",
10452
+ "go.mod",
10453
+ "go.work",
10454
+ "pom.xml",
10455
+ "build.gradle",
10456
+ "build.gradle.kts",
10457
+ "settings.gradle",
10458
+ "composer.json",
10459
+ "gemfile",
10460
+ "mix.exs",
10461
+ "pubspec.yaml",
10462
+ "cmakelists.txt",
10463
+ "makefile",
10464
+ "project.clj",
10465
+ // TypeScript / JavaScript project configs
10466
+ "tsconfig.json",
10467
+ "jsconfig.json",
10468
+ // Bundler / framework configs
10469
+ "vite.config.ts",
10470
+ "vite.config.js",
10471
+ "webpack.config.js",
10472
+ "webpack.config.ts",
10473
+ "rollup.config.js",
10474
+ "rollup.config.ts",
10475
+ "esbuild.config.js",
10476
+ "next.config.js",
10477
+ "next.config.ts",
10478
+ "nuxt.config.ts"
10479
+ ]);
10480
+ var MANIFEST_EXTENSIONS = /* @__PURE__ */ new Set([
10481
+ ".cabal"
10482
+ ]);
10483
+ var MANIFEST_BASENAME_PATTERNS = [
10484
+ /^tsconfig(\..+)?\.json$/i
10485
+ ];
10486
+ var BUILD_DIR_NAMES = /* @__PURE__ */ new Set([
10487
+ "dist",
10488
+ "target",
10489
+ "build",
10490
+ "out",
10491
+ "__pycache__",
10492
+ ".next",
10493
+ ".nuxt",
10494
+ ".output",
10495
+ ".gradle",
10496
+ "_build",
10497
+ ".build",
10498
+ "pkg",
10499
+ "obj"
10500
+ ]);
10501
+ var ALWAYS_GENERATED_EXTS = /* @__PURE__ */ new Set([
10502
+ ".pyc",
10503
+ ".pyo",
10504
+ ".pyd",
10505
+ ".class",
10506
+ ".o",
10507
+ ".a",
10508
+ ".so",
10509
+ ".dylib",
10510
+ ".dll",
10511
+ ".tsbuildinfo"
10512
+ ]);
10513
+ var CONDITIONALLY_GENERATED_EXTS = /* @__PURE__ */ new Set([
10514
+ ".map",
10515
+ ".d.ts"
10516
+ ]);
10517
+ var BUILD_COMMAND_PATTERNS = [
10518
+ // Rust / Cargo
10519
+ /^\s*cargo\s+(build|test|run|check|clippy)\b/i,
10520
+ // Go
10521
+ /^\s*go\s+(build|test|run|vet)\b/i,
10522
+ // Maven
10523
+ /^\s*mvn\b/i,
10524
+ // Gradle (direct or wrapper)
10525
+ /^\s*(?:gradle|\.\/gradlew|gradlew)\b/i,
10526
+ // Python / pip
10527
+ /^\s*pip\s+(install|freeze)\b/i,
10528
+ // Poetry
10529
+ /^\s*poetry\s+(install|update)\b/i,
10530
+ // uv
10531
+ /^\s*uv\s+(sync|pip\s+install)\b/i,
10532
+ // Bundler (Ruby)
10533
+ /^\s*bundle\s+(install|update)\b/i,
10534
+ // Mix (Elixir)
10535
+ /^\s*mix\s+(deps\.get|compile|test)\b/i,
10536
+ // dotnet
10537
+ /^\s*dotnet\s+(build|test|restore)\b/i,
10538
+ // Make
10539
+ /^\s*make\b/i,
10540
+ // CMake build
10541
+ /^\s*cmake\s+--build\b/i,
10542
+ // Rake (Ruby)
10543
+ /^\s*rake\b/i,
10544
+ // TypeScript compiler
10545
+ /^\s*tsc\b/i,
10546
+ // Vite
10547
+ /^\s*vite\s+(build|dev|preview)\b/i,
10548
+ // Next.js
10549
+ /^\s*next\s+(build|dev|start)\b/i,
10550
+ // Nuxt
10551
+ /^\s*nuxt\s+(build|dev)\b/i,
10552
+ // Webpack
10553
+ /^\s*webpack\b/i,
10554
+ // esbuild
10555
+ /^\s*esbuild\b/i,
10556
+ // Rollup
10557
+ /^\s*rollup\b/i,
10558
+ // Turbo
10559
+ /^\s*turbo\s+(build|dev)\b/i
10560
+ ];
10561
+ function isLockFile(basename6) {
10562
+ return LOCK_FILE_NAMES.has(basename6.toLowerCase());
10563
+ }
10564
+ function isManifestFile(basename6) {
10565
+ const lower = basename6.toLowerCase();
10566
+ if (MANIFEST_FILE_NAMES.has(lower)) return true;
10567
+ const dot = lower.lastIndexOf(".");
10568
+ if (dot !== -1 && MANIFEST_EXTENSIONS.has(lower.slice(dot))) return true;
10569
+ if (MANIFEST_BASENAME_PATTERNS.some((re) => re.test(basename6))) return true;
10570
+ return false;
10571
+ }
10572
+ function pathSegments(filePath) {
10573
+ return filePath.split(/[/\\]/).filter((s) => s.length > 0);
10574
+ }
10575
+ function isInBuildDir(filePath) {
10576
+ const segments = pathSegments(filePath);
10577
+ for (let i = 0; i < segments.length - 1; i++) {
10578
+ const seg = segments[i];
10579
+ if (seg !== void 0 && BUILD_DIR_NAMES.has(seg.toLowerCase())) return true;
10580
+ }
10581
+ return false;
10582
+ }
10583
+ function isGeneratedFile(filePath) {
10584
+ const lower = filePath.toLowerCase();
10585
+ for (const ext of ALWAYS_GENERATED_EXTS) {
10586
+ if (lower.endsWith(ext)) return true;
10587
+ }
10588
+ for (const ext of CONDITIONALLY_GENERATED_EXTS) {
10589
+ if (lower.endsWith(ext) && isInBuildDir(filePath)) return true;
10590
+ }
10591
+ return false;
10592
+ }
10593
+ function isBuildCommand(cmd) {
10594
+ return BUILD_COMMAND_PATTERNS.some((re) => re.test(cmd));
10595
+ }
10596
+ var MONITORING_COMMAND_PATTERNS = [
10597
+ // GitHub CI
10598
+ { pattern: /^gh run (?:watch|view|list)/, recallHint: '--grep "fail|error|pass|\u2713|\u2717|conclusion"' },
10599
+ { pattern: /^gh run view.*--log/, recallHint: '--tail 100 --grep "Error|FAIL|error"' },
10600
+ { pattern: /^gh pr checks/, recallHint: '--grep "fail|error|pass|pending"' },
10601
+ { pattern: /^gh workflow (?:run|list|view)/, recallHint: '--grep "completed|failed|in_progress"' },
10602
+ // Dev servers (Next, Vite, Nuxt, Remix, Astro)
10603
+ { pattern: /^(?:npx\s+)?next dev/, recallHint: '--tail 30 --grep "error|warn|ready|compiled"' },
10604
+ { pattern: /^(?:npx\s+)?next build/, recallHint: '--grep "error|warn|Failed|\u2713"' },
10605
+ { pattern: /^(?:npx\s+)?vite(?:\s+dev|\s+build|\s+preview)?$/, recallHint: '--tail 20 --grep "error|warn|ready"' },
10606
+ { pattern: /^(?:npx\s+)?nuxt dev/, recallHint: '--tail 30 --grep "error|warn|ready"' },
10607
+ { pattern: /^(?:npx\s+)?remix dev/, recallHint: '--tail 20 --grep "error|warn|ready"' },
10608
+ { pattern: /^(?:npx\s+)?astro dev/, recallHint: '--tail 20 --grep "error|warn|ready"' },
10609
+ // Test watchers
10610
+ { pattern: /^(?:npx\s+)?vitest(?:\s+run|\s+watch)?/, recallHint: '--grep "FAIL|PASS|Error|\u2713|\u2717"' },
10611
+ { pattern: /^(?:npx\s+)?jest(?:\s+--watch)?/, recallHint: '--grep "FAIL|PASS|Error|Tests:"' },
10612
+ { pattern: /^pytest(?:\s|$)/, recallHint: '--grep "FAILED|PASSED|ERROR|passed|failed"' },
10613
+ { pattern: /^(?:cargo\s+test|cargo\s+watch)/, recallHint: '--grep "FAILED|ok|error\\["' },
10614
+ { pattern: /^go test/, recallHint: '--grep "FAIL|ok|---"' },
10615
+ // Docker / compose
10616
+ { pattern: /^docker(?:\s+compose)?\s+logs/, recallHint: '--tail 50 --grep "error|warn|Error|WARN"' },
10617
+ { pattern: /^docker-compose\s+logs/, recallHint: '--tail 50 --grep "error|warn|Error|WARN"' },
10618
+ // File watchers / hot-reload
10619
+ { pattern: /^nodemon/, recallHint: '--tail 20 --grep "error|crash|restart"' },
10620
+ { pattern: /^air(?:\s|$)/, recallHint: '--tail 20 --grep "error|build failed"' },
10621
+ { pattern: /^cargo watch/, recallHint: '--tail 20 --grep "error\\[|warning\\["' },
10622
+ { pattern: /^watchexec/, recallHint: '--tail 20 --grep "error|warn"' },
10623
+ // Linters / formatters run repeatedly
10624
+ { pattern: /^(?:npx\s+)?eslint(?:\s|$)/, recallHint: '--grep "error|warning|\u2716|problems"' },
10625
+ { pattern: /^(?:npx\s+)?prettier(?:\s|$)/, recallHint: '--grep "unchanged|reformatted|error"' },
10626
+ { pattern: /^ruff(?:\s|$)/, recallHint: '--grep "error|warning|Found"' },
10627
+ { pattern: /^(?:cargo\s+)?clippy/, recallHint: '--grep "error\\[|warning\\["' }
10628
+ ];
10629
+ function getMonitoringRecallHint(cmd) {
10630
+ for (const { pattern, recallHint } of MONITORING_COMMAND_PATTERNS) {
10631
+ if (pattern.test(cmd.trim())) return recallHint;
10632
+ }
10633
+ return null;
10634
+ }
10635
+
10636
+ // src/hints/markdown_hints.ts
10637
+ init_define_import_meta_env();
10638
+ var MARKDOWN_SIZE_THRESHOLD = 8e3;
10639
+ var MAX_HEADINGS = 40;
10640
+ var MAX_OUTPUT_LINES = 60;
10641
+ function extractMarkdownHeadings(content) {
10642
+ const headings = [];
10643
+ const lines = content.split("\n");
10644
+ for (let i = 0; i < lines.length; i++) {
10645
+ const line = lines[i];
10646
+ if (!line) continue;
10647
+ const match = /^(#+)\s+(.+?)\s*$/.exec(line);
10648
+ if (!match || match.length < 3) continue;
10649
+ const hashes = match[1];
10650
+ const headingText = match[2];
10651
+ const level = hashes.length;
10652
+ if (level > 3) continue;
10653
+ const text = headingText.trim();
10654
+ if (!text) continue;
10655
+ headings.push({
10656
+ level,
10657
+ text,
10658
+ lineNumber: i + 1
10659
+ });
10660
+ if (headings.length >= MAX_HEADINGS) break;
10661
+ }
10662
+ return headings;
10663
+ }
10664
+ function formatHeadingTree(headings, filePath) {
10665
+ if (headings.length === 0) return "";
10666
+ const seenTexts = /* @__PURE__ */ new Map();
10667
+ const dedupedHeadings = [];
10668
+ for (const h of headings) {
10669
+ const count = (seenTexts.get(h.text) ?? 0) + 1;
10670
+ seenTexts.set(h.text, count);
10671
+ const suffix = count > 1 ? ` #${count}` : "";
10672
+ dedupedHeadings.push({
10673
+ text: h.text + suffix,
10674
+ level: h.level
10675
+ });
10676
+ }
10677
+ const lines = [];
10678
+ lines.push(`Large markdown file (${headings.length} headings). Use token-goat section to read a specific section:`);
10679
+ lines.push(` token-goat section "${filePath}::Heading Name"`);
10680
+ lines.push(``);
10681
+ lines.push(`Sections:`);
10682
+ let headingsAdded = 0;
10683
+ for (const h of dedupedHeadings) {
10684
+ if (lines.length + 1 >= MAX_OUTPUT_LINES) {
10685
+ const remaining = dedupedHeadings.length - headingsAdded;
10686
+ lines.push(` ... (${remaining} more headings)`);
10687
+ break;
10688
+ }
10689
+ const indent = h.level === 1 ? "" : h.level === 2 ? " " : " ";
10690
+ const marker = "#".repeat(h.level);
10691
+ lines.push(` ${indent}${marker} ${h.text}`);
10692
+ headingsAdded++;
10693
+ }
10694
+ return lines.join("\n");
10695
+ }
10696
+ var WELL_KNOWN_SECTIONS = {
10697
+ "CHANGELOG.md": ["Unreleased"],
10698
+ "README.md": ["Install", "Usage", "API", "Configuration", "Getting Started"],
10699
+ "CONTRIBUTING.md": ["Setup", "Commands", "Testing", "Development"],
10700
+ "CLAUDE.md": ["Commands", "Architecture"],
10701
+ "CLAUDE.arch.md": ["Component Map", "Architecture"]
10702
+ };
10703
+ function getWellKnownSections(basename6) {
10704
+ return WELL_KNOWN_SECTIONS[basename6] ?? [];
10705
+ }
10706
+ function extractChangelogVersionHint(content, filePath) {
10707
+ const lines = content.split("\n");
10708
+ let foundUnreleased = false;
10709
+ for (const line of lines) {
10710
+ const m = /^##\s+(\[?[\d]+\.[\d]+\.[\d]+\]?)/.exec(line);
10711
+ if (m) {
10712
+ if (foundUnreleased || !line.toLowerCase().includes("unreleased")) {
10713
+ const ver = m[1];
10714
+ return ` | token-goat section "${filePath}::${ver}"`;
10715
+ }
10716
+ }
10717
+ if (/^##\s+\[?unreleased\]?/i.test(line)) {
10718
+ foundUnreleased = true;
10719
+ }
10720
+ }
10721
+ return "";
10722
+ }
10723
+
10724
+ // src/hints/file_type_handler.ts
10725
+ init_define_import_meta_env();
10726
+ var FILE_TYPE_THRESHOLDS = {
10727
+ pdf: 0,
10728
+ // always intercept (any size)
10729
+ html: 5e4,
10730
+ txt: 2e4,
10731
+ csv: 1e4,
10732
+ tsv: 1e4,
10733
+ office: 0,
10734
+ // always block (.docx etc)
10735
+ generic: 1e5
10736
+ // catch-all for unrecognized large files
10737
+ };
10738
+ function formatBytes(n) {
10739
+ if (n < 1024) return `${n} B`;
10740
+ if (n < 1048576) return `${(n / 1024).toFixed(1)} KB`;
10741
+ return `${(n / 1048576).toFixed(1)} MB`;
10742
+ }
10743
+ function handlePdf(filePath, contentLength) {
10744
+ return {
10745
+ shouldBlock: true,
10746
+ message: [
10747
+ `PDF file (${formatBytes(contentLength)}).`,
10748
+ `Use the \`pages\` parameter to scope the read: Read({ file_path: "${filePath}", pages: "1-5" })`,
10749
+ `For the full page count, run: pdfinfo "${filePath}" | grep Pages`
10750
+ ].join("\n")
10751
+ };
10752
+ }
10753
+ function handleHtml(filePath, content) {
10754
+ if (content.length < FILE_TYPE_THRESHOLDS.html) return { shouldBlock: false, message: "" };
10755
+ const title = content.match(/<title[^>]*>([^<]+)<\/title>/i)?.[1]?.trim();
10756
+ const headings = [...content.matchAll(/<h([1-6])[^>]*>([^<]+)<\/h\1>/gi)].slice(0, 20).map((m) => {
10757
+ const level = m[1];
10758
+ const text = m[2];
10759
+ if (!level || !text) return "";
10760
+ return `${" ".repeat(Number(level) - 1)}h${level}: ${text.trim()}`;
10761
+ }).filter(Boolean);
10762
+ const isMinified = !content.includes("\n") || content.length > 5e4 && content.split("\n").length < 10;
10763
+ if (isMinified) {
10764
+ return {
10765
+ shouldBlock: true,
10766
+ message: `HTML file appears minified (${formatBytes(content.length)}). Consider fetching the source or converting with: pandoc "${filePath}" -t plain`
10767
+ };
10768
+ }
10769
+ return {
10770
+ shouldBlock: true,
10771
+ message: [
10772
+ `Large HTML file (${formatBytes(content.length)})${title ? `: "${title}"` : ""}.`,
10773
+ headings.length > 0 ? `Headings:
10774
+ ${headings.join("\n")}` : "",
10775
+ `Use token-goat section to extract a section by heading, or convert to text: pandoc "${filePath}" -t plain`
10776
+ ].filter(Boolean).join("\n")
10777
+ };
10778
+ }
10779
+ function handleTxt(filePath, content) {
10780
+ if (content.length < FILE_TYPE_THRESHOLDS.txt) return { shouldBlock: false, message: "" };
10781
+ const lines = content.split("\n");
10782
+ const isLog = /\.(log|out|err|trace)$/i.test(filePath) || filePath.includes("/logs/");
10783
+ const preview = [
10784
+ "--- first 5 lines ---",
10785
+ ...lines.slice(0, 5),
10786
+ `... (${lines.length.toLocaleString()} lines total) ...`,
10787
+ "--- last 5 lines ---",
10788
+ ...lines.slice(-5)
10789
+ ].join("\n");
10790
+ const recall = isLog ? 'Log file \u2014 use Read with offset/limit params, or: token-goat bash-output <id> --tail 100 --grep "error|ERROR"' : "Use Read with offset and limit params to sample specific line ranges.";
10791
+ return {
10792
+ shouldBlock: true,
10793
+ message: `Large text file (${formatBytes(content.length)}, ${lines.length.toLocaleString()} lines).
10794
+ ${preview}
10795
+
10796
+ ${recall}`
10797
+ };
10798
+ }
10799
+ function handleOfficeBinary(filePath) {
10800
+ const ext = filePath.split(".").pop()?.toLowerCase();
10801
+ return {
10802
+ shouldBlock: true,
10803
+ message: [
10804
+ `Binary Office file (.${ext}) \u2014 cannot be read as text.`,
10805
+ `Extract content first: pandoc "${filePath}" -t plain > "${filePath}.txt"`,
10806
+ `Then read the extracted .txt file.`
10807
+ ].join("\n")
10808
+ };
10809
+ }
10810
+ function handleCsv(filePath, content) {
10811
+ if (content.length < FILE_TYPE_THRESHOLDS.csv) return { shouldBlock: false, message: "" };
10812
+ const lines = content.split("\n").filter((l) => l.trim());
10813
+ const headers = lines[0] ?? "";
10814
+ const sampleRows = lines.slice(1, 4);
10815
+ const sep = filePath.endsWith(".tsv") ? " " : ",";
10816
+ const colCount = headers.split(sep).length;
10817
+ return {
10818
+ shouldBlock: true,
10819
+ message: [
10820
+ `Large CSV file (${formatBytes(content.length)}, ~${lines.length.toLocaleString()} rows, ${colCount} columns).`,
10821
+ `Columns: ${headers}`,
10822
+ `Sample rows:
10823
+ ${sampleRows.join("\n")}`,
10824
+ `Use Read with offset/limit to sample rows, or query with DuckDB: duckdb -c "SELECT * FROM '${filePath}' LIMIT 10"`
10825
+ ].join("\n")
10826
+ };
10827
+ }
10828
+ function handleGenericLarge(filePath, contentLength) {
10829
+ if (contentLength < FILE_TYPE_THRESHOLDS.generic) return { shouldBlock: false, message: "" };
10830
+ return {
10831
+ shouldBlock: true,
10832
+ message: `Large file (${formatBytes(contentLength)}). Use Read with offset and limit parameters to read specific line ranges rather than loading the entire file.`
10833
+ };
10834
+ }
10835
+ function dispatchFileTypeHandler(filePath, content, contentLengthHint) {
10836
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
10837
+ if (["md", "mdx", "markdown", "rst"].includes(ext)) return null;
10838
+ const effectiveLength = contentLengthHint ?? content.length;
10839
+ if (ext === "pdf") return handlePdf(filePath, effectiveLength);
10840
+ if (["html", "htm", "xhtml"].includes(ext)) return handleHtml(filePath, content);
10841
+ if (["txt", "log", "out", "err", "trace"].includes(ext)) return handleTxt(filePath, content);
10842
+ if (["docx", "xlsx", "pptx", "odt", "ods", "ott", "odp"].includes(ext)) return handleOfficeBinary(filePath);
10843
+ if (ext === "csv" || ext === "tsv") return handleCsv(filePath, content);
10844
+ return handleGenericLarge(filePath, effectiveLength);
10845
+ }
10846
+
10385
10847
  // src/hooks_read.ts
10848
+ function isTsConfigFile(basename6) {
10849
+ const lower = basename6.toLowerCase();
10850
+ return /^tsconfig(\..+)?\.json$/i.test(lower) || lower === "jsconfig.json";
10851
+ }
10386
10852
  var LARGE_FILE_BYTES = 100 * 1024;
10853
+ function isNodeModulesPath(path14) {
10854
+ const isWindows = process.platform === "win32";
10855
+ const check = isWindows ? path14.toLowerCase() : path14;
10856
+ return check.includes("/node_modules/") || check.includes("\\node_modules\\");
10857
+ }
10858
+ function _isDocFile(filePath) {
10859
+ const lower = filePath.toLowerCase();
10860
+ return lower.endsWith(".md") || lower.endsWith(".mdx") || lower.endsWith(".markdown") || lower.endsWith(".rst");
10861
+ }
10387
10862
  function statSize(absPath) {
10388
10863
  try {
10389
10864
  return fs4.statSync(absPath).size;
@@ -10395,41 +10870,124 @@ function preReadHandler(event) {
10395
10870
  const filePath = getFilePath(event);
10396
10871
  if (filePath === void 0) return passOutput();
10397
10872
  const normalized = normalizePath(filePath);
10873
+ if (isNodeModulesPath(normalized)) {
10874
+ return denyOutput(
10875
+ "node_modules is typically noise; use npm ls, npm outdated, or npm audit instead for dependency info. To force access, use: token-goat read node_modules/package/file.js::symbol-name or token-goat section node_modules/package/file.js::heading"
10876
+ );
10877
+ }
10878
+ const basename6 = path6.basename(normalized);
10879
+ if (isLockFile(basename6)) {
10880
+ return denyOutput(
10881
+ 'Lock files are rarely useful to read in full. Use `token-goat section "' + normalized + '::<section>"` to extract a specific dependency, or read the relevant manifest instead.'
10882
+ );
10883
+ }
10884
+ if (normalized.toLowerCase().endsWith(".tsbuildinfo")) {
10885
+ return denyOutput(
10886
+ "This is a TypeScript incremental build cache file. You don't need to read it directly."
10887
+ );
10888
+ }
10889
+ if (isInBuildDir(normalized) || isGeneratedFile(normalized)) {
10890
+ return denyOutput(
10891
+ "Generated/build artifact \u2014 read the source file instead."
10892
+ );
10893
+ }
10894
+ const manifestHint = buildPackageManifestHint({ file_path: normalized });
10895
+ if (manifestHint) {
10896
+ recordFileRead(normalized);
10897
+ return contextOutput(manifestHint.text);
10898
+ }
10899
+ if (isTsConfigFile(basename6) && wasFileReadThisSession(normalized)) {
10900
+ recordFileRead(normalized);
10901
+ return contextOutput(
10902
+ "Already read " + basename6 + '. Use `token-goat section "' + normalized + '::compilerOptions"` to extract compiler options, or `token-goat config-get ' + normalized + " compilerOptions.target` for a single value."
10903
+ );
10904
+ }
10905
+ if (isManifestFile(basename6) && wasFileReadThisSession(normalized)) {
10906
+ recordFileRead(normalized);
10907
+ return contextOutput(
10908
+ "You've already read " + basename6 + '. Use `token-goat section "' + normalized + '::<field>"` or `token-goat config-get ' + normalized + " <key>` to extract just the value you need."
10909
+ );
10910
+ }
10911
+ const isMarkdown = /\.(md|mdx|markdown|rst)$/i.test(basename6);
10912
+ if (isMarkdown) {
10913
+ let fileContent = null;
10914
+ try {
10915
+ const sz = statSize(normalized);
10916
+ if (sz !== null && sz >= MARKDOWN_SIZE_THRESHOLD) {
10917
+ fileContent = fs4.readFileSync(normalized, "utf8");
10918
+ }
10919
+ } catch {
10920
+ }
10921
+ if (fileContent !== null) {
10922
+ const headings = extractMarkdownHeadings(fileContent);
10923
+ if (headings.length >= 3) {
10924
+ recordFileRead(normalized);
10925
+ const hintText = formatHeadingTree(headings, normalized);
10926
+ const wellKnown = getWellKnownSections(basename6);
10927
+ const wellKnownText = wellKnown.length > 0 ? "\nQuick access: " + wellKnown.map((s) => 'token-goat section "' + normalized + "::" + s + '"').join(" | ") : "";
10928
+ const changelogExtra = basename6.toLowerCase() === "changelog.md" ? extractChangelogVersionHint(fileContent, normalized) : "";
10929
+ return denyOutput(hintText + wellKnownText + changelogExtra);
10930
+ }
10931
+ }
10932
+ }
10398
10933
  if (wasFileReadThisSession(normalized)) {
10399
10934
  const entry = getSessionFiles().get(normalized);
10400
10935
  const reads = entry?.readCount ?? 1;
10401
10936
  const plural = reads === 1 ? "read" : "reads";
10402
10937
  recordFileRead(normalized);
10938
+ const hint = _isDocFile(normalized) ? 'Use `token-goat section "' + normalized + '::SectionName"` to read one section.' : "Use token-goat read/section/symbol to re-read surgically.";
10403
10939
  return contextOutput(
10404
- `Note: ${normalized} was already read this session (${reads} ${plural}). Use token-goat read/section/symbol to re-read surgically.`
10940
+ "Note: " + normalized + " was already read this session (" + reads + " " + plural + "). " + hint
10405
10941
  );
10406
10942
  }
10407
10943
  const size = statSize(normalized);
10408
10944
  if (size !== null && size >= LARGE_FILE_BYTES) {
10409
10945
  const kb = Math.round(size / 1024);
10410
10946
  recordFileRead(normalized);
10947
+ const hint = _isDocFile(normalized) ? 'Use `token-goat section "' + normalized + '::SectionName"` to read one section.' : "Consider token-goat skeleton or token-goat section.";
10411
10948
  return contextOutput(
10412
- `Note: ${normalized} is large (${kb}kb). Consider token-goat skeleton or token-goat section.`
10949
+ "Note: " + normalized + " is large (" + kb + "kb). " + hint
10413
10950
  );
10414
10951
  }
10952
+ const fileTypeExt = path6.extname(normalized).slice(1).toLowerCase();
10953
+ const binaryExts = /* @__PURE__ */ new Set(["pdf", "docx", "xlsx", "pptx", "odt", "ods", "ott", "odp"]);
10954
+ const textTypeExts = /* @__PURE__ */ new Set(["html", "htm", "xhtml", "txt", "log", "out", "err", "trace", "csv", "tsv"]);
10955
+ const fileStatSize = size ?? statSize(normalized) ?? 0;
10956
+ const isKnownFileType = binaryExts.has(fileTypeExt) || textTypeExts.has(fileTypeExt);
10957
+ if (isKnownFileType || fileStatSize >= FILE_TYPE_THRESHOLDS.generic) {
10958
+ let ftContent = "";
10959
+ if (!binaryExts.has(fileTypeExt)) {
10960
+ try {
10961
+ ftContent = fs4.readFileSync(normalized, "utf8");
10962
+ } catch {
10963
+ }
10964
+ }
10965
+ const ftResult = dispatchFileTypeHandler(normalized, ftContent, fileStatSize);
10966
+ if (ftResult?.shouldBlock) {
10967
+ recordFileRead(normalized);
10968
+ return denyOutput(ftResult.message);
10969
+ }
10970
+ }
10415
10971
  recordFileRead(normalized);
10416
10972
  return passOutput();
10417
10973
  }
10418
10974
  registerHook("pre_tool_use", preReadHandler, { toolName: "Read" });
10975
+ registerHook("pre_tool_use", preReadHandler, { toolName: "Grep" });
10419
10976
 
10420
10977
  // src/hooks_edit.ts
10421
10978
  init_define_import_meta_env();
10979
+ import * as path8 from "node:path";
10422
10980
 
10423
10981
  // src/hooks_index.ts
10424
10982
  init_define_import_meta_env();
10425
10983
  import * as fs5 from "node:fs";
10426
- import * as path6 from "node:path";
10984
+ import * as path7 from "node:path";
10427
10985
  function dirtyQueuePath() {
10428
- return path6.join(dataDir(), "queue", "dirty.txt");
10986
+ return path7.join(dataDir(), "queue", "dirty.txt");
10429
10987
  }
10430
10988
  function appendDirtyPath(normalizedPath) {
10431
10989
  const queuePath = dirtyQueuePath();
10432
- fs5.mkdirSync(path6.dirname(queuePath), { recursive: true });
10990
+ fs5.mkdirSync(path7.dirname(queuePath), { recursive: true });
10433
10991
  fs5.appendFileSync(queuePath, `${normalizedPath}
10434
10992
  `);
10435
10993
  }
@@ -10460,9 +11018,9 @@ function clearDirtyQueue() {
10460
11018
  function preCompactIndexHandler(_event) {
10461
11019
  const paths = getDirtyPaths();
10462
11020
  if (paths.length > 0) {
10463
- const sidecar = path6.join(dataDir(), "queue", "pending.txt");
11021
+ const sidecar = path7.join(dataDir(), "queue", "pending.txt");
10464
11022
  try {
10465
- fs5.mkdirSync(path6.dirname(sidecar), { recursive: true });
11023
+ fs5.mkdirSync(path7.dirname(sidecar), { recursive: true });
10466
11024
  atomicWriteBytes(sidecar, Buffer.from(`${paths.join("\n")}
10467
11025
  `, "utf8"));
10468
11026
  } catch {
@@ -10480,6 +11038,12 @@ function postEditHandler(event) {
10480
11038
  const normalized = normalizePath(filePath);
10481
11039
  recordFileEdit(normalized);
10482
11040
  appendDirtyPath(normalized);
11041
+ const editedBasename = path8.basename(normalized);
11042
+ if (/\.(md|mdx|markdown|rst)$/i.test(editedBasename)) {
11043
+ return contextOutput(
11044
+ editedBasename + ' was edited. Use `token-goat section "' + normalized + '::HeadingName"` to re-read a specific section rather than the full file.'
11045
+ );
11046
+ }
10483
11047
  return passOutput();
10484
11048
  }
10485
11049
  registerHook("post_tool_use", postEditHandler, { toolName: "Write" });
@@ -10748,10 +11312,75 @@ function postSkillHandler(event) {
10748
11312
  registerHook("pre_tool_use", preSkillHandler, { toolName: "Skill" });
10749
11313
  registerHook("post_tool_use", postSkillHandler, { toolName: "Skill" });
10750
11314
 
10751
- // src/image_shrink.ts
11315
+ // src/hooks_bash.ts
10752
11316
  init_define_import_meta_env();
11317
+
11318
+ // src/fingerprint.ts
11319
+ init_define_import_meta_env();
11320
+ import { createHash } from "node:crypto";
10753
11321
  import * as fs6 from "node:fs";
10754
- import * as path7 from "node:path";
11322
+ function fingerprintContent(content) {
11323
+ const hash = createHash("sha256");
11324
+ hash.update(typeof content === "string" ? Buffer.from(content, "utf-8") : content);
11325
+ return hash.digest("hex");
11326
+ }
11327
+ function fingerprintFile(filePath) {
11328
+ let data;
11329
+ try {
11330
+ data = fs6.readFileSync(filePath);
11331
+ } catch {
11332
+ return null;
11333
+ }
11334
+ return fingerprintContent(data);
11335
+ }
11336
+
11337
+ // src/hooks_bash.ts
11338
+ function extractCommand(event) {
11339
+ const cmd = event.toolInput["command"];
11340
+ return typeof cmd === "string" && cmd.trim() !== "" ? cmd.trim() : void 0;
11341
+ }
11342
+ function isTscCommand(cmd) {
11343
+ return /^\s*tsc(\s|$)/i.test(cmd);
11344
+ }
11345
+ function isDevServerCommand(cmd) {
11346
+ return /^\s*(vite\s+dev|next\s+dev|nuxt\s+dev)\b/i.test(cmd);
11347
+ }
11348
+ function buildRecallHint(cmd, outputId) {
11349
+ const cmdPreview = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
11350
+ if (isTscCommand(cmd)) {
11351
+ return "Output from a prior `" + cmdPreview + "` run is cached. Use `token-goat bash-output " + outputId + ' --grep "error TS"` to filter TypeScript errors, or `--grep "Cannot find"` for missing module errors.';
11352
+ }
11353
+ if (isDevServerCommand(cmd)) {
11354
+ return "Dev server output cached (`" + cmdPreview + "`). Use `token-goat bash-output " + outputId + ' --tail 20` to see the latest output, or `--grep "error\\|warn"` to filter issues.';
11355
+ }
11356
+ return "Output from a prior `" + cmdPreview + "` run is cached. Use `token-goat bash-output " + outputId + "` (or `--tail 50`, `--grep ERROR`) to re-inspect it without re-running.";
11357
+ }
11358
+ function preBashHandler(event) {
11359
+ const cmd = extractCommand(event);
11360
+ if (cmd === void 0) return passOutput();
11361
+ const monitoringHint = getMonitoringRecallHint(cmd);
11362
+ if (monitoringHint !== null) {
11363
+ const monCmdHash = fingerprintContent(cmd).slice(0, 16);
11364
+ const monOutputId = getBashOutputId(monCmdHash);
11365
+ if (monOutputId !== null) {
11366
+ const cmdSummary = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
11367
+ return contextOutput(
11368
+ "Prior output from `" + cmdSummary + "` is cached.\nUse `token-goat bash-output " + monOutputId + " " + monitoringHint + "` to re-inspect without re-running."
11369
+ );
11370
+ }
11371
+ }
11372
+ if (!isBuildCommand(cmd)) return passOutput();
11373
+ const cmdHash = fingerprintContent(cmd).slice(0, 16);
11374
+ const outputId = getBashOutputId(cmdHash);
11375
+ if (outputId === null) return passOutput();
11376
+ return contextOutput(buildRecallHint(cmd, outputId));
11377
+ }
11378
+ registerHook("pre_tool_use", preBashHandler, { toolName: "Bash" });
11379
+
11380
+ // src/image_shrink.ts
11381
+ init_define_import_meta_env();
11382
+ import * as fs7 from "node:fs";
11383
+ import * as path9 from "node:path";
10755
11384
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
10756
11385
  ".png",
10757
11386
  ".jpg",
@@ -10778,7 +11407,7 @@ async function loadSharp() {
10778
11407
  return _sharpCache;
10779
11408
  }
10780
11409
  function isImagePath(p) {
10781
- return IMAGE_EXTENSIONS.has(path7.extname(p).toLowerCase());
11410
+ return IMAGE_EXTENSIONS.has(path9.extname(p).toLowerCase());
10782
11411
  }
10783
11412
  async function shrinkImage(input, opts) {
10784
11413
  const maxDimension = opts?.maxDimension ?? DEFAULT_MAX_DIMENSION;
@@ -10816,7 +11445,7 @@ async function shrinkImage(input, opts) {
10816
11445
  }
10817
11446
  function statSize2(absPath) {
10818
11447
  try {
10819
- const st = fs6.statSync(absPath);
11448
+ const st = fs7.statSync(absPath);
10820
11449
  return st.isFile() ? st.size : null;
10821
11450
  } catch {
10822
11451
  return null;
@@ -10830,7 +11459,7 @@ async function preReadImageHandler(event) {
10830
11459
  if (size === null || size < DEFAULT_SIZE_THRESHOLD_BYTES) return passOutput();
10831
11460
  let input;
10832
11461
  try {
10833
- input = fs6.readFileSync(filePath);
11462
+ input = fs7.readFileSync(filePath);
10834
11463
  } catch {
10835
11464
  return passOutput();
10836
11465
  }
@@ -10839,7 +11468,7 @@ async function preReadImageHandler(event) {
10839
11468
  const saved = result.originalBytes - result.shrunkBytes;
10840
11469
  const pct = Math.round(saved / result.originalBytes * 100);
10841
11470
  const dataUrl = `data:image/${result.format};base64,${result.data.toString("base64")}`;
10842
- const summary = `token-goat shrank ${path7.basename(filePath)}: ${Math.round(result.originalBytes / 1024)}kb -> ${Math.round(result.shrunkBytes / 1024)}kb (${pct}% smaller, ${result.width}x${result.height} ${result.format}).`;
11471
+ const summary = `token-goat shrank ${path9.basename(filePath)}: ${Math.round(result.originalBytes / 1024)}kb -> ${Math.round(result.shrunkBytes / 1024)}kb (${pct}% smaller, ${result.width}x${result.height} ${result.format}).`;
10843
11472
  return contextOutput(`${summary}
10844
11473
  ${dataUrl}`);
10845
11474
  }
@@ -10918,7 +11547,7 @@ async function relay(eventName) {
10918
11547
 
10919
11548
  // src/section_reader.ts
10920
11549
  init_define_import_meta_env();
10921
- import { readFileSync as readFileSync3 } from "node:fs";
11550
+ import { readFileSync as readFileSync5 } from "node:fs";
10922
11551
  function parseHeadingSpec(spec) {
10923
11552
  const m = /^(.*?)#(\d+)$/.exec(spec);
10924
11553
  if (m !== null && m[1] !== void 0 && m[2] !== void 0) {
@@ -11000,7 +11629,7 @@ function sectionEndIndex(headers, headerPos, totalLines) {
11000
11629
  function readSection(filePath, headingSpec) {
11001
11630
  let text;
11002
11631
  try {
11003
- text = readFileSync3(filePath, "utf-8");
11632
+ text = readFileSync5(filePath, "utf-8");
11004
11633
  } catch {
11005
11634
  return null;
11006
11635
  }
@@ -11037,9 +11666,9 @@ function readSection(filePath, headingSpec) {
11037
11666
 
11038
11667
  // src/install.ts
11039
11668
  init_define_import_meta_env();
11040
- import * as fs7 from "node:fs";
11669
+ import * as fs8 from "node:fs";
11041
11670
  import * as os2 from "node:os";
11042
- import * as path8 from "node:path";
11671
+ import * as path10 from "node:path";
11043
11672
  var HOOK_EVENT_MAP = [
11044
11673
  ["PreToolUse", "pre_tool_use"],
11045
11674
  ["PostToolUse", "post_tool_use"],
@@ -11050,13 +11679,13 @@ function hookCommand(eventArg) {
11050
11679
  return `token-goat hook ${eventArg}`;
11051
11680
  }
11052
11681
  function settingsPath(scope) {
11053
- const base = scope === "user" ? path8.join(os2.homedir(), ".claude") : path8.join(process.cwd(), ".claude");
11054
- return path8.join(base, "settings.json");
11682
+ const base = scope === "user" ? path10.join(os2.homedir(), ".claude") : path10.join(process.cwd(), ".claude");
11683
+ return path10.join(base, "settings.json");
11055
11684
  }
11056
11685
  function readSettings(p) {
11057
11686
  let raw;
11058
11687
  try {
11059
- raw = fs7.readFileSync(p, "utf8");
11688
+ raw = fs8.readFileSync(p, "utf8");
11060
11689
  } catch {
11061
11690
  return {};
11062
11691
  }
@@ -11096,7 +11725,7 @@ function installHooks(scope = "user") {
11096
11725
  return { scope, settingsPath: p, alreadyInstalled: true };
11097
11726
  }
11098
11727
  settings.hooks = hooks;
11099
- fs7.mkdirSync(path8.dirname(p), { recursive: true });
11728
+ fs8.mkdirSync(path10.dirname(p), { recursive: true });
11100
11729
  atomicWriteText(p, `${JSON.stringify(settings, null, 2)}
11101
11730
  `);
11102
11731
  return { scope, settingsPath: p, alreadyInstalled: false };
@@ -11135,7 +11764,7 @@ function uninstallHooks(scope = "user") {
11135
11764
  } else {
11136
11765
  settings.hooks = hooks;
11137
11766
  }
11138
- fs7.mkdirSync(path8.dirname(p), { recursive: true });
11767
+ fs8.mkdirSync(path10.dirname(p), { recursive: true });
11139
11768
  atomicWriteText(p, `${JSON.stringify(settings, null, 2)}
11140
11769
  `);
11141
11770
  return true;
@@ -11145,36 +11774,15 @@ function uninstallHooks(scope = "user") {
11145
11774
  init_define_import_meta_env();
11146
11775
  import { spawn } from "node:child_process";
11147
11776
  import * as fs9 from "node:fs";
11148
- import * as path9 from "node:path";
11777
+ import * as path11 from "node:path";
11149
11778
  import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
11150
11779
  import { fileURLToPath } from "node:url";
11151
-
11152
- // src/fingerprint.ts
11153
- init_define_import_meta_env();
11154
- import { createHash } from "node:crypto";
11155
- import * as fs8 from "node:fs";
11156
- function fingerprintContent(content) {
11157
- const hash = createHash("sha256");
11158
- hash.update(typeof content === "string" ? Buffer.from(content, "utf-8") : content);
11159
- return hash.digest("hex");
11160
- }
11161
- function fingerprintFile(filePath) {
11162
- let data;
11163
- try {
11164
- data = fs8.readFileSync(filePath);
11165
- } catch {
11166
- return null;
11167
- }
11168
- return fingerprintContent(data);
11169
- }
11170
-
11171
- // src/worker.ts
11172
11780
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
11173
11781
  function dirtyQueuePathFor(dir) {
11174
- return path9.join(dir, "queue", "dirty.txt");
11782
+ return path11.join(dir, "queue", "dirty.txt");
11175
11783
  }
11176
11784
  function workerPidPath(dir = dataDir()) {
11177
- return path9.join(dir, "worker.pid");
11785
+ return path11.join(dir, "worker.pid");
11178
11786
  }
11179
11787
  function getDirtyPathsFor(dir) {
11180
11788
  let raw;
@@ -12683,7 +13291,686 @@ function _buildConfig(raw) {
12683
13291
 
12684
13292
  // src/stats.ts
12685
13293
  init_define_import_meta_env();
12686
- import * as path10 from "node:path";
13294
+ import * as path12 from "node:path";
13295
+
13296
+ // src/render/stats_renderer.ts
13297
+ init_define_import_meta_env();
13298
+
13299
+ // src/render/ansi.ts
13300
+ init_define_import_meta_env();
13301
+ function _colorStream(isatty) {
13302
+ if (process.env["NO_COLOR"]) return false;
13303
+ return isatty;
13304
+ }
13305
+ function colorStdout() {
13306
+ return _colorStream(process.stdout.isTTY === true);
13307
+ }
13308
+ var USE_COLOR = colorStdout();
13309
+ var _E = "\x1B";
13310
+ var RESET = `${_E}[0m`;
13311
+ var _ANSI_ESCAPE_RE = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)|\x1B[PX^_].*?\x1B\\|\x1B[@-Z\\\-_]/gs;
13312
+ var _PUA_RE = /[\u{E000}-\u{F8FF}\u{F0000}-\u{FFFDD}]/gu;
13313
+ function stripAnsi(s) {
13314
+ if (!s.includes("\x1B")) {
13315
+ return s;
13316
+ }
13317
+ const text = s.replace(_ANSI_ESCAPE_RE, "");
13318
+ return text.replace(_PUA_RE, "");
13319
+ }
13320
+ function fg(r, g, b) {
13321
+ return `${_E}[38;2;${r};${g};${b}m`;
13322
+ }
13323
+ function vlen(s) {
13324
+ return stripAnsi(s).length;
13325
+ }
13326
+ function padR(s, w) {
13327
+ return s + " ".repeat(Math.max(0, w - vlen(s)));
13328
+ }
13329
+ function padL(s, w) {
13330
+ return " ".repeat(Math.max(0, w - vlen(s))) + s;
13331
+ }
13332
+ function lerpRgb(a, b, t) {
13333
+ return [
13334
+ Math.round(a[0] + (b[0] - a[0]) * t),
13335
+ Math.round(a[1] + (b[1] - a[1]) * t),
13336
+ Math.round(a[2] + (b[2] - a[2]) * t)
13337
+ ];
13338
+ }
13339
+ var C = {
13340
+ TEXT_PRIMARY: [201, 209, 217],
13341
+ TEXT_BRIGHT: [240, 246, 252],
13342
+ TEXT_MUTED: [125, 133, 144],
13343
+ TEXT_DIM: [72, 79, 88],
13344
+ BG_TILE: [22, 27, 34],
13345
+ TRACK: [28, 35, 41],
13346
+ GREEN1: [31, 77, 44],
13347
+ GREEN2: [46, 160, 67],
13348
+ GREEN3: [63, 185, 80],
13349
+ GREEN4: [86, 211, 100],
13350
+ GREEN5: [126, 231, 135],
13351
+ BLUE: [88, 166, 255],
13352
+ PURPLE: [188, 140, 255],
13353
+ TEAL: [138, 212, 255],
13354
+ ORANGE: [235, 165, 80],
13355
+ YELLOW: [240, 215, 80],
13356
+ RED: [200, 60, 60]
13357
+ };
13358
+
13359
+ // src/render/stats_renderer.ts
13360
+ var _STATS_MESSAGES_FALLBACK = {
13361
+ bytesModeOnlyNote: "tracks bytes, not vision tokens",
13362
+ sessionHintSplitNote: "session_hint shows realized savings; session_hint_overhead shows injected hint cost",
13363
+ insights: {
13364
+ biggestSaver: "Biggest saver ",
13365
+ mostActive: "Most active ",
13366
+ tokenLeader: "Token leader "
13367
+ }
13368
+ };
13369
+ var _STATS_MESSAGES = _STATS_MESSAGES_FALLBACK;
13370
+ var _TERM_W = process.stdout.columns || 100;
13371
+ var _CONTENT_W = Math.min(Math.max(_TERM_W, 80), 140);
13372
+ var _M = " ";
13373
+ var _COL_NAME = 18;
13374
+ var _COL_DATA = 10;
13375
+ var _COL_TOKENS = 12;
13376
+ var _COL_SHARE = 6;
13377
+ var _COL_EVENTS = 6;
13378
+ var _COLS_FIXED = _COL_NAME + 1 + 2 + _COL_DATA + 2 + _COL_TOKENS + 2 + _COL_SHARE + 2 + _COL_EVENTS;
13379
+ var _BAR_W = Math.max(16, _CONTENT_W - _M.length * 2 - _COLS_FIXED);
13380
+ var _RULE = _M + fg(...C.TEXT_DIM) + "\u2500".repeat(_CONTENT_W - _M.length * 2) + RESET;
13381
+ var _BYTE_TIERS = [
13382
+ { threshold: 1e15, divisor: 1e15, unit: "PB", color: C.PURPLE },
13383
+ { threshold: 1e12, divisor: 1e12, unit: "TB", color: C.BLUE },
13384
+ { threshold: 1e9, divisor: 1e9, unit: "GB", color: C.TEAL },
13385
+ { threshold: 1e6, divisor: 1e6, unit: "MB", color: C.GREEN4 },
13386
+ { threshold: 1e3, divisor: 1e3, unit: "KB", color: C.TEXT_MUTED },
13387
+ { threshold: 0, divisor: 1, unit: "B", color: C.TEXT_DIM }
13388
+ ];
13389
+ var _TOKEN_TIERS = [
13390
+ { threshold: 1e12, divisor: 1e12, unit: "Tt", color: C.GREEN5 },
13391
+ { threshold: 1e9, divisor: 1e9, unit: "Gt", color: C.TEAL },
13392
+ { threshold: 1e6, divisor: 1e6, unit: "Mt", color: C.PURPLE },
13393
+ { threshold: 1e3, divisor: 1e3, unit: "kt", color: C.BLUE },
13394
+ { threshold: 0, divisor: 1, unit: "t", color: C.TEXT_DIM }
13395
+ ];
13396
+ function _fmtMagnitude(n, tiers, zeroLabel) {
13397
+ if (zeroLabel !== void 0 && n === 0) {
13398
+ return `${fg(...C.TEXT_DIM)}${zeroLabel}${RESET}`;
13399
+ }
13400
+ if (n < 0) {
13401
+ const a = -n;
13402
+ const color = C.TEXT_DIM;
13403
+ for (const tier of tiers) {
13404
+ if (a >= tier.threshold && tier.threshold > 0) {
13405
+ return `${fg(...color)}-${(a / tier.divisor).toLocaleString("en", { maximumFractionDigits: 1 })} ${tier.unit}${RESET}`;
13406
+ }
13407
+ }
13408
+ const lastTier2 = tiers[tiers.length - 1];
13409
+ if (lastTier2) {
13410
+ return `${fg(...color)}-${a} ${lastTier2.unit}${RESET}`;
13411
+ }
13412
+ return `${fg(...color)}-${a}${RESET}`;
13413
+ }
13414
+ for (const tier of tiers) {
13415
+ if (n >= tier.threshold && tier.threshold > 0) {
13416
+ return `${fg(...tier.color)}${(n / tier.divisor).toLocaleString("en", { maximumFractionDigits: 1 })} ${tier.unit}${RESET}`;
13417
+ }
13418
+ }
13419
+ const lastTier = tiers[tiers.length - 1];
13420
+ if (lastTier) {
13421
+ return `${fg(...lastTier.color)}${n} ${lastTier.unit}${RESET}`;
13422
+ }
13423
+ return `${n}${RESET}`;
13424
+ }
13425
+ function _fmtBytes(n) {
13426
+ return _fmtMagnitude(n, _BYTE_TIERS);
13427
+ }
13428
+ function _fmtTokens(n) {
13429
+ return _fmtMagnitude(n, _TOKEN_TIERS, "0 t");
13430
+ }
13431
+ function _fmtPct(fraction) {
13432
+ return `${(fraction * 100).toFixed(1)}%`;
13433
+ }
13434
+ function _fmtDelta(delta) {
13435
+ if (!delta && delta !== 0) {
13436
+ return "";
13437
+ }
13438
+ const up = (delta ?? 0) >= 0;
13439
+ const color = up ? C.GREEN5 : C.RED;
13440
+ const arrow = up ? "\u2191" : "\u2193";
13441
+ return ` ${fg(...color)}${arrow} ${Math.round(Math.abs(delta ?? 0))}%${RESET}`;
13442
+ }
13443
+ var _EIGHTHS = ["\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589"];
13444
+ var _BLOCK = "\u2588";
13445
+ var _TRACK = "\u2591";
13446
+ var _GRADIENT = [C.GREEN1, C.GREEN2, C.GREEN3, C.GREEN4, C.GREEN5];
13447
+ function _distribute(total, n) {
13448
+ if (total <= 0 || n <= 0) {
13449
+ return Array(Math.max(0, n)).fill(0);
13450
+ }
13451
+ const base = Math.floor(total / n);
13452
+ const rem = total % n;
13453
+ return Array.from({ length: n }, (_, i) => base + (i >= n - rem ? 1 : 0));
13454
+ }
13455
+ function _renderBar(fraction, width = _BAR_W) {
13456
+ const f = Math.max(0, Math.min(1, fraction));
13457
+ const raw = f * width;
13458
+ let nFull = Math.floor(raw);
13459
+ const eighths = Math.round((raw - nFull) * 8);
13460
+ if (eighths >= 8) {
13461
+ nFull += 1;
13462
+ }
13463
+ const hasPartial = eighths > 0 && eighths < 8;
13464
+ const nTrack = Math.max(0, width - nFull - (hasPartial ? 1 : 0));
13465
+ const counts = _distribute(nFull, _GRADIENT.length);
13466
+ let bar = counts.map((count, i) => count > 0 ? `${fg(_GRADIENT[i][0], _GRADIENT[i][1], _GRADIENT[i][2])}${_BLOCK.repeat(count)}` : "").join("");
13467
+ if (hasPartial) {
13468
+ const lastGrad = _GRADIENT[_GRADIENT.length - 1];
13469
+ bar += `${fg(lastGrad[0], lastGrad[1], lastGrad[2])}${_EIGHTHS[eighths - 1]}`;
13470
+ }
13471
+ if (nTrack > 0) {
13472
+ bar += `${fg(...C.TRACK)}${_TRACK.repeat(nTrack)}`;
13473
+ }
13474
+ return bar + RESET;
13475
+ }
13476
+ var _SPARK = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
13477
+ function _resample(vals, length) {
13478
+ if (vals.length === 0) {
13479
+ return Array(length).fill(0);
13480
+ }
13481
+ if (vals.length === length) {
13482
+ return [...vals];
13483
+ }
13484
+ const result = [];
13485
+ for (let i = 0; i < length; i++) {
13486
+ const src = i / (length - 1 || 1) * (vals.length - 1);
13487
+ const lo = Math.floor(src);
13488
+ const hi = Math.min(vals.length - 1, lo + 1);
13489
+ const t = src - lo;
13490
+ const loVal = vals[lo] ?? 0;
13491
+ const hiVal = vals[hi] ?? 0;
13492
+ result.push(loVal * (1 - t) + hiVal * t);
13493
+ }
13494
+ return result;
13495
+ }
13496
+ function _renderSparkline(values, width = 8) {
13497
+ const pts = _resample(values, width);
13498
+ const hi = pts.length > 0 ? Math.max(...pts) : 1;
13499
+ const lo = pts.length > 0 ? Math.min(...pts) : 0;
13500
+ const span = hi - lo || 1;
13501
+ const chars = [];
13502
+ for (let i = 0; i < pts.length; i++) {
13503
+ const v = pts[i];
13504
+ if (v === void 0) continue;
13505
+ const idx = Math.min(7, Math.floor((v - lo) / span * 8));
13506
+ const color = lerpRgb(C.GREEN1, C.GREEN5, i / (width - 1 || 1));
13507
+ chars.push(`${fg(color[0], color[1], color[2])}${_SPARK[idx]}`);
13508
+ }
13509
+ return chars.join("") + RESET;
13510
+ }
13511
+ function _tokenOrByteShare(itemTokens, itemBytes, totalTokens, totalBytes) {
13512
+ if (totalTokens > 0) {
13513
+ return itemTokens / totalTokens;
13514
+ }
13515
+ if (totalBytes > 0) {
13516
+ return itemBytes / totalBytes;
13517
+ }
13518
+ return 0;
13519
+ }
13520
+ function _barFraction(itemBytes, grossBytes) {
13521
+ return itemBytes > 0 ? itemBytes / grossBytes : 0;
13522
+ }
13523
+ function _computeShareDenominators(items) {
13524
+ let grossBytesSum = 0;
13525
+ let shareByteSum = 0;
13526
+ let shareTokensSum = 0;
13527
+ for (const item of items) {
13528
+ if (item.bytes > 0) {
13529
+ grossBytesSum += item.bytes;
13530
+ }
13531
+ shareByteSum += Math.abs(item.bytes);
13532
+ shareTokensSum += Math.abs(item.tokens);
13533
+ }
13534
+ return {
13535
+ grossBytes: Math.max(grossBytesSum, 1),
13536
+ shareBytesDenom: Math.max(shareByteSum, 1),
13537
+ shareTokensDenom: shareTokensSum
13538
+ };
13539
+ }
13540
+ function _absShare(itemBytes, itemTokens, shareBytesDenom, shareTokensDenom) {
13541
+ if (shareTokensDenom === 0) {
13542
+ return itemBytes / shareBytesDenom;
13543
+ }
13544
+ return itemTokens / shareTokensDenom;
13545
+ }
13546
+ function _sectionHeader(title, subtitle = "") {
13547
+ const sub = subtitle ? ` ${fg(...C.TEXT_MUTED)}${subtitle}${RESET}` : "";
13548
+ return [
13549
+ "",
13550
+ `${_M}${fg(...C.TEXT_BRIGHT)}${title}${RESET}${sub}`,
13551
+ _RULE
13552
+ ];
13553
+ }
13554
+ function _tableHeader(firstColLabel) {
13555
+ return [
13556
+ _M,
13557
+ padR(`${fg(...C.TEXT_DIM)}${firstColLabel}${RESET}`, _COL_NAME),
13558
+ " ",
13559
+ padR(`${fg(...C.TEXT_DIM)}savings${RESET}`, _BAR_W),
13560
+ " ",
13561
+ padL(`${fg(...C.TEXT_DIM)}data saved${RESET}`, _COL_DATA),
13562
+ " ",
13563
+ padL(`${fg(...C.TEXT_DIM)}tokens saved${RESET}`, _COL_TOKENS),
13564
+ " ",
13565
+ padL(`${fg(...C.TEXT_DIM)}share${RESET}`, _COL_SHARE),
13566
+ " ",
13567
+ padL(`${fg(...C.TEXT_DIM)}events${RESET}`, _COL_EVENTS)
13568
+ ].join("");
13569
+ }
13570
+ function _tableRow({
13571
+ name,
13572
+ fraction,
13573
+ bytes,
13574
+ tokens,
13575
+ events,
13576
+ share,
13577
+ bytesModeOnly = false,
13578
+ namePrefix = "",
13579
+ nameColor = C.TEXT_PRIMARY
13580
+ }) {
13581
+ const prefixW = vlen(namePrefix);
13582
+ const maxName = _COL_NAME - prefixW;
13583
+ const truncated = name.length > maxName ? name.slice(0, maxName - 1) + "\u2026" : name;
13584
+ const nameStr = padR(`${namePrefix}${fg(...nameColor)}${truncated}${RESET}`, _COL_NAME);
13585
+ const dataStr = padL(_fmtBytes(bytes), _COL_DATA);
13586
+ const tokStr = bytesModeOnly ? padL(`${fg(...C.TEXT_DIM)}\u2014${RESET}`, _COL_TOKENS) : padL(_fmtTokens(tokens), _COL_TOKENS);
13587
+ const sharePct = share * 100;
13588
+ let shareColor;
13589
+ if (sharePct < 0) {
13590
+ shareColor = C.RED;
13591
+ } else if (sharePct >= 50) {
13592
+ shareColor = C.GREEN5;
13593
+ } else if (sharePct >= 10) {
13594
+ shareColor = C.TEXT_PRIMARY;
13595
+ } else {
13596
+ shareColor = C.TEXT_MUTED;
13597
+ }
13598
+ const shareStr = padL(`${fg(...shareColor)}${_fmtPct(share)}${RESET}`, _COL_SHARE);
13599
+ const evStr = padL(`${fg(...C.TEXT_PRIMARY)}${events.toLocaleString()}${RESET}`, _COL_EVENTS);
13600
+ return [_M, nameStr, " ", _renderBar(fraction), " ", dataStr, " ", tokStr, " ", shareStr, " ", evStr].join("");
13601
+ }
13602
+ function _renderKpiSection(stats) {
13603
+ const totals = stats.totals;
13604
+ const colW = Math.floor((_CONTENT_W - _M.length * 2) / 3);
13605
+ function card(label, value, delta, spark2) {
13606
+ return [
13607
+ padR(`${fg(C.TEXT_MUTED[0], C.TEXT_MUTED[1], C.TEXT_MUTED[2])}${label}${RESET}`, colW),
13608
+ padR(`${fg(C.TEXT_BRIGHT[0], C.TEXT_BRIGHT[1], C.TEXT_BRIGHT[2])}${value}${RESET}${delta}`, colW),
13609
+ spark2 !== null ? padR(spark2, colW) : padR("", colW)
13610
+ ];
13611
+ }
13612
+ const spark = totals.sparklines;
13613
+ const c1 = card(
13614
+ "events",
13615
+ `${totals.events.toLocaleString()}`,
13616
+ _fmtDelta(totals.events_delta ?? null),
13617
+ spark ? _renderSparkline(spark.events) : null
13618
+ );
13619
+ const c2 = card(
13620
+ "data saved",
13621
+ _fmtBytes(totals.bytes),
13622
+ _fmtDelta(totals.bytes_delta ?? null),
13623
+ spark ? _renderSparkline(spark.bytes) : null
13624
+ );
13625
+ const c3 = card(
13626
+ "tokens saved",
13627
+ _fmtTokens(totals.tokens),
13628
+ _fmtDelta(totals.tokens_delta ?? null),
13629
+ spark ? _renderSparkline(spark.tokens) : null
13630
+ );
13631
+ const border = fg(C.TEXT_DIM[0], C.TEXT_DIM[1], C.TEXT_DIM[2]);
13632
+ const frameBar = "\u2500".repeat(colW * 3 + 2);
13633
+ function framed(content) {
13634
+ return `${_M}${border}\u2502${RESET} ${content} ${border}\u2502${RESET}`;
13635
+ }
13636
+ const lines = [
13637
+ "",
13638
+ `${_M}${border}\u256D${frameBar}\u256E${RESET}`,
13639
+ framed(c1[0] + c2[0] + c3[0]),
13640
+ framed(c1[1] + c2[1] + c3[1])
13641
+ ];
13642
+ if (spark) {
13643
+ lines.push(framed(c1[2] + c2[2] + c3[2]));
13644
+ }
13645
+ lines.push(`${_M}${border}\u2570${frameBar}\u256F${RESET}`);
13646
+ return lines;
13647
+ }
13648
+ var _KIND_GROUPS = [
13649
+ {
13650
+ label: "Read savings",
13651
+ members: /* @__PURE__ */ new Set([
13652
+ "read_replacement",
13653
+ "section_replacement",
13654
+ "symbol_read",
13655
+ "section_read",
13656
+ "stub_view",
13657
+ "outline",
13658
+ "exports"
13659
+ ])
13660
+ },
13661
+ { label: "Lookups", members: /* @__PURE__ */ new Set(["symbol_lookup", "semantic_search", "map_lookup"]) },
13662
+ {
13663
+ label: "Images",
13664
+ members: /* @__PURE__ */ new Set(["image_shrink", "gdrive_image", "webfetch_image", "image_shrink_skipped"])
13665
+ },
13666
+ {
13667
+ label: "Hints",
13668
+ members: /* @__PURE__ */ new Set([
13669
+ "session_hint",
13670
+ "session_hint_overhead",
13671
+ "read_dedup_hint",
13672
+ "grep_dedup_hint",
13673
+ "diff_hint",
13674
+ "predictive_prefetch_hit",
13675
+ "read_partial_overlap_hint"
13676
+ ])
13677
+ },
13678
+ {
13679
+ label: "Bash",
13680
+ members: /* @__PURE__ */ new Set([
13681
+ "bash_dedup_hint",
13682
+ "bash_output_cached",
13683
+ "bash_output_recall",
13684
+ "bash_output_recall_miss",
13685
+ "bash_dedup_stale",
13686
+ "bash_range_read_hint",
13687
+ "bash_streak_hint",
13688
+ "bash_poll_hint",
13689
+ "env_probe_cache_hit",
13690
+ "git_diff_scope_hint",
13691
+ "dep_list_cache_hit",
13692
+ "bash_read_equiv_already_read",
13693
+ "bash_grep_result_cache_hit",
13694
+ "git_diff_context_trimmed"
13695
+ ])
13696
+ },
13697
+ {
13698
+ label: "Web",
13699
+ members: /* @__PURE__ */ new Set([
13700
+ "web_dedup_hint",
13701
+ "web_output_cached",
13702
+ "web_output_recall",
13703
+ "web_output_recall_miss",
13704
+ "web_dedup_stale"
13705
+ ])
13706
+ },
13707
+ {
13708
+ label: "Compact / Skills",
13709
+ members: /* @__PURE__ */ new Set([
13710
+ "compact_manifest",
13711
+ "compact_assist",
13712
+ "compact_recovery",
13713
+ "skill_body_recall",
13714
+ "skill_compact_served",
13715
+ "skill_cached",
13716
+ "resume_packet",
13717
+ "decision_log"
13718
+ ])
13719
+ }
13720
+ ];
13721
+ function _kindGroupLabel(kind) {
13722
+ if (kind.startsWith("bash_compress:")) {
13723
+ return "Bash";
13724
+ }
13725
+ for (const group of _KIND_GROUPS) {
13726
+ if (group.members.has(kind)) {
13727
+ return group.label;
13728
+ }
13729
+ }
13730
+ return "Other";
13731
+ }
13732
+ function _groupSeparator(label) {
13733
+ return `${_M} ${fg(...C.TEXT_DIM)}${label}${RESET}`;
13734
+ }
13735
+ function _renderByKindSection(stats) {
13736
+ if (stats.by_kind.length === 0) {
13737
+ return [];
13738
+ }
13739
+ const lines = [..._sectionHeader("By kind"), _tableHeader("name")];
13740
+ const { grossBytes, shareBytesDenom, shareTokensDenom } = _computeShareDenominators(stats.by_kind);
13741
+ const kindNames = new Set(stats.by_kind.map((k) => k.kind));
13742
+ const bytesModeKinds = stats.by_kind.filter((k) => k.bytes_mode_only).map((k) => k.kind);
13743
+ function share(k) {
13744
+ if (k.bytes_mode_only) {
13745
+ return k.bytes / shareBytesDenom;
13746
+ }
13747
+ return _absShare(k.bytes, k.tokens, shareBytesDenom, shareTokensDenom);
13748
+ }
13749
+ const byGroup = /* @__PURE__ */ new Map();
13750
+ for (const k of stats.by_kind) {
13751
+ const grp = _kindGroupLabel(k.kind);
13752
+ if (!byGroup.has(grp)) {
13753
+ byGroup.set(grp, []);
13754
+ }
13755
+ byGroup.get(grp).push(k);
13756
+ }
13757
+ for (const grpKinds of byGroup.values()) {
13758
+ grpKinds.sort((a, b) => share(b) - share(a));
13759
+ }
13760
+ let firstGroup = true;
13761
+ for (const group of _KIND_GROUPS) {
13762
+ const groupKinds = byGroup.get(group.label);
13763
+ if (!groupKinds || groupKinds.length === 0) {
13764
+ continue;
13765
+ }
13766
+ if (!firstGroup) {
13767
+ lines.push("");
13768
+ }
13769
+ firstGroup = false;
13770
+ lines.push(_groupSeparator(group.label));
13771
+ for (const k of groupKinds) {
13772
+ const s = share(k);
13773
+ lines.push(
13774
+ _tableRow({
13775
+ name: k.kind,
13776
+ fraction: _barFraction(k.bytes, grossBytes),
13777
+ bytes: k.bytes,
13778
+ tokens: k.tokens,
13779
+ events: k.events,
13780
+ share: s,
13781
+ bytesModeOnly: k.bytes_mode_only ?? false
13782
+ })
13783
+ );
13784
+ }
13785
+ }
13786
+ if (bytesModeKinds.length > 0) {
13787
+ const names = bytesModeKinds.join(", ");
13788
+ lines.push(`${_M}${fg(...C.TEXT_DIM)}i ${names} ${_STATS_MESSAGES.bytesModeOnlyNote}${RESET}`);
13789
+ }
13790
+ if (kindNames.has("session_hint") && kindNames.has("session_hint_overhead")) {
13791
+ lines.push(`${_M}${fg(...C.TEXT_DIM)}i ${_STATS_MESSAGES.sessionHintSplitNote}${RESET}`);
13792
+ }
13793
+ return lines;
13794
+ }
13795
+ var _SOURCE_COLORS = {
13796
+ image: C.PURPLE,
13797
+ hint: C.BLUE,
13798
+ read: C.GREEN4,
13799
+ compact: C.TEAL,
13800
+ bash: C.ORANGE,
13801
+ web: C.YELLOW,
13802
+ other: C.TEXT_MUTED
13803
+ };
13804
+ function _sourceColor(source) {
13805
+ const color = _SOURCE_COLORS[source];
13806
+ return color || C.TEXT_MUTED;
13807
+ }
13808
+ function _renderBySourceSection(stats) {
13809
+ if (!stats.by_source || stats.by_source.length === 0) {
13810
+ return [];
13811
+ }
13812
+ const lines = [..._sectionHeader("By source"), _tableHeader("source")];
13813
+ const { grossBytes, shareBytesDenom, shareTokensDenom } = _computeShareDenominators(stats.by_source);
13814
+ function share(s) {
13815
+ return _absShare(s.bytes, s.tokens, shareBytesDenom, shareTokensDenom);
13816
+ }
13817
+ for (const s of [...stats.by_source].sort((a, b) => share(b) - share(a))) {
13818
+ const s_val = share(s);
13819
+ const color = _sourceColor(s.source);
13820
+ lines.push(
13821
+ _tableRow({
13822
+ name: s.source,
13823
+ fraction: _barFraction(s.bytes, grossBytes),
13824
+ bytes: s.bytes,
13825
+ tokens: s.tokens,
13826
+ events: s.events,
13827
+ share: s_val,
13828
+ namePrefix: `${fg(...color)}\u25CF${RESET} `,
13829
+ nameColor: C.TEXT_PRIMARY
13830
+ })
13831
+ );
13832
+ }
13833
+ return lines;
13834
+ }
13835
+ function _renderByCommandSection(stats) {
13836
+ if (!stats.by_command || stats.by_command.length === 0) {
13837
+ return [];
13838
+ }
13839
+ const lines = [..._sectionHeader("By command"), _tableHeader("command")];
13840
+ const { grossBytes, shareBytesDenom, shareTokensDenom } = _computeShareDenominators(stats.by_command);
13841
+ function share(c) {
13842
+ return _absShare(c.bytes, c.tokens, shareBytesDenom, shareTokensDenom);
13843
+ }
13844
+ for (const c of [...stats.by_command].sort((a, b) => share(b) - share(a))) {
13845
+ const s_val = share(c);
13846
+ lines.push(
13847
+ _tableRow({
13848
+ name: c.command,
13849
+ fraction: _barFraction(c.bytes, grossBytes),
13850
+ bytes: c.bytes,
13851
+ tokens: c.tokens,
13852
+ events: c.events,
13853
+ share: s_val,
13854
+ nameColor: C.TEXT_PRIMARY
13855
+ })
13856
+ );
13857
+ }
13858
+ return lines;
13859
+ }
13860
+ function _renderByDaySection(stats) {
13861
+ if (stats.by_day.length === 0) {
13862
+ return [];
13863
+ }
13864
+ const lines = [..._sectionHeader("By day"), _tableHeader("date")];
13865
+ function share(d) {
13866
+ return _tokenOrByteShare(d.tokens, d.bytes, stats.totals.tokens, stats.totals.bytes);
13867
+ }
13868
+ for (const d of [...stats.by_day].sort((a, b) => b.date.localeCompare(a.date))) {
13869
+ const s = share(d);
13870
+ lines.push(
13871
+ _tableRow({
13872
+ name: d.date,
13873
+ fraction: s,
13874
+ bytes: d.bytes,
13875
+ tokens: d.tokens,
13876
+ events: d.events,
13877
+ share: s
13878
+ })
13879
+ );
13880
+ }
13881
+ return lines;
13882
+ }
13883
+ var _PROJECT_COLORS = [C.PURPLE, C.TEAL, C.BLUE, C.GREEN4, C.TEXT_MUTED];
13884
+ function _hashColor(hashStr) {
13885
+ let n = 0;
13886
+ for (const c of hashStr) {
13887
+ n += c.charCodeAt(0);
13888
+ }
13889
+ return _PROJECT_COLORS[n % _PROJECT_COLORS.length] ?? C.TEXT_MUTED;
13890
+ }
13891
+ function _renderByProjectSection(stats) {
13892
+ if (stats.by_project.length === 0) {
13893
+ return [];
13894
+ }
13895
+ const projectTotalBytes = stats.by_project.reduce((s, p) => s + p.bytes, 0);
13896
+ const projectTotalTokens = stats.by_project.reduce((s, p) => s + p.tokens, 0);
13897
+ const lines = [..._sectionHeader(`By project (top ${stats.by_project.length})`), _tableHeader("project")];
13898
+ function share(p) {
13899
+ return _tokenOrByteShare(p.tokens, p.bytes, projectTotalTokens, projectTotalBytes);
13900
+ }
13901
+ for (const p of [...stats.by_project].sort((a, b) => share(b) - share(a))) {
13902
+ const s = share(p);
13903
+ const color = _hashColor(p.hash);
13904
+ lines.push(
13905
+ _tableRow({
13906
+ name: p.project,
13907
+ fraction: s,
13908
+ bytes: p.bytes,
13909
+ tokens: p.tokens,
13910
+ events: p.events,
13911
+ share: s,
13912
+ namePrefix: `${fg(...color)}\u25CF${RESET} `,
13913
+ nameColor: C.TEXT_PRIMARY
13914
+ })
13915
+ );
13916
+ lines.push(`${_M} ${fg(...C.TEXT_DIM)}\u2514\u2500 ${p.hash} ${stripAnsi(p.path)}${RESET}`);
13917
+ }
13918
+ return lines;
13919
+ }
13920
+ function _renderInsightsSection(stats) {
13921
+ const lines = [..._sectionHeader("Insights")];
13922
+ const bullet = `${fg(...C.GREEN3)}\u25B8${RESET}`;
13923
+ function dim(s) {
13924
+ return `${fg(...C.TEXT_MUTED)}${s}${RESET}`;
13925
+ }
13926
+ const topKind = stats.by_kind.reduce((max, k) => k.bytes > (max?.bytes || -Infinity) ? k : max, stats.by_kind[0]);
13927
+ if (topKind) {
13928
+ const share = stats.totals.bytes > 0 ? topKind.bytes / stats.totals.bytes : 0;
13929
+ lines.push(
13930
+ `${_M}${bullet} ${dim(_STATS_MESSAGES.insights.biggestSaver)}${fg(...C.TEXT_PRIMARY)}${topKind.kind}${RESET}${dim(" \u2014 ")}${fg(...C.GREEN5)}${_fmtPct(share)}${RESET}${dim(` of saved data across ${topKind.events.toLocaleString()} events`)}`
13931
+ );
13932
+ }
13933
+ const topDay = stats.by_day.reduce((max, d) => d.events > (max?.events || -Infinity) ? d : max, stats.by_day[0]);
13934
+ if (topDay) {
13935
+ lines.push(
13936
+ `${_M}${bullet} ${dim(_STATS_MESSAGES.insights.mostActive)}${fg(...C.TEXT_PRIMARY)}${topDay.date}${RESET}${dim(" \u2014 ")}${topDay.events.toLocaleString()} events, ${_fmtBytes(topDay.bytes)}${dim(" saved")}`
13937
+ );
13938
+ }
13939
+ const tokenKinds = stats.by_kind.filter((k) => !k.bytes_mode_only);
13940
+ const topToken = tokenKinds.reduce((max, k) => k.tokens > (max?.tokens || -Infinity) ? k : max, tokenKinds[0]);
13941
+ if (topToken) {
13942
+ lines.push(
13943
+ `${_M}${bullet} ${dim(_STATS_MESSAGES.insights.tokenLeader)}${fg(...C.TEXT_PRIMARY)}${topToken.kind}${RESET}${dim(" \u2014 ")}${_fmtTokens(topToken.tokens)}${dim(` saved in ${topToken.events.toLocaleString()} events`)}`
13944
+ );
13945
+ }
13946
+ return lines;
13947
+ }
13948
+ function _renderHeader(stats) {
13949
+ let line = `${_M}${fg(...C.TEXT_BRIGHT)}token-goat${RESET}`;
13950
+ if (stats.version) {
13951
+ line += ` ${fg(...C.TEXT_MUTED)}v${stats.version}${RESET}`;
13952
+ }
13953
+ if (stats.window_label) {
13954
+ line += ` ${fg(...C.TEXT_DIM)}\xB7 ${stats.window_label}${RESET}`;
13955
+ }
13956
+ return [line];
13957
+ }
13958
+ function renderStats(stats) {
13959
+ const sections = [
13960
+ _renderHeader(stats),
13961
+ _renderKpiSection(stats),
13962
+ _renderByKindSection(stats),
13963
+ _renderBySourceSection(stats),
13964
+ _renderByCommandSection(stats),
13965
+ _renderByDaySection(stats),
13966
+ _renderByProjectSection(stats),
13967
+ _renderInsightsSection(stats),
13968
+ [""]
13969
+ ];
13970
+ return sections.flatMap((s) => s).join("\n");
13971
+ }
13972
+
13973
+ // src/stats.ts
12687
13974
  var SOURCE_IMAGE = "image";
12688
13975
  var SOURCE_HINT = "hint";
12689
13976
  var SOURCE_READ = "read";
@@ -12692,6 +13979,7 @@ var SOURCE_WEB = "web";
12692
13979
  var SOURCE_MCP = "mcp";
12693
13980
  var SOURCE_SKILL = "skill";
12694
13981
  var SOURCE_OTHER = "other";
13982
+ var _BYTES_MODE_ONLY_KINDS = /* @__PURE__ */ new Set(["webfetch_image", "gdrive_image"]);
12695
13983
  var KIND_TO_SOURCE = {
12696
13984
  image_shrink: SOURCE_IMAGE,
12697
13985
  image_shrink_cache_hit: SOURCE_IMAGE,
@@ -12734,7 +14022,15 @@ var COMMAND_KINDS = {
12734
14022
  skeleton: /* @__PURE__ */ new Set(["stub_view"]),
12735
14023
  refs: /* @__PURE__ */ new Set(["symbol_read"]),
12736
14024
  map: /* @__PURE__ */ new Set(["map_lookup"]),
12737
- changed: /* @__PURE__ */ new Set(["changed_lookup"])
14025
+ changed: /* @__PURE__ */ new Set(["changed_lookup"]),
14026
+ npm: /* @__PURE__ */ new Set([
14027
+ "bash_compress:npm_install",
14028
+ "bash_compress:npm_ci",
14029
+ "bash_compress:npm_audit",
14030
+ "bash_compress:npm_ls",
14031
+ "bash_compress:npm_outdated",
14032
+ "bash_compress:npx"
14033
+ ])
12738
14034
  };
12739
14035
  var OVERHEAD_SUFFIX = "_overhead";
12740
14036
  function kindToSource(kind) {
@@ -12759,7 +14055,7 @@ function incBucket(bucket, bytesSaved, tokensSaved) {
12759
14055
  bucket.tokens_saved += tokensSaved;
12760
14056
  }
12761
14057
  function getGlobalDb() {
12762
- const dbPath = path10.join(dataDir(), "global.db");
14058
+ const dbPath = path12.join(dataDir(), "global.db");
12763
14059
  return getDb(dbPath);
12764
14060
  }
12765
14061
  function summarize(windowDays = 30, testDb) {
@@ -12833,12 +14129,7 @@ function summarize(windowDays = 30, testDb) {
12833
14129
  window_days: windowDays
12834
14130
  };
12835
14131
  }
12836
- function renderStats(opts) {
12837
- const summary = summarize(opts?.windowDays ?? 30);
12838
- if (summary.total_events === 0) {
12839
- console.log("No stats recorded yet.");
12840
- return;
12841
- }
14132
+ function _plainTextStats(summary) {
12842
14133
  const fmtBytes = (n) => {
12843
14134
  if (n < 1024) return `${n}B`;
12844
14135
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
@@ -12878,11 +14169,71 @@ function renderStats(opts) {
12878
14169
  }
12879
14170
  console.log(lines.join("\n"));
12880
14171
  }
14172
+ function renderStats2(opts) {
14173
+ const windowDays = opts?.windowDays ?? 30;
14174
+ const summary = summarize(windowDays);
14175
+ if (summary.total_events === 0) {
14176
+ console.log("No stats recorded yet.");
14177
+ return;
14178
+ }
14179
+ const useTty = process.stdout.isTTY === true && !process.env["NO_COLOR"];
14180
+ if (!useTty) {
14181
+ _plainTextStats(summary);
14182
+ return;
14183
+ }
14184
+ const now = /* @__PURE__ */ new Date();
14185
+ const periodStart = windowDays > 0 ? new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1e3) : /* @__PURE__ */ new Date(0);
14186
+ const sparkDays = [...summary.by_day].reverse().slice(-30);
14187
+ const sparklines = sparkDays.length > 1 ? {
14188
+ events: sparkDays.map((d) => d.events),
14189
+ bytes: sparkDays.map((d) => d.bytes_saved),
14190
+ tokens: sparkDays.map((d) => d.tokens_saved)
14191
+ } : null;
14192
+ const statsData = {
14193
+ period_start: periodStart,
14194
+ period_end: now,
14195
+ version: VERSION,
14196
+ window_label: windowDays > 0 ? `last ${windowDays} days` : "all time",
14197
+ totals: {
14198
+ events: summary.total_events,
14199
+ bytes: summary.total_bytes_saved,
14200
+ tokens: summary.total_tokens_saved,
14201
+ sparklines
14202
+ },
14203
+ by_kind: Object.entries(summary.by_kind).map(([kind, bucket]) => ({
14204
+ kind,
14205
+ bytes: bucket.bytes_saved,
14206
+ tokens: bucket.tokens_saved,
14207
+ events: bucket.events,
14208
+ bytes_mode_only: _BYTES_MODE_ONLY_KINDS.has(kind)
14209
+ })).sort((a, b) => b.bytes - a.bytes),
14210
+ by_day: summary.by_day.map((d) => ({
14211
+ date: d.date,
14212
+ bytes: d.bytes_saved,
14213
+ tokens: d.tokens_saved,
14214
+ events: d.events
14215
+ })),
14216
+ by_project: [],
14217
+ by_source: Object.entries(summary.by_source).filter(([, b]) => b.events > 0).map(([source, bucket]) => ({
14218
+ source,
14219
+ bytes: bucket.bytes_saved,
14220
+ tokens: bucket.tokens_saved,
14221
+ events: bucket.events
14222
+ })).sort((a, b) => b.bytes - a.bytes),
14223
+ by_command: summary.by_command.map((c) => ({
14224
+ command: c.command,
14225
+ bytes: c.bytes_saved,
14226
+ tokens: c.tokens_saved,
14227
+ events: c.events
14228
+ }))
14229
+ };
14230
+ process.stdout.write(renderStats(statsData) + "\n");
14231
+ }
12881
14232
 
12882
14233
  // src/cli_doctor.ts
12883
14234
  init_define_import_meta_env();
12884
14235
  import * as fs12 from "fs";
12885
- import * as path11 from "path";
14236
+ import * as path13 from "path";
12886
14237
  import { execSync } from "child_process";
12887
14238
  function checkWorkerRunning() {
12888
14239
  try {
@@ -12893,7 +14244,7 @@ function checkWorkerRunning() {
12893
14244
  }
12894
14245
  }
12895
14246
  function checkDbExists(dataDir2) {
12896
- const dbPath = path11.join(dataDir2, "index.db");
14247
+ const dbPath = path13.join(dataDir2, "index.db");
12897
14248
  if (!fs12.existsSync(dbPath)) {
12898
14249
  return {
12899
14250
  name: "Database",
@@ -12966,9 +14317,9 @@ function runDoctor(dataDir2, configPath2) {
12966
14317
  results.push(checkInstall());
12967
14318
  results.push(checkWorkerRunning() ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
12968
14319
  const homeDir = process.env["HOME"] || process.env["USERPROFILE"] || "~";
12969
- const actualDataDir = dataDir2 || path11.join(homeDir, ".token-goat");
14320
+ const actualDataDir = dataDir2 || path13.join(homeDir, ".token-goat");
12970
14321
  results.push(checkDbExists(actualDataDir));
12971
- const actualConfigPath = configPath2 || path11.join(actualDataDir, "config.json");
14322
+ const actualConfigPath = configPath2 || path13.join(actualDataDir, "config.json");
12972
14323
  results.push(checkConfigValid(actualConfigPath));
12973
14324
  results.push(checkDiskSpace(actualDataDir));
12974
14325
  return results;
@@ -13258,7 +14609,7 @@ function cmdWorkerStatus() {
13258
14609
  out(isWorkerRunning() ? "Worker is running." : "Worker is not running.");
13259
14610
  }
13260
14611
  function cmdStats() {
13261
- renderStats({ windowDays: 30 });
14612
+ renderStats2({ windowDays: 30 });
13262
14613
  }
13263
14614
  function cmdDoctor() {
13264
14615
  const code = runDoctorAndExit();
@@ -13266,14 +14617,12 @@ function cmdDoctor() {
13266
14617
  throw new CliError("doctor checks failed");
13267
14618
  }
13268
14619
  }
13269
- function cmdBashOutput(id, opts) {
13270
- const entry = getBashOutput(id);
13271
- if (entry === null) {
13272
- throw new CliError(`no cached bash output for id: ${id}`);
13273
- }
13274
- let content = entry.output;
14620
+ function _applyFiltersAndPrint(content, opts) {
13275
14621
  if (opts.grep !== void 0) {
13276
- const pattern = opts.grep;
14622
+ let pattern = opts.grep;
14623
+ if (pattern.startsWith("-E ") || pattern.startsWith("--extended-regexp ")) {
14624
+ pattern = pattern.replace(/^(?:-E\s+|--extended-regexp\s+)/, "");
14625
+ }
13277
14626
  try {
13278
14627
  const re = new RegExp(pattern);
13279
14628
  content = content.split(/\r?\n/).filter((line) => re.test(line)).join("\n");
@@ -13294,6 +14643,26 @@ function cmdBashOutput(id, opts) {
13294
14643
  }
13295
14644
  out(result.join("\n"));
13296
14645
  }
14646
+ function cmdBashOutput(id, opts) {
14647
+ if (opts.file !== void 0) {
14648
+ let content;
14649
+ try {
14650
+ content = fs13.readFileSync(opts.file, "utf-8");
14651
+ } catch {
14652
+ throw new CliError(`cannot read file: ${opts.file}`);
14653
+ }
14654
+ _applyFiltersAndPrint(content, opts);
14655
+ return;
14656
+ }
14657
+ if (id === void 0) {
14658
+ throw new CliError("provide an <id> or --file <path>");
14659
+ }
14660
+ const entry = getBashOutput(id);
14661
+ if (entry === null) {
14662
+ throw new CliError(`no cached bash output for id: ${id}`);
14663
+ }
14664
+ _applyFiltersAndPrint(entry.output, opts);
14665
+ }
13297
14666
  async function cmdSkillBody(name, opts) {
13298
14667
  const filePath = await getSkillFilePath(name);
13299
14668
  if (filePath === null) {
@@ -13445,7 +14814,7 @@ function buildProgram() {
13445
14814
  worker.command("status").description("check if the indexer is running").action(guard(cmdWorkerStatus));
13446
14815
  program2.command("stats").description("show session statistics").action(guard(cmdStats));
13447
14816
  program2.command("doctor").description("diagnose token-goat health").action(guard(cmdDoctor));
13448
- program2.command("bash-output <id>").description("retrieve cached bash output by ID").option("--head <n>", "show first N lines").option("--tail <n>", "show last N lines").option("--grep <pattern>", "filter lines matching regex").action(guard(cmdBashOutput));
14817
+ program2.command("bash-output [id]").description("retrieve cached bash output by ID or file").option("--head <n>", "show first N lines").option("--tail <n>", "show last N lines").option("--grep <pattern>", "filter lines matching regex").option("--file <path>", "read from raw output file instead of cache").action(guard(cmdBashOutput));
13449
14818
  program2.command("skill-body <name>").description("retrieve a skill's cached body").option("-c, --compact", "print compact slice instead of full body").action(guard(cmdSkillBody));
13450
14819
  program2.command("skill-compact <name>").description("regenerate and cache compact slice for a skill").action(guard(cmdSkillCompact));
13451
14820
  program2.command("skill-list").description("list all cached skills with token counts").option("-j, --json", "output as JSON").option("--session-id <id>", "filter by session").action(guard(cmdSkillList));