@ashx-j/lunr 0.2.13 → 0.2.14

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 (211) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/README.md +6 -3
  3. package/dist/builtin-extensions/ashxj-tui.d.ts +16 -1
  4. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  5. package/dist/builtin-extensions/ashxj-tui.js +44 -20
  6. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  7. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +2 -0
  8. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  9. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +27 -22
  10. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  11. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
  12. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +5 -2
  13. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
  14. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +12 -1
  16. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.d.ts +1 -0
  18. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.js +3 -2
  20. package/dist/builtin-extensions/pi-subagents/src/runs/background/async-status.js.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-append.d.ts.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-append.js +1 -0
  23. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-append.js.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.d.ts.map +1 -1
  25. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.js +3 -0
  26. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.js.map +1 -1
  27. package/dist/builtin-extensions/pi-subagents/src/runs/background/fleet-view.d.ts.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/runs/background/fleet-view.js +3 -3
  29. package/dist/builtin-extensions/pi-subagents/src/runs/background/fleet-view.js.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.d.ts.map +1 -1
  31. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js +2 -2
  32. package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js.map +1 -1
  33. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +3 -0
  35. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
  37. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +7 -1
  38. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
  39. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  40. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +2 -0
  41. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  42. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +1 -0
  43. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  44. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +19 -3
  45. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  46. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +10 -1
  47. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +19 -0
  49. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
  50. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +1 -0
  51. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
  53. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +2 -1
  54. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -1
  55. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +17 -0
  56. package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -1
  57. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts +3 -1
  58. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.d.ts.map +1 -1
  59. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js +14 -3
  60. package/dist/builtin-extensions/pi-subagents/src/shared/formatters.js.map +1 -1
  61. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +14 -0
  62. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  63. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  64. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.d.ts.map +1 -1
  65. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.js +2 -2
  66. package/dist/builtin-extensions/pi-subagents/src/tui/fleet.js.map +1 -1
  67. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  68. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +12 -10
  69. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  70. package/dist/catalog/models.json +1 -1
  71. package/dist/catalog/providers/amazon-bedrock.json +1 -1
  72. package/dist/catalog/providers/anthropic.json +1 -1
  73. package/dist/catalog/providers/azure-openai-responses.json +1 -1
  74. package/dist/catalog/providers/cloudflare-ai-gateway.json +1 -1
  75. package/dist/catalog/providers/fireworks.json +1 -1
  76. package/dist/catalog/providers/github-copilot.json +1 -1
  77. package/dist/catalog/providers/google-vertex.json +1 -1
  78. package/dist/catalog/providers/google.json +1 -1
  79. package/dist/catalog/providers/groq.json +1 -1
  80. package/dist/catalog/providers/huggingface.json +1 -1
  81. package/dist/catalog/providers/nvidia.json +1 -1
  82. package/dist/catalog/providers/openai-codex.json +1 -1
  83. package/dist/catalog/providers/openai.json +1 -1
  84. package/dist/catalog/providers/opencode-go.json +1 -1
  85. package/dist/catalog/providers/opencode.json +1 -1
  86. package/dist/catalog/providers/openrouter.json +1 -1
  87. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  88. package/dist/catalog/publication.json +131 -3
  89. package/dist/cli/list-models.d.ts.map +1 -1
  90. package/dist/cli/list-models.js +1 -0
  91. package/dist/cli/list-models.js.map +1 -1
  92. package/dist/cli.d.ts.map +1 -1
  93. package/dist/cli.js +52 -27
  94. package/dist/cli.js.map +1 -1
  95. package/dist/core/catalog-cache-scope.d.ts +3 -0
  96. package/dist/core/catalog-cache-scope.d.ts.map +1 -0
  97. package/dist/core/catalog-cache-scope.js +12 -0
  98. package/dist/core/catalog-cache-scope.js.map +1 -0
  99. package/dist/core/catalog-merge.d.ts +2 -0
  100. package/dist/core/catalog-merge.d.ts.map +1 -1
  101. package/dist/core/catalog-merge.js +29 -2
  102. package/dist/core/catalog-merge.js.map +1 -1
  103. package/dist/core/catalog-refresh-polling.d.ts +8 -0
  104. package/dist/core/catalog-refresh-polling.d.ts.map +1 -0
  105. package/dist/core/catalog-refresh-polling.js +30 -0
  106. package/dist/core/catalog-refresh-polling.js.map +1 -0
  107. package/dist/core/codex-catalog.d.ts +3 -0
  108. package/dist/core/codex-catalog.d.ts.map +1 -0
  109. package/dist/core/codex-catalog.js +15 -0
  110. package/dist/core/codex-catalog.js.map +1 -0
  111. package/dist/core/customize.d.ts +2 -4
  112. package/dist/core/customize.d.ts.map +1 -1
  113. package/dist/core/customize.js +2 -8
  114. package/dist/core/customize.js.map +1 -1
  115. package/dist/core/footer-data-provider.d.ts +3 -4
  116. package/dist/core/footer-data-provider.d.ts.map +1 -1
  117. package/dist/core/footer-data-provider.js +54 -82
  118. package/dist/core/footer-data-provider.js.map +1 -1
  119. package/dist/core/live-catalog.d.ts +5 -2
  120. package/dist/core/live-catalog.d.ts.map +1 -1
  121. package/dist/core/live-catalog.js +108 -27
  122. package/dist/core/live-catalog.js.map +1 -1
  123. package/dist/core/model-runtime.d.ts +8 -0
  124. package/dist/core/model-runtime.d.ts.map +1 -1
  125. package/dist/core/model-runtime.js +64 -12
  126. package/dist/core/model-runtime.js.map +1 -1
  127. package/dist/core/official-catalog.d.ts +4 -0
  128. package/dist/core/official-catalog.d.ts.map +1 -1
  129. package/dist/core/official-catalog.js +86 -21
  130. package/dist/core/official-catalog.js.map +1 -1
  131. package/dist/core/settings-manager.d.ts +0 -6
  132. package/dist/core/settings-manager.d.ts.map +1 -1
  133. package/dist/core/settings-manager.js +0 -17
  134. package/dist/core/settings-manager.js.map +1 -1
  135. package/dist/core/slash-commands.d.ts.map +1 -1
  136. package/dist/core/slash-commands.js +1 -0
  137. package/dist/core/slash-commands.js.map +1 -1
  138. package/dist/main.d.ts +2 -0
  139. package/dist/main.d.ts.map +1 -1
  140. package/dist/main.js +106 -50
  141. package/dist/main.js.map +1 -1
  142. package/dist/migrations.d.ts +2 -0
  143. package/dist/migrations.d.ts.map +1 -1
  144. package/dist/migrations.js +14 -4
  145. package/dist/migrations.js.map +1 -1
  146. package/dist/modes/interactive/components/assistant-message.d.ts +1 -3
  147. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  148. package/dist/modes/interactive/components/assistant-message.js +4 -32
  149. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  150. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  151. package/dist/modes/interactive/components/custom-editor.js +2 -1
  152. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  153. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  154. package/dist/modes/interactive/components/model-selector.js +4 -1
  155. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  156. package/dist/modes/interactive/components/plan-message.d.ts +1 -3
  157. package/dist/modes/interactive/components/plan-message.d.ts.map +1 -1
  158. package/dist/modes/interactive/components/plan-message.js +1 -16
  159. package/dist/modes/interactive/components/plan-message.js.map +1 -1
  160. package/dist/modes/interactive/components/settings-selector.d.ts +0 -4
  161. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  162. package/dist/modes/interactive/components/settings-selector.js +10 -30
  163. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  164. package/dist/modes/interactive/components/user-message.d.ts +1 -3
  165. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  166. package/dist/modes/interactive/components/user-message.js +2 -19
  167. package/dist/modes/interactive/components/user-message.js.map +1 -1
  168. package/dist/modes/interactive/interactive-mode.d.ts +12 -1
  169. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  170. package/dist/modes/interactive/interactive-mode.js +137 -127
  171. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  172. package/dist/modes/interactive/theme/moon.json +0 -1
  173. package/dist/modes/interactive/theme/theme-schema.json +3 -3
  174. package/dist/modes/interactive/theme/theme.d.ts +1 -1
  175. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  176. package/dist/modes/interactive/theme/theme.js +95 -90
  177. package/dist/modes/interactive/theme/theme.js.map +1 -1
  178. package/dist/startup/interactive-view.d.ts +55 -0
  179. package/dist/startup/interactive-view.d.ts.map +1 -0
  180. package/dist/startup/interactive-view.js +332 -0
  181. package/dist/startup/interactive-view.js.map +1 -0
  182. package/dist/startup/launch-routing.d.ts +8 -0
  183. package/dist/startup/launch-routing.d.ts.map +1 -0
  184. package/dist/startup/launch-routing.js +29 -0
  185. package/dist/startup/launch-routing.js.map +1 -0
  186. package/dist/startup/startup-milestones.d.ts +4 -0
  187. package/dist/startup/startup-milestones.d.ts.map +1 -0
  188. package/dist/startup/startup-milestones.js +14 -0
  189. package/dist/startup/startup-milestones.js.map +1 -0
  190. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  191. package/dist/utils/syntax-highlight.js +10 -4
  192. package/dist/utils/syntax-highlight.js.map +1 -1
  193. package/docs/features.md +2 -2
  194. package/docs/interactive-startup.md +58 -0
  195. package/docs/keybindings.md +1 -1
  196. package/docs/model-catalog.md +29 -0
  197. package/docs/quickstart.md +1 -1
  198. package/docs/settings.md +1 -3
  199. package/docs/terminal-setup.md +2 -2
  200. package/docs/usage.md +2 -1
  201. package/docs/windows.md +1 -1
  202. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  203. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  204. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  205. package/examples/extensions/gondolin/package-lock.json +2 -2
  206. package/examples/extensions/gondolin/package.json +1 -1
  207. package/examples/extensions/sandbox/package-lock.json +2 -2
  208. package/examples/extensions/sandbox/package.json +1 -1
  209. package/examples/extensions/with-deps/package-lock.json +2 -2
  210. package/examples/extensions/with-deps/package.json +1 -1
  211. package/package.json +16 -16
