topchester-ai 0.50.0 → 0.52.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/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as runTopchesterCli } from "./cli-BWa58EWv.mjs";
2
+ import { t as runTopchesterCli } from "./cli-yf0DNAUH.mjs";
3
3
  //#region src/bin.ts
4
4
  await runTopchesterCli();
5
5
  //#endregion
@@ -9551,12 +9551,16 @@ var BusyIndicator = class {
9551
9551
  }
9552
9552
  render() {
9553
9553
  if (this.activityOverride) {
9554
- this.app.setEphemeralLine(`${this.frames[this.index]} ${this.activityOverride}`);
9554
+ this.app.setEphemeralLine(this.formatActivityLine(this.activityOverride));
9555
9555
  return;
9556
9556
  }
9557
9557
  const activityEveryMs = this.options.activityEveryMs ?? 1200;
9558
9558
  const activityIndex = Math.floor(this.ticks * 80 / activityEveryMs) % this.options.activities.length;
9559
- this.app.setEphemeralLine(`${this.frames[this.index]} ${this.options.activities[activityIndex]}`);
9559
+ this.app.setEphemeralLine(this.formatActivityLine(this.options.activities[activityIndex] ?? ""));
9560
+ }
9561
+ formatActivityLine(activity) {
9562
+ const hint = this.options.activityHint ? ` · ${this.options.activityHint}` : "";
9563
+ return `${this.frames[this.index]} ${activity}${hint}`;
9560
9564
  }
9561
9565
  };
