context-compress 2026.3.3 → 2026.3.13

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 (41) hide show
  1. package/README.md +14 -4
  2. package/dist/cli/doctor.d.ts.map +1 -1
  3. package/dist/cli/doctor.js +22 -1
  4. package/dist/cli/doctor.js.map +1 -1
  5. package/dist/config.d.ts +3 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +97 -4
  8. package/dist/config.js.map +1 -1
  9. package/dist/executor.d.ts +11 -0
  10. package/dist/executor.d.ts.map +1 -1
  11. package/dist/executor.js +104 -3
  12. package/dist/executor.js.map +1 -1
  13. package/dist/hooks/pretooluse.js +12 -6
  14. package/dist/hooks/pretooluse.js.map +1 -1
  15. package/dist/network.d.ts +5 -0
  16. package/dist/network.d.ts.map +1 -0
  17. package/dist/network.js +42 -0
  18. package/dist/network.js.map +1 -0
  19. package/dist/runtime/languages/go.js +3 -3
  20. package/dist/runtime/languages/go.js.map +1 -1
  21. package/dist/runtime/languages/javascript.js +1 -1
  22. package/dist/runtime/languages/javascript.js.map +1 -1
  23. package/dist/runtime/languages/r.js +1 -1
  24. package/dist/runtime/languages/rust.js +2 -2
  25. package/dist/runtime/languages/rust.js.map +1 -1
  26. package/dist/runtime/languages/typescript.js +1 -1
  27. package/dist/runtime/languages/typescript.js.map +1 -1
  28. package/dist/server.bundle.mjs +460 -120
  29. package/dist/server.bundle.mjs.map +4 -4
  30. package/dist/server.d.ts.map +1 -1
  31. package/dist/server.js +112 -31
  32. package/dist/server.js.map +1 -1
  33. package/dist/stats.d.ts.map +1 -1
  34. package/dist/stats.js +27 -4
  35. package/dist/stats.js.map +1 -1
  36. package/dist/store.d.ts +14 -0
  37. package/dist/store.d.ts.map +1 -1
  38. package/dist/store.js +41 -10
  39. package/dist/store.js.map +1 -1
  40. package/hooks/pretooluse.mjs +10 -5
  41. package/package.json +6 -4