@@ -15,13 +15,13 @@ import { parseSkillBlock } from "../../core/agent-session.js";
15
15
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
16
16
  import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
17
17
  import { formatCatalogRefreshSummary } from "../../core/catalog-merge.js";
18
+ import { startCatalogRefreshPolling } from "../../core/catalog-refresh-polling.js";
18
19
  import { computeContextBreakdown } from "../../core/context-breakdown.js";
19
20
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
20
21
  import { hasGrokCliXaiAuth } from "../../core/grok-cli-auth.js";
21
22
  import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
22
23
  import { detectInjectedPrompt } from "../../core/injected-prompt.js";
23
24
  import { KeybindingsManager } from "../../core/keybindings.js";
24
- import { applyIncompleteAnswers, describeIncomplete, INCOMPLETE_PROMPT_CAP, parsePositiveInt, } from "../../core/live-catalog.js";
25
25
  import { createCompactionSummaryMessage } from "../../core/messages.js";
26
26
  import { isValidProviderBaseUrl, parseModelIds, slugifyProviderId, upsertCustomProvider, } from "../../core/model-config-writer.js";
27
27
  import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
@@ -43,7 +43,7 @@ import { time } from "../../core/timings.js";
43
43
  import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
44
44
  import { checkForUpdate, markUpdateNotified } from "../../core/update-check.js";