9562
9566
  var ReasoningTailBuffer = class {
@@ -9641,6 +9645,18 @@ const slashCommandSuggestions = [
9641
9645
  value: "/skill",
9642
9646
  description: "activate a skill"
9643
9647
  },
9648
+ {
9649
+ value: "/queue",
9650
+ description: "queue a follow-up prompt"
9651
+ },
9652
+ {
9653
+ value: "/q",
9654
+ description: "queue a follow-up prompt"
9655
+ },
9656
+ {
9657
+ value: "/steer",
9658
+ description: "steer the active turn"
9659
+ },
9644
9660
  {
9645
9661
  value: "/new",
9646
9662
  description: "start a fresh session"
@@ -9709,6 +9725,21 @@ const slashCommands = [
9709
9725
  name: "restore",
9710
9726
  description: "restore a previous interactive TUI session",
9711
9727
  execute: executeRestoreCommand
9728
+ },
9729
+ {
9730
+ name: "queue",
9731
+ description: "queue a follow-up prompt",
9732
+ execute: executeInteractiveOnlyCommand("/queue")
9733
+ },
9734
+ {
9735
+ name: "q",
9736
+ description: "queue a follow-up prompt",
9737
+ execute: executeInteractiveOnlyCommand("/q")
9738
+ },
9739
+ {
9740
+ name: "steer",
9741
+ description: "steer the active turn",
9742
+ execute: executeInteractiveOnlyCommand("/steer")
9712
9743
  }
9713
9744
  ];
9714
9745
  function parseSlashCommand(input) {
@@ -10192,6 +10223,7 @@ var ChatLayout = class {
10192
10223
  temporaryLine;
10193
10224
  taskPlanNoticeLine;
10194
10225
  noticeLine;
10226
+ queuedFollowUpCount = 0;
10195
10227
  promptHint;
10196
10228
  taskPlan;
10197
10229
  cancelPending;
@@ -10277,6 +10309,9 @@ var ChatLayout = class {
10277
10309
  setNoticeLine(line) {
10278
10310
  this.noticeLine = line;
10279
10311
  }
10312
+ setQueuedFollowUpCount(count) {
10313
+ this.queuedFollowUpCount = Math.max(0, count);
10314
+ }
10280
10315
  setPromptHint(hint) {
10281
10316
  this.promptHint = hint;
10282
10317
  }
@@ -10332,6 +10367,7 @@ var ChatLayout = class {
10332
10367
  this.temporaryLine = void 0;
10333
10368
  this.taskPlanNoticeLine = void 0;
10334
10369
  this.noticeLine = void 0;
10370
+ this.queuedFollowUpCount = 0;
10335
10371
  this.promptHint = void 0;
10336
10372
  this.taskPlan = void 0;
10337
10373
  this.cancelPending = void 0;
@@ -10430,6 +10466,10 @@ var ChatLayout = class {
10430
10466
  if (this.temporaryLine) lines.push(...this.renderThreadMessageLines([` ${ui.muted(this.temporaryLine)}`], innerWidth, width, false));
10431
10467
  if (this.taskPlanNoticeLine) lines.push(...this.renderThreadMessageLines([` ${this.taskPlanNoticeLine}`], innerWidth, width, false));
10432
10468
  if (this.noticeLine) lines.push(...this.renderThreadMessageLines([` ${this.noticeLine}`], innerWidth, width, false));
10469
+ if (this.queuedFollowUpCount > 0) {
10470
+ const suffix = this.queuedFollowUpCount === 1 ? "follow-up" : "follow-ups";
10471
+ lines.push(...this.renderThreadMessageLines([` ${ui.muted(`queued: ${this.queuedFollowUpCount} ${suffix}`)}`], innerWidth, width, false));
10472
+ }
10433
10473
  return lines;
10434
10474
  }
10435
10475
  renderThreadMessageLines(lines, innerWidth, width, highlight) {
@@ -10795,12 +10835,11 @@ var ChatLayout = class {
10795
10835
  submitPromptValue() {
10796
10836
  if (this.promptValue.trim().length === 0) return;
10797
10837
  const message = this.expandPastedContent(this.promptValue).trim();
10798
- this.addMessage(userMessage(message));
10799
10838
  this.promptValue = "";
10800
10839
  this.promptCursor = 0;
10801
10840
  this.pastedContent.clear();
10802
10841
  this.pasteCounter = 0;
10803
- this.submitUserInput(message);
10842
+ if (this.submitUserInput(message) !== "queued") this.addMessage(userMessage(message));
10804
10843
  }
10805
10844
  handlePromptEditInput(data) {
10806
10845
  if (this.promptHint) return false;
@@ -10927,12 +10966,21 @@ var ChatLayout = class {
10927
10966
  this.activeModalActionIndex = 0;
10928
10967
  }
10929
10968
  submitUserInput(message) {
10969
+ this.promptHistory.add(message);
10970
+ if (message.startsWith("/")) {
10971
+ const result = this.submitCommand?.(message);
10972
+ if (result === "queued") return result;
10973
+ this.setTaskPlanNotice(void 0);
10974
+ this.setStartupHintLine(void 0);
10975
+ this.setEphemeralLine(void 0);
10976
+ return result;
10977
+ }
10978
+ const result = this.submitMessage?.(message);
10979
+ if (result === "queued") return result;
10930
10980
  this.setTaskPlanNotice(void 0);
10931
10981
  this.setStartupHintLine(void 0);
10932
10982
  this.setEphemeralLine(void 0);
10933
- this.promptHistory.add(message);
10934
- if (message.startsWith("/")) this.submitCommand?.(message);
10935
- else this.submitMessage?.(message);
10983
+ return result;
10936
10984
  }
10937
10985
  };
10938
10986
  function getVisibleSuggestionWindowStart(total, activeIndex, visibleRows) {
@@ -12431,6 +12479,30 @@ function formatStartupKnowledgeGuidance(status) {
12431
12479
  if ((status.nonCleanFileCount ?? 0) > 0) return "Next: run /kb sync to update project knowledge, or /kb status to inspect the files.";
12432
12480
  }
12433
12481
  //#endregion
12482
+ //#region src/agent/runtime/steering.ts
12483
+ var MutableRuntimeSteeringBuffer = class {
12484
+ prompts = [];
12485
+ push(prompt) {
12486
+ const trimmed = prompt.trim();
12487
+ if (trimmed.length > 0) this.prompts.push(trimmed);
12488
+ }
12489
+ drain() {
12490
+ if (this.prompts.length === 0) return;
12491
+ return this.prompts.splice(0).join("\n\n");
12492
+ }
12493
+ hasPending() {
12494
+ return this.prompts.length > 0;
12495
+ }
12496
+ };
12497
+ function formatRuntimeSteeringPrompt(steering) {
12498
+ return [
12499
+ "User steering received while this turn was running:",
12500
+ steering.trim(),
12501
+ "",
12502
+ "Continue the user's original request, applying this steering if it is still relevant."
12503
+ ].join("\n");
12504
+ }
12505
+ //#endregion
12434
12506
  //#region src/agent/runtime/model.ts
12435
12507
  /**
12436
12508
  * Calls the configured model gateway for a single agent step and normalizes
@@ -12809,7 +12881,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
12809
12881
  }
12810
12882
  }
12811
12883
  afterTool = "task";
12812
- nextPrompt = this.appendHookContextsToPrompt(`${nextPrompt}\n\n${taskResults.map((toolResult) => formatToolResultForPrompt(toolResult)).join("\n\n")}\n\n${formatContinuationInstruction(result.toolProtocol, taskResults.at(-1), isToolAllowed(permissions, "plan_todo"))}`, "PostToolUse", postHookContexts);
12884
+ nextPrompt = this.appendRuntimeSteeringToContinuationPrompt(this.appendHookContextsToPrompt(`${nextPrompt}\n\n${taskResults.map((toolResult) => formatToolResultForPrompt(toolResult)).join("\n\n")}\n\n${formatContinuationInstruction(result.toolProtocol, taskResults.at(-1), isToolAllowed(permissions, "plan_todo"))}`, "PostToolUse", postHookContexts), options.steering);
12813
12885
  continue;
12814
12886
  }
12815
12887
  if (modelToolCalls.length > 1 && modelToolCalls.every((call) => toolCatalog.isParallelSafe(call.tool))) {
@@ -12872,7 +12944,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
12872
12944
  }
12873
12945
  }
12874
12946
  afterTool = parallelCalls.at(-1)?.tool;
12875
- nextPrompt = this.appendHookContextsToPrompt(`${nextPrompt}\n\n${parallelResults.map((toolResult) => formatToolResultForPrompt(toolResult)).join("\n\n")}\n\n${formatContinuationInstruction(result.toolProtocol, parallelResults.at(-1), isToolAllowed(permissions, "plan_todo"))}`, "PostToolUse", postHookContexts);
12947
+ nextPrompt = this.appendRuntimeSteeringToContinuationPrompt(this.appendHookContextsToPrompt(`${nextPrompt}\n\n${parallelResults.map((toolResult) => formatToolResultForPrompt(toolResult)).join("\n\n")}\n\n${formatContinuationInstruction(result.toolProtocol, parallelResults.at(-1), isToolAllowed(permissions, "plan_todo"))}`, "PostToolUse", postHookContexts), options.steering);
12876
12948
  continue;
12877
12949
  }
12878
12950
  const executableToolCall = toolCall;
@@ -12940,7 +13012,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
12940
13012
  return;
12941
13013
  }
12942
13014
  afterTool = executableToolCall.tool;
12943
- nextPrompt = this.appendHookContextsToPrompt(`${nextPrompt}\n\n${formatToolResultForPrompt(toolResult)}\n\n${formatContinuationInstruction(result.toolProtocol, toolResult, isToolAllowed(permissions, "plan_todo"))}`, "PostToolUse", postHook.contexts);
13015
+ nextPrompt = this.appendRuntimeSteeringToContinuationPrompt(this.appendHookContextsToPrompt(`${nextPrompt}\n\n${formatToolResultForPrompt(toolResult)}\n\n${formatContinuationInstruction(result.toolProtocol, toolResult, isToolAllowed(permissions, "plan_todo"))}`, "PostToolUse", postHook.contexts), options.steering);
12944
13016
  }
12945
13017
  const finalMessage = "I stopped because the tool loop ended unexpectedly.";
12946
13018
  yield agentEvent.assistantMessage(finalMessage, formatAgentMessageMeta(lastModelId, totalDurationMs, tokenUsageTotals));
@@ -13219,6 +13291,11 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13219
13291
  nonCleanFileCount: result.files.length
13220
13292
  };
13221
13293
  }
13294
+ appendRuntimeSteeringToContinuationPrompt(prompt, steering) {
13295
+ const text = steering?.drain()?.trim();
13296
+ if (!text) return prompt;
13297
+ return `${prompt}\n\n${formatRuntimeSteeringPrompt(text)}`;
13298
+ }
13222
13299
  /**
13223
13300
  * Adds relevant L1 knowledge context to the conversation prompt when the
13224
13301
  * compiled KB is present and ready. Search failures are logged and then
@@ -13782,6 +13859,9 @@ var TopchesterTuiShell = class {
13782
13859
  knowledgeStatusTimer;
13783
13860
  skillsService;
13784
13861
  pendingSkillActivations = [];
13862
+ chatRunning = false;
13863
+ queuedChatMessages = [];
13864
+ activeSteeringBuffer;
13785
13865
  constructor(context, runtime, options = {}) {
13786
13866
  this.context = context;
13787
13867
  this.options = options;
@@ -13833,10 +13913,26 @@ var TopchesterTuiShell = class {
13833
13913
  app.setTaskPlan(this.options.initialTaskPlan);
13834
13914
  if (!isResumed) app.setStartupHintLine(STARTUP_PROMPT_HINT);
13835
13915
  app.setSubmitMessage((message) => {
13916
+ if (this.chatRunning) {
13917
+ this.enqueueChatMessage(app, tui, message);
13918
+ return "queued";
13919
+ }
13836
13920
  this.startBackgroundTask(app, tui, "Chat", () => this.submitChatMessage(app, tui, message));
13921
+ return "submitted";
13837
13922
  });
13838
13923
  app.setSubmitCommand((command) => {
13924
+ const queuedPrompt = parseQueueCommandPrompt(command);
13925
+ if (queuedPrompt !== void 0) {
13926
+ this.submitQueueCommand(app, tui, queuedPrompt);
13927
+ return "queued";
13928
+ }
13929
+ const steeringPrompt = parseSteerCommandPrompt(command);
13930
+ if (steeringPrompt !== void 0) {
13931
+ this.submitSteerCommand(app, tui, steeringPrompt);
13932
+ return "queued";
13933
+ }
13839
13934
  this.startBackgroundTask(app, tui, "Command", () => this.submitSlashCommand(app, tui, command));
13935
+ return "submitted";
13840
13936
  });
13841
13937
  tui.addChild(app);
13842
13938
  tui.setFocus(app);
@@ -13902,9 +13998,15 @@ var TopchesterTuiShell = class {
13902
13998
  tui.requestRender();
13903
13999
  }
13904
14000
  async submitChatMessage(app, tui, message) {
14001
+ if (this.chatRunning) {
14002
+ this.enqueueChatMessage(app, tui, message);
14003
+ return;
14004
+ }
14005
+ const activeSession = this.session;
14006
+ const steering = new MutableRuntimeSteeringBuffer();
13905
14007
  const busy = new BusyIndicator(app, tui, {
13906
14008
  status: "thinking",
13907
- promptHint: "press Esc to stop",
14009
+ activityHint: "press Esc to stop",
13908
14010
  activities: [
13909
14011
  "Thinking...",
13910
14012
  "Calling model...",
@@ -13914,10 +14016,13 @@ var TopchesterTuiShell = class {
13914
14016
  const abortController = new AbortController();
13915
14017
  const reasoningDisplay = isStreamReasoningEnabledByEnv() ? createBusyReasoningSink(busy) : void 0;
13916
14018
  let cancelled = false;
14019
+ this.activeSteeringBuffer = steering;
13917
14020
  app.setCancelPending(() => {
13918
14021
  cancelled = true;
13919
14022
  abortController.abort();
13920
14023
  });
14024
+ this.chatRunning = true;
14025
+ this.refreshQueuedChatStatus(app);
13921
14026
  busy.start();
13922
14027
  tui.requestRender();
13923
14028
  try {
@@ -13936,7 +14041,8 @@ var TopchesterTuiShell = class {
13936
14041
  for await (const event of this.runtime.submitMessageStream(conversation, modelMessage, abortController.signal, {
13937
14042
  onReasoning: reasoningDisplay?.sink,
13938
14043
  session: this.session,
13939
- requestBashApproval: (request) => this.requestBashApproval(app, tui, busy, request, abortController.signal)
14044
+ requestBashApproval: (request) => this.requestBashApproval(app, tui, busy, request, abortController.signal),
14045
+ steering
13940
14046
  })) {
13941
14047
  if (event.type === "message" && event.role === "assistant") {
13942
14048
  reasoningDisplay?.commit(app);
@@ -13959,9 +14065,93 @@ var TopchesterTuiShell = class {
13959
14065
  }
13960
14066
  } finally {
13961
14067
  app.setCancelPending(void 0);
14068
+ this.chatRunning = false;
14069
+ if (this.activeSteeringBuffer === steering) this.activeSteeringBuffer = void 0;
13962
14070
  busy.stop();
13963
14071
  tui.requestRender();
13964
14072
  }
14073
+ this.handleUnconsumedSteering(app, tui, steering, cancelled);
14074
+ if (this.session === activeSession && app.isReady()) await this.drainQueuedChatMessages(app, tui);
14075
+ }
14076
+ enqueueChatMessage(app, tui, message) {
14077
+ const trimmed = message.trim();
14078
+ if (trimmed.length === 0) return;
14079
+ this.queuedChatMessages.push(trimmed);
14080
+ this.refreshQueuedChatStatus(app);
14081
+ tui.requestRender();
14082
+ }
14083
+ submitQueueCommand(app, tui, prompt) {
14084
+ const trimmed = prompt.trim();
14085
+ if (trimmed.length === 0) {
14086
+ app.addMessage(systemMessage("Usage: /queue <prompt>"));
14087
+ tui.requestRender();
14088
+ return;
14089
+ }
14090
+ if (this.chatRunning) {
14091
+ this.enqueueChatMessage(app, tui, trimmed);
14092
+ return;
14093
+ }
14094
+ app.addMessage(userMessage(trimmed));
14095
+ this.startBackgroundTask(app, tui, "Chat", () => this.submitChatMessage(app, tui, trimmed));
14096
+ tui.requestRender();
14097
+ }
14098
+ submitSteerCommand(app, tui, prompt) {
14099
+ const trimmed = prompt.trim();
14100
+ if (trimmed.length === 0) {
14101
+ app.addMessage(systemMessage("Usage: /steer <prompt>"));
14102
+ tui.requestRender();
14103
+ return;
14104
+ }
14105
+ if (!this.chatRunning) {
14106
+ app.addMessage(userMessage(trimmed));
14107
+ this.startBackgroundTask(app, tui, "Chat", () => this.submitChatMessage(app, tui, trimmed));
14108
+ tui.requestRender();
14109
+ return;
14110
+ }
14111
+ const steering = this.activeSteeringBuffer;
14112
+ if (!steering) {
14113
+ this.enqueueChatMessage(app, tui, trimmed);
14114
+ return;
14115
+ }
14116
+ steering.push(trimmed);
14117
+ app.setTemporaryLine("steering added to active turn", { expireAfterMs: 2e3 });
14118
+ tui.requestRender();
14119
+ }
14120
+ handleUnconsumedSteering(app, tui, steering, cancelled) {
14121
+ const pending = steering.drain();
14122
+ if (!pending) return;
14123
+ if (cancelled) {
14124
+ app.addMessage(systemMessage("Dropped pending steering after response stopped."));
14125
+ tui.requestRender();
14126
+ return;
14127
+ }
14128
+ this.enqueueChatMessage(app, tui, pending);
14129
+ }
14130
+ async drainQueuedChatMessages(app, tui) {
14131
+ while (!this.chatRunning && app.isReady()) {
14132
+ const message = this.queuedChatMessages.shift();
14133
+ this.refreshQueuedChatStatus(app);
14134
+ if (!message) {
14135
+ tui.requestRender();
14136
+ return;
14137
+ }
14138
+ app.addMessage(userMessage(message));
14139
+ tui.requestRender();
14140
+ await this.submitChatMessage(app, tui, message);
14141
+ }
14142
+ }
14143
+ refreshQueuedChatStatus(app) {
14144
+ app.setQueuedFollowUpCount(this.queuedChatMessages.length);
14145
+ }
14146
+ clearQueuedChatMessages(app) {
14147
+ const droppedCount = this.queuedChatMessages.length;
14148
+ this.queuedChatMessages = [];
14149
+ const droppedSteering = this.activeSteeringBuffer?.hasPending() ? this.activeSteeringBuffer.drain() : void 0;
14150
+ this.refreshQueuedChatStatus(app);
14151
+ if (droppedCount === 0 && !droppedSteering) return;
14152
+ const parts = droppedCount > 0 ? [`Dropped ${droppedCount} queued ${droppedCount === 1 ? "follow-up" : "follow-ups"}.`] : [];
14153
+ if (droppedSteering) parts.push("Dropped pending steering.");
14154
+ return parts.join(" ");
13965
14155
  }
13966
14156
  requestBashApproval(app, tui, busy, request, abortSignal) {
13967
14157
  return new Promise((resolve) => {
@@ -14443,7 +14633,9 @@ var TopchesterTuiShell = class {
14443
14633
  this.taskPlanNoticeTimer = void 0;
14444
14634
  }
14445
14635
  const session = await createSession(this.context.workspaceRoot);
14636
+ const droppedQueuedNotice = this.clearQueuedChatMessages(app);
14446
14637
  const messages = getStartupThreadMessages(this.context);
14638
+ if (droppedQueuedNotice) messages.push(systemMessage(droppedQueuedNotice));
14447
14639
  this.session = session;
14448
14640
  this.sessionStartedAt = Date.now();
14449
14641
  this.pendingSkillActivations = [];
@@ -14470,7 +14662,12 @@ var TopchesterTuiShell = class {
14470
14662
  const rehydrated = rehydrateSession((await loadSession(this.context.workspaceRoot, fork.sessionId)).events);
14471
14663
  const forkNoticeText = `Forked session from ${sourceSession.sessionId.slice(0, 8)}.`;
14472
14664
  const forkNotice = systemMessage(forkNoticeText);
14473
- const resetMessages = [...rehydrated.messages, forkNotice];
14665
+ const droppedQueuedNotice = this.clearQueuedChatMessages(app);
14666
+ const resetMessages = [
14667
+ ...rehydrated.messages,
14668
+ forkNotice,
14669
+ ...droppedQueuedNotice ? [systemMessage(droppedQueuedNotice)] : []
14670
+ ];
14474
14671
  this.session = fork;
14475
14672
  this.sessionStartedAt = Date.now();
14476
14673
  this.pendingSkillActivations = [];
@@ -14528,7 +14725,12 @@ var TopchesterTuiShell = class {
14528
14725
  restoredTaskPlan = rehydrated.taskPlan;
14529
14726
  restoredStatus = rehydrated.status;
14530
14727
  const noticeText = `Restored session ${sessionId.slice(0, 8)}.`;
14531
- restoredMessages = [...rehydrated.messages, systemMessage(noticeText)];
14728
+ const droppedQueuedNotice = this.clearQueuedChatMessages(app);
14729
+ restoredMessages = [
14730
+ ...rehydrated.messages,
14731
+ systemMessage(noticeText),
14732
+ ...droppedQueuedNotice ? [systemMessage(droppedQueuedNotice)] : []
14733
+ ];
14532
14734
  this.session = restoredSession;
14533
14735
  this.sessionStartedAt = Date.now();
14534
14736
  this.pendingSkillActivations = [];
@@ -14618,6 +14820,16 @@ var TopchesterTuiShell = class {
14618
14820
  printExitBanner((this.session ?? fallbackSession).sessionId, Date.now() - this.sessionStartedAt);
14619
14821
  }
14620
14822
  };
14823
+ function parseQueueCommandPrompt(command) {
14824
+ const trimmed = command.trim();
14825
+ const match = /^\/(?:queue|q)(?:\s+([\s\S]*))?$/u.exec(trimmed);
14826
+ return match ? match[1] ?? "" : void 0;
14827
+ }
14828
+ function parseSteerCommandPrompt(command) {
14829
+ const trimmed = command.trim();
14830
+ const match = /^\/steer(?:\s+([\s\S]*))?$/u.exec(trimmed);
14831
+ return match ? match[1] ?? "" : void 0;
14832
+ }
14621
14833
  //#endregion
14622
14834
  //#region src/version.ts
14623
14835
  const FALLBACK_VERSION = "0.0.0";
@@ -15125,8 +15337,8 @@ function createTopchesterProgram() {
15125
15337
  console.log(result.lines.join("\n"));
15126
15338
  if (!result.ok) process.exitCode = 1;
15127
15339
  });
15128
- const authCommand = program.command("auth").description("manage global provider authentication");
15129
- authCommand.command("login").description("log in to a provider").argument("<provider>", "provider id").option("--device", "use device-code login").action(async (provider, options) => {
15340
+ const authCommand = program.command("auth").description("manage global provider authentication").addHelpText("after", formatAuthCommandHelp);
15341
+ authCommand.command("login").usage("[options] <provider>").description("log in to a provider").argument("[provider]", "provider id").option("--device", "use device-code login").addHelpText("after", formatAuthLoginHelp).action(async (provider, options) => {
15130
15342
  try {
15131
15343
  await executeAuthLoginCommand(provider, options);
15132
15344
  } catch (error) {
@@ -15259,19 +15471,66 @@ function printStartupSummary(context) {
15259
15471
  }
15260
15472
  }
15261
15473
  }
15474
+ const AUTH_PROVIDERS = [{
15475
+ id: "codex",
15476
+ name: "Codex / ChatGPT",
15477
+ auth: "OAuth device-code login for Codex-backed model access.",
15478
+ example: "topchester auth login codex --device"
15479
+ }];
15480
+ function formatAuthCommandHelp() {
15481
+ return [
15482
+ "",
15483
+ ui.label("Supported providers:"),
15484
+ ...AUTH_PROVIDERS.map((provider) => ` ${ui.modelInline(provider.id.padEnd(8))} ${provider.name} - ${provider.auth}`),
15485
+ "",
15486
+ ui.label("Examples:"),
15487
+ ...AUTH_PROVIDERS.map((provider) => ` ${provider.example}`),
15488
+ " topchester auth status"
15489
+ ].join("\n");
15490
+ }
15491
+ function formatAuthLoginHelp() {
15492
+ return [
15493
+ "",
15494
+ ui.label("Supported providers:"),
15495
+ ...AUTH_PROVIDERS.map((provider) => ` ${ui.modelInline(provider.id.padEnd(8))} ${provider.auth}`),
15496
+ "",
15497
+ ui.label("Examples:"),
15498
+ ...AUTH_PROVIDERS.map((provider) => ` ${provider.example}`),
15499
+ "",
15500
+ ui.label("What happens:"),
15501
+ " Topchester prints a browser URL and one-time code, waits for approval, then stores tokens in the global auth store."
15502
+ ].join("\n");
15503
+ }
15504
+ function formatAuthLoginUsageError(reason) {
15505
+ return [
15506
+ ui.error(reason),
15507
+ "",
15508
+ ui.label("Usage:"),
15509
+ " topchester auth login <provider> --device",
15510
+ "",
15511
+ ui.label("Supported providers:"),
15512
+ ...AUTH_PROVIDERS.map((provider) => ` ${provider.id.padEnd(8)} ${provider.name} - ${provider.auth}`),
15513
+ "",
15514
+ ui.label("Examples:"),
15515
+ ...AUTH_PROVIDERS.map((provider) => ` ${provider.example}`),
15516
+ "",
15517
+ "Run `topchester auth login --help` for details."
15518
+ ].join("\n");
15519
+ }
15262
15520
  async function executeAuthLoginCommand(provider, options) {
15263
- if (provider !== "codex") throw new Error(`Unsupported auth provider "${provider}". Supported providers: codex.`);
15264
- if (!options.device) throw new Error("Usage: topchester auth login codex --device");
15521
+ if (!provider) throw new Error(formatAuthLoginUsageError("Missing provider."));
15522
+ if (provider !== "codex") throw new Error(formatAuthLoginUsageError(`Unsupported auth provider "${provider}".`));
15523
+ if (!options.device) throw new Error(formatAuthLoginUsageError("Codex login currently requires \"--device\"."));
15265
15524
  const deviceCode = await requestCodexDeviceCode();
15266
- console.log("Codex device login");
15267
- console.log(`verification URL: ${deviceCode.verificationUrl}`);
15268
- console.log(`user code: ${deviceCode.userCode}`);
15269
- console.log(`expires: ${new Date(deviceCode.expiresAt).toISOString()}`);
15270
- console.log("Device codes are a common phishing target. Never share this code.");
15271
- console.log("Waiting for browser approval...");
15525
+ console.log(ui.heading("Codex device login"));
15526
+ console.log(`${ui.label("verification URL:")} ${deviceCode.verificationUrl}`);
15527
+ console.log(`${ui.label("user code:")} ${ui.inverse(deviceCode.userCode)}`);
15528
+ console.log(`${ui.label("expires:")} ${new Date(deviceCode.expiresAt).toISOString()}`);
15529
+ console.log(ui.warn("Device codes are a common phishing target. Never share this code."));
15530
+ console.log(ui.label("Waiting for browser approval..."));
15272
15531
  await setAuthProvider("codex", await exchangeCodexAuthorizationCode(await pollCodexDeviceAuthorization(deviceCode), { issuer: deviceCode.issuer }));
15273
15532
  await configureCodexGlobalProvider();
15274
- console.log("Codex login saved.");
15533
+ console.log(ui.ok("Codex login saved."));
15275
15534
  console.log("Configured global Codex provider and starter model choices.");
15276
15535
  }
15277
15536
  async function formatAuthStatus() {
@@ -15370,4 +15629,4 @@ function formatDryRunSyncStatus(status) {
15370
15629
  //#endregion
15371
15630
  export { runTopchesterCli as t };
15372
15631
 
15373
- //# sourceMappingURL=cli-BWa58EWv.mjs.map
15632
+ //# sourceMappingURL=cli-yf0DNAUH.mjs.map