context-mode 1.0.53 → 1.0.56

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 (66) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  4. package/.openclaw-plugin/package.json +1 -1
  5. package/README.md +103 -32
  6. package/build/adapters/antigravity/index.d.ts +1 -3
  7. package/build/adapters/antigravity/index.js +0 -30
  8. package/build/adapters/claude-code/hooks.d.ts +18 -0
  9. package/build/adapters/claude-code/hooks.js +23 -0
  10. package/build/adapters/claude-code/index.d.ts +1 -3
  11. package/build/adapters/claude-code/index.js +48 -35
  12. package/build/adapters/client-map.js +1 -0
  13. package/build/adapters/codex/index.d.ts +1 -3
  14. package/build/adapters/codex/index.js +1 -31
  15. package/build/adapters/cursor/index.d.ts +1 -3
  16. package/build/adapters/cursor/index.js +0 -11
  17. package/build/adapters/detect.d.ts +1 -0
  18. package/build/adapters/detect.js +18 -2
  19. package/build/adapters/gemini-cli/index.d.ts +1 -3
  20. package/build/adapters/gemini-cli/index.js +0 -30
  21. package/build/adapters/kiro/index.d.ts +1 -3
  22. package/build/adapters/kiro/index.js +0 -30
  23. package/build/adapters/openclaw/index.d.ts +1 -3
  24. package/build/adapters/openclaw/index.js +0 -38
  25. package/build/adapters/opencode/index.d.ts +5 -4
  26. package/build/adapters/opencode/index.js +37 -41
  27. package/build/adapters/types.d.ts +1 -14
  28. package/build/adapters/vscode-copilot/index.d.ts +1 -3
  29. package/build/adapters/vscode-copilot/index.js +0 -32
  30. package/build/adapters/zed/index.d.ts +1 -3
  31. package/build/adapters/zed/index.js +0 -30
  32. package/build/cli.js +12 -28
  33. package/build/executor.d.ts +0 -1
  34. package/build/executor.js +28 -16
  35. package/build/openclaw-plugin.js +12 -34
  36. package/build/opencode-plugin.d.ts +1 -0
  37. package/build/opencode-plugin.js +5 -9
  38. package/build/runtime.js +29 -11
  39. package/build/server.d.ts +2 -0
  40. package/build/server.js +69 -61
  41. package/build/store.d.ts +4 -3
  42. package/build/store.js +101 -34
  43. package/build/truncate.d.ts +4 -17
  44. package/build/truncate.js +4 -52
  45. package/cli.bundle.mjs +184 -157
  46. package/configs/codex/AGENTS.md +19 -0
  47. package/configs/kilo/AGENTS.md +58 -0
  48. package/configs/kilo/kilo.json +10 -0
  49. package/hooks/core/tool-naming.mjs +1 -0
  50. package/hooks/ensure-deps.mjs +80 -2
  51. package/hooks/pretooluse.mjs +25 -20
  52. package/hooks/routing-block.mjs +10 -1
  53. package/hooks/session-snapshot.bundle.mjs +13 -13
  54. package/hooks/sessionstart.mjs +25 -1
  55. package/openclaw.plugin.json +1 -1
  56. package/package.json +1 -1
  57. package/server.bundle.mjs +159 -129
  58. package/skills/context-mode-ops/SKILL.md +111 -0
  59. package/skills/context-mode-ops/agent-teams.md +198 -0
  60. package/skills/context-mode-ops/communication.md +224 -0
  61. package/skills/context-mode-ops/release.md +199 -0
  62. package/skills/context-mode-ops/review-pr.md +269 -0
  63. package/skills/context-mode-ops/tdd.md +329 -0
  64. package/skills/context-mode-ops/triage-issue.md +218 -0
  65. package/skills/context-mode-ops/validation.md +238 -0
  66. package/start.mjs +5 -52
package/build/cli.js CHANGED
@@ -13,8 +13,8 @@
13
13
  */