45
45
  import { getAllPlanUsageResults, getUsageServiceBridge } from "../../core/usage-service.js";
46
- import { modelToUserEntry } from "../../core/user-models.js";
46
+ import { markStartupMilestone } from "../../startup/startup-milestones.js";
47
47
  import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
48
48
  import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
49
49
  import { parseGitUrl } from "../../utils/git.js";
@@ -199,6 +199,11 @@ function formatLoginProviderCompletionDescription(provider) {
199
199
  const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
200
200
  return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
201
201
  }
202
+ export async function startInteractiveToolInstalls(currentFdPath, onFdInstalled, installer = ensureTool) {
203
+ const [fdPath] = await Promise.all([installer("fd", true), installer("rg", true)]);
204
+ if (fdPath && fdPath !== currentFdPath)
205
+ onFdInstalled(fdPath);
206
+ }
202
207
  export class InteractiveMode {
203
208
  runtimeHost;
204
209
  ui;
@@ -219,6 +224,7 @@ export class InteractiveMode {
219
224
  keybindings;
220
225
  version;
221
226
  isInitialized = false;
227
+ stopCatalogRefresh;
222
228
  onInputCallback;
223
229
  pendingUserInputs = [];
224
230
  stagedSubmitImages;
@@ -313,6 +319,8 @@ export class InteractiveMode {
313
319
  autoTrustOnReloadCwd;
314
320
  themeController;
315
321
  deferredBuiltinsAttached = false;
322
+ startupReadyPromise;
323
+ startupFeatureError;
316
324
  deferredBuiltinAttachPromise;
317
325
  // Convenience accessors
318
326
  get session() {
@@ -353,7 +361,7 @@ export class InteractiveMode {
353
361
  this.syncPermissionModeEffects(this.settingsManager.getDefaultPermissionMode());
354
362
  });
355
363
  this.version = VERSION;
356
- this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
364
+ this.ui = options.startupView?.ui ?? new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
357
365
  this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
358
366
  // lunr: register the permission-mode footer bridge (provider-side: InteractiveMode owns the state).
359
367
  registerPermissionModeBridge(() => getPermissionMode());
@@ -364,21 +372,29 @@ export class InteractiveMode {
364
372
  // lunr: reset permission mode to configured default on startup.
365
373
  resetPermissions(this.settingsManager.getDefaultPermissionMode());
366
374
  this.headerContainer = new Container();
375
+ if (options.startupView) {
376
+ this.headerContainer.addChild(new Spacer(1));
377
+ this.headerContainer.addChild(options.startupView.header);
378
+ this.customFooter = options.startupView.footer;
379
+ }
367
380
  this.loadedResourcesContainer = new Container();
368
381
  this.chatContainer = new Container();
369
382
  this.pendingMessagesContainer = new Container();
370
- this.statusContainer = new Container();
383
+ this.statusContainer = options.startupView?.statusContainer ?? new Container();
371
384
  this.widgetContainerAbove = new Container();
372
385
  this.widgetContainerBelow = new Container();
373
- this.keybindings = KeybindingsManager.create();
386
+ this.keybindings = options.startupView?.keybindings ?? KeybindingsManager.create();
374
387
  setKeybindings(this.keybindings);
375
388
  const editorPaddingX = this.settingsManager.getEditorPaddingX();
376
389
  const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
377
- this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
378
- paddingX: editorPaddingX,
379
- autocompleteMaxVisible,
380
- });
390
+ this.defaultEditor =
391
+ options.startupView?.editor ??
392
+ new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
393
+ paddingX: editorPaddingX,
394
+ autocompleteMaxVisible,
395
+ });
381
396
  this.editor = this.defaultEditor;
