token-goat 2.0.0 → 2.0.2

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 +1746 -188
  3. package/package.json +1 -1
@@ -981,8 +981,8 @@ 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");
985
- var fs14 = __require("node:fs");
984
+ var path14 = __require("node:path");
985
+ var fs15 = __require("node:fs");
986
986
  var process2 = __require("node:process");
987
987
  var { Argument: Argument2, humanReadableArgName } = require_argument();
988
988
  var { CommanderError: CommanderError2 } = require_error();
@@ -1914,11 +1914,11 @@ 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);
1918
- if (fs14.existsSync(localBin)) return localBin;
1919
- if (sourceExt.includes(path12.extname(baseName))) return void 0;
1917
+ const localBin = path14.resolve(baseDir, baseName);
1918
+ if (fs15.existsSync(localBin)) return localBin;
1919
+ if (sourceExt.includes(path14.extname(baseName))) return void 0;
1920
1920
  const foundExt = sourceExt.find(
1921
- (ext) => fs14.existsSync(`${localBin}${ext}`)
1921
+ (ext) => fs15.existsSync(`${localBin}${ext}`)
1922
1922
  );
1923
1923
  if (foundExt) return `${localBin}${foundExt}`;
1924
1924
  return void 0;
@@ -1930,21 +1930,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1930
1930
  if (this._scriptPath) {
1931
1931
  let resolvedScriptPath;
1932
1932
  try {
1933
- resolvedScriptPath = fs14.realpathSync(this._scriptPath);
1933
+ resolvedScriptPath = fs15.realpathSync(this._scriptPath);
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
  /**
@@ -3158,27 +3158,27 @@ var require_filesystem = __commonJS({
3158
3158
  "node_modules/detect-libc/lib/filesystem.js"(exports, module) {
3159
3159
  "use strict";
3160
3160
  init_define_import_meta_env();
3161
- var fs14 = __require("fs");
3161
+ var fs15 = __require("fs");
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 = fs15.openSync(path14, "r");
3167
3167
  const buffer = Buffer.alloc(MAX_LENGTH);
3168
- const bytesRead = fs14.readSync(fd, buffer, 0, MAX_LENGTH, 0);
3169
- fs14.close(fd, () => {
3168
+ const bytesRead = fs15.readSync(fd, buffer, 0, MAX_LENGTH, 0);
3169
+ fs15.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 readFile3 = (path14) => new Promise((resolve5, reject) => {
3174
+ fs15.open(path14, "r", (err2, fd) => {
3175
3175
  if (err2) {
3176
3176
  reject(err2);
3177
3177
  } else {
3178
3178
  const buffer = Buffer.alloc(MAX_LENGTH);
3179
- fs14.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
3180
- resolve4(buffer.subarray(0, bytesRead));
3181
- fs14.close(fd, () => {
3179
+ fs15.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
3180
+ resolve5(buffer.subarray(0, bytesRead));
3181
+ fs15.close(fd, () => {
3182
3182
  });
3183
3183
  });
3184
3184
  }
@@ -3187,8 +3187,8 @@ var require_filesystem = __commonJS({
3187
3187
  module.exports = {
3188
3188
  LDD_PATH,
3189
3189
  SELF_PATH,
3190
- readFileSync: readFileSync10,
3191
- readFile: readFile2
3190
+ readFileSync: readFileSync11,
3191
+ readFile: readFile3
3192
3192
  };
3193
3193
  }
3194
3194
  });
@@ -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: readFile3, readFileSync: readFileSync11 } = require_filesystem();
3242
3242
  var { interpreterPath } = require_elf();
3243
3243
  var cachedFamilyInterpreter;
3244
3244
  var cachedFamilyFilesystem;
@@ -3247,10 +3247,10 @@ var require_detect_libc = __commonJS({
3247
3247
  var commandOut = "";
3248
3248
  var safeCommand = () => {
3249
3249
  if (!commandOut) {
3250
- return new Promise((resolve4) => {
3250
+ return new Promise((resolve5) => {
3251
3251
  childProcess.exec(command, (err2, out2) => {
3252
3252
  commandOut = err2 ? " " : out2;
3253
- resolve4(commandOut);
3253
+ resolve5(commandOut);
3254
3254
  });
3255
3255
  });
3256
3256
  }
@@ -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
  }
@@ -3318,7 +3318,7 @@ var require_detect_libc = __commonJS({
3318
3318
  }
3319
3319
  cachedFamilyFilesystem = null;
3320
3320
  try {
3321
- const lddContent = await readFile2(LDD_PATH);
3321
+ const lddContent = await readFile3(LDD_PATH);
3322
3322
  cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
3323
3323
  } catch (e) {
3324
3324
  }
@@ -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
  }
@@ -3342,9 +3342,9 @@ var require_detect_libc = __commonJS({
3342
3342
  }
3343
3343
  cachedFamilyInterpreter = null;
3344
3344
  try {
3345
- const selfContent = await readFile2(SELF_PATH);
3346
- const path12 = interpreterPath(selfContent);
3347
- cachedFamilyInterpreter = familyFromInterpreterPath(path12);
3345
+ const selfContent = await readFile3(SELF_PATH);
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;
@@ -3404,7 +3404,7 @@ var require_detect_libc = __commonJS({
3404
3404
  }
3405
3405
  cachedVersionFilesystem = null;
3406
3406
  try {
3407
- const lddContent = await readFile2(LDD_PATH);
3407
+ const lddContent = await readFile3(LDD_PATH);
3408
3408
  const versionMatch = lddContent.match(RE_GLIBC_VERSION);
3409
3409
  if (versionMatch) {
3410
3410
  cachedVersionFilesystem = versionMatch[1];
@@ -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) {
@@ -7392,14 +7392,14 @@ var require_input = __commonJS({
7392
7392
  return this;
7393
7393
  } else {
7394
7394
  if (this._isStreamInput()) {
7395
- return new Promise((resolve4, reject) => {
7395
+ return new Promise((resolve5, reject) => {
7396
7396
  const finished = () => {
7397
7397
  this._flattenBufferIn();
7398
7398
  sharp.metadata(this.options, (err2, metadata2) => {
7399
7399
  if (err2) {
7400
7400
  reject(is.nativeError(err2, stack));
7401
7401
  } else {
7402
- resolve4(metadata2);
7402
+ resolve5(metadata2);
7403
7403
  }
7404
7404
  });
7405
7405
  };
@@ -7410,12 +7410,12 @@ var require_input = __commonJS({
7410
7410
  }
7411
7411
  });
7412
7412
  } else {
7413
- return new Promise((resolve4, reject) => {
7413
+ return new Promise((resolve5, reject) => {
7414
7414
  sharp.metadata(this.options, (err2, metadata2) => {
7415
7415
  if (err2) {
7416
7416
  reject(is.nativeError(err2, stack));
7417
7417
  } else {
7418
- resolve4(metadata2);
7418
+ resolve5(metadata2);
7419
7419
  }
7420
7420
  });
7421
7421
  });
@@ -7448,25 +7448,25 @@ var require_input = __commonJS({
7448
7448
  return this;
7449
7449
  } else {
7450
7450
  if (this._isStreamInput()) {
7451
- return new Promise((resolve4, reject) => {
7451
+ return new Promise((resolve5, reject) => {
7452
7452
  this.on("finish", function() {
7453
7453
  this._flattenBufferIn();
7454
7454
  sharp.stats(this.options, (err2, stats2) => {
7455
7455
  if (err2) {
7456
7456
  reject(is.nativeError(err2, stack));
7457
7457
  } else {
7458
- resolve4(stats2);
7458
+ resolve5(stats2);
7459
7459
  }
7460
7460
  });
7461
7461
  });
7462
7462
  });
7463
7463
  } else {
7464
- return new Promise((resolve4, reject) => {
7464
+ return new Promise((resolve5, reject) => {
7465
7465
  sharp.stats(this.options, (err2, stats2) => {
7466
7466
  if (err2) {
7467
7467
  reject(is.nativeError(err2, stack));
7468
7468
  } else {
7469
- resolve4(stats2);
7469
+ resolve5(stats2);
7470
7470
  }
7471
7471
  });
7472
7472
  });
@@ -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) {
@@ -9242,7 +9242,7 @@ var require_output = __commonJS({
9242
9242
  return this;
9243
9243
  } else {
9244
9244
  if (this._isStreamInput()) {
9245
- return new Promise((resolve4, reject) => {
9245
+ return new Promise((resolve5, reject) => {
9246
9246
  this.once("finish", () => {
9247
9247
  this._flattenBufferIn();
9248
9248
  sharp.pipeline(this.options, (err2, data, info) => {
@@ -9250,24 +9250,24 @@ var require_output = __commonJS({
9250
9250
  reject(is.nativeError(err2, stack));
9251
9251
  } else {
9252
9252
  if (this.options.resolveWithObject) {
9253
- resolve4({ data, info });
9253
+ resolve5({ data, info });
9254
9254
  } else {
9255
- resolve4(data);
9255
+ resolve5(data);
9256
9256
  }
9257
9257
  }
9258
9258
  });
9259
9259
  });
9260
9260
  });
9261
9261
  } else {
9262
- return new Promise((resolve4, reject) => {
9262
+ return new Promise((resolve5, reject) => {
9263
9263
  sharp.pipeline(this.options, (err2, data, info) => {
9264
9264
  if (err2) {
9265
9265
  reject(is.nativeError(err2, stack));
9266
9266
  } else {
9267
9267
  if (this.options.resolveWithObject) {
9268
- resolve4({ data, info });
9268
+ resolve5({ data, info });
9269
9269
  } else {
9270
- resolve4(data);
9270
+ resolve5(data);
9271
9271
  }
9272
9272
  }
9273
9273
  });
@@ -9475,7 +9475,7 @@ var {
9475
9475
  } = import_index.default;
9476
9476
 
9477
9477
  // src/cli.ts
9478
- import * as fs13 from "fs";
9478
+ import * as fs14 from "fs";
9479
9479
 
9480
9480
  // src/baseline.ts
9481
9481
  init_define_import_meta_env();
@@ -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.2";
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,12 @@ function wasFileReadThisSession(filePath) {
10297
10297
  function getSessionWebFetches() {
10298
10298
  return _webFetches;
10299
10299
  }
10300
+ function recordBashOutput(commandHash2, outputId, _sizeBytes) {
10301
+ _bashOutputs.set(commandHash2, outputId);
10302
+ }
10303
+ function getBashOutputId(commandHash2) {
10304
+ return _bashOutputs.get(commandHash2) ?? null;
10305
+ }
10300
10306
  registerReset(() => {
10301
10307
  _files = /* @__PURE__ */ new Map();
10302
10308
  _hintsShown = /* @__PURE__ */ new Set();
@@ -10362,6 +10368,7 @@ var HOOK_EVENTS = [
10362
10368
  // src/hooks_read.ts
10363
10369
  init_define_import_meta_env();
10364
10370
  import * as fs4 from "node:fs";
10371
+ import * as path6 from "node:path";
10365
10372
 
10366
10373
  // src/hooks_common.ts
10367
10374
  init_define_import_meta_env();
@@ -10378,12 +10385,495 @@ function getFilePath(event) {
10378
10385
  function passOutput() {
10379
10386
  return { hookType: "pass" };
10380
10387
  }
10388
+ function denyOutput(message) {
10389
+ return { hookType: "deny", message };
10390
+ }
10381
10391
  function contextOutput(context) {
10382
10392
  return { hookType: "context", context };
10383
10393
  }
10384
10394
 
10395
+ // src/hints.ts
10396
+ init_define_import_meta_env();
10397
+ var HINT_PRIORITY_MEDIUM = 3;
10398
+ var STALE_READ_AGE_SECONDS = 30 * 60;
10399
+ function buildPackageManifestHint(options) {
10400
+ try {
10401
+ const hasOffset = options.offset !== null && options.offset !== void 0 && options.offset >= 0;
10402
+ const hasLimit = options.limit !== null && options.limit !== void 0 && options.limit > 0;
10403
+ if (hasOffset || hasLimit) {
10404
+ return null;
10405
+ }
10406
+ const fname = _sanitizeHintPath(options.file_path.split(/[/\\]/).pop() ?? "");
10407
+ const basenameLower = fname.toLowerCase();
10408
+ if (basenameLower === "package.json") {
10409
+ const text = `\`${fname}\` is a package manifest. Consider \`token-goat section package.json::dependencies\` or \`token-goat section package.json::devDependencies\` for focused reads.`;
10410
+ return {
10411
+ text,
10412
+ hint_priority: HINT_PRIORITY_MEDIUM
10413
+ };
10414
+ }
10415
+ if (basenameLower === "package-lock.json") {
10416
+ const text = `\`${fname}\` is a large lockfile. Consider \`npm ls\`, \`npm outdated\`, or \`npm audit\` instead for targeted info.`;
10417
+ return {
10418
+ text,
10419
+ hint_priority: HINT_PRIORITY_MEDIUM
10420
+ };
10421
+ }
10422
+ return null;
10423
+ } catch {
10424
+ return null;
10425
+ }
10426
+ }
10427
+ function _sanitizeHintPath(path14) {
10428
+ if (typeof path14 !== "string") {
10429
+ return "???";
10430
+ }
10431
+ return path14.replace(/[\x00]/g, "").slice(0, 200);
10432
+ }
10433
+
10434
+ // src/hints/lang_patterns.ts
10435
+ init_define_import_meta_env();
10436
+ var LOCK_FILE_NAMES = /* @__PURE__ */ new Set([
10437
+ "package-lock.json",
10438
+ "yarn.lock",
10439
+ "pnpm-lock.yaml",
10440
+ "cargo.lock",
10441
+ "poetry.lock",
10442
+ "pipfile.lock",
10443
+ "uv.lock",
10444
+ "gemfile.lock",
10445
+ "go.sum",
10446
+ "composer.lock",
10447
+ "mix.lock",
10448
+ "pubspec.lock",
10449
+ "package.resolved"
10450
+ ]);
10451
+ var MANIFEST_FILE_NAMES = /* @__PURE__ */ new Set([
10452
+ "package.json",
10453
+ "pyproject.toml",
10454
+ "cargo.toml",
10455
+ "go.mod",
10456
+ "go.work",
10457
+ "pom.xml",
10458
+ "build.gradle",
10459
+ "build.gradle.kts",
10460
+ "settings.gradle",
10461
+ "composer.json",
10462
+ "gemfile",
10463
+ "mix.exs",
10464
+ "pubspec.yaml",
10465
+ "cmakelists.txt",
10466
+ "makefile",
10467
+ "project.clj",
10468
+ // TypeScript / JavaScript project configs
10469
+ "tsconfig.json",
10470
+ "jsconfig.json",
10471
+ // Bundler / framework configs
10472
+ "vite.config.ts",
10473
+ "vite.config.js",
10474
+ "webpack.config.js",
10475
+ "webpack.config.ts",
10476
+ "rollup.config.js",
10477
+ "rollup.config.ts",
10478
+ "esbuild.config.js",
10479
+ "next.config.js",
10480
+ "next.config.ts",
10481
+ "nuxt.config.ts"
10482
+ ]);
10483
+ var MANIFEST_EXTENSIONS = /* @__PURE__ */ new Set([
10484
+ ".cabal"
10485
+ ]);
10486
+ var MANIFEST_BASENAME_PATTERNS = [
10487
+ /^tsconfig(\..+)?\.json$/i
10488
+ ];
10489
+ var BUILD_DIR_NAMES = /* @__PURE__ */ new Set([
10490
+ "dist",
10491
+ "target",
10492
+ "build",
10493
+ "out",
10494
+ "__pycache__",
10495
+ ".next",
10496
+ ".nuxt",
10497
+ ".output",
10498
+ ".gradle",
10499
+ "_build",
10500
+ ".build",
10501
+ "pkg",
10502
+ "obj"
10503
+ ]);
10504
+ var ALWAYS_GENERATED_EXTS = /* @__PURE__ */ new Set([
10505
+ ".pyc",
10506
+ ".pyo",
10507
+ ".pyd",
10508
+ ".class",
10509
+ ".o",
10510
+ ".a",
10511
+ ".so",
10512
+ ".dylib",
10513
+ ".dll",
10514
+ ".tsbuildinfo"
10515
+ ]);
10516
+ var CONDITIONALLY_GENERATED_EXTS = /* @__PURE__ */ new Set([
10517
+ ".map",
10518
+ ".d.ts"
10519
+ ]);
10520
+ var BUILD_COMMAND_PATTERNS = [
10521
+ // Rust / Cargo
10522
+ /^\s*cargo\s+(build|test|run|check|clippy)\b/i,
10523
+ // Go
10524
+ /^\s*go\s+(build|test|run|vet)\b/i,
10525
+ // Maven
10526
+ /^\s*mvn\b/i,
10527
+ // Gradle (direct or wrapper)
10528
+ /^\s*(?:gradle|\.\/gradlew|gradlew)\b/i,
10529
+ // Python / pip
10530
+ /^\s*pip\s+(install|freeze)\b/i,
10531
+ // Poetry
10532
+ /^\s*poetry\s+(install|update)\b/i,
10533
+ // uv
10534
+ /^\s*uv\s+(sync|pip\s+install)\b/i,
10535
+ // Bundler (Ruby)
10536
+ /^\s*bundle\s+(install|update)\b/i,
10537
+ // Mix (Elixir)
10538
+ /^\s*mix\s+(deps\.get|compile|test)\b/i,
10539
+ // dotnet
10540
+ /^\s*dotnet\s+(build|test|restore)\b/i,
10541
+ // Make
10542
+ /^\s*make\b/i,
10543
+ // CMake build
10544
+ /^\s*cmake\s+--build\b/i,
10545
+ // Rake (Ruby)
10546
+ /^\s*rake\b/i,
10547
+ // TypeScript compiler
10548
+ /^\s*tsc\b/i,
10549
+ // Vite
10550
+ /^\s*vite\s+(build|dev|preview)\b/i,
10551
+ // Next.js
10552
+ /^\s*next\s+(build|dev|start)\b/i,
10553
+ // Nuxt
10554
+ /^\s*nuxt\s+(build|dev)\b/i,
10555
+ // Webpack
10556
+ /^\s*webpack\b/i,
10557
+ // esbuild
10558
+ /^\s*esbuild\b/i,
10559
+ // Rollup
10560
+ /^\s*rollup\b/i,
10561
+ // Turbo
10562
+ /^\s*turbo\s+(build|dev)\b/i
10563
+ ];
10564
+ function isLockFile(basename6) {
10565
+ return LOCK_FILE_NAMES.has(basename6.toLowerCase());
10566
+ }
10567
+ function isManifestFile(basename6) {
10568
+ const lower = basename6.toLowerCase();
10569
+ if (MANIFEST_FILE_NAMES.has(lower)) return true;
10570
+ const dot = lower.lastIndexOf(".");
10571
+ if (dot !== -1 && MANIFEST_EXTENSIONS.has(lower.slice(dot))) return true;
10572
+ if (MANIFEST_BASENAME_PATTERNS.some((re) => re.test(basename6))) return true;
10573
+ return false;
10574
+ }
10575
+ function pathSegments(filePath) {
10576
+ return filePath.split(/[/\\]/).filter((s) => s.length > 0);
10577
+ }
10578
+ function isInBuildDir(filePath) {
10579
+ const segments = pathSegments(filePath);
10580
+ for (let i = 0; i < segments.length - 1; i++) {
10581
+ const seg = segments[i];
10582
+ if (seg !== void 0 && BUILD_DIR_NAMES.has(seg.toLowerCase())) return true;
10583
+ }
10584
+ return false;
10585
+ }
10586
+ function isGeneratedFile(filePath) {
10587
+ const lower = filePath.toLowerCase();
10588
+ for (const ext of ALWAYS_GENERATED_EXTS) {
10589
+ if (lower.endsWith(ext)) return true;
10590
+ }
10591
+ for (const ext of CONDITIONALLY_GENERATED_EXTS) {
10592
+ if (lower.endsWith(ext) && isInBuildDir(filePath)) return true;
10593
+ }
10594
+ return false;
10595
+ }
10596
+ function isBuildCommand(cmd) {
10597
+ return BUILD_COMMAND_PATTERNS.some((re) => re.test(cmd));
10598
+ }
10599
+ var MONITORING_COMMAND_PATTERNS = [
10600
+ // GitHub CI
10601
+ { pattern: /^gh run (?:watch|view|list)/, recallHint: '--grep "fail|error|pass|\u2713|\u2717|conclusion"' },
10602
+ { pattern: /^gh run view.*--log/, recallHint: '--tail 100 --grep "Error|FAIL|error"' },
10603
+ { pattern: /^gh pr checks/, recallHint: '--grep "fail|error|pass|pending"' },
10604
+ { pattern: /^gh workflow (?:run|list|view)/, recallHint: '--grep "completed|failed|in_progress"' },
10605
+ // Dev servers (Next, Vite, Nuxt, Remix, Astro)
10606
+ { pattern: /^(?:npx\s+)?next dev/, recallHint: '--tail 30 --grep "error|warn|ready|compiled"' },
10607
+ { pattern: /^(?:npx\s+)?next build/, recallHint: '--grep "error|warn|Failed|\u2713"' },
10608
+ { pattern: /^(?:npx\s+)?vite(?:\s+dev|\s+build|\s+preview)?$/, recallHint: '--tail 20 --grep "error|warn|ready"' },
10609
+ { pattern: /^(?:npx\s+)?nuxt dev/, recallHint: '--tail 30 --grep "error|warn|ready"' },
10610
+ { pattern: /^(?:npx\s+)?remix dev/, recallHint: '--tail 20 --grep "error|warn|ready"' },
10611
+ { pattern: /^(?:npx\s+)?astro dev/, recallHint: '--tail 20 --grep "error|warn|ready"' },
10612
+ // Test watchers
10613
+ { pattern: /^(?:npx\s+)?vitest(?:\s+run|\s+watch)?/, recallHint: '--grep "FAIL|PASS|Error|\u2713|\u2717"' },
10614
+ { pattern: /^(?:npx\s+)?jest(?:\s+--watch)?/, recallHint: '--grep "FAIL|PASS|Error|Tests:"' },
10615
+ { pattern: /^pytest(?:\s|$)/, recallHint: '--grep "FAILED|PASSED|ERROR|passed|failed"' },
10616
+ { pattern: /^(?:cargo\s+test|cargo\s+watch)/, recallHint: '--grep "FAILED|ok|error\\["' },
10617
+ { pattern: /^go test/, recallHint: '--grep "FAIL|ok|---"' },
10618
+ // Docker / compose
10619
+ { pattern: /^docker(?:\s+compose)?\s+logs/, recallHint: '--tail 50 --grep "error|warn|Error|WARN"' },
10620
+ { pattern: /^docker-compose\s+logs/, recallHint: '--tail 50 --grep "error|warn|Error|WARN"' },
10621
+ // File watchers / hot-reload
10622
+ { pattern: /^nodemon/, recallHint: '--tail 20 --grep "error|crash|restart"' },
10623
+ { pattern: /^air(?:\s|$)/, recallHint: '--tail 20 --grep "error|build failed"' },
10624
+ { pattern: /^cargo watch/, recallHint: '--tail 20 --grep "error\\[|warning\\["' },
10625
+ { pattern: /^watchexec/, recallHint: '--tail 20 --grep "error|warn"' },
10626
+ // Linters / formatters run repeatedly
10627
+ { pattern: /^(?:npx\s+)?eslint(?:\s|$)/, recallHint: '--grep "error|warning|\u2716|problems"' },
10628
+ { pattern: /^(?:npx\s+)?prettier(?:\s|$)/, recallHint: '--grep "unchanged|reformatted|error"' },
10629
+ { pattern: /^ruff(?:\s|$)/, recallHint: '--grep "error|warning|Found"' },
10630
+ { pattern: /^(?:cargo\s+)?clippy/, recallHint: '--grep "error\\[|warning\\["' },
10631
+ // git diff (full diff output — can be very large; excludes --stat which is small)
10632
+ { pattern: /^git diff(?!\s+--stat)(?:\s+HEAD)?(?:\s|$)/, recallHint: '--grep "@@|\\+\\+\\+|---|diff --git"' },
10633
+ { pattern: /^git diff\s+--cached(?!\s+--stat)/, recallHint: '--grep "@@|\\+\\+\\+|---|diff --git"' },
10634
+ // npm run * wrappers (npm test is excluded — too generic; npm run test is explicit)
10635
+ { pattern: /^npm run (?:test|spec)(?:\s|$)/, recallHint: '--grep "FAIL|PASS|Error|Tests:|\u2713|\u2717"' },
10636
+ { pattern: /^npm run build(?:\s|$)/, recallHint: '--grep "error|Built|Failed|\u2713|\u2717"' },
10637
+ { pattern: /^npm run (?:lint|typecheck|check|type-check)(?:\s|$)/, recallHint: '--grep "error|warning|\u2716|problems"' },
10638
+ // External AI peer-review CLI tools (produce large outputs, run repeatedly per session)
10639
+ { pattern: /^codex(?:\s|$)/, recallHint: '--tail 100 --grep "error|suggestion|verdict|conclusion"' },
10640
+ { pattern: /^(?:~\/\.claude\/bin\/|\.claude\/bin\/)?glm\.sh(?:\s|$)/, recallHint: '--tail 100 --grep "error|verdict|conclusion|suggestion"' },
10641
+ // cat of a single source file — output is the full file; pre-bash emits a token-goat read suggestion
10642
+ { pattern: /^cat\s+\S+\.(java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj)\s*$/, recallHint: "--tail 50" }
10643
+ ];
10644
+ function getMonitoringRecallHint(cmd) {
10645
+ for (const { pattern, recallHint } of MONITORING_COMMAND_PATTERNS) {
10646
+ if (pattern.test(cmd.trim())) return recallHint;
10647
+ }
10648
+ return null;
10649
+ }
10650
+
10651
+ // src/hints/markdown_hints.ts
10652
+ init_define_import_meta_env();
10653
+ var MARKDOWN_SIZE_THRESHOLD = 8e3;
10654
+ var MAX_HEADINGS = 40;
10655
+ var MAX_OUTPUT_LINES = 60;
10656
+ function extractMarkdownHeadings(content) {
10657
+ const headings = [];
10658
+ const lines = content.split("\n");
10659
+ for (let i = 0; i < lines.length; i++) {
10660
+ const line = lines[i];
10661
+ if (!line) continue;
10662
+ const match = /^(#+)\s+(.+?)\s*$/.exec(line);
10663
+ if (!match || match.length < 3) continue;
10664
+ const hashes = match[1];
10665
+ const headingText = match[2];
10666
+ const level = hashes.length;
10667
+ if (level > 3) continue;
10668
+ const text = headingText.trim();
10669
+ if (!text) continue;
10670
+ headings.push({
10671
+ level,
10672
+ text,
10673
+ lineNumber: i + 1
10674
+ });
10675
+ if (headings.length >= MAX_HEADINGS) break;
10676
+ }
10677
+ return headings;
10678
+ }
10679
+ function formatHeadingTree(headings, filePath) {
10680
+ if (headings.length === 0) return "";
10681
+ const seenTexts = /* @__PURE__ */ new Map();
10682
+ const dedupedHeadings = [];
10683
+ for (const h of headings) {
10684
+ const count = (seenTexts.get(h.text) ?? 0) + 1;
10685
+ seenTexts.set(h.text, count);
10686
+ const suffix = count > 1 ? ` #${count}` : "";
10687
+ dedupedHeadings.push({
10688
+ text: h.text + suffix,
10689
+ level: h.level
10690
+ });
10691
+ }
10692
+ const lines = [];
10693
+ lines.push(`Large markdown file (${headings.length} headings). Use token-goat section to read a specific section:`);
10694
+ lines.push(` token-goat section "${filePath}::Heading Name"`);
10695
+ lines.push(``);
10696
+ lines.push(`Sections:`);
10697
+ let headingsAdded = 0;
10698
+ for (const h of dedupedHeadings) {
10699
+ if (lines.length + 1 >= MAX_OUTPUT_LINES) {
10700
+ const remaining = dedupedHeadings.length - headingsAdded;
10701
+ lines.push(` ... (${remaining} more headings)`);
10702
+ break;
10703
+ }
10704
+ const indent = h.level === 1 ? "" : h.level === 2 ? " " : " ";
10705
+ const marker = "#".repeat(h.level);
10706
+ lines.push(` ${indent}${marker} ${h.text}`);
10707
+ headingsAdded++;
10708
+ }
10709
+ return lines.join("\n");
10710
+ }
10711
+ var WELL_KNOWN_SECTIONS = {
10712
+ "CHANGELOG.md": ["Unreleased"],
10713
+ "README.md": ["Install", "Usage", "API", "Configuration", "Getting Started"],
10714
+ "CONTRIBUTING.md": ["Setup", "Commands", "Testing", "Development"],
10715
+ "CLAUDE.md": ["Commands", "Architecture"],
10716
+ "CLAUDE.arch.md": ["Component Map", "Architecture"]
10717
+ };
10718
+ function getWellKnownSections(basename6) {
10719
+ return WELL_KNOWN_SECTIONS[basename6] ?? [];
10720
+ }
10721
+ function extractChangelogVersionHint(content, filePath) {
10722
+ const lines = content.split("\n");
10723
+ let foundUnreleased = false;
10724
+ for (const line of lines) {
10725
+ const m = /^##\s+(\[?[\d]+\.[\d]+\.[\d]+\]?)/.exec(line);
10726
+ if (m) {
10727
+ if (foundUnreleased || !line.toLowerCase().includes("unreleased")) {
10728
+ const ver = m[1];
10729
+ return ` | token-goat section "${filePath}::${ver}"`;
10730
+ }
10731
+ }
10732
+ if (/^##\s+\[?unreleased\]?/i.test(line)) {
10733
+ foundUnreleased = true;
10734
+ }
10735
+ }
10736
+ return "";
10737
+ }
10738
+
10739
+ // src/hints/file_type_handler.ts
10740
+ init_define_import_meta_env();
10741
+ var FILE_TYPE_THRESHOLDS = {
10742
+ pdf: 0,
10743
+ // always intercept (any size)
10744
+ html: 5e4,
10745
+ txt: 2e4,
10746
+ csv: 1e4,
10747
+ tsv: 1e4,
10748
+ office: 0,
10749
+ // always block (.docx etc)
10750
+ generic: 1e5
10751
+ // catch-all for unrecognized large files
10752
+ };
10753
+ function formatBytes(n) {
10754
+ if (n < 1024) return `${n} B`;
10755
+ if (n < 1048576) return `${(n / 1024).toFixed(1)} KB`;
10756
+ return `${(n / 1048576).toFixed(1)} MB`;
10757
+ }
10758
+ function handlePdf(filePath, contentLength) {
10759
+ return {
10760
+ shouldBlock: true,
10761
+ message: [
10762
+ `PDF file (${formatBytes(contentLength)}).`,
10763
+ `Use the \`pages\` parameter to scope the read: Read({ file_path: "${filePath}", pages: "1-5" })`,
10764
+ `For the full page count, run: pdfinfo "${filePath}" | grep Pages`
10765
+ ].join("\n")
10766
+ };
10767
+ }
10768
+ function handleHtml(filePath, content) {
10769
+ if (content.length < FILE_TYPE_THRESHOLDS.html) return { shouldBlock: false, message: "" };
10770
+ const title = content.match(/<title[^>]*>([^<]+)<\/title>/i)?.[1]?.trim();
10771
+ const headings = [...content.matchAll(/<h([1-6])[^>]*>([^<]+)<\/h\1>/gi)].slice(0, 20).map((m) => {
10772
+ const level = m[1];
10773
+ const text = m[2];
10774
+ if (!level || !text) return "";
10775
+ return `${" ".repeat(Number(level) - 1)}h${level}: ${text.trim()}`;
10776
+ }).filter(Boolean);
10777
+ const isMinified = !content.includes("\n") || content.length > 5e4 && content.split("\n").length < 10;
10778
+ if (isMinified) {
10779
+ return {
10780
+ shouldBlock: true,
10781
+ message: `HTML file appears minified (${formatBytes(content.length)}). Consider fetching the source or converting with: pandoc "${filePath}" -t plain`
10782
+ };
10783
+ }
10784
+ return {
10785
+ shouldBlock: true,
10786
+ message: [
10787
+ `Large HTML file (${formatBytes(content.length)})${title ? `: "${title}"` : ""}.`,
10788
+ headings.length > 0 ? `Headings:
10789
+ ${headings.join("\n")}` : "",
10790
+ `Use token-goat section to extract a section by heading, or convert to text: pandoc "${filePath}" -t plain`
10791
+ ].filter(Boolean).join("\n")
10792
+ };
10793
+ }
10794
+ function handleTxt(filePath, content) {
10795
+ if (content.length < FILE_TYPE_THRESHOLDS.txt) return { shouldBlock: false, message: "" };
10796
+ const lines = content.split("\n");
10797
+ const isLog = /\.(log|out|err|trace)$/i.test(filePath) || filePath.includes("/logs/");
10798
+ const preview = [
10799
+ "--- first 5 lines ---",
10800
+ ...lines.slice(0, 5),
10801
+ `... (${lines.length.toLocaleString()} lines total) ...`,
10802
+ "--- last 5 lines ---",
10803
+ ...lines.slice(-5)
10804
+ ].join("\n");
10805
+ 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.";
10806
+ return {
10807
+ shouldBlock: true,
10808
+ message: `Large text file (${formatBytes(content.length)}, ${lines.length.toLocaleString()} lines).
10809
+ ${preview}
10810
+
10811
+ ${recall}`
10812
+ };
10813
+ }
10814
+ function handleOfficeBinary(filePath) {
10815
+ const ext = filePath.split(".").pop()?.toLowerCase();
10816
+ return {
10817
+ shouldBlock: true,
10818
+ message: [
10819
+ `Binary Office file (.${ext}) \u2014 cannot be read as text.`,
10820
+ `Extract content first: pandoc "${filePath}" -t plain > "${filePath}.txt"`,
10821
+ `Then read the extracted .txt file.`
10822
+ ].join("\n")
10823
+ };
10824
+ }
10825
+ function handleCsv(filePath, content) {
10826
+ if (content.length < FILE_TYPE_THRESHOLDS.csv) return { shouldBlock: false, message: "" };
10827
+ const lines = content.split("\n").filter((l) => l.trim());
10828
+ const headers = lines[0] ?? "";
10829
+ const sampleRows = lines.slice(1, 4);
10830
+ const sep = filePath.endsWith(".tsv") ? " " : ",";
10831
+ const colCount = headers.split(sep).length;
10832
+ return {
10833
+ shouldBlock: true,
10834
+ message: [
10835
+ `Large CSV file (${formatBytes(content.length)}, ~${lines.length.toLocaleString()} rows, ${colCount} columns).`,
10836
+ `Columns: ${headers}`,
10837
+ `Sample rows:
10838
+ ${sampleRows.join("\n")}`,
10839
+ `Use Read with offset/limit to sample rows, or query with DuckDB: duckdb -c "SELECT * FROM '${filePath}' LIMIT 10"`
10840
+ ].join("\n")
10841
+ };
10842
+ }
10843
+ function handleGenericLarge(filePath, contentLength) {
10844
+ if (contentLength < FILE_TYPE_THRESHOLDS.generic) return { shouldBlock: false, message: "" };
10845
+ return {
10846
+ shouldBlock: true,
10847
+ message: `Large file (${formatBytes(contentLength)}). Use Read with offset and limit parameters to read specific line ranges rather than loading the entire file.`
10848
+ };
10849
+ }
10850
+ function dispatchFileTypeHandler(filePath, content, contentLengthHint) {
10851
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
10852
+ if (["md", "mdx", "markdown", "rst"].includes(ext)) return null;
10853
+ const effectiveLength = contentLengthHint ?? content.length;
10854
+ if (ext === "pdf") return handlePdf(filePath, effectiveLength);
10855
+ if (["html", "htm", "xhtml"].includes(ext)) return handleHtml(filePath, content);
10856
+ if (["txt", "log", "out", "err", "trace"].includes(ext)) return handleTxt(filePath, content);
10857
+ if (["docx", "xlsx", "pptx", "odt", "ods", "ott", "odp"].includes(ext)) return handleOfficeBinary(filePath);
10858
+ if (ext === "csv" || ext === "tsv") return handleCsv(filePath, content);
10859
+ return handleGenericLarge(filePath, effectiveLength);
10860
+ }
10861
+
10385
10862
  // src/hooks_read.ts
10863
+ function isTsConfigFile(basename6) {
10864
+ const lower = basename6.toLowerCase();
10865
+ return /^tsconfig(\..+)?\.json$/i.test(lower) || lower === "jsconfig.json";
10866
+ }
10386
10867
  var LARGE_FILE_BYTES = 100 * 1024;
10868
+ function isNodeModulesPath(path14) {
10869
+ const isWindows = process.platform === "win32";
10870
+ const check = isWindows ? path14.toLowerCase() : path14;
10871
+ return check.includes("/node_modules/") || check.includes("\\node_modules\\");
10872
+ }
10873
+ function _isDocFile(filePath) {
10874
+ const lower = filePath.toLowerCase();
10875
+ return lower.endsWith(".md") || lower.endsWith(".mdx") || lower.endsWith(".markdown") || lower.endsWith(".rst");
10876
+ }
10387
10877
  function statSize(absPath) {
10388
10878
  try {
10389
10879
  return fs4.statSync(absPath).size;
@@ -10395,41 +10885,124 @@ function preReadHandler(event) {
10395
10885
  const filePath = getFilePath(event);
10396
10886
  if (filePath === void 0) return passOutput();
10397
10887
  const normalized = normalizePath(filePath);
10888
+ if (isNodeModulesPath(normalized)) {
10889
+ return denyOutput(
10890
+ "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"
10891
+ );
10892
+ }
10893
+ const basename6 = path6.basename(normalized);
10894
+ if (isLockFile(basename6)) {
10895
+ return denyOutput(
10896
+ '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.'
10897
+ );
10898
+ }
10899
+ if (normalized.toLowerCase().endsWith(".tsbuildinfo")) {
10900
+ return denyOutput(
10901
+ "This is a TypeScript incremental build cache file. You don't need to read it directly."
10902
+ );
10903
+ }
10904
+ if (isInBuildDir(normalized) || isGeneratedFile(normalized)) {
10905
+ return denyOutput(
10906
+ "Generated/build artifact \u2014 read the source file instead."
10907
+ );
10908
+ }
10909
+ const manifestHint = buildPackageManifestHint({ file_path: normalized });
10910
+ if (manifestHint) {
10911
+ recordFileRead(normalized);
10912
+ return contextOutput(manifestHint.text);
10913
+ }
10914
+ if (isTsConfigFile(basename6) && wasFileReadThisSession(normalized)) {
10915
+ recordFileRead(normalized);
10916
+ return contextOutput(
10917
+ "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."
10918
+ );
10919
+ }
10920
+ if (isManifestFile(basename6) && wasFileReadThisSession(normalized)) {
10921
+ recordFileRead(normalized);
10922
+ return contextOutput(
10923
+ "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."
10924
+ );
10925
+ }
10926
+ const isMarkdown = /\.(md|mdx|markdown|rst)$/i.test(basename6);
10927
+ if (isMarkdown) {
10928
+ let fileContent = null;
10929
+ try {
10930
+ const sz = statSize(normalized);
10931
+ if (sz !== null && sz >= MARKDOWN_SIZE_THRESHOLD) {
10932
+ fileContent = fs4.readFileSync(normalized, "utf8");
10933
+ }
10934
+ } catch {
10935
+ }
10936
+ if (fileContent !== null) {
10937
+ const headings = extractMarkdownHeadings(fileContent);
10938
+ if (headings.length >= 3) {
10939
+ recordFileRead(normalized);
10940
+ const hintText = formatHeadingTree(headings, normalized);
10941
+ const wellKnown = getWellKnownSections(basename6);
10942
+ const wellKnownText = wellKnown.length > 0 ? "\nQuick access: " + wellKnown.map((s) => 'token-goat section "' + normalized + "::" + s + '"').join(" | ") : "";
10943
+ const changelogExtra = basename6.toLowerCase() === "changelog.md" ? extractChangelogVersionHint(fileContent, normalized) : "";
10944
+ return denyOutput(hintText + wellKnownText + changelogExtra);
10945
+ }
10946
+ }
10947
+ }
10398
10948
  if (wasFileReadThisSession(normalized)) {
10399
10949
  const entry = getSessionFiles().get(normalized);
10400
10950
  const reads = entry?.readCount ?? 1;
10401
10951
  const plural = reads === 1 ? "read" : "reads";
10402
10952
  recordFileRead(normalized);
10953
+ 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
10954
  return contextOutput(
10404
- `Note: ${normalized} was already read this session (${reads} ${plural}). Use token-goat read/section/symbol to re-read surgically.`
10955
+ "Note: " + normalized + " was already read this session (" + reads + " " + plural + "). " + hint
10405
10956
  );
10406
10957
  }
10407
10958
  const size = statSize(normalized);
10408
10959
  if (size !== null && size >= LARGE_FILE_BYTES) {
10409
10960
  const kb = Math.round(size / 1024);
10410
10961
  recordFileRead(normalized);
10962
+ const hint = _isDocFile(normalized) ? 'Use `token-goat section "' + normalized + '::SectionName"` to read one section.' : "Consider token-goat skeleton or token-goat section.";
10411
10963
  return contextOutput(
10412
- `Note: ${normalized} is large (${kb}kb). Consider token-goat skeleton or token-goat section.`
10964
+ "Note: " + normalized + " is large (" + kb + "kb). " + hint
10413
10965
  );
10414
10966
  }
10967
+ const fileTypeExt = path6.extname(normalized).slice(1).toLowerCase();
10968
+ const binaryExts = /* @__PURE__ */ new Set(["pdf", "docx", "xlsx", "pptx", "odt", "ods", "ott", "odp"]);
10969
+ const textTypeExts = /* @__PURE__ */ new Set(["html", "htm", "xhtml", "txt", "log", "out", "err", "trace", "csv", "tsv"]);
10970
+ const fileStatSize = size ?? statSize(normalized) ?? 0;
10971
+ const isKnownFileType = binaryExts.has(fileTypeExt) || textTypeExts.has(fileTypeExt);
10972
+ if (isKnownFileType || fileStatSize >= FILE_TYPE_THRESHOLDS.generic) {
10973
+ let ftContent = "";
10974
+ if (!binaryExts.has(fileTypeExt)) {
10975
+ try {
10976
+ ftContent = fs4.readFileSync(normalized, "utf8");
10977
+ } catch {
10978
+ }
10979
+ }
10980
+ const ftResult = dispatchFileTypeHandler(normalized, ftContent, fileStatSize);
10981
+ if (ftResult?.shouldBlock) {
10982
+ recordFileRead(normalized);
10983
+ return denyOutput(ftResult.message);
10984
+ }
10985
+ }
10415
10986
  recordFileRead(normalized);
10416
10987
  return passOutput();
10417
10988
  }
10418
10989
  registerHook("pre_tool_use", preReadHandler, { toolName: "Read" });
10990
+ registerHook("pre_tool_use", preReadHandler, { toolName: "Grep" });
10419
10991
 
10420
10992
  // src/hooks_edit.ts
10421
10993
  init_define_import_meta_env();
10994
+ import * as path8 from "node:path";
10422
10995
 
10423
10996
  // src/hooks_index.ts
10424
10997
  init_define_import_meta_env();
10425
10998
  import * as fs5 from "node:fs";
10426
- import * as path6 from "node:path";
10999
+ import * as path7 from "node:path";
10427
11000
  function dirtyQueuePath() {
10428
- return path6.join(dataDir(), "queue", "dirty.txt");
11001
+ return path7.join(dataDir(), "queue", "dirty.txt");
10429
11002
  }
10430
11003
  function appendDirtyPath(normalizedPath) {
10431
11004
  const queuePath = dirtyQueuePath();
10432
- fs5.mkdirSync(path6.dirname(queuePath), { recursive: true });
11005
+ fs5.mkdirSync(path7.dirname(queuePath), { recursive: true });
10433
11006
  fs5.appendFileSync(queuePath, `${normalizedPath}
10434
11007
  `);
10435
11008
  }
@@ -10460,9 +11033,9 @@ function clearDirtyQueue() {
10460
11033
  function preCompactIndexHandler(_event) {
10461
11034
  const paths = getDirtyPaths();
10462
11035
  if (paths.length > 0) {
10463
- const sidecar = path6.join(dataDir(), "queue", "pending.txt");
11036
+ const sidecar = path7.join(dataDir(), "queue", "pending.txt");
10464
11037
  try {
10465
- fs5.mkdirSync(path6.dirname(sidecar), { recursive: true });
11038
+ fs5.mkdirSync(path7.dirname(sidecar), { recursive: true });
10466
11039
  atomicWriteBytes(sidecar, Buffer.from(`${paths.join("\n")}
10467
11040
  `, "utf8"));
10468
11041
  } catch {
@@ -10480,6 +11053,12 @@ function postEditHandler(event) {
10480
11053
  const normalized = normalizePath(filePath);
10481
11054
  recordFileEdit(normalized);
10482
11055
  appendDirtyPath(normalized);
11056
+ const editedBasename = path8.basename(normalized);
11057
+ if (/\.(md|mdx|markdown|rst)$/i.test(editedBasename)) {
11058
+ return contextOutput(
11059
+ editedBasename + ' was edited. Use `token-goat section "' + normalized + '::HeadingName"` to re-read a specific section rather than the full file.'
11060
+ );
11061
+ }
10483
11062
  return passOutput();
10484
11063
  }
10485
11064
  registerHook("post_tool_use", postEditHandler, { toolName: "Write" });
@@ -10748,10 +11327,263 @@ function postSkillHandler(event) {
10748
11327
  registerHook("pre_tool_use", preSkillHandler, { toolName: "Skill" });
10749
11328
  registerHook("post_tool_use", postSkillHandler, { toolName: "Skill" });
10750
11329
 
10751
- // src/image_shrink.ts
11330
+ // src/hooks_bash.ts
10752
11331
  init_define_import_meta_env();
11332
+
11333
+ // src/fingerprint.ts
11334
+ init_define_import_meta_env();
11335
+ import { createHash } from "node:crypto";
10753
11336
  import * as fs6 from "node:fs";
10754
- import * as path7 from "node:path";
11337
+ function fingerprintContent(content) {
11338
+ const hash = createHash("sha256");
11339
+ hash.update(typeof content === "string" ? Buffer.from(content, "utf-8") : content);
11340
+ return hash.digest("hex");
11341
+ }
11342
+ function fingerprintFile(filePath) {
11343
+ let data;
11344
+ try {
11345
+ data = fs6.readFileSync(filePath);
11346
+ } catch {
11347
+ return null;
11348
+ }
11349
+ return fingerprintContent(data);
11350
+ }
11351
+
11352
+ // src/bash_output_cache.ts
11353
+ init_define_import_meta_env();
11354
+ import * as fs7 from "fs/promises";
11355
+ import { resolve as resolve3 } from "path";
11356
+ var _byId = /* @__PURE__ */ new Map();
11357
+ var _globsByKey = /* @__PURE__ */ new Map();
11358
+ var _grepsByKey = /* @__PURE__ */ new Map();
11359
+ var GIT_MUTABLE_RE = /^\s*git\s+(diff|status)\b/i;
11360
+ var LS_CMD_RE = /^\s*(?:ls|eza|exa|dir|Get-ChildItem|gci)\b/i;
11361
+ var DEP_LIST_RE = /^\s*(?:npm\s+(?:-\S+\s+)*(?:ls|list)\b|pip\s+(?:-\S+\s+)*(?:list|freeze)\b|uv\s+pip\s+(?:-\S+\s+)*(?:list|freeze)\b|pnpm\s+(?:-\S+\s+)*(?:list|ls)\b|yarn\s+(?:-\S+\s+)*(?:list)\b|cargo\s+(?:-\S+\s+)*tree\b|bundle\s+(?:-\S+\s+)*(?:list|show)\b|composer\s+(?:-\S+\s+)*show\b)/i;
11362
+ var NPM_INSTALL_RE = /^\s*npm\s+(?:-\S+\s+)*(?:install|ci)\b/i;
11363
+ var DEP_LOCKFILES = {
11364
+ npm: ["package-lock.json", "yarn.lock"],
11365
+ pip: ["requirements.txt"],
11366
+ uv: ["uv.lock", "requirements.txt"],
11367
+ pnpm: ["pnpm-lock.yaml"],
11368
+ yarn: ["yarn.lock"],
11369
+ cargo: ["Cargo.lock"],
11370
+ bundle: ["Gemfile.lock"],
11371
+ composer: ["composer.lock"]
11372
+ };
11373
+ function isGitMutableCommand(cmd) {
11374
+ return GIT_MUTABLE_RE.test(cmd);
11375
+ }
11376
+ function isDirListingCommand(cmd) {
11377
+ return LS_CMD_RE.test(cmd);
11378
+ }
11379
+ function isDepListCommand(cmd) {
11380
+ return DEP_LIST_RE.test(cmd);
11381
+ }
11382
+ function isNpmInstallCommand(cmd) {
11383
+ return NPM_INSTALL_RE.test(cmd);
11384
+ }
11385
+ async function gitStateFingerprint(cwd) {
11386
+ try {
11387
+ const headResult = runGit(["rev-parse", "HEAD"], { cwd });
11388
+ if (headResult.exitCode !== 0) return null;
11389
+ const headSha = headResult.stdout.trim();
11390
+ let indexMtime = "";
11391
+ try {
11392
+ const stat3 = await fs7.stat(resolve3(cwd, ".git", "index"));
11393
+ indexMtime = stat3.mtimeMs.toString();
11394
+ } catch {
11395
+ }
11396
+ const key = `${headSha}\0${indexMtime}`;
11397
+ return fingerprintContent(key).slice(0, 16);
11398
+ } catch {
11399
+ return null;
11400
+ }
11401
+ }
11402
+ async function dirStateFingerprint(path14) {
11403
+ try {
11404
+ const stat3 = await fs7.stat(path14);
11405
+ if (!stat3.isDirectory()) return null;
11406
+ return fingerprintContent(stat3.mtimeMs.toString()).slice(0, 16);
11407
+ } catch {
11408
+ return null;
11409
+ }
11410
+ }
11411
+ async function depLockfileFingerprint(cmd, cwd) {
11412
+ if (!cwd) return null;
11413
+ const stripped = cmd.trim();
11414
+ const firstToken = stripped.split(/\s+/)[0]?.toLowerCase() || "";
11415
+ const candidates = firstToken === "uv" ? DEP_LOCKFILES["uv"] : DEP_LOCKFILES[firstToken];
11416
+ if (!candidates) return null;
11417
+ for (const lockfile of candidates) {
11418
+ try {
11419
+ const content = await fs7.readFile(resolve3(cwd, lockfile));
11420
+ return fingerprintContent(content).slice(0, 16);
11421
+ } catch {
11422
+ continue;
11423
+ }
11424
+ }
11425
+ return null;
11426
+ }
11427
+ function normalizeCommandForCacheKey(cmd) {
11428
+ let normalized = cmd.trim();
11429
+ normalized = normalized.replace(/\s+/g, " ");
11430
+ normalized = normalized.replace(/\\/g, "/");
11431
+ const tokens = normalized.split(" ");
11432
+ const normalized_tokens = tokens.map((token) => {
11433
+ if (token.startsWith("-") || ["&&", "||", "|", ">", ">>", ";", "&"].includes(token)) {
11434
+ return token;
11435
+ }
11436
+ if (token.startsWith("./") && !token.startsWith("../")) {
11437
+ token = token.slice(2);
11438
+ }
11439
+ if (token.endsWith("/") && token !== "/") {
11440
+ token = token.slice(0, -1);
11441
+ }
11442
+ return token || ".";
11443
+ });
11444
+ return normalized_tokens.join(" ");
11445
+ }
11446
+ async function commandHash(command, cwd = null) {
11447
+ const normalized = normalizeCommandForCacheKey(command);
11448
+ let key = cwd ? `${normalizePath(cwd)}\0${normalized}` : normalized;
11449
+ if (cwd && isGitMutableCommand(command)) {
11450
+ const fp = await gitStateFingerprint(cwd);
11451
+ if (fp) key = `${key}\0git:${fp}`;
11452
+ }
11453
+ if (cwd && isDirListingCommand(command)) {
11454
+ const target = extractLsTarget(command, cwd);
11455
+ if (target) {
11456
+ const fp = await dirStateFingerprint(target);
11457
+ if (fp) key = `${key}\0dir:${fp}`;
11458
+ }
11459
+ }
11460
+ if (isDepListCommand(command)) {
11461
+ const fp = await depLockfileFingerprint(command, cwd);
11462
+ if (fp) key = `${key}\0lockfile:${fp}`;
11463
+ }
11464
+ if (cwd && isNpmInstallCommand(command)) {
11465
+ const fp = await depLockfileFingerprint(command, cwd);
11466
+ if (fp) key = `${key}\0npm-install:${fp}`;
11467
+ }
11468
+ return fingerprintContent(key).slice(0, 16);
11469
+ }
11470
+ function extractLsTarget(cmd, cwd) {
11471
+ const tokens = cmd.trim().split(/\s+/);
11472
+ for (let i = 1; i < tokens.length; i++) {
11473
+ if (!tokens[i].startsWith("-")) {
11474
+ return tokens[i];
11475
+ }
11476
+ }
11477
+ return cwd;
11478
+ }
11479
+ async function storeBashOutput(command, output, exitCode, cwd = null) {
11480
+ const id = await commandHash(command, cwd);
11481
+ const entry = {
11482
+ id,
11483
+ command,
11484
+ output,
11485
+ exitCode,
11486
+ storedAt: Date.now(),
11487
+ sizeBytes: Buffer.byteLength(output, "utf-8")
11488
+ };
11489
+ _byId.set(id, entry);
11490
+ return id;
11491
+ }
11492
+ function getBashOutput(id) {
11493
+ return _byId.get(id) ?? null;
11494
+ }
11495
+ registerReset(() => {
11496
+ _byId = /* @__PURE__ */ new Map();
11497
+ _globsByKey = /* @__PURE__ */ new Map();
11498
+ _grepsByKey = /* @__PURE__ */ new Map();
11499
+ });
11500
+
11501
+ // src/hooks_bash.ts
11502
+ function extractCommand(event) {
11503
+ const cmd = event.toolInput["command"];
11504
+ return typeof cmd === "string" && cmd.trim() !== "" ? cmd.trim() : void 0;
11505
+ }
11506
+ function extractCatSourceFile(cmd) {
11507
+ const m = /^cat\s+(\S+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj))\s*$/.exec(cmd);
11508
+ return m?.[1] ?? null;
11509
+ }
11510
+ function isTscCommand(cmd) {
11511
+ return /^\s*tsc(\s|$)/i.test(cmd);
11512
+ }
11513
+ function isDevServerCommand(cmd) {
11514
+ return /^\s*(vite\s+dev|next\s+dev|nuxt\s+dev)\b/i.test(cmd);
11515
+ }
11516
+ function buildRecallHint(cmd, outputId) {
11517
+ const cmdPreview = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
11518
+ if (isTscCommand(cmd)) {
11519
+ 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.';
11520
+ }
11521
+ if (isDevServerCommand(cmd)) {
11522
+ 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.';
11523
+ }
11524
+ 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.";
11525
+ }
11526
+ function preBashHandler(event) {
11527
+ const cmd = extractCommand(event);
11528
+ if (cmd === void 0) return passOutput();
11529
+ const monitoringHint = getMonitoringRecallHint(cmd);
11530
+ if (monitoringHint !== null) {
11531
+ const monCmdHash = fingerprintContent(cmd).slice(0, 16);
11532
+ const monOutputId = getBashOutputId(monCmdHash);
11533
+ if (monOutputId !== null) {
11534
+ const catFile = extractCatSourceFile(cmd);
11535
+ if (catFile !== null) {
11536
+ return contextOutput(
11537
+ "Prior output from `" + cmd + "` is cached. Use `token-goat bash-output " + monOutputId + "` to recall the full file, or `token-goat read '" + catFile + "::SymbolName'` to extract only the symbol you need."
11538
+ );
11539
+ }
11540
+ const cmdSummary = cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
11541
+ return contextOutput(
11542
+ "Prior output from `" + cmdSummary + "` is cached.\nUse `token-goat bash-output " + monOutputId + " " + monitoringHint + "` to re-inspect without re-running."
11543
+ );
11544
+ }
11545
+ }
11546
+ if (!isBuildCommand(cmd)) return passOutput();
11547
+ const cmdHash = fingerprintContent(cmd).slice(0, 16);
11548
+ const outputId = getBashOutputId(cmdHash);
11549
+ if (outputId === null) return passOutput();
11550
+ return contextOutput(buildRecallHint(cmd, outputId));
11551
+ }
11552
+ registerHook("pre_tool_use", preBashHandler, { toolName: "Bash" });
11553
+ var MIN_CACHE_BYTES = 512;
11554
+ function extractBashOutput(raw) {
11555
+ const resp = raw["tool_response"];
11556
+ if (typeof resp === "string") return resp;
11557
+ if (resp !== null && typeof resp === "object") {
11558
+ const r = resp;
11559
+ for (const key of ["output", "content", "text", "body"]) {
11560
+ if (typeof r[key] === "string") return r[key];
11561
+ }
11562
+ }
11563
+ return "";
11564
+ }
11565
+ async function postBashHandler(event) {
11566
+ try {
11567
+ const cmd = extractCommand(event);
11568
+ if (cmd === void 0) return passOutput();
11569
+ const isMonitoring = getMonitoringRecallHint(cmd) !== null;
11570
+ if (!isMonitoring && !isBuildCommand(cmd)) return passOutput();
11571
+ const output = extractBashOutput(event.raw);
11572
+ if (Buffer.byteLength(output, "utf-8") < MIN_CACHE_BYTES) return passOutput();
11573
+ const cwd = typeof event.raw["cwd"] === "string" ? event.raw["cwd"] : null;
11574
+ const simpleHash = fingerprintContent(cmd).slice(0, 16);
11575
+ const id = await storeBashOutput(cmd, output, 0, cwd);
11576
+ recordBashOutput(simpleHash, id, Buffer.byteLength(output, "utf-8"));
11577
+ } catch {
11578
+ }
11579
+ return passOutput();
11580
+ }
11581
+ registerHook("post_tool_use", postBashHandler, { toolName: "Bash" });
11582
+
11583
+ // src/image_shrink.ts
11584
+ init_define_import_meta_env();
11585
+ import * as fs8 from "node:fs";
11586
+ import * as path9 from "node:path";
10755
11587
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
10756
11588
  ".png",
10757
11589
  ".jpg",
@@ -10778,7 +11610,7 @@ async function loadSharp() {
10778
11610
  return _sharpCache;
10779
11611
  }
10780
11612
  function isImagePath(p) {
10781
- return IMAGE_EXTENSIONS.has(path7.extname(p).toLowerCase());
11613
+ return IMAGE_EXTENSIONS.has(path9.extname(p).toLowerCase());
10782
11614
  }
10783
11615
  async function shrinkImage(input, opts) {
10784
11616
  const maxDimension = opts?.maxDimension ?? DEFAULT_MAX_DIMENSION;
@@ -10816,7 +11648,7 @@ async function shrinkImage(input, opts) {
10816
11648
  }
10817
11649
  function statSize2(absPath) {
10818
11650
  try {
10819
- const st = fs6.statSync(absPath);
11651
+ const st = fs8.statSync(absPath);
10820
11652
  return st.isFile() ? st.size : null;
10821
11653
  } catch {
10822
11654
  return null;
@@ -10830,7 +11662,7 @@ async function preReadImageHandler(event) {
10830
11662
  if (size === null || size < DEFAULT_SIZE_THRESHOLD_BYTES) return passOutput();
10831
11663
  let input;
10832
11664
  try {
10833
- input = fs6.readFileSync(filePath);
11665
+ input = fs8.readFileSync(filePath);
10834
11666
  } catch {
10835
11667
  return passOutput();
10836
11668
  }
@@ -10839,7 +11671,7 @@ async function preReadImageHandler(event) {
10839
11671
  const saved = result.originalBytes - result.shrunkBytes;
10840
11672
  const pct = Math.round(saved / result.originalBytes * 100);
10841
11673
  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}).`;
11674
+ 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
11675
  return contextOutput(`${summary}
10844
11676
  ${dataUrl}`);
10845
11677
  }
@@ -10848,7 +11680,7 @@ registerHook("pre_tool_use", preReadImageHandler, { toolName: "Read" });
10848
11680
  // src/relay.ts
10849
11681
  var DEFAULT_STDIN_TIMEOUT_MS = 5e3;
10850
11682
  function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS) {
10851
- return new Promise((resolve4, reject) => {
11683
+ return new Promise((resolve5, reject) => {
10852
11684
  const chunks = [];
10853
11685
  let settled = false;
10854
11686
  const finish = (fn) => {
@@ -10874,7 +11706,7 @@ function readStdinJson(timeoutMs = DEFAULT_STDIN_TIMEOUT_MS) {
10874
11706
  return;
10875
11707
  }
10876
11708
  try {
10877
- resolve4(JSON.parse(text));
11709
+ resolve5(JSON.parse(text));
10878
11710
  } catch (err2) {
10879
11711
  reject(err2 instanceof Error ? err2 : new Error(String(err2)));
10880
11712
  }
@@ -10918,7 +11750,7 @@ async function relay(eventName) {
10918
11750
 
10919
11751
  // src/section_reader.ts
10920
11752
  init_define_import_meta_env();
10921
- import { readFileSync as readFileSync3 } from "node:fs";
11753
+ import { readFileSync as readFileSync5 } from "node:fs";
10922
11754
  function parseHeadingSpec(spec) {
10923
11755
  const m = /^(.*?)#(\d+)$/.exec(spec);
10924
11756
  if (m !== null && m[1] !== void 0 && m[2] !== void 0) {
@@ -11000,7 +11832,7 @@ function sectionEndIndex(headers, headerPos, totalLines) {
11000
11832
  function readSection(filePath, headingSpec) {
11001
11833
  let text;
11002
11834
  try {
11003
- text = readFileSync3(filePath, "utf-8");
11835
+ text = readFileSync5(filePath, "utf-8");
11004
11836
  } catch {
11005
11837
  return null;
11006
11838
  }
@@ -11037,9 +11869,9 @@ function readSection(filePath, headingSpec) {
11037
11869
 
11038
11870
  // src/install.ts
11039
11871
  init_define_import_meta_env();
11040
- import * as fs7 from "node:fs";
11872
+ import * as fs9 from "node:fs";
11041
11873
  import * as os2 from "node:os";
11042
- import * as path8 from "node:path";
11874
+ import * as path10 from "node:path";
11043
11875
  var HOOK_EVENT_MAP = [
11044
11876
  ["PreToolUse", "pre_tool_use"],
11045
11877
  ["PostToolUse", "post_tool_use"],
@@ -11050,13 +11882,13 @@ function hookCommand(eventArg) {
11050
11882
  return `token-goat hook ${eventArg}`;
11051
11883
  }
11052
11884
  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");
11885
+ const base = scope === "user" ? path10.join(os2.homedir(), ".claude") : path10.join(process.cwd(), ".claude");
11886
+ return path10.join(base, "settings.json");
11055
11887
  }
11056
11888
  function readSettings(p) {
11057
11889
  let raw;
11058
11890
  try {
11059
- raw = fs7.readFileSync(p, "utf8");
11891
+ raw = fs9.readFileSync(p, "utf8");
11060
11892
  } catch {
11061
11893
  return {};
11062
11894
  }
@@ -11096,7 +11928,7 @@ function installHooks(scope = "user") {
11096
11928
  return { scope, settingsPath: p, alreadyInstalled: true };
11097
11929
  }
11098
11930
  settings.hooks = hooks;
11099
- fs7.mkdirSync(path8.dirname(p), { recursive: true });
11931
+ fs9.mkdirSync(path10.dirname(p), { recursive: true });
11100
11932
  atomicWriteText(p, `${JSON.stringify(settings, null, 2)}
11101
11933
  `);
11102
11934
  return { scope, settingsPath: p, alreadyInstalled: false };
@@ -11135,7 +11967,7 @@ function uninstallHooks(scope = "user") {
11135
11967
  } else {
11136
11968
  settings.hooks = hooks;
11137
11969
  }
11138
- fs7.mkdirSync(path8.dirname(p), { recursive: true });
11970
+ fs9.mkdirSync(path10.dirname(p), { recursive: true });
11139
11971
  atomicWriteText(p, `${JSON.stringify(settings, null, 2)}
11140
11972
  `);
11141
11973
  return true;
@@ -11144,42 +11976,21 @@ function uninstallHooks(scope = "user") {
11144
11976
  // src/worker.ts
11145
11977
  init_define_import_meta_env();
11146
11978
  import { spawn } from "node:child_process";
11147
- import * as fs9 from "node:fs";
11148
- import * as path9 from "node:path";
11979
+ import * as fs10 from "node:fs";
11980
+ import * as path11 from "node:path";
11149
11981
  import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
11150
11982
  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
11983
  var DEFAULT_POLL_INTERVAL_MS = 2e3;
11173
11984
  function dirtyQueuePathFor(dir) {
11174
- return path9.join(dir, "queue", "dirty.txt");
11985
+ return path11.join(dir, "queue", "dirty.txt");
11175
11986
  }
11176
11987
  function workerPidPath(dir = dataDir()) {
11177
- return path9.join(dir, "worker.pid");
11988
+ return path11.join(dir, "worker.pid");
11178
11989
  }
11179
11990
  function getDirtyPathsFor(dir) {
11180
11991
  let raw;
11181
11992
  try {
11182
- raw = fs9.readFileSync(dirtyQueuePathFor(dir), "utf8");
11993
+ raw = fs10.readFileSync(dirtyQueuePathFor(dir), "utf8");
11183
11994
  } catch {
11184
11995
  return [];
11185
11996
  }
@@ -11195,7 +12006,7 @@ function getDirtyPathsFor(dir) {
11195
12006
  }
11196
12007
  function clearDirtyQueueFor(dir) {
11197
12008
  try {
11198
- fs9.rmSync(dirtyQueuePathFor(dir), { force: true });
12009
+ fs10.rmSync(dirtyQueuePathFor(dir), { force: true });
11199
12010
  } catch {
11200
12011
  }
11201
12012
  }
@@ -11205,7 +12016,7 @@ function processDirtyBatch(paths, index = (absPath) => {
11205
12016
  }) {
11206
12017
  let indexed = 0;
11207
12018
  for (const p of paths) {
11208
- if (!fs9.existsSync(p)) continue;
12019
+ if (!fs10.existsSync(p)) continue;
11209
12020
  const sha = fingerprintFile(p);
11210
12021
  if (sha === null) continue;
11211
12022
  index(p, sha);
@@ -11230,7 +12041,7 @@ function pidAlive(pid) {
11230
12041
  }
11231
12042
  function readPidFile(dir) {
11232
12043
  try {
11233
- const raw = fs9.readFileSync(workerPidPath(dir), "utf8").trim();
12044
+ const raw = fs10.readFileSync(workerPidPath(dir), "utf8").trim();
11234
12045
  if (!/^\d+$/.test(raw)) return null;
11235
12046
  return parseInt(raw, 10);
11236
12047
  } catch {
@@ -11253,7 +12064,7 @@ function stopWorker(dir = dataDir()) {
11253
12064
  }
11254
12065
  }
11255
12066
  try {
11256
- fs9.rmSync(workerPidPath(dir), { force: true });
12067
+ fs10.rmSync(workerPidPath(dir), { force: true });
11257
12068
  } catch {
11258
12069
  }
11259
12070
  return alive;
@@ -11261,7 +12072,7 @@ function stopWorker(dir = dataDir()) {
11261
12072
  function startDetachedWorker(opts) {
11262
12073
  const pollIntervalMs = opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
11263
12074
  const dir = opts?.dataDir ?? dataDir();
11264
- fs9.mkdirSync(dir, { recursive: true });
12075
+ fs10.mkdirSync(dir, { recursive: true });
11265
12076
  const child = spawn(
11266
12077
  process.execPath,
11267
12078
  [fileURLToPath(import.meta.url), "--worker-daemon"],
@@ -11280,7 +12091,7 @@ function startDetachedWorker(opts) {
11280
12091
  if (pid === void 0) {
11281
12092
  throw new Error("startDetachedWorker: spawn produced no pid");
11282
12093
  }
11283
- fs9.writeFileSync(workerPidPath(dir), `${pid}
12094
+ fs10.writeFileSync(workerPidPath(dir), `${pid}
11284
12095
  `);
11285
12096
  child.unref();
11286
12097
  return pid;
@@ -11292,7 +12103,7 @@ async function runWorkerLoop(dir, pollIntervalMs, shouldStop = () => false) {
11292
12103
  } catch {
11293
12104
  }
11294
12105
  if (shouldStop()) break;
11295
- await new Promise((resolve4) => setTimeout(resolve4, pollIntervalMs));
12106
+ await new Promise((resolve5) => setTimeout(resolve5, pollIntervalMs));
11296
12107
  }
11297
12108
  }
11298
12109
  function workerEntry() {
@@ -11317,24 +12128,10 @@ function workerEntry() {
11317
12128
  }
11318
12129
  workerEntry();
11319
12130
 
11320
- // src/bash_output_cache.ts
11321
- init_define_import_meta_env();
11322
- var _byId = /* @__PURE__ */ new Map();
11323
- var _globsByKey = /* @__PURE__ */ new Map();
11324
- var _grepsByKey = /* @__PURE__ */ new Map();
11325
- function getBashOutput(id) {
11326
- return _byId.get(id) ?? null;
11327
- }
11328
- registerReset(() => {
11329
- _byId = /* @__PURE__ */ new Map();
11330
- _globsByKey = /* @__PURE__ */ new Map();
11331
- _grepsByKey = /* @__PURE__ */ new Map();
11332
- });
11333
-
11334
12131
  // src/skill_cache.ts
11335
12132
  init_define_import_meta_env();
11336
- import * as fs10 from "fs/promises";
11337
- import { resolve as resolve3 } from "path";
12133
+ import * as fs11 from "fs/promises";
12134
+ import { resolve as resolve4 } from "path";
11338
12135
  var COMPACT_END_MARKER = "<!-- COMPACT_END -->";
11339
12136
  var _skillOutputsDirOverride = null;
11340
12137
  registerReset(() => {
@@ -11342,11 +12139,11 @@ registerReset(() => {
11342
12139
  });
11343
12140
  function skillOutputsDir() {
11344
12141
  if (_skillOutputsDirOverride) return _skillOutputsDirOverride;
11345
- return resolve3(dataDir(), "skills");
12142
+ return resolve4(dataDir(), "skills");
11346
12143
  }
11347
12144
  async function ensureSkillsDir() {
11348
12145
  try {
11349
- await fs10.mkdir(skillOutputsDir(), { recursive: true });
12146
+ await fs11.mkdir(skillOutputsDir(), { recursive: true });
11350
12147
  } catch {
11351
12148
  }
11352
12149
  }
@@ -11388,14 +12185,14 @@ function extractCompactFromMarker(body) {
11388
12185
  async function listOutputs() {
11389
12186
  try {
11390
12187
  const dir = skillOutputsDir();
11391
- const entries = await fs10.readdir(dir, { withFileTypes: true });
12188
+ const entries = await fs11.readdir(dir, { withFileTypes: true });
11392
12189
  const metas = [];
11393
12190
  for (const entry of entries) {
11394
12191
  if (!entry.isFile() || !entry.name.endsWith(".meta")) {
11395
12192
  continue;
11396
12193
  }
11397
12194
  try {
11398
- const content = await fs10.readFile(resolve3(dir, entry.name), "utf-8");
12195
+ const content = await fs11.readFile(resolve4(dir, entry.name), "utf-8");
11399
12196
  const meta = JSON.parse(content);
11400
12197
  metas.push(meta);
11401
12198
  } catch {
@@ -11420,7 +12217,7 @@ async function storeCompact(sessionId, skillName, compactText, sourceSha) {
11420
12217
  text = `<!-- source_sha: ${sourceSha.slice(0, 12)} -->
11421
12218
  ${text}`;
11422
12219
  }
11423
- await atomicWriteText(resolve3(dir, fileId), text);
12220
+ await atomicWriteText(resolve4(dir, fileId), text);
11424
12221
  } catch {
11425
12222
  }
11426
12223
  }
@@ -11440,13 +12237,13 @@ async function listSkills(sessionId) {
11440
12237
  const compactFileId = `${safeSession}-${meta.skillName.replace(":", "_")}-compact`;
11441
12238
  let compactLen = 0;
11442
12239
  try {
11443
- const stat2 = await fs10.stat(resolve3(dir, compactFileId));
11444
- compactLen = stat2.size;
12240
+ const stat3 = await fs11.stat(resolve4(dir, compactFileId));
12241
+ compactLen = stat3.size;
11445
12242
  } catch {
11446
12243
  compactLen = 0;
11447
12244
  }
11448
12245
  const hasMarker = extractCompactFromMarker(
11449
- await fs10.readFile(resolve3(dir, `${meta.outputId}.txt`), "utf-8").catch(() => "")
12246
+ await fs11.readFile(resolve4(dir, `${meta.outputId}.txt`), "utf-8").catch(() => "")
11450
12247
  ) !== null;
11451
12248
  results.push({
11452
12249
  name: meta.skillName,
@@ -11478,7 +12275,7 @@ async function getSkillFilePath(skillName) {
11478
12275
 
11479
12276
  // src/config.ts
11480
12277
  init_define_import_meta_env();
11481
- import * as fs11 from "node:fs";
12278
+ import * as fs12 from "node:fs";
11482
12279
 
11483
12280
  // node_modules/smol-toml/dist/index.js
11484
12281
  init_define_import_meta_env();
@@ -12463,7 +13260,7 @@ function loadConfig() {
12463
13260
  const p = configPath();
12464
13261
  let currentMtime = 0;
12465
13262
  try {
12466
- currentMtime = fs11.statSync(p).mtimeMs;
13263
+ currentMtime = fs12.statSync(p).mtimeMs;
12467
13264
  } catch {
12468
13265
  }
12469
13266
  const envFp = configEnvFingerprint();
@@ -12473,7 +13270,7 @@ function loadConfig() {
12473
13270
  let raw = {};
12474
13271
  if (currentMtime !== 0) {
12475
13272
  try {
12476
- const text = fs11.readFileSync(p, "utf8");
13273
+ const text = fs12.readFileSync(p, "utf8");
12477
13274
  raw = parse(text);
12478
13275
  } catch {
12479
13276
  }
@@ -12683,7 +13480,686 @@ function _buildConfig(raw) {
12683
13480
 
12684
13481
  // src/stats.ts
12685
13482
  init_define_import_meta_env();
12686
- import * as path10 from "node:path";
13483
+ import * as path12 from "node:path";
13484
+
13485
+ // src/render/stats_renderer.ts
13486
+ init_define_import_meta_env();
13487
+
13488
+ // src/render/ansi.ts
13489
+ init_define_import_meta_env();
13490
+ function _colorStream(isatty) {
13491
+ if (process.env["NO_COLOR"]) return false;
13492
+ return isatty;
13493
+ }
13494
+ function colorStdout() {
13495
+ return _colorStream(process.stdout.isTTY === true);
13496
+ }
13497
+ var USE_COLOR = colorStdout();
13498
+ var _E = "\x1B";
13499
+ var RESET = `${_E}[0m`;
13500
+ var _ANSI_ESCAPE_RE = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\].*?(?:\x07|\x1B\\)|\x1B[PX^_].*?\x1B\\|\x1B[@-Z\\\-_]/gs;
13501
+ var _PUA_RE = /[\u{E000}-\u{F8FF}\u{F0000}-\u{FFFDD}]/gu;
13502
+ function stripAnsi(s) {
13503
+ if (!s.includes("\x1B")) {
13504
+ return s;
13505
+ }
13506
+ const text = s.replace(_ANSI_ESCAPE_RE, "");
13507
+ return text.replace(_PUA_RE, "");
13508
+ }
13509
+ function fg(r, g, b) {
13510
+ return `${_E}[38;2;${r};${g};${b}m`;
13511
+ }
13512
+ function vlen(s) {
13513
+ return stripAnsi(s).length;
13514
+ }
13515
+ function padR(s, w) {
13516
+ return s + " ".repeat(Math.max(0, w - vlen(s)));
13517
+ }
13518
+ function padL(s, w) {
13519
+ return " ".repeat(Math.max(0, w - vlen(s))) + s;
13520
+ }
13521
+ function lerpRgb(a, b, t) {
13522
+ return [
13523
+ Math.round(a[0] + (b[0] - a[0]) * t),
13524
+ Math.round(a[1] + (b[1] - a[1]) * t),
13525
+ Math.round(a[2] + (b[2] - a[2]) * t)
13526
+ ];
13527
+ }
13528
+ var C = {
13529
+ TEXT_PRIMARY: [201, 209, 217],
13530
+ TEXT_BRIGHT: [240, 246, 252],
13531
+ TEXT_MUTED: [125, 133, 144],
13532
+ TEXT_DIM: [72, 79, 88],
13533
+ BG_TILE: [22, 27, 34],
13534
+ TRACK: [28, 35, 41],
13535
+ GREEN1: [31, 77, 44],
13536
+ GREEN2: [46, 160, 67],
13537
+ GREEN3: [63, 185, 80],
13538
+ GREEN4: [86, 211, 100],
13539
+ GREEN5: [126, 231, 135],
13540
+ BLUE: [88, 166, 255],
13541
+ PURPLE: [188, 140, 255],
13542
+ TEAL: [138, 212, 255],
13543
+ ORANGE: [235, 165, 80],
13544
+ YELLOW: [240, 215, 80],
13545
+ RED: [200, 60, 60]
13546
+ };
13547
+
13548
+ // src/render/stats_renderer.ts
13549
+ var _STATS_MESSAGES_FALLBACK = {
13550
+ bytesModeOnlyNote: "tracks bytes, not vision tokens",
13551
+ sessionHintSplitNote: "session_hint shows realized savings; session_hint_overhead shows injected hint cost",
13552
+ insights: {
13553
+ biggestSaver: "Biggest saver ",
13554
+ mostActive: "Most active ",
13555
+ tokenLeader: "Token leader "
13556
+ }
13557
+ };
13558
+ var _STATS_MESSAGES = _STATS_MESSAGES_FALLBACK;
13559
+ var _TERM_W = process.stdout.columns || 100;
13560
+ var _CONTENT_W = Math.min(Math.max(_TERM_W, 80), 140);
13561
+ var _M = " ";
13562
+ var _COL_NAME = 18;
13563
+ var _COL_DATA = 10;
13564
+ var _COL_TOKENS = 12;
13565
+ var _COL_SHARE = 6;
13566
+ var _COL_EVENTS = 6;
13567
+ var _COLS_FIXED = _COL_NAME + 1 + 2 + _COL_DATA + 2 + _COL_TOKENS + 2 + _COL_SHARE + 2 + _COL_EVENTS;
13568
+ var _BAR_W = Math.max(16, _CONTENT_W - _M.length * 2 - _COLS_FIXED);
13569
+ var _RULE = _M + fg(...C.TEXT_DIM) + "\u2500".repeat(_CONTENT_W - _M.length * 2) + RESET;
13570
+ var _BYTE_TIERS = [
13571
+ { threshold: 1e15, divisor: 1e15, unit: "PB", color: C.PURPLE },
13572
+ { threshold: 1e12, divisor: 1e12, unit: "TB", color: C.BLUE },
13573
+ { threshold: 1e9, divisor: 1e9, unit: "GB", color: C.TEAL },
13574
+ { threshold: 1e6, divisor: 1e6, unit: "MB", color: C.GREEN4 },
13575
+ { threshold: 1e3, divisor: 1e3, unit: "KB", color: C.TEXT_MUTED },
13576
+ { threshold: 0, divisor: 1, unit: "B", color: C.TEXT_DIM }
13577
+ ];
13578
+ var _TOKEN_TIERS = [
13579
+ { threshold: 1e12, divisor: 1e12, unit: "Tt", color: C.GREEN5 },
13580
+ { threshold: 1e9, divisor: 1e9, unit: "Gt", color: C.TEAL },
13581
+ { threshold: 1e6, divisor: 1e6, unit: "Mt", color: C.PURPLE },
13582
+ { threshold: 1e3, divisor: 1e3, unit: "kt", color: C.BLUE },
13583
+ { threshold: 0, divisor: 1, unit: "t", color: C.TEXT_DIM }
13584
+ ];
13585
+ function _fmtMagnitude(n, tiers, zeroLabel) {
13586
+ if (zeroLabel !== void 0 && n === 0) {
13587
+ return `${fg(...C.TEXT_DIM)}${zeroLabel}${RESET}`;
13588
+ }
13589
+ if (n < 0) {
13590
+ const a = -n;
13591
+ const color = C.TEXT_DIM;
13592
+ for (const tier of tiers) {
13593
+ if (a >= tier.threshold && tier.threshold > 0) {
13594
+ return `${fg(...color)}-${(a / tier.divisor).toLocaleString("en", { maximumFractionDigits: 1 })} ${tier.unit}${RESET}`;
13595
+ }
13596
+ }
13597
+ const lastTier2 = tiers[tiers.length - 1];
13598
+ if (lastTier2) {
13599
+ return `${fg(...color)}-${a} ${lastTier2.unit}${RESET}`;
13600
+ }
13601
+ return `${fg(...color)}-${a}${RESET}`;
13602
+ }
13603
+ for (const tier of tiers) {
13604
+ if (n >= tier.threshold && tier.threshold > 0) {
13605
+ return `${fg(...tier.color)}${(n / tier.divisor).toLocaleString("en", { maximumFractionDigits: 1 })} ${tier.unit}${RESET}`;
13606
+ }
13607
+ }
13608
+ const lastTier = tiers[tiers.length - 1];
13609
+ if (lastTier) {
13610
+ return `${fg(...lastTier.color)}${n} ${lastTier.unit}${RESET}`;
13611
+ }
13612
+ return `${n}${RESET}`;
13613
+ }
13614
+ function _fmtBytes(n) {
13615
+ return _fmtMagnitude(n, _BYTE_TIERS);
13616
+ }
13617
+ function _fmtTokens(n) {
13618
+ return _fmtMagnitude(n, _TOKEN_TIERS, "0 t");
13619
+ }
13620
+ function _fmtPct(fraction) {
13621
+ return `${(fraction * 100).toFixed(1)}%`;
13622
+ }
13623
+ function _fmtDelta(delta) {
13624
+ if (!delta && delta !== 0) {
13625
+ return "";
13626
+ }
13627
+ const up = (delta ?? 0) >= 0;
13628
+ const color = up ? C.GREEN5 : C.RED;
13629
+ const arrow = up ? "\u2191" : "\u2193";
13630
+ return ` ${fg(...color)}${arrow} ${Math.round(Math.abs(delta ?? 0))}%${RESET}`;
13631
+ }
13632
+ var _EIGHTHS = ["\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589"];
13633
+ var _BLOCK = "\u2588";
13634
+ var _TRACK = "\u2591";
13635
+ var _GRADIENT = [C.GREEN1, C.GREEN2, C.GREEN3, C.GREEN4, C.GREEN5];
13636
+ function _distribute(total, n) {
13637
+ if (total <= 0 || n <= 0) {
13638
+ return Array(Math.max(0, n)).fill(0);
13639
+ }
13640
+ const base = Math.floor(total / n);
13641
+ const rem = total % n;
13642
+ return Array.from({ length: n }, (_, i) => base + (i >= n - rem ? 1 : 0));
13643
+ }
13644
+ function _renderBar(fraction, width = _BAR_W) {
13645
+ const f = Math.max(0, Math.min(1, fraction));
13646
+ const raw = f * width;
13647
+ let nFull = Math.floor(raw);
13648
+ const eighths = Math.round((raw - nFull) * 8);
13649
+ if (eighths >= 8) {
13650
+ nFull += 1;
13651
+ }
13652
+ const hasPartial = eighths > 0 && eighths < 8;
13653
+ const nTrack = Math.max(0, width - nFull - (hasPartial ? 1 : 0));
13654
+ const counts = _distribute(nFull, _GRADIENT.length);
13655
+ let bar = counts.map((count, i) => count > 0 ? `${fg(_GRADIENT[i][0], _GRADIENT[i][1], _GRADIENT[i][2])}${_BLOCK.repeat(count)}` : "").join("");
13656
+ if (hasPartial) {
13657
+ const lastGrad = _GRADIENT[_GRADIENT.length - 1];
13658
+ bar += `${fg(lastGrad[0], lastGrad[1], lastGrad[2])}${_EIGHTHS[eighths - 1]}`;
13659
+ }
13660
+ if (nTrack > 0) {
13661
+ bar += `${fg(...C.TRACK)}${_TRACK.repeat(nTrack)}`;
13662
+ }
13663
+ return bar + RESET;
13664
+ }
13665
+ var _SPARK = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
13666
+ function _resample(vals, length) {
13667
+ if (vals.length === 0) {
13668
+ return Array(length).fill(0);
13669
+ }
13670
+ if (vals.length === length) {
13671
+ return [...vals];
13672
+ }
13673
+ const result = [];
13674
+ for (let i = 0; i < length; i++) {
13675
+ const src = i / (length - 1 || 1) * (vals.length - 1);
13676
+ const lo = Math.floor(src);
13677
+ const hi = Math.min(vals.length - 1, lo + 1);
13678
+ const t = src - lo;
13679
+ const loVal = vals[lo] ?? 0;
13680
+ const hiVal = vals[hi] ?? 0;
13681
+ result.push(loVal * (1 - t) + hiVal * t);
13682
+ }
13683
+ return result;
13684
+ }
13685
+ function _renderSparkline(values, width = 8) {
13686
+ const pts = _resample(values, width);
13687
+ const hi = pts.length > 0 ? Math.max(...pts) : 1;
13688
+ const lo = pts.length > 0 ? Math.min(...pts) : 0;
13689
+ const span = hi - lo || 1;
13690
+ const chars = [];
13691
+ for (let i = 0; i < pts.length; i++) {
13692
+ const v = pts[i];
13693
+ if (v === void 0) continue;
13694
+ const idx = Math.min(7, Math.floor((v - lo) / span * 8));
13695
+ const color = lerpRgb(C.GREEN1, C.GREEN5, i / (width - 1 || 1));
13696
+ chars.push(`${fg(color[0], color[1], color[2])}${_SPARK[idx]}`);
13697
+ }
13698
+ return chars.join("") + RESET;
13699
+ }
13700
+ function _tokenOrByteShare(itemTokens, itemBytes, totalTokens, totalBytes) {
13701
+ if (totalTokens > 0) {
13702
+ return itemTokens / totalTokens;
13703
+ }
13704
+ if (totalBytes > 0) {
13705
+ return itemBytes / totalBytes;
13706
+ }
13707
+ return 0;
13708
+ }
13709
+ function _barFraction(itemBytes, grossBytes) {
13710
+ return itemBytes > 0 ? itemBytes / grossBytes : 0;
13711
+ }
13712
+ function _computeShareDenominators(items) {
13713
+ let grossBytesSum = 0;
13714
+ let shareByteSum = 0;
13715
+ let shareTokensSum = 0;
13716
+ for (const item of items) {
13717
+ if (item.bytes > 0) {
13718
+ grossBytesSum += item.bytes;
13719
+ }
13720
+ shareByteSum += Math.abs(item.bytes);
13721
+ shareTokensSum += Math.abs(item.tokens);
13722
+ }
13723
+ return {
13724
+ grossBytes: Math.max(grossBytesSum, 1),
13725
+ shareBytesDenom: Math.max(shareByteSum, 1),
13726
+ shareTokensDenom: shareTokensSum
13727
+ };
13728
+ }
13729
+ function _absShare(itemBytes, itemTokens, shareBytesDenom, shareTokensDenom) {
13730
+ if (shareTokensDenom === 0) {
13731
+ return itemBytes / shareBytesDenom;
13732
+ }
13733
+ return itemTokens / shareTokensDenom;
13734
+ }
13735
+ function _sectionHeader(title, subtitle = "") {
13736
+ const sub = subtitle ? ` ${fg(...C.TEXT_MUTED)}${subtitle}${RESET}` : "";
13737
+ return [
13738
+ "",
13739
+ `${_M}${fg(...C.TEXT_BRIGHT)}${title}${RESET}${sub}`,
13740
+ _RULE
13741
+ ];
13742
+ }
13743
+ function _tableHeader(firstColLabel) {
13744
+ return [
13745
+ _M,
13746
+ padR(`${fg(...C.TEXT_DIM)}${firstColLabel}${RESET}`, _COL_NAME),
13747
+ " ",
13748
+ padR(`${fg(...C.TEXT_DIM)}savings${RESET}`, _BAR_W),
13749
+ " ",
13750
+ padL(`${fg(...C.TEXT_DIM)}data saved${RESET}`, _COL_DATA),
13751
+ " ",
13752
+ padL(`${fg(...C.TEXT_DIM)}tokens saved${RESET}`, _COL_TOKENS),
13753
+ " ",
13754
+ padL(`${fg(...C.TEXT_DIM)}share${RESET}`, _COL_SHARE),
13755
+ " ",
13756
+ padL(`${fg(...C.TEXT_DIM)}events${RESET}`, _COL_EVENTS)
13757
+ ].join("");
13758
+ }
13759
+ function _tableRow({
13760
+ name,
13761
+ fraction,
13762
+ bytes,
13763
+ tokens,
13764
+ events,
13765
+ share,
13766
+ bytesModeOnly = false,
13767
+ namePrefix = "",
13768
+ nameColor = C.TEXT_PRIMARY
13769
+ }) {
13770
+ const prefixW = vlen(namePrefix);
13771
+ const maxName = _COL_NAME - prefixW;
13772
+ const truncated = name.length > maxName ? name.slice(0, maxName - 1) + "\u2026" : name;
13773
+ const nameStr = padR(`${namePrefix}${fg(...nameColor)}${truncated}${RESET}`, _COL_NAME);
13774
+ const dataStr = padL(_fmtBytes(bytes), _COL_DATA);
13775
+ const tokStr = bytesModeOnly ? padL(`${fg(...C.TEXT_DIM)}\u2014${RESET}`, _COL_TOKENS) : padL(_fmtTokens(tokens), _COL_TOKENS);
13776
+ const sharePct = share * 100;
13777
+ let shareColor;
13778
+ if (sharePct < 0) {
13779
+ shareColor = C.RED;
13780
+ } else if (sharePct >= 50) {
13781
+ shareColor = C.GREEN5;
13782
+ } else if (sharePct >= 10) {
13783
+ shareColor = C.TEXT_PRIMARY;
13784
+ } else {
13785
+ shareColor = C.TEXT_MUTED;
13786
+ }
13787
+ const shareStr = padL(`${fg(...shareColor)}${_fmtPct(share)}${RESET}`, _COL_SHARE);
13788
+ const evStr = padL(`${fg(...C.TEXT_PRIMARY)}${events.toLocaleString()}${RESET}`, _COL_EVENTS);
13789
+ return [_M, nameStr, " ", _renderBar(fraction), " ", dataStr, " ", tokStr, " ", shareStr, " ", evStr].join("");
13790
+ }
13791
+ function _renderKpiSection(stats) {
13792
+ const totals = stats.totals;
13793
+ const colW = Math.floor((_CONTENT_W - _M.length * 2) / 3);
13794
+ function card(label, value, delta, spark2) {
13795
+ return [
13796
+ padR(`${fg(C.TEXT_MUTED[0], C.TEXT_MUTED[1], C.TEXT_MUTED[2])}${label}${RESET}`, colW),
13797
+ padR(`${fg(C.TEXT_BRIGHT[0], C.TEXT_BRIGHT[1], C.TEXT_BRIGHT[2])}${value}${RESET}${delta}`, colW),
13798
+ spark2 !== null ? padR(spark2, colW) : padR("", colW)
13799
+ ];
13800
+ }
13801
+ const spark = totals.sparklines;
13802
+ const c1 = card(
13803
+ "events",
13804
+ `${totals.events.toLocaleString()}`,
13805
+ _fmtDelta(totals.events_delta ?? null),
13806
+ spark ? _renderSparkline(spark.events) : null
13807
+ );
13808
+ const c2 = card(
13809
+ "data saved",
13810
+ _fmtBytes(totals.bytes),
13811
+ _fmtDelta(totals.bytes_delta ?? null),
13812
+ spark ? _renderSparkline(spark.bytes) : null
13813
+ );
13814
+ const c3 = card(
13815
+ "tokens saved",
13816
+ _fmtTokens(totals.tokens),
13817
+ _fmtDelta(totals.tokens_delta ?? null),
13818
+ spark ? _renderSparkline(spark.tokens) : null
13819
+ );
13820
+ const border = fg(C.TEXT_DIM[0], C.TEXT_DIM[1], C.TEXT_DIM[2]);
13821
+ const frameBar = "\u2500".repeat(colW * 3 + 2);
13822
+ function framed(content) {
13823
+ return `${_M}${border}\u2502${RESET} ${content} ${border}\u2502${RESET}`;
13824
+ }
13825
+ const lines = [
13826
+ "",
13827
+ `${_M}${border}\u256D${frameBar}\u256E${RESET}`,
13828
+ framed(c1[0] + c2[0] + c3[0]),
13829
+ framed(c1[1] + c2[1] + c3[1])
13830
+ ];
13831
+ if (spark) {
13832
+ lines.push(framed(c1[2] + c2[2] + c3[2]));
13833
+ }
13834
+ lines.push(`${_M}${border}\u2570${frameBar}\u256F${RESET}`);
13835
+ return lines;
13836
+ }
13837
+ var _KIND_GROUPS = [
13838
+ {
13839
+ label: "Read savings",
13840
+ members: /* @__PURE__ */ new Set([
13841
+ "read_replacement",
13842
+ "section_replacement",
13843
+ "symbol_read",
13844
+ "section_read",
13845
+ "stub_view",
13846
+ "outline",
13847
+ "exports"
13848
+ ])
13849
+ },
13850
+ { label: "Lookups", members: /* @__PURE__ */ new Set(["symbol_lookup", "semantic_search", "map_lookup"]) },
13851
+ {
13852
+ label: "Images",
13853
+ members: /* @__PURE__ */ new Set(["image_shrink", "gdrive_image", "webfetch_image", "image_shrink_skipped"])
13854
+ },
13855
+ {
13856
+ label: "Hints",
13857
+ members: /* @__PURE__ */ new Set([
13858
+ "session_hint",
13859
+ "session_hint_overhead",
13860
+ "read_dedup_hint",
13861
+ "grep_dedup_hint",
13862
+ "diff_hint",
13863
+ "predictive_prefetch_hit",
13864
+ "read_partial_overlap_hint"
13865
+ ])
13866
+ },
13867
+ {
13868
+ label: "Bash",
13869
+ members: /* @__PURE__ */ new Set([
13870
+ "bash_dedup_hint",
13871
+ "bash_output_cached",
13872
+ "bash_output_recall",
13873
+ "bash_output_recall_miss",
13874
+ "bash_dedup_stale",
13875
+ "bash_range_read_hint",
13876
+ "bash_streak_hint",
13877
+ "bash_poll_hint",
13878
+ "env_probe_cache_hit",
13879
+ "git_diff_scope_hint",
13880
+ "dep_list_cache_hit",
13881
+ "bash_read_equiv_already_read",
13882
+ "bash_grep_result_cache_hit",
13883
+ "git_diff_context_trimmed"
13884
+ ])
13885
+ },
13886
+ {
13887
+ label: "Web",
13888
+ members: /* @__PURE__ */ new Set([
13889
+ "web_dedup_hint",
13890
+ "web_output_cached",
13891
+ "web_output_recall",
13892
+ "web_output_recall_miss",
13893
+ "web_dedup_stale"
13894
+ ])
13895
+ },
13896
+ {
13897
+ label: "Compact / Skills",
13898
+ members: /* @__PURE__ */ new Set([
13899
+ "compact_manifest",
13900
+ "compact_assist",
13901
+ "compact_recovery",
13902
+ "skill_body_recall",
13903
+ "skill_compact_served",
13904
+ "skill_cached",
13905
+ "resume_packet",
13906
+ "decision_log"
13907
+ ])
13908
+ }
13909
+ ];
13910
+ function _kindGroupLabel(kind) {
13911
+ if (kind.startsWith("bash_compress:")) {
13912
+ return "Bash";
13913
+ }
13914
+ for (const group of _KIND_GROUPS) {
13915
+ if (group.members.has(kind)) {
13916
+ return group.label;
13917
+ }
13918
+ }
13919
+ return "Other";
13920
+ }
13921
+ function _groupSeparator(label) {
13922
+ return `${_M} ${fg(...C.TEXT_DIM)}${label}${RESET}`;
13923
+ }
13924
+ function _renderByKindSection(stats) {
13925
+ if (stats.by_kind.length === 0) {
13926
+ return [];
13927
+ }
13928
+ const lines = [..._sectionHeader("By kind"), _tableHeader("name")];
13929
+ const { grossBytes, shareBytesDenom, shareTokensDenom } = _computeShareDenominators(stats.by_kind);
13930
+ const kindNames = new Set(stats.by_kind.map((k) => k.kind));
13931
+ const bytesModeKinds = stats.by_kind.filter((k) => k.bytes_mode_only).map((k) => k.kind);
13932
+ function share(k) {
13933
+ if (k.bytes_mode_only) {
13934
+ return k.bytes / shareBytesDenom;
13935
+ }
13936
+ return _absShare(k.bytes, k.tokens, shareBytesDenom, shareTokensDenom);
13937
+ }
13938
+ const byGroup = /* @__PURE__ */ new Map();
13939
+ for (const k of stats.by_kind) {
13940
+ const grp = _kindGroupLabel(k.kind);
13941
+ if (!byGroup.has(grp)) {
13942
+ byGroup.set(grp, []);
13943
+ }
13944
+ byGroup.get(grp).push(k);
13945
+ }
13946
+ for (const grpKinds of byGroup.values()) {
13947
+ grpKinds.sort((a, b) => share(b) - share(a));
13948
+ }
13949
+ let firstGroup = true;
13950
+ for (const group of _KIND_GROUPS) {
13951
+ const groupKinds = byGroup.get(group.label);
13952
+ if (!groupKinds || groupKinds.length === 0) {
13953
+ continue;
13954
+ }
13955
+ if (!firstGroup) {
13956
+ lines.push("");
13957
+ }
13958
+ firstGroup = false;
13959
+ lines.push(_groupSeparator(group.label));
13960
+ for (const k of groupKinds) {
13961
+ const s = share(k);
13962
+ lines.push(
13963
+ _tableRow({
13964
+ name: k.kind,
13965
+ fraction: _barFraction(k.bytes, grossBytes),
13966
+ bytes: k.bytes,
13967
+ tokens: k.tokens,
13968
+ events: k.events,
13969
+ share: s,
13970
+ bytesModeOnly: k.bytes_mode_only ?? false
13971
+ })
13972
+ );
13973
+ }
13974
+ }
13975
+ if (bytesModeKinds.length > 0) {
13976
+ const names = bytesModeKinds.join(", ");
13977
+ lines.push(`${_M}${fg(...C.TEXT_DIM)}i ${names} ${_STATS_MESSAGES.bytesModeOnlyNote}${RESET}`);
13978
+ }
13979
+ if (kindNames.has("session_hint") && kindNames.has("session_hint_overhead")) {
13980
+ lines.push(`${_M}${fg(...C.TEXT_DIM)}i ${_STATS_MESSAGES.sessionHintSplitNote}${RESET}`);
13981
+ }
13982
+ return lines;
13983
+ }
13984
+ var _SOURCE_COLORS = {
13985
+ image: C.PURPLE,
13986
+ hint: C.BLUE,
13987
+ read: C.GREEN4,
13988
+ compact: C.TEAL,
13989
+ bash: C.ORANGE,
13990
+ web: C.YELLOW,
13991
+ other: C.TEXT_MUTED
13992
+ };
13993
+ function _sourceColor(source) {
13994
+ const color = _SOURCE_COLORS[source];
13995
+ return color || C.TEXT_MUTED;
13996
+ }
13997
+ function _renderBySourceSection(stats) {
13998
+ if (!stats.by_source || stats.by_source.length === 0) {
13999
+ return [];
14000
+ }
14001
+ const lines = [..._sectionHeader("By source"), _tableHeader("source")];
14002
+ const { grossBytes, shareBytesDenom, shareTokensDenom } = _computeShareDenominators(stats.by_source);
14003
+ function share(s) {
14004
+ return _absShare(s.bytes, s.tokens, shareBytesDenom, shareTokensDenom);
14005
+ }
14006
+ for (const s of [...stats.by_source].sort((a, b) => share(b) - share(a))) {
14007
+ const s_val = share(s);
14008
+ const color = _sourceColor(s.source);
14009
+ lines.push(
14010
+ _tableRow({
14011
+ name: s.source,
14012
+ fraction: _barFraction(s.bytes, grossBytes),
14013
+ bytes: s.bytes,
14014
+ tokens: s.tokens,
14015
+ events: s.events,
14016
+ share: s_val,
14017
+ namePrefix: `${fg(...color)}\u25CF${RESET} `,
14018
+ nameColor: C.TEXT_PRIMARY
14019
+ })
14020
+ );
14021
+ }
14022
+ return lines;
14023
+ }
14024
+ function _renderByCommandSection(stats) {
14025
+ if (!stats.by_command || stats.by_command.length === 0) {
14026
+ return [];
14027
+ }
14028
+ const lines = [..._sectionHeader("By command"), _tableHeader("command")];
14029
+ const { grossBytes, shareBytesDenom, shareTokensDenom } = _computeShareDenominators(stats.by_command);
14030
+ function share(c) {
14031
+ return _absShare(c.bytes, c.tokens, shareBytesDenom, shareTokensDenom);
14032
+ }
14033
+ for (const c of [...stats.by_command].sort((a, b) => share(b) - share(a))) {
14034
+ const s_val = share(c);
14035
+ lines.push(
14036
+ _tableRow({
14037
+ name: c.command,
14038
+ fraction: _barFraction(c.bytes, grossBytes),
14039
+ bytes: c.bytes,
14040
+ tokens: c.tokens,
14041
+ events: c.events,
14042
+ share: s_val,
14043
+ nameColor: C.TEXT_PRIMARY
14044
+ })
14045
+ );
14046
+ }
14047
+ return lines;
14048
+ }
14049
+ function _renderByDaySection(stats) {
14050
+ if (stats.by_day.length === 0) {
14051
+ return [];
14052
+ }
14053
+ const lines = [..._sectionHeader("By day"), _tableHeader("date")];
14054
+ function share(d) {
14055
+ return _tokenOrByteShare(d.tokens, d.bytes, stats.totals.tokens, stats.totals.bytes);
14056
+ }
14057
+ for (const d of [...stats.by_day].sort((a, b) => b.date.localeCompare(a.date))) {
14058
+ const s = share(d);
14059
+ lines.push(
14060
+ _tableRow({
14061
+ name: d.date,
14062
+ fraction: s,
14063
+ bytes: d.bytes,
14064
+ tokens: d.tokens,
14065
+ events: d.events,
14066
+ share: s
14067
+ })
14068
+ );
14069
+ }
14070
+ return lines;
14071
+ }
14072
+ var _PROJECT_COLORS = [C.PURPLE, C.TEAL, C.BLUE, C.GREEN4, C.TEXT_MUTED];
14073
+ function _hashColor(hashStr) {
14074
+ let n = 0;
14075
+ for (const c of hashStr) {
14076
+ n += c.charCodeAt(0);
14077
+ }
14078
+ return _PROJECT_COLORS[n % _PROJECT_COLORS.length] ?? C.TEXT_MUTED;
14079
+ }
14080
+ function _renderByProjectSection(stats) {
14081
+ if (stats.by_project.length === 0) {
14082
+ return [];
14083
+ }
14084
+ const projectTotalBytes = stats.by_project.reduce((s, p) => s + p.bytes, 0);
14085
+ const projectTotalTokens = stats.by_project.reduce((s, p) => s + p.tokens, 0);
14086
+ const lines = [..._sectionHeader(`By project (top ${stats.by_project.length})`), _tableHeader("project")];
14087
+ function share(p) {
14088
+ return _tokenOrByteShare(p.tokens, p.bytes, projectTotalTokens, projectTotalBytes);
14089
+ }
14090
+ for (const p of [...stats.by_project].sort((a, b) => share(b) - share(a))) {
14091
+ const s = share(p);
14092
+ const color = _hashColor(p.hash);
14093
+ lines.push(
14094
+ _tableRow({
14095
+ name: p.project,
14096
+ fraction: s,
14097
+ bytes: p.bytes,
14098
+ tokens: p.tokens,
14099
+ events: p.events,
14100
+ share: s,
14101
+ namePrefix: `${fg(...color)}\u25CF${RESET} `,
14102
+ nameColor: C.TEXT_PRIMARY
14103
+ })
14104
+ );
14105
+ lines.push(`${_M} ${fg(...C.TEXT_DIM)}\u2514\u2500 ${p.hash} ${stripAnsi(p.path)}${RESET}`);
14106
+ }
14107
+ return lines;
14108
+ }
14109
+ function _renderInsightsSection(stats) {
14110
+ const lines = [..._sectionHeader("Insights")];
14111
+ const bullet = `${fg(...C.GREEN3)}\u25B8${RESET}`;
14112
+ function dim(s) {
14113
+ return `${fg(...C.TEXT_MUTED)}${s}${RESET}`;
14114
+ }
14115
+ const topKind = stats.by_kind.reduce((max, k) => k.bytes > (max?.bytes || -Infinity) ? k : max, stats.by_kind[0]);
14116
+ if (topKind) {
14117
+ const share = stats.totals.bytes > 0 ? topKind.bytes / stats.totals.bytes : 0;
14118
+ lines.push(
14119
+ `${_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`)}`
14120
+ );
14121
+ }
14122
+ const topDay = stats.by_day.reduce((max, d) => d.events > (max?.events || -Infinity) ? d : max, stats.by_day[0]);
14123
+ if (topDay) {
14124
+ lines.push(
14125
+ `${_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")}`
14126
+ );
14127
+ }
14128
+ const tokenKinds = stats.by_kind.filter((k) => !k.bytes_mode_only);
14129
+ const topToken = tokenKinds.reduce((max, k) => k.tokens > (max?.tokens || -Infinity) ? k : max, tokenKinds[0]);
14130
+ if (topToken) {
14131
+ lines.push(
14132
+ `${_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`)}`
14133
+ );
14134
+ }
14135
+ return lines;
14136
+ }
14137
+ function _renderHeader(stats) {
14138
+ let line = `${_M}${fg(...C.TEXT_BRIGHT)}token-goat${RESET}`;
14139
+ if (stats.version) {
14140
+ line += ` ${fg(...C.TEXT_MUTED)}v${stats.version}${RESET}`;
14141
+ }
14142
+ if (stats.window_label) {
14143
+ line += ` ${fg(...C.TEXT_DIM)}\xB7 ${stats.window_label}${RESET}`;
14144
+ }
14145
+ return [line];
14146
+ }
14147
+ function renderStats(stats) {
14148
+ const sections = [
14149
+ _renderHeader(stats),
14150
+ _renderKpiSection(stats),
14151
+ _renderByKindSection(stats),
14152
+ _renderBySourceSection(stats),
14153
+ _renderByCommandSection(stats),
14154
+ _renderByDaySection(stats),
14155
+ _renderByProjectSection(stats),
14156
+ _renderInsightsSection(stats),
14157
+ [""]
14158
+ ];
14159
+ return sections.flatMap((s) => s).join("\n");
14160
+ }
14161
+
14162
+ // src/stats.ts
12687
14163
  var SOURCE_IMAGE = "image";
12688
14164
  var SOURCE_HINT = "hint";
12689
14165
  var SOURCE_READ = "read";
@@ -12692,6 +14168,7 @@ var SOURCE_WEB = "web";
12692
14168
  var SOURCE_MCP = "mcp";
12693
14169
  var SOURCE_SKILL = "skill";
12694
14170
  var SOURCE_OTHER = "other";
14171
+ var _BYTES_MODE_ONLY_KINDS = /* @__PURE__ */ new Set(["webfetch_image", "gdrive_image"]);
12695
14172
  var KIND_TO_SOURCE = {
12696
14173
  image_shrink: SOURCE_IMAGE,
12697
14174
  image_shrink_cache_hit: SOURCE_IMAGE,
@@ -12734,7 +14211,15 @@ var COMMAND_KINDS = {
12734
14211
  skeleton: /* @__PURE__ */ new Set(["stub_view"]),
12735
14212
  refs: /* @__PURE__ */ new Set(["symbol_read"]),
12736
14213
  map: /* @__PURE__ */ new Set(["map_lookup"]),
12737
- changed: /* @__PURE__ */ new Set(["changed_lookup"])
14214
+ changed: /* @__PURE__ */ new Set(["changed_lookup"]),
14215
+ npm: /* @__PURE__ */ new Set([
14216
+ "bash_compress:npm_install",
14217
+ "bash_compress:npm_ci",
14218
+ "bash_compress:npm_audit",
14219
+ "bash_compress:npm_ls",
14220
+ "bash_compress:npm_outdated",
14221
+ "bash_compress:npx"
14222
+ ])
12738
14223
  };
12739
14224
  var OVERHEAD_SUFFIX = "_overhead";
12740
14225
  function kindToSource(kind) {
@@ -12759,7 +14244,7 @@ function incBucket(bucket, bytesSaved, tokensSaved) {
12759
14244
  bucket.tokens_saved += tokensSaved;
12760
14245
  }
12761
14246
  function getGlobalDb() {
12762
- const dbPath = path10.join(dataDir(), "global.db");
14247
+ const dbPath = path12.join(dataDir(), "global.db");
12763
14248
  return getDb(dbPath);
12764
14249
  }
12765
14250
  function summarize(windowDays = 30, testDb) {
@@ -12833,12 +14318,7 @@ function summarize(windowDays = 30, testDb) {
12833
14318
  window_days: windowDays
12834
14319
  };
12835
14320
  }
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
- }
14321
+ function _plainTextStats(summary) {
12842
14322
  const fmtBytes = (n) => {
12843
14323
  if (n < 1024) return `${n}B`;
12844
14324
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
@@ -12878,11 +14358,71 @@ function renderStats(opts) {
12878
14358
  }
12879
14359
  console.log(lines.join("\n"));
12880
14360
  }
14361
+ function renderStats2(opts) {
14362
+ const windowDays = opts?.windowDays ?? 30;
14363
+ const summary = summarize(windowDays);
14364
+ if (summary.total_events === 0) {
14365
+ console.log("No stats recorded yet.");
14366
+ return;
14367
+ }
14368
+ const useTty = process.stdout.isTTY === true && !process.env["NO_COLOR"];
14369
+ if (!useTty) {
14370
+ _plainTextStats(summary);
14371
+ return;
14372
+ }
14373
+ const now = /* @__PURE__ */ new Date();
14374
+ const periodStart = windowDays > 0 ? new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1e3) : /* @__PURE__ */ new Date(0);
14375
+ const sparkDays = [...summary.by_day].reverse().slice(-30);
14376
+ const sparklines = sparkDays.length > 1 ? {
14377
+ events: sparkDays.map((d) => d.events),
14378
+ bytes: sparkDays.map((d) => d.bytes_saved),
14379
+ tokens: sparkDays.map((d) => d.tokens_saved)
14380
+ } : null;
14381
+ const statsData = {
14382
+ period_start: periodStart,
14383
+ period_end: now,
14384
+ version: VERSION,
14385
+ window_label: windowDays > 0 ? `last ${windowDays} days` : "all time",
14386
+ totals: {
14387
+ events: summary.total_events,
14388
+ bytes: summary.total_bytes_saved,
14389
+ tokens: summary.total_tokens_saved,
14390
+ sparklines
14391
+ },
14392
+ by_kind: Object.entries(summary.by_kind).map(([kind, bucket]) => ({
14393
+ kind,
14394
+ bytes: bucket.bytes_saved,
14395
+ tokens: bucket.tokens_saved,
14396
+ events: bucket.events,
14397
+ bytes_mode_only: _BYTES_MODE_ONLY_KINDS.has(kind)
14398
+ })).sort((a, b) => b.bytes - a.bytes),
14399
+ by_day: summary.by_day.map((d) => ({
14400
+ date: d.date,
14401
+ bytes: d.bytes_saved,
14402
+ tokens: d.tokens_saved,
14403
+ events: d.events
14404
+ })),
14405
+ by_project: [],
14406
+ by_source: Object.entries(summary.by_source).filter(([, b]) => b.events > 0).map(([source, bucket]) => ({
14407
+ source,
14408
+ bytes: bucket.bytes_saved,
14409
+ tokens: bucket.tokens_saved,
14410
+ events: bucket.events
14411
+ })).sort((a, b) => b.bytes - a.bytes),
14412
+ by_command: summary.by_command.map((c) => ({
14413
+ command: c.command,
14414
+ bytes: c.bytes_saved,
14415
+ tokens: c.tokens_saved,
14416
+ events: c.events
14417
+ }))
14418
+ };
14419
+ process.stdout.write(renderStats(statsData) + "\n");
14420
+ }
12881
14421
 
12882
14422
  // src/cli_doctor.ts
12883
14423
  init_define_import_meta_env();
12884
- import * as fs12 from "fs";
12885
- import * as path11 from "path";
14424
+ import * as fs13 from "fs";
14425
+ import * as path13 from "path";
12886
14426
  import { execSync } from "child_process";
12887
14427
  function checkWorkerRunning() {
12888
14428
  try {
@@ -12893,8 +14433,8 @@ function checkWorkerRunning() {
12893
14433
  }
12894
14434
  }
12895
14435
  function checkDbExists(dataDir2) {
12896
- const dbPath = path11.join(dataDir2, "index.db");
12897
- if (!fs12.existsSync(dbPath)) {
14436
+ const dbPath = path13.join(dataDir2, "index.db");
14437
+ if (!fs13.existsSync(dbPath)) {
12898
14438
  return {
12899
14439
  name: "Database",
12900
14440
  status: "warn",
@@ -12904,7 +14444,7 @@ function checkDbExists(dataDir2) {
12904
14444
  return {
12905
14445
  name: "Database",
12906
14446
  status: "ok",
12907
- message: `index.db exists (${Math.round(fs12.statSync(dbPath).size / 1024)} KB)`
14447
+ message: `index.db exists (${Math.round(fs13.statSync(dbPath).size / 1024)} KB)`
12908
14448
  };
12909
14449
  }
12910
14450
  function checkInstall() {
@@ -12924,7 +14464,7 @@ function checkInstall() {
12924
14464
  }
12925
14465
  }
12926
14466
  function checkConfigValid(configPath2) {
12927
- if (!fs12.existsSync(configPath2)) {
14467
+ if (!fs13.existsSync(configPath2)) {
12928
14468
  return {
12929
14469
  name: "Config",
12930
14470
  status: "warn",
@@ -12932,7 +14472,7 @@ function checkConfigValid(configPath2) {
12932
14472
  };
12933
14473
  }
12934
14474
  try {
12935
- const content = fs12.readFileSync(configPath2, "utf-8");
14475
+ const content = fs13.readFileSync(configPath2, "utf-8");
12936
14476
  JSON.parse(content);
12937
14477
  return {
12938
14478
  name: "Config",
@@ -12966,9 +14506,9 @@ function runDoctor(dataDir2, configPath2) {
12966
14506
  results.push(checkInstall());
12967
14507
  results.push(checkWorkerRunning() ? { name: "Worker", status: "ok", message: "running" } : { name: "Worker", status: "warn", message: "not running" });
12968
14508
  const homeDir = process.env["HOME"] || process.env["USERPROFILE"] || "~";
12969
- const actualDataDir = dataDir2 || path11.join(homeDir, ".token-goat");
14509
+ const actualDataDir = dataDir2 || path13.join(homeDir, ".token-goat");
12970
14510
  results.push(checkDbExists(actualDataDir));
12971
- const actualConfigPath = configPath2 || path11.join(actualDataDir, "config.json");
14511
+ const actualConfigPath = configPath2 || path13.join(actualDataDir, "config.json");
12972
14512
  results.push(checkConfigValid(actualConfigPath));
12973
14513
  results.push(checkDiskSpace(actualDataDir));
12974
14514
  return results;
@@ -13258,7 +14798,7 @@ function cmdWorkerStatus() {
13258
14798
  out(isWorkerRunning() ? "Worker is running." : "Worker is not running.");
13259
14799
  }
13260
14800
  function cmdStats() {
13261
- renderStats({ windowDays: 30 });
14801
+ renderStats2({ windowDays: 30 });
13262
14802
  }
13263
14803
  function cmdDoctor() {
13264
14804
  const code = runDoctorAndExit();
@@ -13266,14 +14806,12 @@ function cmdDoctor() {
13266
14806
  throw new CliError("doctor checks failed");
13267
14807
  }
13268
14808
  }
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;
14809
+ function _applyFiltersAndPrint(content, opts) {
13275
14810
  if (opts.grep !== void 0) {
13276
- const pattern = opts.grep;
14811
+ let pattern = opts.grep;
14812
+ if (pattern.startsWith("-E ") || pattern.startsWith("--extended-regexp ")) {
14813
+ pattern = pattern.replace(/^(?:-E\s+|--extended-regexp\s+)/, "");
14814
+ }
13277
14815
  try {
13278
14816
  const re = new RegExp(pattern);
13279
14817
  content = content.split(/\r?\n/).filter((line) => re.test(line)).join("\n");
@@ -13294,12 +14832,32 @@ function cmdBashOutput(id, opts) {
13294
14832
  }
13295
14833
  out(result.join("\n"));
13296
14834
  }
14835
+ function cmdBashOutput(id, opts) {
14836
+ if (opts.file !== void 0) {
14837
+ let content;
14838
+ try {
14839
+ content = fs14.readFileSync(opts.file, "utf-8");
14840
+ } catch {
14841
+ throw new CliError(`cannot read file: ${opts.file}`);
14842
+ }
14843
+ _applyFiltersAndPrint(content, opts);
14844
+ return;
14845
+ }
14846
+ if (id === void 0) {
14847
+ throw new CliError("provide an <id> or --file <path>");
14848
+ }
14849
+ const entry = getBashOutput(id);
14850
+ if (entry === null) {
14851
+ throw new CliError(`no cached bash output for id: ${id}`);
14852
+ }
14853
+ _applyFiltersAndPrint(entry.output, opts);
14854
+ }
13297
14855
  async function cmdSkillBody(name, opts) {
13298
14856
  const filePath = await getSkillFilePath(name);
13299
14857
  if (filePath === null) {
13300
14858
  throw new CliError(`skill '${name}' not found`);
13301
14859
  }
13302
- const body = fs13.readFileSync(filePath, "utf-8");
14860
+ const body = fs14.readFileSync(filePath, "utf-8");
13303
14861
  if (opts.compact === true) {
13304
14862
  const lines = body.split("\n");
13305
14863
  const end = lines.findIndex((l) => l.includes("COMPACT_END"));
@@ -13317,7 +14875,7 @@ async function cmdSkillCompact(name) {
13317
14875
  if (filePath === null) {
13318
14876
  throw new CliError(`skill '${name}' not found`);
13319
14877
  }
13320
- const body = fs13.readFileSync(filePath, "utf-8");
14878
+ const body = fs14.readFileSync(filePath, "utf-8");
13321
14879
  const sessionFiles = getSessionFiles();
13322
14880
  const sessionId = Array.from(sessionFiles.keys())[0] ?? "default";
13323
14881
  await storeCompact(sessionId, name, body);
@@ -13445,7 +15003,7 @@ function buildProgram() {
13445
15003
  worker.command("status").description("check if the indexer is running").action(guard(cmdWorkerStatus));
13446
15004
  program2.command("stats").description("show session statistics").action(guard(cmdStats));
13447
15005
  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));
15006
+ 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
15007
  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
15008
  program2.command("skill-compact <name>").description("regenerate and cache compact slice for a skill").action(guard(cmdSkillCompact));
13451
15009
  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));