@@ -6800,85 +6800,8 @@ var require_dist = __commonJS({
6800
6800
 
6801
6801
  // src/config.ts
6802
6802
  import { readFileSync } from "node:fs";
6803
+ import { homedir } from "node:os";
6803
6804
  import { join } from "node:path";
6804
- var DEFAULTS = {
6805
- passthroughEnvVars: [],
6806
- debug: false,
6807
- blockCurl: true,
6808
- blockWebFetch: true,
6809
- nudgeOnRead: true,
6810
- nudgeOnGrep: true,
6811
- intentSearchThreshold: 5e3,
6812
- maxOutputBytes: 102400,
6813
- hardCapBytes: 100 * 1024 * 1024,
6814
- searchMaxBytes: 40960,
6815
- batchMaxBytes: 81920,
6816
- searchLimit: 3,
6817
- searchWindowMs: 6e4,
6818
- searchReduceAfter: 3,
6819
- searchBlockAfter: 8
6820
- };
6821
- function loadFileConfig(projectDir2) {
6822
- const paths = [
6823
- projectDir2 && join(projectDir2, ".context-compress.json"),
6824
- join(process.env.HOME ?? "~", ".context-compress.json")
6825
- ].filter(Boolean);
6826
- for (const p of paths) {
6827
- try {
6828
- const raw = readFileSync(p, "utf-8");
6829
- return JSON.parse(raw);
6830
- } catch {
6831
- }
6832
- }
6833
- return {};
6834
- }
6835
- function loadEnvConfig() {
6836
- const partial2 = {};
6837
- if (process.env.CONTEXT_COMPRESS_DEBUG === "1") {
6838
- partial2.debug = true;
6839
- }
6840
- if (process.env.CONTEXT_COMPRESS_PASSTHROUGH_ENV) {
6841
- partial2.passthroughEnvVars = process.env.CONTEXT_COMPRESS_PASSTHROUGH_ENV.split(",").map((s) => s.trim()).filter(Boolean);
6842
- }
6843
- if (process.env.CONTEXT_COMPRESS_BLOCK_CURL !== void 0) {
6844
- partial2.blockCurl = process.env.CONTEXT_COMPRESS_BLOCK_CURL !== "0";
6845
- }
6846
- if (process.env.CONTEXT_COMPRESS_BLOCK_WEBFETCH !== void 0) {
6847
- partial2.blockWebFetch = process.env.CONTEXT_COMPRESS_BLOCK_WEBFETCH !== "0";
6848
- }
6849
- if (process.env.CONTEXT_COMPRESS_NUDGE_READ !== void 0) {
6850
- partial2.nudgeOnRead = process.env.CONTEXT_COMPRESS_NUDGE_READ !== "0";
6851
- }
6852
- if (process.env.CONTEXT_COMPRESS_NUDGE_GREP !== void 0) {
6853
- partial2.nudgeOnGrep = process.env.CONTEXT_COMPRESS_NUDGE_GREP !== "0";
6854
- }
6855
- return partial2;
6856
- }
6857
- var _config = null;
6858
- function loadConfig(projectDir2) {
6859
- if (_config) return _config;
6860
- const fileConfig = loadFileConfig(projectDir2);
6861
- const envConfig = loadEnvConfig();
6862
- _config = { ...DEFAULTS, ...fileConfig, ...envConfig };
6863
- return _config;
6864
- }
6865
- function getConfig() {
6866
- if (!_config) return loadConfig();
6867
- return _config;
6868
- }
6869
-
6870
- // src/logger.ts
6871
- function debug(...args) {
6872
- if (getConfig().debug) {
6873
- process.stderr.write(`[context-compress] ${args.map(String).join(" ")}
6874
- `);
6875
- }
6876
- }
6877
-
6878
- // src/server.ts
6879
- import { readFileSync as readFileSync2 } from "node:fs";
6880
- import { dirname, join as join4, resolve } from "node:path";
6881
- import { fileURLToPath } from "node:url";
6882
6805
 
6883
6806
  // node_modules/zod/v3/external.js
6884
6807
  var external_exports = {};
@@ -10921,6 +10844,163 @@ var coerce = {
10921
10844
  };
10922
10845
  var NEVER = INVALID;
10923
10846
 
10847
+ // src/config.ts
10848
+ var DEFAULTS = {
10849
+ passthroughEnvVars: [],
10850
+ debug: false,
10851
+ blockCurl: true,
10852
+ blockWebFetch: true,
10853
+ nudgeOnRead: true,
10854
+ nudgeOnGrep: true,
10855
+ intentSearchThreshold: 5e3,
10856
+ maxOutputBytes: 102400,
10857
+ hardCapBytes: 100 * 1024 * 1024,
10858
+ searchMaxBytes: 40960,
10859
+ batchMaxBytes: 81920,
10860
+ searchLimit: 3,
10861
+ searchWindowMs: 6e4,
10862
+ searchReduceAfter: 3,
10863
+ searchBlockAfter: 8,
10864
+ compressionLevel: "normal"
10865
+ };
10866
+ var LEVEL_OVERRIDES = {
10867
+ normal: {},
10868
+ compact: {
10869
+ maxOutputBytes: 51200,
10870
+ searchMaxBytes: 20480,
10871
+ batchMaxBytes: 40960,
10872
+ searchLimit: 2,
10873
+ intentSearchThreshold: 3e3
10874
+ },
10875
+ ultra: {
10876
+ maxOutputBytes: 25600,
10877
+ searchMaxBytes: 10240,
10878
+ batchMaxBytes: 20480,
10879
+ searchLimit: 1,
10880
+ intentSearchThreshold: 2e3
10881
+ }
10882
+ };
10883
+ var ConfigSchema = external_exports.object({
10884
+ passthroughEnvVars: external_exports.array(external_exports.string()).optional(),
10885
+ debug: external_exports.boolean().optional(),
10886
+ blockCurl: external_exports.boolean().optional(),
10887
+ blockWebFetch: external_exports.boolean().optional(),
10888
+ nudgeOnRead: external_exports.boolean().optional(),
10889
+ nudgeOnGrep: external_exports.boolean().optional(),
10890
+ intentSearchThreshold: external_exports.number().int().positive().optional(),
10891
+ maxOutputBytes: external_exports.number().int().positive().optional(),
10892
+ hardCapBytes: external_exports.number().int().positive().optional(),
10893
+ searchMaxBytes: external_exports.number().int().positive().optional(),
10894
+ batchMaxBytes: external_exports.number().int().positive().optional(),
10895
+ searchLimit: external_exports.number().int().positive().optional(),
10896
+ searchWindowMs: external_exports.number().int().positive().optional(),
10897
+ searchReduceAfter: external_exports.number().int().nonnegative().optional(),
10898
+ searchBlockAfter: external_exports.number().int().positive().optional(),
10899
+ compressionLevel: external_exports.enum(["normal", "compact", "ultra"]).optional()
10900
+ });
10901
+ function parseIntEnv(key) {
10902
+ const val = process.env[key];
10903
+ if (val === void 0) return void 0;
10904
+ const n = Number.parseInt(val, 10);
10905
+ return Number.isNaN(n) ? void 0 : n;
10906
+ }
10907
+ function loadFileConfig(projectDir2) {
10908
+ const paths = [
10909
+ projectDir2 && join(projectDir2, ".context-compress.json"),
10910
+ join(homedir(), ".context-compress.json")
10911
+ ].filter(Boolean);
10912
+ for (const p of paths) {
10913
+ try {
10914
+ const raw = readFileSync(p, "utf-8");
10915
+ const parsed = JSON.parse(raw);
10916
+ const result = ConfigSchema.safeParse(parsed);
10917
+ if (result.success) {
10918
+ return result.data;
10919
+ }
10920
+ return {};
10921
+ } catch {
10922
+ }
10923
+ }
10924
+ return {};
10925
+ }
10926
+ function loadEnvConfig() {
10927
+ const partial2 = {};
10928
+ if (process.env.CONTEXT_COMPRESS_DEBUG === "1") {
10929
+ partial2.debug = true;
10930
+ }
10931
+ if (process.env.CONTEXT_COMPRESS_PASSTHROUGH_ENV) {
10932
+ partial2.passthroughEnvVars = process.env.CONTEXT_COMPRESS_PASSTHROUGH_ENV.split(",").map((s) => s.trim()).filter(Boolean);
10933
+ }
10934
+ if (process.env.CONTEXT_COMPRESS_BLOCK_CURL !== void 0) {
10935
+ partial2.blockCurl = process.env.CONTEXT_COMPRESS_BLOCK_CURL !== "0";
10936
+ }
10937
+ if (process.env.CONTEXT_COMPRESS_BLOCK_WEBFETCH !== void 0) {
10938
+ partial2.blockWebFetch = process.env.CONTEXT_COMPRESS_BLOCK_WEBFETCH !== "0";
10939
+ }
10940
+ if (process.env.CONTEXT_COMPRESS_NUDGE_READ !== void 0) {
10941
+ partial2.nudgeOnRead = process.env.CONTEXT_COMPRESS_NUDGE_READ !== "0";
10942
+ }
10943
+ if (process.env.CONTEXT_COMPRESS_NUDGE_GREP !== void 0) {
10944
+ partial2.nudgeOnGrep = process.env.CONTEXT_COMPRESS_NUDGE_GREP !== "0";
10945
+ }
10946
+ const maxOutput = parseIntEnv("CONTEXT_COMPRESS_MAX_OUTPUT_BYTES");
10947
+ if (maxOutput !== void 0) partial2.maxOutputBytes = maxOutput;
10948
+ const hardCap = parseIntEnv("CONTEXT_COMPRESS_HARD_CAP_BYTES");
10949
+ if (hardCap !== void 0) partial2.hardCapBytes = hardCap;
10950
+ const searchMax = parseIntEnv("CONTEXT_COMPRESS_SEARCH_MAX_BYTES");
10951
+ if (searchMax !== void 0) partial2.searchMaxBytes = searchMax;
10952
+ const batchMax = parseIntEnv("CONTEXT_COMPRESS_BATCH_MAX_BYTES");
10953
+ if (batchMax !== void 0) partial2.batchMaxBytes = batchMax;
10954
+ const searchLimit = parseIntEnv("CONTEXT_COMPRESS_SEARCH_LIMIT");
10955
+ if (searchLimit !== void 0) partial2.searchLimit = searchLimit;
10956
+ const searchWindow = parseIntEnv("CONTEXT_COMPRESS_SEARCH_WINDOW_MS");
10957
+ if (searchWindow !== void 0) partial2.searchWindowMs = searchWindow;
10958
+ const searchReduce = parseIntEnv("CONTEXT_COMPRESS_SEARCH_REDUCE_AFTER");
10959
+ if (searchReduce !== void 0) partial2.searchReduceAfter = searchReduce;
10960
+ const searchBlock = parseIntEnv("CONTEXT_COMPRESS_SEARCH_BLOCK_AFTER");
10961
+ if (searchBlock !== void 0) partial2.searchBlockAfter = searchBlock;
10962
+ const intentThreshold = parseIntEnv("CONTEXT_COMPRESS_INTENT_SEARCH_THRESHOLD");
10963
+ if (intentThreshold !== void 0) partial2.intentSearchThreshold = intentThreshold;
10964
+ const level = process.env.CONTEXT_COMPRESS_LEVEL;
10965
+ if (level === "normal" || level === "compact" || level === "ultra") {
10966
+ partial2.compressionLevel = level;
10967
+ }
10968
+ return partial2;
10969
+ }
10970
+ var _config = null;
10971
+ function loadConfig(projectDir2) {
10972
+ if (_config) return _config;
10973
+ const fileConfig = loadFileConfig(projectDir2);
10974
+ const envConfig = loadEnvConfig();
10975
+ const merged = { ...DEFAULTS, ...fileConfig, ...envConfig };
10976
+ const levelOverrides = LEVEL_OVERRIDES[merged.compressionLevel];
10977
+ for (const [key, value] of Object.entries(levelOverrides)) {
10978
+ const k = key;
10979
+ if (!(k in fileConfig) && !(k in envConfig)) {
10980
+ merged[k] = value;
10981
+ }
10982
+ }
10983
+ _config = merged;
10984
+ return _config;
10985
+ }
10986
+ function getConfig() {
10987
+ if (!_config) return loadConfig();
10988
+ return _config;
10989
+ }
10990
+
10991
+ // src/logger.ts
10992
+ function debug(...args) {
10993
+ if (getConfig().debug) {
10994
+ process.stderr.write(`[context-compress] ${args.map(String).join(" ")}
10995
+ `);
10996
+ }
10997
+ }
10998
+
10999
+ // src/server.ts
11000
+ import { readFileSync as readFileSync2, statSync } from "node:fs";
11001
+ import { dirname, join as join4, resolve } from "node:path";
11002
+ import { fileURLToPath } from "node:url";
11003
+
10924
11004
  // node_modules/zod/v4/core/core.js
10925
11005
  var NEVER2 = Object.freeze({
10926
11006
  status: "aborted"
@@ -21073,7 +21153,6 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
21073
21153
  import { tmpdir } from "node:os";
21074
21154
  import { join as join2 } from "node:path";
21075
21155
  var DEFAULT_TIMEOUT = 3e4;
21076
- var DEFAULT_HARD_CAP = 100 * 1024 * 1024;
21077
21156
  var SAFE_ENV_KEYS = [
21078
21157
  "PATH",
21079
21158
  "HOME",
@@ -21126,6 +21205,86 @@ function killProcessTree(pid) {
21126
21205
  }
21127
21206
  }
21128
21207
  }
21208
+ function deduplicateLines(output) {
21209
+ const lines = output.split("\n");
21210
+ if (lines.length < 3) return output;
21211
+ const result = [];
21212
+ let prevLine = lines[0];
21213
+ let count = 1;
21214
+ for (let i = 1; i < lines.length; i++) {
21215
+ if (lines[i] === prevLine && prevLine.trim().length > 0) {
21216
+ count++;
21217
+ } else {
21218
+ if (count > 2) {
21219
+ result.push(prevLine);
21220
+ result.push(` ... (\xD7${count} identical lines)`);
21221
+ } else {
21222
+ for (let j = 0; j < count; j++) result.push(prevLine);
21223
+ }
21224
+ prevLine = lines[i];
21225
+ count = 1;
21226
+ }
21227
+ }
21228
+ if (count > 2) {
21229
+ result.push(prevLine);
21230
+ result.push(` ... (\xD7${count} identical lines)`);
21231
+ } else {
21232
+ for (let j = 0; j < count; j++) result.push(prevLine);
21233
+ }
21234
+ return result.join("\n");
21235
+ }
21236
+ function groupErrorLines(output) {
21237
+ const lines = output.split("\n");
21238
+ if (lines.length < 5) return output;
21239
+ const ERROR_RE = /^(.*?(?:error|warning|Error|Warning|ERR|WARN)[:\s])\s*(.+?)(?:\s+(?:at|in|on)\s+(?:line\s+)?(\d+))?$/i;
21240
+ const errorGroups = /* @__PURE__ */ new Map();
21241
+ const resultLines = [];
21242
+ let groupedCount = 0;
21243
+ for (const line of lines) {
21244
+ const match = line.match(ERROR_RE);
21245
+ if (match) {
21246
+ const prefix = match[1].trim();
21247
+ const msg = match[2].trim();
21248
+ const key = `${prefix}|${msg}`;
21249
+ const existing = errorGroups.get(key);
21250
+ if (existing) {
21251
+ existing.count++;
21252
+ if (match[3]) existing.locations.push(match[3]);
21253
+ groupedCount++;
21254
+ continue;
21255
+ }
21256
+ errorGroups.set(key, {
21257
+ message: `${prefix} ${msg}`,
21258
+ locations: match[3] ? [match[3]] : [],
21259
+ count: 1
21260
+ });
21261
+ groupedCount++;
21262
+ continue;
21263
+ }
21264
+ resultLines.push(line);
21265
+ }
21266
+ if (groupedCount < 4 || errorGroups.size === groupedCount) return output;
21267
+ const grouped = [];
21268
+ for (const [, group] of errorGroups) {
21269
+ if (group.count === 1) {
21270
+ grouped.push(
21271
+ group.message + (group.locations.length ? ` at line ${group.locations[0]}` : "")
21272
+ );
21273
+ } else {
21274
+ let line = `${group.message} (\xD7${group.count})`;
21275
+ if (group.locations.length > 0) {
21276
+ line += ` [lines: ${group.locations.join(", ")}]`;
21277
+ }
21278
+ grouped.push(line);
21279
+ }
21280
+ }
21281
+ if (grouped.length > 0) {
21282
+ resultLines.push("");
21283
+ resultLines.push(`\u2500\u2500 Grouped errors/warnings (${groupedCount} \u2192 ${errorGroups.size}) \u2500\u2500`);
21284
+ resultLines.push(...grouped);
21285
+ }
21286
+ return resultLines.join("\n");
21287
+ }
21129
21288
  function smartTruncate(output, maxBytes) {
21130
21289
  if (Buffer.byteLength(output) <= maxBytes) return output;
21131
21290
  const lines = output.split("\n");
@@ -21231,7 +21390,7 @@ var SubprocessExecutor = class {
21231
21390
  plugin.needsShell
21232
21391
  );
21233
21392
  } finally {
21234
- setTimeout(() => this.cleanupTempDir(tmpDir), 100);
21393
+ setTimeout(() => this.cleanupTempDir(tmpDir), 100).unref();
21235
21394
  }
21236
21395
  }
21237
21396
  /**
@@ -21317,6 +21476,8 @@ var SubprocessExecutor = class {
21317
21476
  stdout += `
21318
21477
  [output capped at ${formatBytes(hardCap)} \u2014 process killed]`;
21319
21478
  }
21479
+ stdout = deduplicateLines(stdout);
21480
+ stdout = groupErrorLines(stdout);
21320
21481
  const truncated = Buffer.byteLength(stdout) > maxOutput;
21321
21482
  if (truncated) {
21322
21483
  stdout = smartTruncate(stdout, maxOutput);
@@ -21354,6 +21515,25 @@ async function __cm_main(){${code}}
21354
21515
  __cm_main().then(()=>{${epilogue}}).catch(e=>{console.error(e);${epilogue}process.exit(1)});`;
21355
21516
  }
21356
21517
 
21518
+ // src/network.ts
21519
+ function isPrivateHost(hostname2) {
21520
+ const h = hostname2.startsWith("[") && hostname2.endsWith("]") ? hostname2.slice(1, -1) : hostname2;
21521
+ const lower = h.toLowerCase();
21522
+ if (lower === "localhost" || lower === "0.0.0.0") return true;
21523
+ if (/^127\./.test(h)) return true;
21524
+ if (/^10\./.test(h)) return true;
21525
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
21526
+ if (/^192\.168\./.test(h)) return true;
21527
+ if (/^169\.254\./.test(h)) return true;
21528
+ if (/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(h)) return true;
21529
+ if (lower === "::1") return true;
21530
+ const mappedMatch = lower.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
21531
+ if (mappedMatch) return isPrivateHost(mappedMatch[1]);
21532
+ if (/^fe[89ab]/i.test(h)) return true;
21533
+ if (/^f[cd]/i.test(h)) return true;
21534
+ return false;
21535
+ }
21536
+
21357
21537
  // src/runtime/index.ts
21358
21538
  import { exec } from "node:child_process";
21359
21539
  import { promisify } from "node:util";
@@ -21385,8 +21565,8 @@ var goPlugin = {
21385
21565
  return [runtime, "run", filePath];
21386
21566
  },
21387
21567
  preprocessCode(code) {
21388
- if (!code.includes("package ")) {
21389
- const hasImport = code.includes("import ");
21568
+ if (!/^package\s/m.test(code)) {
21569
+ const hasImport = /^import\s/m.test(code);
21390
21570
  if (hasImport) {
21391
21571
  return `package main
21392
21572
 
@@ -21405,7 +21585,7 @@ _ = fmt.Sprintf("")
21405
21585
  },
21406
21586
  wrapWithFileContent(code, filePath) {
21407
21587
  const escaped = JSON.stringify(filePath);
21408
- const hasPackage = code.includes("package ");
21588
+ const hasPackage = /^package\s/m.test(code);
21409
21589
  if (hasPackage) {
21410
21590
  return code.replace(
21411
21591
  /(package\s+\w+\n)/,
@@ -21446,8 +21626,9 @@ var javascriptPlugin = {
21446
21626
  },
21447
21627
  wrapWithFileContent(code, filePath) {
21448
21628
  const escaped = JSON.stringify(filePath);
21449
- return `const FILE_CONTENT_PATH = ${escaped};
21450
- const FILE_CONTENT = require("fs").readFileSync(FILE_CONTENT_PATH, "utf-8");
21629
+ return `const {readFileSync: __cm_readFileSync} = await import("node:fs");
21630
+ const FILE_CONTENT_PATH = ${escaped};
21631
+ const FILE_CONTENT = __cm_readFileSync(FILE_CONTENT_PATH, "utf-8");
21451
21632
  ${code}`;
21452
21633
  }
21453
21634
  };
@@ -21519,7 +21700,7 @@ ${code}`;
21519
21700
  // src/runtime/languages/r.ts
21520
21701
  var rPlugin = {
21521
21702
  language: "r",
21522
- runtimeCandidates: ["Rscript", "r"],
21703
+ runtimeCandidates: ["Rscript", "R"],
21523
21704
  fileExtension: ".R",
21524
21705
  buildCommand(runtime, filePath) {
21525
21706
  return [runtime, filePath];
@@ -21562,7 +21743,7 @@ var rustPlugin = {
21562
21743
  return [runtime, srcPath, "-o", binPath];
21563
21744
  },
21564
21745
  preprocessCode(code) {
21565
- if (!code.includes("fn main")) {
21746
+ if (!/^fn\s+main\s*\(/m.test(code)) {
21566
21747
  return `fn main() {
21567
21748
  ${code}
21568
21749
  }`;
@@ -21575,7 +21756,7 @@ ${code}
21575
21756
  let file_content_path = ${escaped};
21576
21757
  let file_content = fs::read_to_string(file_content_path).unwrap();
21577
21758
  `;
21578
- if (code.includes("fn main")) {
21759
+ if (/^fn\s+main\s*\(/m.test(code)) {
21579
21760
  return code.replace(/fn main\s*\(\s*\)\s*\{/, `fn main() {
21580
21761
  ${preamble}`);
21581
21762
  }
@@ -21611,8 +21792,9 @@ var typescriptPlugin = {
21611
21792
  },
21612
21793
  wrapWithFileContent(code, filePath) {
21613
21794
  const escaped = JSON.stringify(filePath);
21614
- return `const FILE_CONTENT_PATH = ${escaped};
21615
- const FILE_CONTENT = require("fs").readFileSync(FILE_CONTENT_PATH, "utf-8");
21795
+ return `const {readFileSync: __cm_readFileSync} = await import("node:fs");
21796
+ const FILE_CONTENT_PATH = ${escaped};
21797
+ const FILE_CONTENT = __cm_readFileSync(FILE_CONTENT_PATH, "utf-8");
21616
21798
  ${code}`;
21617
21799
  },
21618
21800
  // tsx and ts-node may be .cmd shims on Windows
@@ -21668,6 +21850,16 @@ function hasBun(runtimes) {
21668
21850
  }
21669
21851
 
21670
21852
  // src/stats.ts
21853
+ var BAR_WIDTH = 20;
21854
+ function asciiBar(ratio, width = BAR_WIDTH) {
21855
+ const filled = Math.round(ratio * width);
21856
+ const empty = width - filled;
21857
+ return `[${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}] ${(ratio * 100).toFixed(0)}%`;
21858
+ }
21859
+ function tokenCost(tokens) {
21860
+ const cost = tokens / 1e6 * 3;
21861
+ return cost >= 0.01 ? `~$${cost.toFixed(2)}` : "<$0.01";
21862
+ }
21671
21863
  var SessionTracker = class {
21672
21864
  stats = {
21673
21865
  calls: {},
@@ -21701,6 +21893,7 @@ var SessionTracker = class {
21701
21893
  const savingsRatio = totalReturned > 0 ? totalProcessed / totalReturned : 1;
21702
21894
  const reductionPct = totalProcessed > 0 ? ((1 - totalReturned / totalProcessed) * 100).toFixed(1) : "0.0";
21703
21895
  const estTokens = Math.round(totalReturned / 4);
21896
+ const estTokensAvoided = Math.round(keptOut / 4);
21704
21897
  const lines = [];
21705
21898
  lines.push("## Session Statistics\n");
21706
21899
  lines.push("| Metric | Value |");
@@ -21710,18 +21903,31 @@ var SessionTracker = class {
21710
21903
  lines.push(`| Total data processed | ${formatBytes(totalProcessed)} |`);
21711
21904
  lines.push(`| Kept in sandbox | ${formatBytes(keptOut)} |`);
21712
21905
  lines.push(`| Context consumed | ${formatBytes(totalReturned)} |`);
21713
- lines.push(`| Est. tokens | ~${estTokens.toLocaleString()} |`);
21906
+ lines.push(`| Est. tokens used | ~${estTokens.toLocaleString()} (${tokenCost(estTokens)}) |`);
21907
+ lines.push(
21908
+ `| Est. tokens saved | ~${estTokensAvoided.toLocaleString()} (${tokenCost(estTokensAvoided)}) |`
21909
+ );
21714
21910
  lines.push(
21715
21911
  `| **Savings ratio** | **${savingsRatio.toFixed(1)}x** (${reductionPct}% reduction) |`
21716
21912
  );
21913
+ if (totalProcessed > 0) {
21914
+ const savingsBar = asciiBar(keptOut / totalProcessed);
21915
+ lines.push(`
21916
+ **Context savings:** ${savingsBar}`);
21917
+ lines.push(
21918
+ ` Sandbox: ${formatBytes(keptOut)} kept out | Context: ${formatBytes(totalReturned)} entered`
21919
+ );
21920
+ }
21717
21921
  if (totalCalls > 0) {
21718
21922
  lines.push("\n## Per-Tool Breakdown\n");
21719
- lines.push("| Tool | Calls | Context bytes | Est. tokens |");
21720
- lines.push("|------|-------|--------------|-------------|");
21923
+ const maxBytes = Math.max(...Object.values(snap.bytesReturned));
21721
21924
  for (const [name, calls] of Object.entries(snap.calls)) {
21722
21925
  const bytes = snap.bytesReturned[name] ?? 0;
21926
+ const tokens = Math.round(bytes / 4);
21927
+ const barRatio = maxBytes > 0 ? bytes / maxBytes : 0;
21928
+ const bar = "\u2588".repeat(Math.max(1, Math.round(barRatio * 15)));
21723
21929
  lines.push(
21724
- `| ${name} | ${calls} | ${formatBytes(bytes)} | ~${Math.round(bytes / 4).toLocaleString()} |`
21930
+ ` ${name.padEnd(16)} ${String(calls).padStart(3)} calls ${bar} ${formatBytes(bytes)} (~${tokens.toLocaleString()} tok)`
21725
21931
  );
21726
21932
  }
21727
21933
  }
@@ -21917,7 +22123,7 @@ var WORD_SPLIT_RE = /[^\p{L}\p{N}_-]+/u;
21917
22123
  function sanitizeQuery(raw) {
21918
22124
  const q = raw.replace(FTS_SPECIAL_RE, " ").replace(FTS_OPERATORS_RE, " ").trim();
21919
22125
  const words = q.split(/\s+/).filter((w) => w.length >= 2).map((w) => `"${w}"`);
21920
- return words.length > 0 ? words.join(" OR ") : '""';
22126
+ return words.length > 0 ? words.join(" OR ") : "";
21921
22127
  }
21922
22128
  function levenshtein(a, b) {
21923
22129
  if (a.length === 0) return b.length;
@@ -21937,6 +22143,11 @@ function levenshtein(a, b) {
21937
22143
  var ContentStore = class {
21938
22144
  db;
21939
22145
  hasTrigramTable = false;
22146
+ // Cached prepared statements (initialized in initSchema, always available after constructor)
22147
+ insertSourceStmt;
22148
+ insertChunkStmt;
22149
+ vocabCountStmt;
22150
+ vocabInsertStmt;
21940
22151
  constructor(dbPath) {
21941
22152
  const path = dbPath ?? join3(tmpdir2(), `context-compress-${process.pid}.db`);
21942
22153
  this.db = new Database(path);
@@ -21966,6 +22177,14 @@ var ContentStore = class {
21966
22177
  word TEXT PRIMARY KEY
21967
22178
  );
21968
22179
  `);
22180
+ this.insertSourceStmt = this.db.prepare(
22181
+ "INSERT INTO sources (label, chunk_count, code_chunk_count) VALUES (?, ?, ?)"
22182
+ );
22183
+ this.insertChunkStmt = this.db.prepare(
22184
+ "INSERT INTO chunks (title, content, source_id, content_type) VALUES (?, ?, ?, ?)"
22185
+ );
22186
+ this.vocabCountStmt = this.db.prepare("SELECT COUNT(*) as cnt FROM vocabulary");
22187
+ this.vocabInsertStmt = this.db.prepare("INSERT OR IGNORE INTO vocabulary (word) VALUES (?)");
21969
22188
  }
21970
22189
  /** Lazily create trigram table only when porter search returns 0 results */
21971
22190
  ensureTrigramTable() {
@@ -21991,12 +22210,8 @@ var ContentStore = class {
21991
22210
  index(content, label) {
21992
22211
  const isMarkdown = HEADING_RE.test(content) || content.includes("```") || content.includes("---");
21993
22212
  const chunks = isMarkdown ? chunkMarkdown(content) : chunkPlainText(content);
21994
- const insertSource = this.db.prepare(
21995
- "INSERT INTO sources (label, chunk_count, code_chunk_count) VALUES (?, ?, ?)"
21996
- );
21997
- const insertChunk = this.db.prepare(
21998
- "INSERT INTO chunks (title, content, source_id, content_type) VALUES (?, ?, ?, ?)"
21999
- );
22213
+ const insertSource = this.insertSourceStmt;
22214
+ const insertChunk = this.insertChunkStmt;
22000
22215
  const insertTrigram = this.hasTrigramTable ? this.db.prepare(
22001
22216
  "INSERT INTO chunks_trigram (title, content, source_id, content_type) VALUES (?, ?, ?, ?)"
22002
22217
  ) : null;
@@ -22028,6 +22243,9 @@ var ContentStore = class {
22028
22243
  search(query, options) {
22029
22244
  const limit = options?.limit ?? 3;
22030
22245
  const sanitized = sanitizeQuery(query);
22246
+ if (!sanitized) {
22247
+ return { query, results: [] };
22248
+ }
22031
22249
  let hits = this.porterSearch(sanitized, options?.source, limit);
22032
22250
  if (hits.length > 0) {
22033
22251
  return { query, results: hits };
@@ -22040,9 +22258,11 @@ var ContentStore = class {
22040
22258
  const corrected = this.fuzzyCorrect(query);
22041
22259
  if (corrected && corrected !== query) {
22042
22260
  const correctedSanitized = sanitizeQuery(corrected);
22043
- hits = this.porterSearch(correctedSanitized, options?.source, limit);
22044
- if (hits.length > 0) {
22045
- return { query, results: hits, corrected };
22261
+ if (correctedSanitized) {
22262
+ hits = this.porterSearch(correctedSanitized, options?.source, limit);
22263
+ if (hits.length > 0) {
22264
+ return { query, results: hits, corrected };
22265
+ }
22046
22266
  }
22047
22267
  }
22048
22268
  return { query, results: [] };
@@ -22123,7 +22343,7 @@ var ContentStore = class {
22123
22343
  const maxDist = word.length <= 4 ? 1 : word.length <= 12 ? 2 : 3;
22124
22344
  const minLen = word.length - maxDist;
22125
22345
  const maxLen = word.length + maxDist;
22126
- const candidates = this.db.prepare("SELECT word FROM vocabulary WHERE length(word) BETWEEN ? AND ?").all(minLen, maxLen);
22346
+ const candidates = this.db.prepare("SELECT word FROM vocabulary WHERE length(word) BETWEEN ? AND ? LIMIT 500").all(minLen, maxLen);
22127
22347
  let bestWord = word;
22128
22348
  let bestDist = maxDist + 1;
22129
22349
  for (const { word: candidate } of candidates) {
@@ -22142,11 +22362,11 @@ var ContentStore = class {
22142
22362
  * Update vocabulary table from content (bounded to MAX_VOCABULARY).
22143
22363
  */
22144
22364
  updateVocabulary(content) {
22145
- const currentCount = this.db.prepare("SELECT COUNT(*) as cnt FROM vocabulary").get().cnt;
22365
+ const currentCount = this.vocabCountStmt.get().cnt;
22146
22366
  if (currentCount >= MAX_VOCABULARY) return;
22147
22367
  const words = content.split(WORD_SPLIT_RE).filter((w) => w.length >= 3 && !STOPWORDS.has(w.toLowerCase()));
22148
22368
  const unique = new Set(words.map((w) => w.toLowerCase()));
22149
- const insert = this.db.prepare("INSERT OR IGNORE INTO vocabulary (word) VALUES (?)");
22369
+ const insert = this.vocabInsertStmt;
22150
22370
  let added = 0;
22151
22371
  for (const word of unique) {
22152
22372
  if (currentCount + added >= MAX_VOCABULARY) break;
@@ -22163,7 +22383,7 @@ var ContentStore = class {
22163
22383
  ).get(...sourceId ? [sourceId] : []).cnt;
22164
22384
  if (totalChunks === 0) return [];
22165
22385
  const filter = sourceId ? " WHERE source_id = ?" : "";
22166
- const stmt = this.db.prepare(`SELECT content FROM chunks${filter}`);
22386
+ const stmt = this.db.prepare(`SELECT content FROM chunks${filter} LIMIT 500`);
22167
22387
  const rows = sourceId ? stmt.all(sourceId) : stmt.all();
22168
22388
  const docFreq = /* @__PURE__ */ new Map();
22169
22389
  for (const row of rows) {
@@ -22188,6 +22408,21 @@ var ContentStore = class {
22188
22408
  scored.sort((a, b) => b.score - a.score);
22189
22409
  return scored.slice(0, 40).map((s) => s.word);
22190
22410
  }
22411
+ /**
22412
+ * List all indexed sources with metadata.
22413
+ */
22414
+ listSources() {
22415
+ const rows = this.db.prepare(
22416
+ "SELECT id, label, chunk_count, code_chunk_count, indexed_at FROM sources ORDER BY indexed_at DESC"
22417
+ ).all();
22418
+ return rows.map((row) => ({
22419
+ id: row.id,
22420
+ label: row.label,
22421
+ chunkCount: row.chunk_count,
22422
+ codeChunks: row.code_chunk_count,
22423
+ indexedAt: row.indexed_at
22424
+ }));
22425
+ }
22191
22426
  /**
22192
22427
  * Get store statistics.
22193
22428
  */
@@ -22313,8 +22548,8 @@ function cleanupStaleDbs() {
22313
22548
  return cleaned;
22314
22549
  }
22315
22550
 
22316
- // src/server.ts
22317
- var LANGUAGES = [
22551
+ // src/types.ts
22552
+ var ALL_LANGUAGES = [
22318
22553
  "javascript",
22319
22554
  "typescript",
22320
22555
  "python",
@@ -22327,6 +22562,9 @@ var LANGUAGES = [
22327
22562
  "r",
22328
22563
  "elixir"
22329
22564
  ];
22565
+
22566
+ // src/server.ts
22567
+ var LANGUAGE_ENUM = ALL_LANGUAGES;
22330
22568
  var projectDir = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
22331
22569
  function isWithinProject(absPath) {
22332
22570
  const normalized = resolve(absPath);
@@ -22342,6 +22580,18 @@ function getVersion() {
22342
22580
  return "1.0.0";
22343
22581
  }
22344
22582
  }
22583
+ function compactLabel(normal, level) {
22584
+ if (level === "ultra") {
22585
+ return normal.replace(/\*\*/g, "").replace(/Use search\(queries: \[\.\.\.]\) to retrieve.*$/gm, "\u2192 search() for more").replace(/Searchable terms: .+$/gm, "");
22586
+ }
22587
+ if (level === "compact") {
22588
+ return normal.replace(
22589
+ /Use search\(queries: \[\.\.\.]\) to retrieve full content of any section\./,
22590
+ "\u2192 search() for details"
22591
+ );
22592
+ }
22593
+ return normal;
22594
+ }
22345
22595
  async function createServer(config3) {
22346
22596
  const version2 = getVersion();
22347
22597
  debug("Version:", version2);
@@ -22352,6 +22602,15 @@ async function createServer(config3) {
22352
22602
  const executor = new SubprocessExecutor(runtimes, config3);
22353
22603
  const store = new ContentStore();
22354
22604
  const tracker = new SessionTracker();
22605
+ const shutdown = () => {
22606
+ try {
22607
+ store.close();
22608
+ } catch {
22609
+ }
22610
+ };
22611
+ process.on("SIGINT", shutdown);
22612
+ process.on("SIGTERM", shutdown);
22613
+ process.on("beforeExit", shutdown);
22355
22614
  const searchCalls = [];
22356
22615
  const server2 = new McpServer({
22357
22616
  name: "context-compress",
@@ -22359,11 +22618,11 @@ async function createServer(config3) {
22359
22618
  });
22360
22619
  server2.tool(
22361
22620
  "execute",
22362
- `Execute code in a sandboxed subprocess. Only stdout enters context \u2014 raw data stays in the subprocess. Use instead of bash/cat when output would exceed 20 lines. ${bunDetected ? "(Bun detected \u2014 JS/TS runs 3-5x faster) " : ""}Available: ${LANGUAGES.join(", ")}.
22621
+ `Execute code in a sandboxed subprocess. Only stdout enters context \u2014 raw data stays in the subprocess. Use instead of bash/cat when output would exceed 20 lines. ${bunDetected ? "(Bun detected \u2014 JS/TS runs 3-5x faster) " : ""}Available: ${ALL_LANGUAGES.join(", ")}.
22363
22622
 
22364
22623
  PREFER THIS OVER BASH for: API calls (gh, curl, aws), test runners (npm test, pytest), git queries (git log, git diff), data processing, and ANY CLI command that may produce large output. Bash should only be used for file mutations, git writes, and navigation.`,
22365
22624
  {
22366
- language: external_exports.enum(LANGUAGES).describe("Runtime language"),
22625
+ language: external_exports.enum(LANGUAGE_ENUM).describe("Runtime language"),
22367
22626
  code: external_exports.string().describe(
22368
22627
  "Source code to execute. Use console.log (JS/TS), print (Python/Ruby/Perl/R), echo (Shell), echo (PHP), fmt.Println (Go), or IO.puts (Elixir) to output a summary to context."
22369
22628
  ),
@@ -22398,13 +22657,13 @@ ${result.stderr}`;
22398
22657
  filtered += ` - **${hit.title}**: ${hit.snippet.slice(0, 200)}
22399
22658
  `;
22400
22659
  }
22401
- if (terms.length > 0) {
22660
+ if (terms.length > 0 && config3.compressionLevel !== "ultra") {
22402
22661
  filtered += `
22403
22662
  Searchable terms: ${terms.join(", ")}
22404
22663
  `;
22405
22664
  }
22406
22665
  filtered += "\nUse search(queries: [...]) to retrieve full content of any section.";
22407
- output = filtered;
22666
+ output = compactLabel(filtered, config3.compressionLevel);
22408
22667
  }
22409
22668
  const responseBytes = Buffer.byteLength(output);
22410
22669
  tracker.trackCall("execute", responseBytes);
@@ -22416,7 +22675,7 @@ Searchable terms: ${terms.join(", ")}
22416
22675
  "Read a file and process it without loading contents into context. The file is read into a FILE_CONTENT variable inside the sandbox. Only your printed summary enters context.\n\nPREFER THIS OVER Read/cat for: log files, data files (CSV, JSON, XML), large source files for analysis, and any file where you need to extract specific information rather than read the entire content.",
22417
22676
  {
22418
22677
  path: external_exports.string().describe("Absolute file path or relative to project root"),
22419
- language: external_exports.enum(LANGUAGES).describe("Runtime language"),
22678
+ language: external_exports.enum(LANGUAGE_ENUM).describe("Runtime language"),
22420
22679
  code: external_exports.string().describe(
22421
22680
  "Code to process FILE_CONTENT. Print summary via console.log/print/echo/IO.puts."
22422
22681
  ),
@@ -22462,13 +22721,13 @@ ${result.stderr}`;
22462
22721
  filtered += ` - **${hit.title}**: ${hit.snippet.slice(0, 200)}
22463
22722
  `;
22464
22723
  }
22465
- if (terms.length > 0) {
22724
+ if (terms.length > 0 && config3.compressionLevel !== "ultra") {
22466
22725
  filtered += `
22467
22726
  Searchable terms: ${terms.join(", ")}
22468
22727
  `;
22469
22728
  }
22470
22729
  filtered += "\nUse search(queries: [...]) to retrieve full content of any section.";
22471
- output = filtered;
22730
+ output = compactLabel(filtered, config3.compressionLevel);
22472
22731
  }
22473
22732
  const responseBytes = Buffer.byteLength(output);
22474
22733
  tracker.trackCall("execute_file", responseBytes);
@@ -22498,6 +22757,17 @@ Searchable terms: ${terms.join(", ")}
22498
22757
  ]
22499
22758
  };
22500
22759
  }
22760
+ const fileStat = statSync(absPath);
22761
+ if (fileStat.size > 50 * 1024 * 1024) {
22762
+ return {
22763
+ content: [
22764
+ {
22765
+ type: "text",
22766
+ text: `Error: file "${filePath}" is too large (${(fileStat.size / 1024 / 1024).toFixed(1)}MB). Max 50MB.`
22767
+ }
22768
+ ]
22769
+ };
22770
+ }
22501
22771
  text = readFileSync2(absPath, "utf-8");
22502
22772
  label = source ?? filePath;
22503
22773
  } else if (content) {
@@ -22587,8 +22857,7 @@ ${hit.snippet}
22587
22857
  content: [{ type: "text", text: "Error: only http/https URLs are allowed" }]
22588
22858
  };
22589
22859
  }
22590
- const hostname2 = parsed.hostname;
22591
- if (hostname2 === "localhost" || hostname2 === "127.0.0.1" || hostname2 === "::1" || hostname2 === "0.0.0.0" || hostname2.startsWith("10.") || hostname2.startsWith("172.16.") || hostname2.startsWith("192.168.") || hostname2.startsWith("169.254.")) {
22860
+ if (isPrivateHost(parsed.hostname)) {
22592
22861
  return {
22593
22862
  content: [
22594
22863
  { type: "text", text: "Error: internal/private URLs are not allowed" }
@@ -22729,7 +22998,7 @@ Searchable terms: ${terms.join(", ")}`;
22729
22998
  );
22730
22999
  server2.tool(
22731
23000
  "stats",
22732
- "Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage, and context savings ratio.",
23001
+ "Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage, context savings ratio, and visual charts.",
22733
23002
  {},
22734
23003
  async () => {
22735
23004
  const report = tracker.formatReport();
@@ -22737,6 +23006,77 @@ Searchable terms: ${terms.join(", ")}`;
22737
23006
  return { content: [{ type: "text", text: report }] };
22738
23007
  }
22739
23008
  );
23009
+ server2.tool(
23010
+ "discover",
23011
+ "Shows what's in the knowledge base and suggests optimization opportunities. Lists all indexed sources, chunk counts, searchable terms, and recommends next actions. Use this to understand what data is available for search.",
23012
+ {},
23013
+ async () => {
23014
+ const storeStats = store.getStats();
23015
+ const snap = tracker.getSnapshot();
23016
+ const lines = [];
23017
+ lines.push("## Knowledge Base Discovery\n");
23018
+ if (storeStats.totalSources === 0) {
23019
+ lines.push("No content indexed yet. Use these tools to build the knowledge base:\n");
23020
+ lines.push("- `batch_execute` \u2014 run commands and auto-index output");
23021
+ lines.push("- `execute` with `intent` \u2014 auto-indexes large output");
23022
+ lines.push("- `index` \u2014 index documentation or files");
23023
+ lines.push("- `fetch_and_index` \u2014 fetch and index web pages");
23024
+ } else {
23025
+ lines.push("| Metric | Value |");
23026
+ lines.push("|--------|-------|");
23027
+ lines.push(`| Indexed sources | ${storeStats.totalSources} |`);
23028
+ lines.push(`| Total chunks | ${storeStats.totalChunks} |`);
23029
+ lines.push(`| Vocabulary size | ${storeStats.vocabularySize} |`);
23030
+ lines.push(
23031
+ `| Trigram index | ${storeStats.hasTrigramTable ? "active" : "lazy (not yet needed)"} |`
23032
+ );
23033
+ const sources = store.listSources();
23034
+ if (sources.length > 0) {
23035
+ lines.push("\n### Indexed Sources\n");
23036
+ for (const src of sources) {
23037
+ lines.push(
23038
+ `- **${src.label}** \u2014 ${src.chunkCount} chunks${src.codeChunks > 0 ? ` (${src.codeChunks} with code)` : ""}`
23039
+ );
23040
+ }
23041
+ }
23042
+ const terms = store.getDistinctiveTerms();
23043
+ if (terms.length > 0) {
23044
+ lines.push("\n### Top Searchable Terms\n");
23045
+ lines.push(terms.slice(0, 20).join(", "));
23046
+ }
23047
+ }
23048
+ lines.push("\n### Optimization Suggestions\n");
23049
+ const totalCalls = Object.values(snap.calls).reduce((a, b) => a + b, 0);
23050
+ if (totalCalls === 0) {
23051
+ lines.push("- Start by using `batch_execute` to run multiple commands at once");
23052
+ } else {
23053
+ const searchCalls2 = snap.calls.search ?? 0;
23054
+ const executeCalls = snap.calls.execute ?? 0;
23055
+ const batchCalls = snap.calls.batch_execute ?? 0;
23056
+ if (executeCalls > 3 && batchCalls === 0) {
23057
+ lines.push(
23058
+ "- **Use batch_execute** \u2014 you've made multiple execute calls that could be batched into one"
23059
+ );
23060
+ }
23061
+ if (searchCalls2 > 5) {
23062
+ lines.push("- **Batch your searches** \u2014 pass multiple queries in a single search() call");
23063
+ }
23064
+ if (storeStats.totalChunks > 50) {
23065
+ lines.push(
23066
+ "- **Use source filtering** \u2014 scope searches with `source` parameter for faster, targeted results"
23067
+ );
23068
+ }
23069
+ if (storeStats.totalSources === 0 && totalCalls > 2) {
23070
+ lines.push(
23071
+ "- **Index more content** \u2014 use `intent` parameter in execute calls to auto-index large output"
23072
+ );
23073
+ }
23074
+ }
23075
+ const output = lines.join("\n");
23076
+ tracker.trackCall("discover", Buffer.byteLength(output));
23077
+ return { content: [{ type: "text", text: output }] };
23078
+ }
23079
+ );
22740
23080
  return {
22741
23081
  async start() {
22742
23082
  const transport = new StdioServerTransport();