397
+ options.startupView?.setCurrentEditor(() => this.editor);
382
398
  this.editorContainer = new Container();
383
399
  this.editorContainer.addChild(this.editor);
384
400
  this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
@@ -540,14 +556,10 @@ export class InteractiveMode {
540
556
  if (this.isInitialized)
541
557
  return;
542
558
  this.registerSignalHandlers();
543
- // Use already-installed fd immediately. Download in the background so a
544
- // missing binary or GitHub outage cannot block first paint.
545
559
  this.fdPath = getToolPath("fd") ?? undefined;
546
- void Promise.all([ensureTool("fd"), ensureTool("rg")]).then(([fdPath]) => {
547
- if (fdPath && fdPath !== this.fdPath) {
548
- this.fdPath = fdPath;
549
- this.setupAutocompleteProvider();
550
- }
560
+ void startInteractiveToolInstalls(this.fdPath, (fdPath) => {
561
+ this.fdPath = fdPath;
562
+ this.setupAutocompleteProvider();
551
563
  });
552
564
  time("ensureTools");
553
565
  if (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {
@@ -563,6 +575,10 @@ export class InteractiveMode {
563
575
  : "";
564
576
  console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
565
577
  }
578
+ if (this.options.startupView) {
579
+ this.ui.pinFrom(null);
580
+ this.ui.clear();
581
+ }
566
582
  // Add header container as first child. Populate it after applying theme settings.
567
583
  // Keep loaded resources before chat so restored session messages never precede them.
568
584
  this.ui.addChild(this.headerContainer);
@@ -574,14 +590,16 @@ export class InteractiveMode {
574
590
  this.ui.addChild(this.widgetContainerAbove);
575
591
  this.ui.addChild(this.editorContainer);
576
592
  this.ui.addChild(this.widgetContainerBelow);
577
- this.ui.addChild(this.footer);
593
+ this.ui.addChild(this.customFooter ?? this.footer);
578
594
  this.ui.setFocus(this.editor);
579
- this.setupKeyHandlers();
580
- this.setupEditorSubmitHandler();
581
595
  this.ui.pinFrom(this.widgetContainerAbove);
596
+ this.setupKeyHandlers();
597
+ if (!this.options.startupView)
598
+ this.setupEditorSubmitHandler();
582
599
  this.ui.setAlternateScreen(true);
583
600
  // Start the UI before initializing extensions so session_start handlers can use interactive dialogs
584
- this.ui.start();
601
+ if (!this.options.startupView)
602
+ this.ui.start();
585
603
  this.isInitialized = true;
586
604
  time("ui.start");
587
605
  void this.maybeNotifyCliUpdate();
@@ -618,6 +636,7 @@ export class InteractiveMode {
618
636
  setRollbackWarningHandler((msg) => this.showStatus(msg));
619
637
  this.syncPermissionModeEffects(this.settingsManager.getDefaultPermissionMode());
620
638
  await this.themeController.applyFromSettings();
639
+ this.headerContainer.clear();
621
640
  // Add boot screen header (unless silenced)
622
641
  if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
623
642
  const header = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
@@ -637,9 +656,32 @@ export class InteractiveMode {
637
656
  await this.rebindCurrentSession();
638
657
  time("rebindCurrentSession");
639
658
  this.renderInitialMessages();
659
+ this.ui.requestRender();
660
+ for (const diagnostic of this.options.startupDiagnostics ?? [])
661
+ this.showStatus(diagnostic.message);
662
+ for (const warning of this.options.deprecationWarnings ?? [])
663
+ this.showWarning(warning);
640
664
  this.deferredBuiltinAttachPromise = this.attachDeferredBuiltinExtensions();
641
- void this.deferredBuiltinAttachPromise.then(() => {
665
+ this.startupReadyPromise = this.deferredBuiltinAttachPromise.then(() => {
666
+ if (!this.isInitialized)
667
+ return;
668
+ if (this.startupFeatureError)
669
+ throw this.startupFeatureError;
670
+ if (this.options.startupView) {
671
+ this.setupEditorSubmitHandler();
672
+ // Extensions can replace the editor. Wire its submit callback after readiness as well.
673
+ this.editor.onSubmit = this.defaultEditor.onSubmit;
674
+ this.options.startupView.activate();
675
+ }
642
676
  time("attachDeferredBuiltins");
677
+ markStartupMilestone("prompt_barrier_open");
678
+ void this.options.deferredMaintenance?.();
679
+ });
680
+ void this.startupReadyPromise.catch((error) => {
681
+ if (this.options.startupView)
682
+ this.options.startupView.showError(error);
683
+ else
684
+ this.showError(String(error));
643
685
  });
644
686
  // Set up theme file watcher
645
687
  onThemeChange(() => {
@@ -655,6 +697,21 @@ export class InteractiveMode {
655
697
  void this.updateAvailableProviderCount().then(() => {
656
698
  time("updateAvailableProviderCount");
657
699
  });
700
+ void this.waitForDeferredBuiltins().then(() => {
701
+ if (!this.isInitialized)
702
+ return;
703
+ this.stopCatalogRefresh = startCatalogRefreshPolling({
704
+ refresh: (signal) => this.session.modelRuntime.refreshIfStale({ signal }),
705
+ isBusy: () => this.session.isStreaming,
706
+ onUpdate: () => {
707
+ this.session.refreshModelFromRegistry();
708
+ this.footer.invalidate();
709
+ },
710
+ });
711
+ });
712
+ }
713
+ async waitForStartupReady() {
714
+ await this.startupReadyPromise;
658
715
  }
659
716
  /** Wait until deferred builtins have attached (or were skipped). */
660
717
  async waitForDeferredBuiltins() {
@@ -715,6 +772,7 @@ export class InteractiveMode {
715
772
  if (!this.isInitialized) {
716
773
  await this.init();
717
774
  }
775
+ await this.waitForStartupReady();
718
776
  // Check tmux keyboard setup asynchronously
719
777
  this.checkTmuxKeyboardSetup().then((warning) => {
720
778
  if (warning) {
@@ -1282,6 +1340,8 @@ export class InteractiveMode {
1282
1340
  catch (error) {
1283
1341
  this.deferredBuiltinsAttached = false;
1284
1342
  const message = error instanceof Error ? error.message : String(error);
1343
+ if (this.options.startupView)
1344
+ this.startupFeatureError = new Error(message);
1285
1345
  this.showError(`Failed to load deferred extensions: ${message}`);
1286
1346
  }
1287
1347
  finally {
@@ -1942,6 +2002,7 @@ export class InteractiveMode {
1942
2002
  this.editorComponentFactory = factory;
1943
2003
  // Save text from current editor before switching
1944
2004
  const currentText = this.editor.getText();
2005
+ const startupImages = this.options.startupView ? this.editor.getPendingImages?.() : undefined;
1945
2006
  this.editorContainer.clear();
1946
2007
  if (factory) {
1947
2008
  // Create the custom editor with tui, theme, and keybindings
@@ -1950,7 +2011,16 @@ export class InteractiveMode {
1950
2011
  newEditor.onSubmit = this.defaultEditor.onSubmit;
1951
2012
  newEditor.onChange = this.defaultEditor.onChange;
1952
2013
  // Copy text from previous editor
1953
- newEditor.setText(currentText);
2014
+ if (newEditor !== this.editor) {
2015
+ newEditor.setText(currentText);
2016
+ if (startupImages?.length) {
2017
+ if (newEditor.restoreImageMarkers)
2018
+ newEditor.restoreImageMarkers(startupImages);
2019
+ else
2020
+ for (const image of startupImages)
2021
+ this.fallbackImageAttachments.set(image.id, image);
2022
+ }
2023
+ }
1954
2024
  // Copy appearance settings if supported
1955
2025
  if (newEditor.borderColor !== undefined) {
1956
2026
  newEditor.borderColor = this.defaultEditor.borderColor;
@@ -1965,16 +2035,15 @@ export class InteractiveMode {
1965
2035
  // If extending CustomEditor, copy app-level handlers
1966
2036
  // Use duck typing since instanceof fails across jiti module boundaries
1967
2037
  const customEditor = newEditor;
1968
- if ("actionHandlers" in customEditor && customEditor.actionHandlers instanceof Map) {
2038
+ if (newEditor !== this.defaultEditor &&
2039
+ "actionHandlers" in customEditor &&
2040
+ customEditor.actionHandlers instanceof Map) {
1969
2041
  if (!customEditor.onEscape) {
1970
2042
  customEditor.onEscape = () => this.defaultEditor.onEscape?.();
1971
2043
  }
1972
2044
  if (!customEditor.onCtrlD) {
1973
2045
  customEditor.onCtrlD = () => this.defaultEditor.onCtrlD?.();
1974
2046
  }
1975
- if (!customEditor.onPasteImage) {
1976
- customEditor.onPasteImage = () => this.defaultEditor.onPasteImage?.();
1977
- }
1978
2047
  if (!customEditor.onExtensionShortcut) {
1979
2048
  customEditor.onExtensionShortcut = (data) => this.defaultEditor.onExtensionShortcut?.(data);
1980
2049
  }
@@ -1987,7 +2056,8 @@ export class InteractiveMode {
1987
2056
  }
1988
2057
  else {
1989
2058
  // Restore default editor with text from custom editor
1990
- this.defaultEditor.setText(currentText);
2059
+ if (this.editor !== this.defaultEditor)
2060
+ this.defaultEditor.setText(currentText);
1991
2061
  this.editor = this.defaultEditor;
1992
2062
  }
1993
2063
  this.editorContainer.addChild(this.editor);
@@ -2149,6 +2219,7 @@ export class InteractiveMode {
2149
2219
  // lunr: tool/thinking expand is per-item click; ctrl+o is not bound (tree filter still uses ctrl+o).
2150
2220
  this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
2151
2221
  this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
2222
+ this.defaultEditor.onAction("app.clipboard.pasteImage", () => void this.handleClipboardPaste());
2152
2223
  this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand());
2153
2224
  this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
2154
2225
  this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
@@ -2163,12 +2234,6 @@ export class InteractiveMode {
2163
2234
  this.updateEditorBorderColor();
2164
2235
  }
2165
2236
  };
2166
- // Handle clipboard paste (keybinding: app.clipboard.pasteImage, Alt+V on Windows —
2167
- // Ctrl+V is owned by the terminal and never reaches us). Images become `[image_n]`
2168
- // chips; otherwise, paste plain text from the system clipboard.
2169
- this.defaultEditor.onPasteImage = () => {
2170
- void this.handleClipboardPaste();
2171
- };
2172
2237
  this.ui.addInputListener((data) => {
2173
2238
  if (!this.ui.isPinned())
2174
2239
  return;
@@ -2290,7 +2355,7 @@ export class InteractiveMode {
2290
2355
  restoreEditorFromTreeResult(result) {
2291
2356
  this.restoreEditorDraft(result.editorText ?? "", result.editorImages);
2292
2357
  }
2293
- async handleClipboardPaste() {
2358
+ async handleClipboardPaste(options = {}) {
2294
2359
  try {
2295
2360
  const image = await readClipboardImage();
2296
2361
  if (image) {
@@ -2300,13 +2365,15 @@ export class InteractiveMode {
2300
2365
  this.showStatus(`Pasted ${formatImageMarker(id)}`);
2301
2366
  return;
2302
2367
  }
2303
- const text = await readClipboardText();
2304
- if (text) {
2305
- this.editor.insertTextAtCursor?.(text);
2306
- this.ui.requestRender();
2307
- return;
2368
+ if (options.textFallback !== false) {
2369
+ const text = await readClipboardText();
2370
+ if (text) {
2371
+ this.editor.insertTextAtCursor?.(text);
2372
+ this.ui.requestRender();
2373
+ return;
2374
+ }
2308
2375
  }
2309
- this.showStatus("Clipboard contains no image or text");
2376
+ this.showStatus(options.textFallback === false ? "Clipboard contains no image" : "Clipboard contains no image or text");
2310
2377
  }
2311
2378
  catch (err) {
2312
2379
  this.showStatus(`Clipboard paste failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -2322,6 +2389,11 @@ export class InteractiveMode {
2322
2389
  const images = this.consumeStagedSubmitImages() ?? (await this.loadImageAttachments(this.takeSubmittedImages(text)));
2323
2390
  this.ui.setChatScroll(0);
2324
2391
  // Handle commands
2392
+ if (text === "/paste-image") {
2393
+ this.editor.setText("");
2394
+ await this.handleClipboardPaste({ textFallback: false });
2395
+ return;
2396
+ }
2325
2397
  if (text === "/settings") {
2326
2398
  void this.showSettingsSelector();
2327
2399
  this.editor.setText("");
@@ -2741,7 +2813,7 @@ export class InteractiveMode {
2741
2813
  this.ui.requestRender();
2742
2814
  }
2743
2815
  else if (event.message.role === "assistant") {
2744
- this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.settingsManager.getGutterRail(), this.thinkingCollapse);
2816
+ this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.thinkingCollapse);
2745
2817
  // lunr: collapsible reasoning — fresh timing array for this message.
2746
2818
  this.thinkingRunTimings = [];
2747
2819
  this.streamingComponent.setThinkingTimings(this.thinkingRunTimings);
@@ -2878,6 +2950,7 @@ export class InteractiveMode {
2878
2950
  break;
2879
2951
  }
2880
2952
  case "agent_end":
2953
+ this.session.refreshModelFromRegistry();
2881
2954
  if (this.settingsManager.getShowTerminalProgress()) {
2882
2955
  this.ui.terminal.setProgress(false);
2883
2956
  }
@@ -3089,12 +3162,12 @@ export class InteractiveMode {
3089
3162
  // Render user message separately if present
3090
3163
  if (skillBlock.userMessage) {
3091
3164
  this.chatContainer.addChild(new Spacer(1));
3092
- const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad, this.settingsManager.getGutterRail());
3165
+ const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad);
3093
3166
  this.chatContainer.addChild(userComponent);
3094
3167
  }
3095
3168
  }
3096
3169
  else {
3097
- const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad, this.settingsManager.getGutterRail());
3170
+ const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad);
3098
3171
  this.chatContainer.addChild(userComponent);
3099
3172
  }
3100
3173
  if (options?.populateHistory) {
@@ -3104,7 +3177,7 @@ export class InteractiveMode {
3104
3177
  break;
3105
3178
  }
3106
3179
  case "assistant": {
3107
- const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.settingsManager.getGutterRail(), this.thinkingCollapse);
3180
+ const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.thinkingCollapse);
3108
3181
  // lunr: collapsible reasoning — re-attach live timings when this
3109
3182
  // message was streamed in this session (undefined = history).
3110
3183
  assistantComponent.setThinkingTimings(this.thinkingTimingsByMessage.get(message));
@@ -3929,8 +4002,6 @@ export class InteractiveMode {
3929
4002
  memoryCharCap: this.settingsManager.getMemoryCharCap(),
3930
4003
  searchCurator: getSearchCuratorSetting(),
3931
4004
  // lunr: TUI customize settings
3932
- gutterRail: this.settingsManager.getGutterRail(),
3933
- promptSymbol: this.settingsManager.getPromptSymbol(),
3934
4005
  footerMcp: this.settingsManager.getFooterMcp(),
3935
4006
  footerLsp: this.settingsManager.getFooterLsp(),
3936
4007
  footerContext: this.settingsManager.getFooterContext(),
@@ -4132,12 +4203,6 @@ export class InteractiveMode {
4132
4203
  }
4133
4204
  },
4134
4205
  // lunr: TUI customize callbacks
4135
- onGutterRailChange: (enabled) => {
4136
- this.settingsManager.setGutterRail(enabled);
4137
- },
4138
- onPromptSymbolChange: (enabled) => {
4139
- this.settingsManager.setPromptSymbol(enabled);
4140
- },
4141
4206
  onFooterMcpChange: (enabled) => {
4142
4207
  this.settingsManager.setFooterMcp(enabled);
4143
4208
  },
@@ -4257,40 +4322,9 @@ export class InteractiveMode {
4257
4322
  this.footer.invalidate();
4258
4323
  return;
4259
4324
  }
4260
- const incomplete = result.incomplete;
4261
- const toPrompt = incomplete.slice(0, INCOMPLETE_PROMPT_CAP);
4262
- const extraDefaults = Math.max(0, incomplete.length - toPrompt.length);
4263
- const persisted = [];
4325
+ // Discovered metadata stays in its cache, not in user overrides. Enrichment is automatic.
4326
+ const extraDefaults = result.incomplete.length;
4264
4327
  const asked = [];
4265
- let skipRemaining = false;
4266
- for (const item of toPrompt) {
4267
- if (skipRemaining) {
4268
- persisted.push(modelToUserEntry(item.draft));
4269
- continue;
4270
- }
4271
- const choice = await this.showExtensionSelector(describeIncomplete(item), [
4272
- "Enter details",
4273
- "Use defaults",
4274
- "Skip remaining",
4275
- ]);
4276
- asked.push(`${item.provider}/${item.id}`);
4277
- if (choice === "Skip remaining") {
4278
- skipRemaining = true;
4279
- persisted.push(modelToUserEntry(item.draft));
4280
- continue;
4281
- }
4282
- if (choice === "Enter details") {
4283
- const detailed = await this.promptIncompleteModelDetails(item);
4284
- persisted.push(modelToUserEntry(detailed ?? item.draft));
4285
- continue;
4286
- }
4287
- persisted.push(modelToUserEntry(item.draft));
4288
- }
4289
- for (const item of incomplete.slice(toPrompt.length)) {
4290
- persisted.push(modelToUserEntry(item.draft));
4291
- }
4292
- if (persisted.length > 0)
4293
- runtime.persistUserModels(persisted);
4294
4328
  let ollama;
4295
4329
  const runner = this.session.extensionRunner;
4296
4330
  const cloudRefresh = globalThis[Symbol.for("@lunr/ollama-cloud-refresh")];
@@ -4311,53 +4345,26 @@ export class InteractiveMode {
4311
4345
  evictedUserRows: result.official.evictedUserRows,
4312
4346
  }
4313
4347
  : undefined,
4314
- providers: result.live.map((entry) => ({
4315
- id: entry.providerId,
4316
- status: entry.status,
4317
- added: entry.added,
4318
- total: entry.total,
4319
- error: entry.error,
4320
- })),
4348
+ providers: [
4349
+ ...result.live.map((entry) => ({
4350
+ id: entry.providerId,
4351
+ status: entry.status,
4352
+ added: entry.added,
4353
+ total: entry.total,
4354
+ error: entry.error,
4355
+ })),
4356
+ ...Object.entries(result.official?.errors ?? {}).map(([id, error]) => ({
4357
+ id,
4358
+ status: "error",
4359
+ error,
4360
+ })),
4361
+ ],
4321
4362
  asked,
4322
4363
  extraDefaults,
4323
4364
  ollama,
4324
4365
  }));
4325
4366
  this.footer.invalidate();
4326
4367
  }
4327
- async promptIncompleteModelDetails(item) {
4328
- const contextInput = await this.showExtensionInput(`${item.provider}/${item.id} — context window:`, String(item.draft.contextWindow || 128000));
4329
- if (contextInput === undefined)
4330
- return undefined;
4331
- const maxTokensInput = await this.showExtensionInput(`${item.provider}/${item.id} — max output tokens:`, String(item.draft.maxTokens || 8192));
4332
- if (maxTokensInput === undefined)
4333
- return undefined;
4334
- const inputChoice = await this.showExtensionSelector(`${item.provider}/${item.id} — input:`, [
4335
- "text",
4336
- "text + image",
4337
- ]);
4338
- if (inputChoice === undefined)
4339
- return undefined;
4340
- const reasoningChoice = await this.showExtensionSelector(`${item.provider}/${item.id} — reasoning:`, [
4341
- "yes",
4342
- "no",
4343
- ]);
4344
- if (reasoningChoice === undefined)
4345
- return undefined;
4346
- let api = item.draft.api;
4347
- if (item.availableApis.length > 1) {
4348
- const apiChoice = await this.showExtensionSelector(`${item.provider}/${item.id} — API protocol:`, item.availableApis.map(String));
4349
- if (apiChoice === undefined)
4350
- return undefined;
4351
- api = apiChoice;
4352
- }
4353
- return applyIncompleteAnswers(item.draft, {
4354
- contextWindow: parsePositiveInt(contextInput, item.draft.contextWindow),
4355
- maxTokens: parsePositiveInt(maxTokensInput, item.draft.maxTokens),
4356
- input: inputChoice === "text + image" ? ["text", "image"] : ["text"],
4357
- reasoning: reasoningChoice === "yes",
4358
- api,
4359
- });
4360
- }
4361
4368
  async findExactModelMatch(searchTerm) {
4362
4369
  const models = await this.getModelCandidates();
4363
4370
  return findExactModelReferenceMatch(searchTerm, models);
@@ -6400,7 +6407,7 @@ export class InteractiveMode {
6400
6407
  // the result text the model sees. Esc/close counts as Decline.
6401
6408
  showPlanInChat(summary) {
6402
6409
  this.chatContainer.addChild(new Spacer(1));
6403
- this.chatContainer.addChild(new PlanMessageComponent(summary, this.getMarkdownThemeWithSettings(), this.outputPad, this.settingsManager.getGutterRail()));
6410
+ this.chatContainer.addChild(new PlanMessageComponent(summary, this.getMarkdownThemeWithSettings(), this.outputPad));
6404
6411
  this.ui.requestRender();
6405
6412
  }
6406
6413
  async showPlanApprovalDialog(summary) {
@@ -6614,6 +6621,7 @@ ${cycleThinkingLevel ? `| \`${cycleThinkingLevel}\` | Cycle thinking level |\n`
6614
6621
  | \`${followUp}\` | Queue follow-up message |
6615
6622
  | \`${dequeue}\` | Restore queued messages |
6616
6623
  | \`${pasteImage}\` | Paste image or text from clipboard |
6624
+ | \`/paste-image\` | Paste image without a terminal shortcut |
6617
6625
  | \`/\` | Slash commands |
6618
6626
  | \`!\` | Run bash command |
6619
6627
  | \`!!\` | Run bash command (excluded from context) |
@@ -6782,6 +6790,8 @@ ${cycleThinkingLevel ? `| \`${cycleThinkingLevel}\` | Cycle thinking level |\n`
6782
6790
  }
6783
6791
  }
6784
6792
  stop() {
6793
+ this.stopCatalogRefresh?.();
6794
+ this.stopCatalogRefresh = undefined;
6785
6795
  if (this.settingsManager.getShowTerminalProgress()) {
6786
6796
  this.ui.terminal.setProgress(false);
6787
6797
  }