topchester-ai 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.mts CHANGED
@@ -1 +1,7 @@
1
- export { };
1
+ //#region src/cli.d.ts
2
+ declare function runTopchesterCli(argv?: string[], options?: {
3
+ exitOverride?: boolean;
4
+ }): Promise<void>;
5
+ //#endregion
6
+ export { runTopchesterCli };
7
+ //# sourceMappingURL=cli.d.mts.map
package/dist/cli.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  import { createRequire } from "node:module";
3
3
  import { cwd, stderr, stdout } from "node:process";
4
4
  import { basename, delimiter, dirname, extname, isAbsolute, join, parse, relative, resolve, sep, win32 } from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
6
  import { Command } from "commander";
6
7
  import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
7
8
  import { generateText, streamText, tool } from "ai";
@@ -11,12 +12,12 @@ import { execFile, spawn } from "node:child_process";
11
12
  import { constants, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
12
13
  import { createHash, randomUUID } from "node:crypto";
13
14
  import { parse as parse$1, parseDocument } from "yaml";
14
- import { fileURLToPath } from "node:url";
15
15
  import { homedir } from "node:os";
16
16
  import pino from "pino";
17
17
  import picomatch from "picomatch";
18
18
  import { uuidv7 } from "uuidv7";
19
19
  import { CURSOR_MARKER, Markdown, ProcessTerminal, TUI, decodeKittyPrintable, isKeyRelease, isKeyRepeat, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
20
+ import { diffWords } from "diff";
20
21
  import { highlight, supportsLanguage } from "cli-highlight";
21
22
  //#region src/agent/tools/ai-sdk-tools.ts
22
23
  function toAiSdkToolSet(definitions) {
@@ -3088,6 +3089,10 @@ const ui = {
3088
3089
  error(text) {
3089
3090
  return color(text, "red");
3090
3091
  },
3092
+ inverse(text) {
3093
+ if (!shouldUseColor()) return text;
3094
+ return `\u001b[7m${text}\u001b[27m`;
3095
+ },
3091
3096
  softBackground(text) {
3092
3097
  return color(text, "bgSoftGray");
3093
3098
  },
@@ -7556,6 +7561,85 @@ const queryStopWords = new Set([
7556
7561
  "when"
7557
7562
  ]);
7558
7563
  //#endregion
7564
+ //#region src/tui/diff.ts
7565
+ function renderUnifiedDiff(diffText, options = {}) {
7566
+ const indent = options.indent ?? "";
7567
+ const wrapWidth = options.width === void 0 ? void 0 : Math.max(1, options.width - indent.length);
7568
+ const rawLines = diffText.split("\n");
7569
+ const rendered = [];
7570
+ let index = 0;
7571
+ while (index < rawLines.length) {
7572
+ const line = rawLines[index];
7573
+ if (isRemovedBodyLine(line)) {
7574
+ const removed = [];
7575
+ while (index < rawLines.length && isRemovedBodyLine(rawLines[index])) {
7576
+ removed.push({
7577
+ kind: "removed",
7578
+ content: rawLines[index].slice(1)
7579
+ });
7580
+ index += 1;
7581
+ }
7582
+ const added = [];
7583
+ while (index < rawLines.length && isAddedBodyLine(rawLines[index])) {
7584
+ added.push({
7585
+ kind: "added",
7586
+ content: rawLines[index].slice(1)
7587
+ });
7588
+ index += 1;
7589
+ }
7590
+ rendered.push(...renderChangedLines(removed, added, indent, wrapWidth));
7591
+ continue;
7592
+ }
7593
+ if (isAddedBodyLine(line)) rendered.push(...renderWrappedLine(ui.ok(`+${expandTabs$1(line.slice(1))}`), indent, wrapWidth));
7594
+ else if (line.startsWith("@@")) rendered.push(...renderWrappedLine(ui.model(line), indent, wrapWidth));
7595
+ else if (line.startsWith("diff --git ") || line.startsWith("--- ") || line.startsWith("+++ ")) rendered.push(...renderWrappedLine(ui.muted(line), indent, wrapWidth));
7596
+ else if (line.startsWith(" ")) rendered.push(...renderWrappedLine(ui.label(` ${expandTabs$1(line.slice(1))}`), indent, wrapWidth));
7597
+ else if (line.length > 0) rendered.push(...renderWrappedLine(ui.label(line), indent, wrapWidth));
7598
+ index += 1;
7599
+ }
7600
+ return rendered;
7601
+ }
7602
+ function renderChangedLines(removedLines, addedLines, indent, wrapWidth) {
7603
+ if (removedLines.length === 1 && addedLines.length === 1) {
7604
+ const { removed, added } = renderIntraLineDiff(expandTabs$1(removedLines[0].content), expandTabs$1(addedLines[0].content));
7605
+ return [...renderWrappedLine(ui.error(`-${removed}`), indent, wrapWidth), ...renderWrappedLine(ui.ok(`+${added}`), indent, wrapWidth)];
7606
+ }
7607
+ return [...removedLines.flatMap((line) => renderWrappedLine(ui.error(`-${expandTabs$1(line.content)}`), indent, wrapWidth)), ...addedLines.flatMap((line) => renderWrappedLine(ui.ok(`+${expandTabs$1(line.content)}`), indent, wrapWidth))];
7608
+ }
7609
+ function renderIntraLineDiff(oldContent, newContent) {
7610
+ const parts = diffWords(oldContent, newContent);
7611
+ let removed = "";
7612
+ let added = "";
7613
+ for (const part of parts) if (part.removed) removed += highlightChangedPart(part.value);
7614
+ else if (part.added) added += highlightChangedPart(part.value);
7615
+ else {
7616
+ removed += part.value;
7617
+ added += part.value;
7618
+ }
7619
+ return {
7620
+ removed,
7621
+ added
7622
+ };
7623
+ }
7624
+ function highlightChangedPart(value) {
7625
+ const leading = value.match(/^\s*/u)?.[0] ?? "";
7626
+ const trailing = value.match(/\s*$/u)?.[0] ?? "";
7627
+ const body = value.slice(leading.length, value.length - trailing.length);
7628
+ return body.length === 0 ? value : `${leading}${ui.inverse(body)}${trailing}`;
7629
+ }
7630
+ function renderWrappedLine(line, indent, width) {
7631
+ return (width === void 0 ? [line] : wrapTextWithAnsi(line, width)).map((part) => `${indent}${part}`);
7632
+ }
7633
+ function isRemovedBodyLine(line) {
7634
+ return line.startsWith("-") && !line.startsWith("--- ");
7635
+ }
7636
+ function isAddedBodyLine(line) {
7637
+ return line.startsWith("+") && !line.startsWith("+++ ");
7638
+ }
7639
+ function expandTabs$1(text) {
7640
+ return text.replace(/\t/gu, " ");
7641
+ }
7642
+ //#endregion
7559
7643
  //#region src/tui/markdown.ts
7560
7644
  const codeFenceSentinel = "topchester-code-fence";
7561
7645
  function renderMarkdown(text, width) {
@@ -7647,16 +7731,13 @@ function thinkingMessage(text) {
7647
7731
  text
7648
7732
  };
7649
7733
  }
7650
- function toolCallMessage(call, label, resultSummary) {
7651
- return resultSummary === void 0 ? {
7652
- kind: "tool_call",
7653
- call,
7654
- label
7655
- } : {
7734
+ function toolCallMessage(call, label, resultSummary, diff) {
7735
+ return {
7656
7736
  kind: "tool_call",
7657
7737
  call,
7658
7738
  label,
7659
- resultSummary
7739
+ ...resultSummary === void 0 ? {} : { resultSummary },
7740
+ ...diff === void 0 ? {} : { diff }
7660
7741
  };
7661
7742
  }
7662
7743
  function hookStatusMessage(label, eventName, statusMessage) {
@@ -7682,7 +7763,7 @@ function modalMessage(message) {
7682
7763
  const DEFAULT_MODAL_VISIBLE_ACTION_LIMIT = 16;
7683
7764
  function renderChatMessage(message, options = {}) {
7684
7765
  if (message.kind === "modal") return renderChatModal(message, options.selectedActionIndex, options.maxModalHeight, options.width);
7685
- if (message.kind === "tool_call") return renderToolCallMessage(message);
7766
+ if (message.kind === "tool_call") return renderToolCallMessage(message, options.width);
7686
7767
  if (message.kind === "hook_status") return renderHookStatusMessage(message);
7687
7768
  if (message.kind === "subagent") return renderSubagentMessage(message);
7688
7769
  if (message.kind === "thinking") return message.text.split("\n").map((line) => ui.muted(line));
@@ -7714,9 +7795,14 @@ function renderSystemMessage(lines) {
7714
7795
  function formatSystemBodyLine(line) {
7715
7796
  return expandTabs(line).replace(/\(changed \+\d+\/-\d+\)$/u, (summary) => ui.muted(summary)).replace(/\(created \+\d+\)$/u, (summary) => ui.muted(summary));
7716
7797
  }
7717
- function renderToolCallMessage(message) {
7798
+ function renderToolCallMessage(message, width) {
7718
7799
  const visibleLabel = message.resultSummary && !message.label.includes(message.resultSummary) ? `${message.label} ${message.resultSummary}` : message.label;
7719
- return [` ${ui.muted(expandTabs(visibleLabel))}`];
7800
+ const label = ` ${ui.muted(expandTabs(visibleLabel))}`;
7801
+ if (!message.diff) return [label];
7802
+ return [label, ...renderUnifiedDiff(message.diff, {
7803
+ indent: " ",
7804
+ width
7805
+ })];
7720
7806
  }
7721
7807
  function renderHookStatusMessage(message) {
7722
7808
  return [` ${ui.muted(expandTabs(message.label))}`];
@@ -7888,7 +7974,8 @@ const messagePayloadSchema = z.object({
7888
7974
  const toolCallPayloadSchema = z.object({
7889
7975
  kind: z.literal("tool_call"),
7890
7976
  label: z.string(),
7891
- call: z.record(z.string(), jsonValueSchema)
7977
+ call: z.record(z.string(), jsonValueSchema),
7978
+ diff: z.string().optional()
7892
7979
  });
7893
7980
  const hookStatusPayloadSchema = z.object({
7894
7981
  kind: z.literal("hook_status"),
@@ -8133,7 +8220,7 @@ function rehydrateSession(events) {
8133
8220
  if (event.role === "user") visibleOnlyActionValues = /* @__PURE__ */ new Set();
8134
8221
  break;
8135
8222
  case "tool_call":
8136
- messages.push(toolCallMessage(event.call, event.label));
8223
+ messages.push(toolCallMessage(event.call, event.label, void 0, event.diff));
8137
8224
  break;
8138
8225
  case "hook_status":
8139
8226
  messages.push(hookStatusMessage(event.label, event.eventName, event.statusMessage));
@@ -8757,11 +8844,16 @@ const agentEvent = {
8757
8844
  meta
8758
8845
  };
8759
8846
  },
8760
- toolCall(call, label) {
8761
- return {
8847
+ toolCall(call, label, diff) {
8848
+ return diff === void 0 ? {
8762
8849
  type: "tool_call",
8763
8850
  call,
8764
8851
  label
8852
+ } : {
8853
+ type: "tool_call",
8854
+ call,
8855
+ label,
8856
+ diff
8765
8857
  };
8766
8858
  },
8767
8859
  hookStatus(eventName, statusMessage) {
@@ -10487,7 +10579,8 @@ function runtimeEventToSessionPayload(event) {
10487
10579
  case "tool_call": return {
10488
10580
  kind: "tool_call",
10489
10581
  label: event.label,
10490
- call: event.call
10582
+ call: event.call,
10583
+ ...event.diff === void 0 ? {} : { diff: event.diff }
10491
10584
  };
10492
10585
  case "hook_status": return {
10493
10586
  kind: "hook_status",
@@ -11380,7 +11473,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
11380
11473
  const call = taskCalls[index];
11381
11474
  const toolResult = taskResults[index];
11382
11475
  for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
11383
- yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult));
11476
+ yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult), getToolCallDisplayDiff(toolResult));
11384
11477
  const postHookRun = this.startPostToolUseHook(call, modelToolCalls[index]?.id, toolResult, session, abortSignal);
11385
11478
  for await (const event of postHookRun.statusEvents) yield event;
11386
11479
  const postHook = await postHookRun.result;
@@ -11442,7 +11535,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
11442
11535
  const call = parallelCalls[index];
11443
11536
  const toolResult = parallelResults[index];
11444
11537
  for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
11445
- yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult));
11538
+ yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult), getToolCallDisplayDiff(toolResult));
11446
11539
  const postHookRun = this.startPostToolUseHook(call, modelToolCalls[index]?.id, toolResult, session, abortSignal);
11447
11540
  for await (const event of postHookRun.statusEvents) yield event;
11448
11541
  const postHook = await postHookRun.result;
@@ -11505,7 +11598,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
11505
11598
  }
11506
11599
  }
11507
11600
  for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
11508
- yield agentEvent.toolCall(executableToolCall, formatToolCallMessage(executableToolCall, toolResult));
11601
+ yield agentEvent.toolCall(executableToolCall, formatToolCallMessage(executableToolCall, toolResult), getToolCallDisplayDiff(toolResult));
11509
11602
  if (!isToolErrorResult(toolResult) && toolResult.tool === "plan_todo") yield agentEvent.taskPlan(toolResult.plan);
11510
11603
  const postHookRun = this.startPostToolUseHook(executableToolCall, toolCall.id, toolResult, session, abortSignal);
11511
11604
  for await (const event of postHookRun.statusEvents) yield event;
@@ -11811,6 +11904,9 @@ function createToolErrorResult(tool, message) {
11811
11904
  warning: message
11812
11905
  };
11813
11906
  }
11907
+ function getToolCallDisplayDiff(result) {
11908
+ if (result.tool === "edit_file" && !isToolErrorResult(result) && "diff" in result) return result.diff;
11909
+ }
11814
11910
  function isL1ContextDisabledByEnv() {
11815
11911
  const value = process.env.TOPCHESTER_DISABLE_L1_CONTEXT?.trim().toLowerCase();
11816
11912
  return value === "1" || value === "true" || value === "yes" || value === "on";
@@ -11851,7 +11947,7 @@ function createInstructionContextEvents(sources, persistedKeys) {
11851
11947
  function renderRuntimeEvent(event) {
11852
11948
  switch (event.type) {
11853
11949
  case "message": return [event.role === "assistant" ? agentMessage(event.text, event.meta) : systemMessage(event.text)];
11854
- case "tool_call": return [toolCallMessage(event.call, event.label)];
11950
+ case "tool_call": return [toolCallMessage(event.call, event.label, void 0, event.diff)];
11855
11951
  case "hook_status": return [hookStatusMessage(event.label, event.eventName, event.statusMessage)];
11856
11952
  case "knowledge_status": return [systemMessage([`KB status: ${formatKnowledgePathStatus(event.status)}${formatKbPathSource(event.status)}`, event.guidance].filter(Boolean).join("\n"))];
11857
11953
  case "choice": return [modalMessage({
@@ -12111,7 +12207,8 @@ function chatMessageToSessionPayload(message) {
12111
12207
  if (message.kind === "tool_call") return {
12112
12208
  kind: "tool_call",
12113
12209
  label: message.label,
12114
- call: message.call
12210
+ call: message.call,
12211
+ ...message.diff === void 0 ? {} : { diff: message.diff }
12115
12212
  };
12116
12213
  }
12117
12214
  function slashCommandToSessionPayload(command) {
@@ -13316,104 +13413,112 @@ function defaultSelfUpdateRunner(command, args) {
13316
13413
  }
13317
13414
  //#endregion
13318
13415
  //#region src/cli.ts
13319
- const program = new Command();
13320
- program.name("topchester").description("KB-first terminal coding agent").version(getTopchesterVersion());
13321
- program.option("-c, --config <path>", "explicit config file path").option("--workspace <path>", "workspace root", cwd()).option("--resume <session>", "resume a project session: latest or an exact session id").option("--dev <flag>", "enable a development flag", collectDevFlag, []);
13322
- program.action(async () => {
13323
- const context = createContextFromOptions();
13324
- const options = program.opts();
13325
- try {
13326
- if (options.resume) {
13327
- const loaded = await loadSession(context.workspaceRoot, options.resume);
13328
- const session = await loadSessionForAppend(context.workspaceRoot, loaded.sessionId);
13329
- const rehydrated = rehydrateSession(loaded.events);
13330
- await new TopchesterTuiShell(context, void 0, {
13331
- session,
13332
- initialMessages: rehydrated.messages,
13333
- initialTaskPlan: rehydrated.taskPlan
13334
- }).render();
13335
- return;
13416
+ async function runTopchesterCli(argv = process.argv, options = {}) {
13417
+ const program = createTopchesterProgram();
13418
+ if (options.exitOverride) program.exitOverride();
13419
+ await program.parseAsync(argv);
13420
+ }
13421
+ function createTopchesterProgram() {
13422
+ const program = new Command();
13423
+ program.name("topchester").description("KB-first terminal coding agent").version(getTopchesterVersion());
13424
+ program.option("-c, --config <path>", "explicit config file path").option("--workspace <path>", "workspace root", cwd()).option("--resume <session>", "resume a project session: latest or an exact session id").option("--dev <flag>", "enable a development flag", collectDevFlag, []);
13425
+ program.action(async () => {
13426
+ const context = createContextFromOptions(program);
13427
+ const options = program.opts();
13428
+ try {
13429
+ if (options.resume) {
13430
+ const loaded = await loadSession(context.workspaceRoot, options.resume);
13431
+ const session = await loadSessionForAppend(context.workspaceRoot, loaded.sessionId);
13432
+ const rehydrated = rehydrateSession(loaded.events);
13433
+ await new TopchesterTuiShell(context, void 0, {
13434
+ session,
13435
+ initialMessages: rehydrated.messages,
13436
+ initialTaskPlan: rehydrated.taskPlan
13437
+ }).render();
13438
+ return;
13439
+ }
13440
+ await new TopchesterTuiShell(context).render();
13441
+ } catch (error) {
13442
+ console.error(formatStartupError(error));
13443
+ process.exitCode = 1;
13336
13444
  }
13337
- await new TopchesterTuiShell(context).render();
13338
- } catch (error) {
13339
- console.error(formatStartupError(error));
13340
- process.exitCode = 1;
13341
- }
13342
- });
13343
- program.command("dev").description("start local development mode").action(() => {
13344
- const context = createContextFromOptions();
13345
- console.log("Topchester local dev mode");
13346
- printStartupSummary(context);
13347
- });
13348
- program.command("run").description("run one prompt or slash command without opening the TUI").argument("<prompt...>", "prompt text or slash command").option("--model <model>", "override the agent.primary model for this run").option("--timeout <ms>", "timeout for the run in milliseconds", parsePositiveInteger).option("--json", "write JSONL run events to stdout").option("--output-json <path>", "write JSONL run events to a file").action(async (promptParts, options) => {
13349
- const context = createContextFromOptions();
13350
- const globalOptions = program.opts();
13351
- try {
13352
- await executeRunCommand(context, {
13353
- prompt: promptParts.join(" "),
13354
- model: options.model,
13355
- timeoutMs: options.timeout,
13356
- json: options.json,
13357
- outputJson: options.outputJson,
13358
- resume: globalOptions.resume
13359
- });
13360
- } catch (error) {
13361
- console.error(formatStartupError(error));
13362
- process.exitCode = 1;
13363
- }
13364
- });
13365
- program.command("search").description("search compiled L1 knowledge entries").argument("<query...>", "search query").option("--limit <count>", "maximum number of matches", parsePositiveInteger).option("--json", "write full JSON search result to stdout").action(async (queryParts, options) => {
13366
- await executeKbSearchCommand(queryParts, options);
13367
- });
13368
- const kbCommand = program.command("kb").description("knowledge base commands");
13369
- kbCommand.command("init").description("initialize a project knowledge base").action(async () => {
13370
- const context = createContextFromOptions();
13371
- const result = await ui.progress("Preparing project knowledge folders...", (report) => initializeKnowledgeBase(context.workspaceRoot, { onProgress: (event) => report(event.message) }));
13372
- console.log(formatKnowledgeInitResult(result).join("\n"));
13373
- });
13374
- kbCommand.command("dry-run").description("list project files that would be synced into the knowledge base").action(async () => {
13375
- const context = createContextFromOptions();
13376
- const result = await ui.spinner("Listing project files for KB sync...", () => dryRunKnowledgeCompile(context.workspaceRoot, { config: context.config }));
13377
- console.log(formatKnowledgeCompileDryRunResult(result, { formatSyncStatus: formatDryRunSyncStatus }).join("\n"));
13378
- });
13379
- kbCommand.command("sync").description("sync project files into the knowledge base").option("--full", "sync all in-scope files and remove orphaned L1 entries").action(async (options) => {
13380
- const context = createContextFromOptions();
13381
- const result = await ui.progress(options.full ? "Syncing all L1 file entries..." : "Syncing non-clean L1 file entries...", (report) => syncKnowledgeBase(context.workspaceRoot, {
13382
- model: context.modelGateway,
13383
- requireModel: true,
13384
- config: context.config,
13385
- full: options.full,
13386
- onProgress: (event) => report(event.message)
13387
- }));
13388
- console.log(formatKnowledgeSyncResult(result, { title: options.full ? "KB sync --full" : "KB sync" }).join("\n"));
13389
- if (isPartialKnowledgeCompileResult(result)) process.exitCode = 2;
13390
- });
13391
- kbCommand.command("search").alias("query").description("search compiled L1 knowledge entries").argument("<query...>", "search query").option("--limit <count>", "maximum number of matches", parsePositiveInteger).option("--json", "write full JSON search result to stdout").action(async (queryParts, options) => {
13392
- await executeKbSearchCommand(queryParts, options);
13393
- });
13394
- kbCommand.command("context").description("create an L1 context pack for a query").argument("<query...>", "context query").option("--limit <count>", "maximum number of relevant files", parsePositiveInteger).option("--min-score <score>", "minimum match score", parseNonNegativeNumber).option("--json", "write JSON context pack to stdout").option("--full-l1", "include full raw L1 entries in JSON output").action(async (queryParts, options) => {
13395
- await executeKbContextCommand(queryParts, options);
13396
- });
13397
- kbCommand.command("reset").description("delete the local project knowledge base and cache").action(async () => {
13398
- const context = createContextFromOptions();
13399
- const result = await ui.progress("Resetting project knowledge base...", (report) => resetKnowledgeBase(context.workspaceRoot, { onProgress: (event) => report(event.message) }));
13400
- console.log(formatKnowledgeResetResult(result).join("\n"));
13401
- });
13402
- kbCommand.command("status").description("show project files that are not current in the knowledge base").action(async () => {
13403
- const context = createContextFromOptions();
13404
- const result = await ui.spinner("Checking KB file status...", async () => filterNonCleanKnowledgeCompileResult(await dryRunKnowledgeCompile(context.workspaceRoot, { config: context.config })));
13405
- console.log(formatKnowledgeCompileStatusResult(result, { formatSyncStatus: formatDryRunSyncStatus }).join("\n"));
13406
- });
13407
- program.command("update").alias("upgrade").description("update Topchester with the package manager that installed it").argument("[target]", "version or npm dist tag to install", "latest").action(async (target) => {
13408
- try {
13409
- const command = await runSelfUpdate({ target });
13410
- console.log(formatSelfUpdateSuccess(command).join("\n"));
13411
- } catch (error) {
13412
- console.error(formatStartupError(error));
13413
- process.exitCode = 1;
13414
- }
13415
- });
13416
- await program.parseAsync();
13445
+ });
13446
+ program.command("dev").description("start local development mode").action(() => {
13447
+ const context = createContextFromOptions(program);
13448
+ console.log("Topchester local dev mode");
13449
+ printStartupSummary(context);
13450
+ });
13451
+ program.command("run").description("run one prompt or slash command without opening the TUI").argument("<prompt...>", "prompt text or slash command").option("--model <model>", "override the agent.primary model for this run").option("--timeout <ms>", "timeout for the run in milliseconds", parsePositiveInteger).option("--json", "write JSONL run events to stdout").option("--output-json <path>", "write JSONL run events to a file").action(async (promptParts, options) => {
13452
+ const context = createContextFromOptions(program);
13453
+ const globalOptions = program.opts();
13454
+ try {
13455
+ await executeRunCommand(context, {
13456
+ prompt: promptParts.join(" "),
13457
+ model: options.model,
13458
+ timeoutMs: options.timeout,
13459
+ json: options.json,
13460
+ outputJson: options.outputJson,
13461
+ resume: globalOptions.resume
13462
+ });
13463
+ } catch (error) {
13464
+ console.error(formatStartupError(error));
13465
+ process.exitCode = 1;
13466
+ }
13467
+ });
13468
+ program.command("search").description("search compiled L1 knowledge entries").argument("<query...>", "search query").option("--limit <count>", "maximum number of matches", parsePositiveInteger).option("--json", "write full JSON search result to stdout").action(async (queryParts, options) => {
13469
+ await executeKbSearchCommand(program, queryParts, options);
13470
+ });
13471
+ const kbCommand = program.command("kb").description("knowledge base commands");
13472
+ kbCommand.command("init").description("initialize a project knowledge base").action(async () => {
13473
+ const context = createContextFromOptions(program);
13474
+ const result = await ui.progress("Preparing project knowledge folders...", (report) => initializeKnowledgeBase(context.workspaceRoot, { onProgress: (event) => report(event.message) }));
13475
+ console.log(formatKnowledgeInitResult(result).join("\n"));
13476
+ });
13477
+ kbCommand.command("dry-run").description("list project files that would be synced into the knowledge base").action(async () => {
13478
+ const context = createContextFromOptions(program);
13479
+ const result = await ui.spinner("Listing project files for KB sync...", () => dryRunKnowledgeCompile(context.workspaceRoot, { config: context.config }));
13480
+ console.log(formatKnowledgeCompileDryRunResult(result, { formatSyncStatus: formatDryRunSyncStatus }).join("\n"));
13481
+ });
13482
+ kbCommand.command("sync").description("sync project files into the knowledge base").option("--full", "sync all in-scope files and remove orphaned L1 entries").action(async (options) => {
13483
+ const context = createContextFromOptions(program);
13484
+ const result = await ui.progress(options.full ? "Syncing all L1 file entries..." : "Syncing non-clean L1 file entries...", (report) => syncKnowledgeBase(context.workspaceRoot, {
13485
+ model: context.modelGateway,
13486
+ requireModel: true,
13487
+ config: context.config,
13488
+ full: options.full,
13489
+ onProgress: (event) => report(event.message)
13490
+ }));
13491
+ console.log(formatKnowledgeSyncResult(result, { title: options.full ? "KB sync --full" : "KB sync" }).join("\n"));
13492
+ if (isPartialKnowledgeCompileResult(result)) process.exitCode = 2;
13493
+ });
13494
+ kbCommand.command("search").alias("query").description("search compiled L1 knowledge entries").argument("<query...>", "search query").option("--limit <count>", "maximum number of matches", parsePositiveInteger).option("--json", "write full JSON search result to stdout").action(async (queryParts, options) => {
13495
+ await executeKbSearchCommand(program, queryParts, options);
13496
+ });
13497
+ kbCommand.command("context").description("create an L1 context pack for a query").argument("<query...>", "context query").option("--limit <count>", "maximum number of relevant files", parsePositiveInteger).option("--min-score <score>", "minimum match score", parseNonNegativeNumber).option("--json", "write JSON context pack to stdout").option("--full-l1", "include full raw L1 entries in JSON output").action(async (queryParts, options) => {
13498
+ await executeKbContextCommand(program, queryParts, options);
13499
+ });
13500
+ kbCommand.command("reset").description("delete the local project knowledge base and cache").action(async () => {
13501
+ const context = createContextFromOptions(program);
13502
+ const result = await ui.progress("Resetting project knowledge base...", (report) => resetKnowledgeBase(context.workspaceRoot, { onProgress: (event) => report(event.message) }));
13503
+ console.log(formatKnowledgeResetResult(result).join("\n"));
13504
+ });
13505
+ kbCommand.command("status").description("show project files that are not current in the knowledge base").action(async () => {
13506
+ const context = createContextFromOptions(program);
13507
+ const result = await ui.spinner("Checking KB file status...", async () => filterNonCleanKnowledgeCompileResult(await dryRunKnowledgeCompile(context.workspaceRoot, { config: context.config })));
13508
+ console.log(formatKnowledgeCompileStatusResult(result, { formatSyncStatus: formatDryRunSyncStatus }).join("\n"));
13509
+ });
13510
+ program.command("update").alias("upgrade").description("update Topchester with the package manager that installed it").argument("[target]", "version or npm dist tag to install", "latest").action(async (target) => {
13511
+ try {
13512
+ const command = await runSelfUpdate({ target });
13513
+ console.log(formatSelfUpdateSuccess(command).join("\n"));
13514
+ } catch (error) {
13515
+ console.error(formatStartupError(error));
13516
+ process.exitCode = 1;
13517
+ }
13518
+ });
13519
+ return program;
13520
+ }
13521
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) await runTopchesterCli();
13417
13522
  function printStartupSummary(context) {
13418
13523
  const assignments = context.config.models?.assignments ?? {};
13419
13524
  const providers = context.config.models?.providers ?? {};
@@ -13441,7 +13546,7 @@ function printStartupSummary(context) {
13441
13546
  }
13442
13547
  }
13443
13548
  }
13444
- function createContextFromOptions() {
13549
+ function createContextFromOptions(program) {
13445
13550
  const options = program.opts();
13446
13551
  return createAppContext({
13447
13552
  workspaceRoot: options.workspace,
@@ -13449,8 +13554,8 @@ function createContextFromOptions() {
13449
13554
  devFlags: options.dev
13450
13555
  });
13451
13556
  }
13452
- async function executeKbSearchCommand(queryParts, options) {
13453
- const context = createContextFromOptions();
13557
+ async function executeKbSearchCommand(program, queryParts, options) {
13558
+ const context = createContextFromOptions(program);
13454
13559
  const query = queryParts.join(" ");
13455
13560
  const result = options.json ? await searchL1Knowledge(context.workspaceRoot, query, { limit: options.limit }) : await ui.spinner("Searching L1 knowledge entries...", () => searchL1Knowledge(context.workspaceRoot, query, { limit: options.limit }));
13456
13561
  if (options.json) {
@@ -13459,8 +13564,8 @@ async function executeKbSearchCommand(queryParts, options) {
13459
13564
  }
13460
13565
  console.log(formatL1KnowledgeSearchResult(result).join("\n"));
13461
13566
  }
13462
- async function executeKbContextCommand(queryParts, options) {
13463
- const context = createContextFromOptions();
13567
+ async function executeKbContextCommand(program, queryParts, options) {
13568
+ const context = createContextFromOptions(program);
13464
13569
  const query = queryParts.join(" ");
13465
13570
  const contextPackOptions = {
13466
13571
  limit: options.limit,
@@ -13498,6 +13603,6 @@ function formatDryRunSyncStatus(status) {
13498
13603
  return ui.warn(status);
13499
13604
  }
13500
13605
  //#endregion
13501
- export {};
13606
+ export { runTopchesterCli };
13502
13607
 
13503
13608
  //# sourceMappingURL=cli.mjs.map