14
14
  import * as p from "@clack/prompts";
15
15
  import color from "picocolors";
16
- import { execSync } from "node:child_process";
17
- import { readFileSync, writeFileSync, cpSync, accessSync, existsSync, readdirSync, rmSync, closeSync, openSync, constants } from "node:fs";
16
+ import { execFileSync } from "node:child_process";
17
+ import { readFileSync, writeFileSync, cpSync, accessSync, existsSync, rmSync, closeSync, openSync, chmodSync, constants } from "node:fs";
18
18
  import { request as httpsRequest } from "node:https";
19
19
  import { resolve, dirname, join } from "node:path";
20
20
  import { tmpdir, devNull } from "node:os";
@@ -351,7 +351,7 @@ async function upgrade() {
351
351
  const tmpDir = join(tmpdir(), `context-mode-upgrade-${Date.now()}`);
352
352
  s.start("Cloning mksglu/context-mode");
353
353
  try {
354
- execSync(`git clone --depth 1 https://github.com/mksglu/context-mode.git "${tmpDir}"`, { stdio: "pipe", timeout: 30000 });
354
+ execFileSync("git", ["clone", "--depth", "1", "https://github.com/mksglu/context-mode.git", tmpDir], { stdio: "pipe", timeout: 30000 });
355
355
  s.stop("Downloaded");
356
356
  const srcDir = tmpDir;
357
357
  const newPkg = JSON.parse(readFileSync(resolve(srcDir, "package.json"), "utf-8"));
@@ -364,12 +364,12 @@ async function upgrade() {
364
364
  }
365
365
  // Step 2: Install dependencies + build
366
366
  s.start("Installing dependencies & building");
367
- execSync("npm install --no-audit --no-fund", {
367
+ execFileSync("npm", ["install", "--no-audit", "--no-fund"], {
368
368
  cwd: srcDir,
369
369
  stdio: "pipe",
370
370
  timeout: 120000,
371
371
  });
372
- execSync("npm run build", {
372
+ execFileSync("npm", ["run", "build"], {
373
373
  cwd: srcDir,
374
374
  stdio: "pipe",
375
375
  timeout: 60000,
@@ -377,24 +377,8 @@ async function upgrade() {
377
377
  s.stop("Built successfully");
378
378
  // Step 3: Update in-place
379
379
  s.start("Updating files in-place");
380
- const cacheParentMatch = pluginRoot.match(/^(.*[\\/]plugins[\\/]cache[\\/][^\\/]+[\\/][^\\/]+[\\/])/);
381
- if (cacheParentMatch) {
382
- const cacheParent = cacheParentMatch[1];
383
- const myDir = pluginRoot.replace(cacheParent, "").replace(/[\\/]/g, "");
384
- try {
385
- const oldDirs = readdirSync(cacheParent).filter(d => d !== myDir);
386
- for (const d of oldDirs) {
387
- try {
388
- rmSync(resolve(cacheParent, d), { recursive: true, force: true });
389
- }
390
- catch { /* skip */ }
391
- }
392
- if (oldDirs.length > 0) {
393
- p.log.info(color.dim(` Cleaned ${oldDirs.length} stale cache dir(s)`));
394
- }
395
- }
396
- catch { /* parent may not exist */ }
397
- }
380
+ // Old version dirs are cleaned lazily by sessionstart.mjs (age-gated >1h)
381
+ // to avoid breaking active sessions that still reference them (#181).
398
382
  const items = [
399
383
  "build", "src", "hooks", "skills", "scripts", ".claude-plugin",
400
384
  "start.mjs", "server.bundle.mjs", "cli.bundle.mjs", "package.json",
@@ -422,7 +406,7 @@ async function upgrade() {
422
406
  p.log.info(color.dim(" Registry synced to " + pluginRoot));
423
407
  // Install production deps
424
408
  s.start("Installing production dependencies");
425
- execSync("npm install --production --no-audit --no-fund", {
409
+ execFileSync("npm", ["install", "--production", "--no-audit", "--no-fund"], {
426
410
  cwd: pluginRoot,
427
411
  stdio: "pipe",
428
412
  timeout: 60000,
@@ -431,7 +415,7 @@ async function upgrade() {
431
415
  // Rebuild native addons for current Node.js ABI (fixes #131)
432
416
  s.start("Rebuilding native addons");
433
417
  try {
434
- execSync("npm rebuild better-sqlite3", {
418
+ execFileSync("npm", ["rebuild", "better-sqlite3"], {
435
419
  cwd: pluginRoot,
436
420
  stdio: "pipe",
437
421
  timeout: 60000,
@@ -449,7 +433,7 @@ async function upgrade() {
449
433
  // Update global npm
450
434
  s.start("Updating npm global package");
451
435
  try {
452
- execSync(`npm install -g "${pluginRoot}" --no-audit --no-fund`, {
436
+ execFileSync("npm", ["install", "-g", pluginRoot, "--no-audit", "--no-fund"], {
453
437
  stdio: "pipe",
454
438
  timeout: 30000,
455
439
  });
@@ -507,7 +491,7 @@ async function upgrade() {
507
491
  const binPath = resolve(pluginRoot, bin);
508
492
  try {
509
493
  accessSync(binPath, constants.F_OK);
510
- execSync(`chmod +x "${binPath}"`, { stdio: "ignore" });
494
+ chmodSync(binPath, 0o755);
511
495
  permSet.push(binPath);
512
496
  }
513
497
  catch { /* not found — skip */ }
@@ -535,7 +519,7 @@ async function upgrade() {
535
519
  const cliBundlePath = resolve(pluginRoot, "cli.bundle.mjs");
536
520
  const cliBuildPath = resolve(pluginRoot, "build", "cli.js");
537
521
  const cliPath = existsSync(cliBundlePath) ? cliBundlePath : cliBuildPath;
538
- execSync(`node "${cliPath}" doctor`, {
522
+ execFileSync("node", [cliPath, "doctor"], {
539
523
  stdio: "inherit",
540
524
  timeout: 30000,
541
525
  cwd: pluginRoot,
@@ -14,7 +14,6 @@ interface ExecuteFileOptions extends ExecuteOptions {
14
14
  export declare class PolyglotExecutor {
15
15
  #private;
16
16
  constructor(opts?: {
17
- maxOutputBytes?: number;
18
17
  hardCapBytes?: number;
19
18
  projectRoot?: string;
20
19
  runtimes?: RuntimeMap;
package/build/executor.js CHANGED
@@ -1,10 +1,26 @@
1
- import { spawn, execSync } from "node:child_process";
1
+ import { spawn, execSync, execFileSync } from "node:child_process";
2
2
  import { mkdtempSync, writeFileSync, rmSync, existsSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
5
  import { detectRuntimes, buildCommand, } from "./runtime.js";
6
- import { smartTruncate } from "./truncate.js";
7
6
  const isWin = process.platform === "win32";
7
+ /**
8
+ * Resolve the real OS temp directory, bypassing any TMPDIR env override.
9
+ * os.tmpdir() reads TMPDIR from the environment, which some shells/tools
10
+ * set to the project root — causing temp files to pollute the working tree.
11
+ */
12
+ const OS_TMPDIR = (() => {
13
+ if (isWin)
14
+ return process.env.TEMP ?? process.env.TMP ?? tmpdir();
15
+ try {
16
+ const result = execFileSync(process.platform === "darwin" ? "getconf" : "mktemp", process.platform === "darwin" ? ["DARWIN_USER_TEMP_DIR"] : ["-u", "-d"], { env: { ...process.env, TMPDIR: undefined }, encoding: "utf-8" }).trim();
17
+ const dir = process.platform === "darwin" ? result : resolve(result, "..");
18
+ if (dir && dir !== process.cwd())
19
+ return dir;
20
+ }
21
+ catch { /* fall through */ }
22
+ return "/tmp";
23
+ })();
8
24
  /** Kill process tree — on Windows uses taskkill /T; on Unix kills the process group. */
9
25
  function killTree(proc) {
10
26
  if (isWin && proc.pid) {
@@ -22,14 +38,12 @@ function killTree(proc) {
22
38
  }
23
39
  }
24
40
  export class PolyglotExecutor {
25
- #maxOutputBytes;
26
41
  #hardCapBytes;
27
42
  #projectRoot;
28
43
  #runtimes;
29
44
  /** PIDs of backgrounded processes — killed on cleanup to prevent zombies. */
30
45
  #backgroundedPids = new Set();
31
46
  constructor(opts) {
32
- this.#maxOutputBytes = opts?.maxOutputBytes ?? 102_400;
33
47
  this.#hardCapBytes = opts?.hardCapBytes ?? 100 * 1024 * 1024; // 100MB
34
48
  this.#projectRoot = opts?.projectRoot ?? process.cwd();
35
49
  this.#runtimes = opts?.runtimes ?? detectRuntimes();
@@ -50,7 +64,7 @@ export class PolyglotExecutor {
50
64
  }
51
65
  async execute(opts) {
52
66
  const { language, code, timeout = 30_000, background = false } = opts;
53
- const tmpDir = mkdtempSync(join(tmpdir(), "ctx-mode-"));
67
+ const tmpDir = mkdtempSync(join(OS_TMPDIR, ".ctx-mode-"));
54
68
  try {
55
69
  const filePath = this.#writeScript(tmpDir, code, language);
56
70
  const cmd = buildCommand(this.#runtimes, language, filePath);
@@ -62,7 +76,7 @@ export class PolyglotExecutor {
62
76
  // and other project-aware tools work naturally. Non-shell languages
63
77
  // run in the temp directory where their script file is written.
64
78
  const cwd = language === "shell" ? this.#projectRoot : tmpDir;
65
- const result = await this.#spawn(cmd, cwd, timeout, background);
79
+ const result = await this.#spawn(cmd, cwd, tmpDir, timeout, background);
66
80
  // Skip tmpDir cleanup if process was backgrounded — it may still need files
67
81
  if (!result.backgrounded) {
68
82
  try {
@@ -127,7 +141,7 @@ export class PolyglotExecutor {
127
141
  const binPath = srcPath.replace(/\.rs$/, "") + binSuffix;
128
142
  // Compile
129
143
  try {
130
- execSync(`rustc ${srcPath} -o ${binPath}`, {
144
+ execFileSync("rustc", [srcPath, "-o", binPath], {
131
145
  cwd,
132
146
  timeout: Math.min(timeout, 60_000),
133
147
  encoding: "utf-8",
@@ -144,9 +158,9 @@ export class PolyglotExecutor {
144
158
  };
145
159
  }
146
160
  // Run
147
- return this.#spawn([binPath], cwd, timeout);
161
+ return this.#spawn([binPath], cwd, cwd, timeout);
148
162
  }
149
- async #spawn(cmd, cwd, timeout, background = false) {
163
+ async #spawn(cmd, cwd, sandboxTmpDir, timeout, background = false) {
150
164
  return new Promise((res) => {
151
165
  // Only .cmd/.bat shims need shell on Windows; real executables don't.
152
166
  // Using shell: true globally causes process-tree kill issues with MSYS2/Git Bash.
@@ -168,7 +182,7 @@ export class PolyglotExecutor {
168
182
  const proc = spawn(spawnCmd, spawnArgs, {
169
183
  cwd,
170
184
  stdio: ["ignore", "pipe", "pipe"],
171
- env: this.#buildSafeEnv(cwd),
185
+ env: this.#buildSafeEnv(sandboxTmpDir),
172
186
  shell: needsShell,
173
187
  // On Unix, create a new process group so killTree can kill all children
174
188
  detached: !isWin,
@@ -187,10 +201,9 @@ export class PolyglotExecutor {
187
201
  proc.stderr.destroy();
188
202
  const rawStdout = Buffer.concat(stdoutChunks).toString("utf-8");
189
203
  const rawStderr = Buffer.concat(stderrChunks).toString("utf-8");
190
- const max = this.#maxOutputBytes;
191
204
  res({
192
- stdout: smartTruncate(rawStdout, max),
193
- stderr: smartTruncate(rawStderr, max),
205
+ stdout: rawStdout,
206
+ stderr: rawStderr,
194
207
  exitCode: 0,
195
208
  timedOut: true,
196
209
  backgrounded: true,
@@ -237,9 +250,8 @@ export class PolyglotExecutor {
237
250
  if (capExceeded) {
238
251
  rawStderr += `\n[output capped at ${(this.#hardCapBytes / 1024 / 1024).toFixed(0)}MB — process killed]`;
239
252
  }
240
- const max = this.#maxOutputBytes;
241
- const stdout = smartTruncate(rawStdout, max);
242
- const stderr = smartTruncate(rawStderr, max);
253
+ const stdout = rawStdout;
254
+ const stderr = rawStderr;
243
255
  res({
244
256
  stdout,
245
257
  stderr,
@@ -36,7 +36,6 @@ import { fileURLToPath, pathToFileURL } from "node:url";
36
36
  import { OpenClawSessionDB } from "./adapters/openclaw/session-db.js";
37
37
  import { extractEvents, extractUserEvents } from "./session/extract.js";
38
38
  import { buildResumeSnapshot } from "./session/snapshot.js";
39
- import { OpenClawAdapter } from "./adapters/openclaw/index.js";
40
39
  import { WorkspaceRouter } from "./openclaw/workspace-router.js";
41
40
  /** Plugin config schema for OpenClaw validation. */
42
41
  const configSchema = {
@@ -130,17 +129,11 @@ export default {
130
129
  catch {
131
130
  // best effort
132
131
  }
133
- // Async init: load routing module + write AGENTS.md. Hooks await this.
132
+ // Async init: load routing module. Hooks await this.
134
133
  const initPromise = (async () => {
135
134
  const routingPath = resolve(buildDir, "..", "hooks", "core", "routing.mjs");
136
135
  const routing = await import(pathToFileURL(routingPath).href);
137
136
  await routing.initSecurity(buildDir);
138
- try {
139
- new OpenClawAdapter().writeRoutingInstructions(projectDir, pluginRoot);
140
- }
141
- catch {
142
- // best effort — never break plugin init
143
- }
144
137
  return { routing };
145
138
  })();
146
139
  // ── 1. tool_call:before — Routing enforcement ──────────
@@ -315,6 +308,10 @@ export default {
315
308
  workspaceRouter.registerSession(key, sessionId);
316
309
  }
317
310
  resumeInjected = false;
311
+ // Write routing instructions (AGENTS.md) now that we know the real
312
+ // workspace. Derive the workspace directory from the sessionKey so we
313
+ // only write into recognised /.openclaw/workspace* paths, never into
314
+ // the gateway's cwd or any other arbitrary directory.
318
315
  }
319
316
  catch {
320
317
  // best effort — never break session start
@@ -402,7 +399,7 @@ export default {
402
399
  info: {
403
400
  id: "context-mode",
404
401
  name: "Context Mode",
405
- ownsCompaction: true,
402
+ ownsCompaction: false,
406
403
  },
407
404
  async ingest() {
408
405
  return { ingested: true };
@@ -410,31 +407,12 @@ export default {
410
407
  async assemble({ messages }) {
411
408
  return { messages, estimatedTokens: 0 };
412
409
  },
413
- async compact({ currentTokenCount } = {}) {
414
- try {
415
- const sid = sessionId;
416
- const events = db.getEvents(sid);
417
- if (events.length === 0)
418
- return { ok: true, compacted: false };
419
- const stats = db.getSessionStats(sid);
420
- const compactCount = (stats?.compact_count ?? 0) + 1;
421
- const snapshot = buildResumeSnapshot(events, { compactCount });
422
- db.upsertResume(sid, snapshot, events.length);
423
- db.incrementCompactCount(sid);
424
- return {
425
- ok: true,
426
- compacted: true,
427
- result: {
428
- summary: snapshot,
429
- firstKeptEntryId: "", // clear all history before this compaction
430
- tokensBefore: currentTokenCount ?? 0,
431
- tokensAfter: 0,
432
- },
433
- };
434
- }
435
- catch {
436
- return { ok: false, compacted: false };
437
- }
410
+ async compact() {
411
+ // No-op: session continuity is handled by before_compaction / after_compaction hooks.
412
+ // Returning ownsCompaction: false + compacted: false lets the host platform (OpenClaw)
413
+ // manage conversation truncation, preserving Anthropic thinking/redacted_thinking blocks.
414
+ // See: https://github.com/mksglu/context-mode/issues/191
415
+ return { ok: true, compacted: false };
438
416
  },
439
417
  }));
440
418
  // ── 10. Auto-reply commands — ctx slash commands ──────
@@ -11,6 +11,7 @@
11
11
  * Constraints:
12
12
  * - No SessionStart hook (OpenCode doesn't support it — #14808, #5409)
13
13
  * - No context injection (canInjectSessionContext: false)
14
+ * - No routing file auto-write (avoid dirtying project trees)
14
15
  * - Session cleanup happens at plugin init (no SessionStart)
15
16
  */
16
17
  /** OpenCode plugin context passed to the factory function. */
@@ -11,6 +11,7 @@
11
11
  * Constraints:
12
12
  * - No SessionStart hook (OpenCode doesn't support it — #14808, #5409)
13
13
  * - No context injection (canInjectSessionContext: false)
14
+ * - No routing file auto-write (avoid dirtying project trees)
14
15
  * - Session cleanup happens at plugin init (no SessionStart)
15
16
  */
16
17
  import { createHash, randomUUID } from "node:crypto";
@@ -21,10 +22,12 @@ import { fileURLToPath, pathToFileURL } from "node:url";
21
22
  import { SessionDB } from "./session/db.js";
22
23
  import { extractEvents } from "./session/extract.js";
23
24
  import { buildResumeSnapshot } from "./session/snapshot.js";
24
- import { OpenCodeAdapter } from "./adapters/opencode/index.js";
25
25
  // ── Helpers ───────────────────────────────────────────────
26
+ function getPlatform() {
27
+ return process.env.KILO ? "kilo" : "opencode";
28
+ }
26
29
  function getSessionDir() {
27
- const dir = join(homedir(), ".config", "opencode", "context-mode", "sessions");
30
+ const dir = join(homedir(), ".config", getPlatform(), "context-mode", "sessions");
28
31
  mkdirSync(dir, { recursive: true });
29
32
  return dir;
30
33
  }
@@ -52,13 +55,6 @@ export const ContextModePlugin = async (ctx) => {
52
55
  const db = new SessionDB({ dbPath: getDBPath(projectDir) });
53
56
  const sessionId = randomUUID();
54
57
  db.ensureSession(sessionId, projectDir);
55
- // Auto-write AGENTS.md on startup for OpenCode projects
56
- try {
57
- new OpenCodeAdapter().writeRoutingInstructions(projectDir, resolve(buildDir, ".."));
58
- }
59
- catch {
60
- // best effort — never break plugin init
61
- }
62
58
  // Clean up old sessions on startup (replaces SessionStart hook)
63
59
  db.cleanupOldSessions(0);
64
60
  return {
package/build/runtime.js CHANGED
@@ -11,6 +11,23 @@ function commandExists(cmd) {
11
11
  return false;
12
12
  }
13
13
  }
14
+ function bunExists() {
15
+ if (commandExists("bun"))
16
+ return true;
17
+ // Bun installs to ~/.bun/bin which may not be in PATH in MCP server environments
18
+ if (!isWindows) {
19
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
20
+ if (home && existsSync(`${home}/.bun/bin/bun`))
21
+ return true;
22
+ }
23
+ return false;
24
+ }
25
+ function bunCommand() {
26
+ if (commandExists("bun"))
27
+ return "bun";
28
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
29
+ return `${home}/.bun/bin/bun`;
30
+ }
14
31
  /**
15
32
  * On Windows, resolve the first non-WSL bash in PATH.
16
33
  * WSL bash (C:\Windows\System32\bash.exe) cannot handle Windows paths,
@@ -59,11 +76,12 @@ function getVersion(cmd) {
59
76
  }
60
77
  }
61
78
  export function detectRuntimes() {
62
- const hasBun = commandExists("bun");
79
+ const hasBun = bunExists();
80
+ const bun = hasBun ? bunCommand() : null;
63
81
  return {
64
- javascript: hasBun ? "bun" : "node",
65
- typescript: hasBun
66
- ? "bun"
82
+ javascript: bun ?? process.execPath,
83
+ typescript: bun
84
+ ? bun
67
85
  : commandExists("tsx")
68
86
  ? "tsx"
69
87
  : commandExists("ts-node")
@@ -91,11 +109,11 @@ export function detectRuntimes() {
91
109
  };
92
110
  }
93
111
  export function hasBunRuntime() {
94
- return commandExists("bun");
112
+ return bunExists();
95
113
  }
96
114
  export function getRuntimeSummary(runtimes) {
97
115
  const lines = [];
98
- const bunPreferred = runtimes.javascript === "bun";
116
+ const bunPreferred = runtimes.javascript?.endsWith("bun") ?? false;
99
117
  lines.push(` JavaScript: ${runtimes.javascript} (${getVersion(runtimes.javascript)})${bunPreferred ? " ⚡" : ""}`);
100
118
  if (runtimes.typescript) {
101
119
  lines.push(` TypeScript: ${runtimes.typescript} (${getVersion(runtimes.typescript)})`);
@@ -156,15 +174,15 @@ export function getAvailableLanguages(runtimes) {
156
174
  export function buildCommand(runtimes, language, filePath) {
157
175
  switch (language) {
158
176
  case "javascript":
159
- return runtimes.javascript === "bun"
160
- ? ["bun", "run", filePath]
161
- : ["node", filePath];
177
+ return runtimes.javascript.endsWith("bun")
178
+ ? [runtimes.javascript, "run", filePath]
179
+ : [runtimes.javascript, filePath];
162
180
  case "typescript":
163
181
  if (!runtimes.typescript) {
164
182
  throw new Error("No TypeScript runtime available. Install one of: bun (recommended), tsx (npm i -g tsx), or ts-node.");
165
183
  }
166
- if (runtimes.typescript === "bun")
167
- return ["bun", "run", filePath];
184
+ if (runtimes.typescript?.endsWith("bun"))
185
+ return [runtimes.typescript, "run", filePath];
168
186
  if (runtimes.typescript === "tsx")
169
187
  return ["tsx", filePath];
170
188
  return ["ts-node", filePath];
package/build/server.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ContentStore } from "./store.js";
2
3
  /**
3
4
  * Parse FTS5 highlight markers to find match positions in the
4
5
  * original (marker-free) text. Returns character offsets into the
@@ -6,3 +7,4 @@
6
7
  */
7
8
  export declare function positionsFromHighlight(highlighted: string): number[];
8
9
  export declare function extractSnippet(content: string, query: string, maxLen?: number, highlighted?: string): string;
10
+ export declare function formatBatchQueryResults(store: ContentStore, queries: string[], source: string, maxOutput?: number): string[];