@pellux/goodvibes-tui 0.19.24 → 0.19.26

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 (76) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +5 -5
  3. package/bin/goodvibes +10 -0
  4. package/bin/goodvibes-daemon +10 -0
  5. package/docs/foundation-artifacts/operator-contract.json +1 -1
  6. package/package.json +3 -2
  7. package/src/cli/bundle-command.ts +225 -0
  8. package/src/cli/completion.ts +90 -0
  9. package/src/cli/config-overrides.ts +159 -0
  10. package/src/cli/endpoints.ts +63 -0
  11. package/src/cli/entrypoint.ts +169 -0
  12. package/src/cli/help.ts +301 -0
  13. package/src/cli/index.ts +11 -0
  14. package/src/cli/management-commands.ts +426 -0
  15. package/src/cli/management.ts +719 -0
  16. package/src/cli/network-posture.ts +46 -0
  17. package/src/cli/package-verification.ts +119 -0
  18. package/src/cli/parser.ts +369 -0
  19. package/src/cli/provider-classification.ts +107 -0
  20. package/src/cli/redaction.ts +105 -0
  21. package/src/cli/service-command.ts +45 -0
  22. package/src/cli/service-posture.ts +247 -0
  23. package/src/cli/status.ts +382 -0
  24. package/src/cli/surface-command.ts +248 -0
  25. package/src/cli/tui-startup.ts +32 -0
  26. package/src/cli/types.ts +69 -0
  27. package/src/cli-flags.ts +18 -55
  28. package/src/config/index.ts +1 -1
  29. package/src/config/secrets.ts +44 -0
  30. package/src/daemon/cli.ts +62 -11
  31. package/src/input/command-registry.ts +3 -0
  32. package/src/input/commands/guidance-runtime.ts +9 -4
  33. package/src/input/commands/local-runtime.ts +21 -7
  34. package/src/input/commands/local-setup.ts +31 -38
  35. package/src/input/commands/onboarding-runtime.ts +14 -0
  36. package/src/input/commands/runtime-services.ts +9 -0
  37. package/src/input/commands.ts +2 -0
  38. package/src/input/feed-context-factory.ts +8 -1
  39. package/src/input/handler-feed.ts +13 -8
  40. package/src/input/handler-interactions.ts +266 -0
  41. package/src/input/handler-modal-stack.ts +23 -3
  42. package/src/input/handler-modal-token-routes.ts +23 -1
  43. package/src/input/handler-onboarding.ts +696 -0
  44. package/src/input/handler-picker-routes.ts +15 -7
  45. package/src/input/handler-ui-state.ts +58 -0
  46. package/src/input/handler.ts +120 -246
  47. package/src/input/onboarding/handler-onboarding-routes.ts +105 -0
  48. package/src/input/onboarding/onboarding-wizard-apply.ts +211 -0
  49. package/src/input/onboarding/onboarding-wizard-constants.ts +148 -0
  50. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +712 -0
  51. package/src/input/onboarding/onboarding-wizard-helpers.ts +218 -0
  52. package/src/input/onboarding/onboarding-wizard-rules.ts +224 -0
  53. package/src/input/onboarding/onboarding-wizard-state.ts +354 -0
  54. package/src/input/onboarding/onboarding-wizard-steps.ts +642 -0
  55. package/src/input/onboarding/onboarding-wizard-types.ts +170 -0
  56. package/src/input/onboarding/onboarding-wizard.ts +594 -0
  57. package/src/main.ts +32 -39
  58. package/src/panels/builtin/operations.ts +0 -10
  59. package/src/panels/index.ts +0 -1
  60. package/src/renderer/conversation-overlays.ts +6 -0
  61. package/src/renderer/help-overlay.ts +1 -1
  62. package/src/renderer/onboarding/onboarding-wizard.ts +533 -0
  63. package/src/runtime/bootstrap-core.ts +1 -0
  64. package/src/runtime/bootstrap.ts +123 -0
  65. package/src/runtime/onboarding/apply.ts +685 -0
  66. package/src/runtime/onboarding/derivation.ts +495 -0
  67. package/src/runtime/onboarding/index.ts +7 -0
  68. package/src/runtime/onboarding/markers.ts +161 -0
  69. package/src/runtime/onboarding/snapshot.ts +400 -0
  70. package/src/runtime/onboarding/state.ts +140 -0
  71. package/src/runtime/onboarding/types.ts +402 -0
  72. package/src/runtime/onboarding/verify.ts +233 -0
  73. package/src/runtime/ui-services.ts +16 -0
  74. package/src/shell/ui-openers.ts +12 -2
  75. package/src/version.ts +1 -1
  76. package/src/panels/welcome-panel.ts +0 -64
@@ -1,4 +1,12 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
1
3
  import { InputTokenizer } from '@pellux/goodvibes-sdk/platform/core/tokenizer';
4
+ import { createOAuthLocalListener } from '@pellux/goodvibes-sdk/platform/config/oauth-local-listener';
5
+ import { clearModalStackForHandler, cleanupMarkerRegistryForHandler, executeBlockActionForHandler, expandPromptForHandler, findMarkerAtPosForHandler, getImageAttachmentsForHandler, handleBlockCopyForHandler, handleBlockRerunForHandler, handleBlockSaveForHandler, handleBlockToggleForHandler, handleBookmarkForHandler, handleCopyForHandler, handleCtrlCForHandler, handleDiffApplyForHandler, handleEscapeForHandler, hydrateOnboardingWizardFromRuntimeForHandler, modalOpenedForHandler, openOnboardingWizardForHandler, registerPasteForHandler } from './handler-interactions.ts';
6
+ import { clearOnboardingModelPickerCancelStateForHandler, clearOnboardingPendingModelPickerTargetForHandler, completeOpenAiSubscriptionFromListenerForHandler, getOnboardingConfigValueForHandler, getOnboardingRuntimePostureForHandler, handleModelPickerCommitForHandler, handleOnboardingActionForHandler, handleOpenAiSubscriptionFinishForHandler, handleOpenAiSubscriptionStartForHandler, openModelPickerWithTargetForHandler, refreshOnboardingHydrationForHandler, restartOnboardingExternalServicesIfNeededForHandler, restoreOnboardingModelPickerCancelStateForHandler, syncRuntimeFromOnboardingRequestForHandler, verifyOnboardingRuntimePostureForHandler, type OnboardingRuntimePosture } from './handler-onboarding.ts';
7
+ import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibes-sdk/platform/config/openai-codex-auth';
8
+ import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils/open-external';
9
+ import { buildProviderAccountSnapshot } from '@pellux/goodvibes-sdk/platform/runtime/provider-accounts/registry';
2
10
  import { SelectionManager } from './selection.ts';
3
11
  import type { InfiniteBuffer } from '../core/history.ts';
4
12
  import type { CommandRegistry, CommandContext } from './command-registry.ts';
@@ -19,6 +27,19 @@ import { BookmarkModal } from './bookmark-modal.ts';
19
27
  import { SettingsModal } from './settings-modal.ts';
20
28
  import { SessionPickerModal } from './session-picker-modal.ts';
21
29
  import { ProfilePickerModal } from './profile-picker-modal.ts';
30
+ import { OnboardingWizardController, type OnboardingWizardAction, type OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
31
+ import {
32
+ applyOnboardingRequest,
33
+ collectOnboardingSnapshot,
34
+ getOnboardingCompletionMarkerPath,
35
+ verifyOnboardingRequest,
36
+ } from '../runtime/onboarding/index.ts';
37
+ import type {
38
+ OnboardingApplyOperation,
39
+ OnboardingApplyRequest,
40
+ OnboardingShellPaths,
41
+ OnboardingVerificationItem,
42
+ } from '../runtime/onboarding/index.ts';
22
43
  import {
23
44
  IMAGE_EXTENSIONS,
24
45
  cleanupMarkerRegistry,
@@ -59,9 +80,14 @@ import {
59
80
  import { clearModalStack, handleEscape, modalOpened } from './handler-modal-stack.ts';
60
81
  import { handleModalTokenRoutes } from './handler-modal-token-routes.ts';
61
82
  import {
83
+ captureOnboardingWizardSnapshot,
62
84
  handleHistorySearchToken,
63
85
  handleOverlayToken,
86
+ openOnboardingWizardState,
87
+ restoreOnboardingWizardSnapshot,
64
88
  handleSearchModeToken,
89
+ type OnboardingWizardSnapshot,
90
+ type OpenOnboardingWizardOptions,
65
91
  } from './handler-ui-state.ts';
66
92
  import {
67
93
  handleBookmarkModalToken,
@@ -86,9 +112,11 @@ import { handlePanelIntegrationAction as runPanelIntegrationAction } from './pan
86
112
  import type { Panel } from '../panels/types.ts';
87
113
  import type { UiRuntimeServices } from '../runtime/ui-services.ts';
88
114
  export { handlePanelIntegrationAction } from './panel-integration-actions.ts';
115
+ import type { ModelPickerTarget } from './model-picker.ts';
89
116
 
90
117
  type SelectionModalCallback = (result: SelectionResult | null) => void;
91
118
 
119
+
92
120
  /**
93
121
  * InputHandler - Owns prompt text, paste registry, and keyboard/mouse handling.
94
122
  * Extracted from main.ts and StateManager.
@@ -109,14 +137,14 @@ export class InputHandler {
109
137
  /** True when keyboard focus is on the active panel (arrow/enter go to panel, not prompt). */
110
138
  public panelFocused = false;
111
139
 
112
- private tokenizer = new InputTokenizer();
113
- private pasteRegistry = new Map<string, string>();
114
- private nextPasteId = 1;
115
- private lastCtrlCTime = 0;
140
+ public tokenizer = new InputTokenizer();
141
+ public pasteRegistry = new Map<string, string>();
142
+ public nextPasteId = 1;
143
+ public lastCtrlCTime = 0;
116
144
  /** Long-lived feed context — reused across every feed() call to avoid per-keystroke allocation. */
117
- private feedContext!: import('./handler-feed.ts').InputFeedContext;
118
- private commandRegistry: CommandRegistry | null = null;
119
- private commandContext: CommandContext | undefined = undefined;
145
+ public feedContext!: import('./handler-feed.ts').InputFeedContext;
146
+ public commandRegistry: CommandRegistry | null = null;
147
+ public commandContext: CommandContext | undefined = undefined;
120
148
  public autocomplete: AutocompleteEngine | null = null;
121
149
  public modelPicker: ModelPickerModal;
122
150
  public selectionModal = new SelectionModal();
@@ -128,6 +156,11 @@ export class InputHandler {
128
156
  public bookmarkModal: BookmarkModal;
129
157
  public blockActionsMenu = new BlockActionsMenu();
130
158
  public settingsModal = new SettingsModal();
159
+ public onboardingWizard = new OnboardingWizardController();
160
+ public onboardingModelPickerCancelSnapshot: OnboardingWizardSnapshot | null = null;
161
+ public onboardingHydrationSerial = 0;
162
+ public onboardingApplyPending = false;
163
+ public onboardingOpenAiListenerSerial = 0;
131
164
 
132
165
  /**
133
166
  * Modal navigation stack. Each element is the name of an open modal.
@@ -142,47 +175,48 @@ export class InputHandler {
142
175
  public helpScrollOffset = 0;
143
176
  public shortcutsOverlayActive = false;
144
177
  public shortcutsScrollOffset = 0;
145
- private inputHistory: InputHistory | null = null;
178
+ public inputHistory: InputHistory | null = null;
146
179
  public filePicker: FilePickerModal;
147
180
  public historySearch: HistorySearch = new HistorySearch(() => this.inputHistory?.getEntries() ?? []);
148
- private conversationManager: ConversationManager | null = null;
149
- private selectionCallback: SelectionModalCallback | null = null;
150
- private syncFeedSelectionCallback: ((callback: SelectionModalCallback | null) => void) | null = null;
181
+ public conversationManager: ConversationManager | null = null;
182
+ public selectionCallback: SelectionModalCallback | null = null;
183
+ public syncFeedSelectionCallback: ((callback: SelectionModalCallback | null) => void) | null = null;
151
184
  /** Time of last [COPIED] block feedback, for brief display. */
152
185
  public lastBlockCopyTime = 0;
153
- private mouseDownRow = -1;
154
- private mouseDownCol = -1;
186
+ public mouseDownRow = -1;
187
+ public mouseDownCol = -1;
155
188
 
156
189
  /** Pasted images: maps marker IDs to base64 image data. */
157
- private imageRegistry = new Map<string, { data: string; mediaType: string }>();
158
- private nextImageId = 1;
190
+ public imageRegistry = new Map<string, { data: string; mediaType: string }>();
191
+ public nextImageId = 1;
159
192
 
160
193
  // ── Undo / Redo ────────────────────────────────────────────────────────────
161
- private undoStack: Array<{ prompt: string; cursorPos: number }> = [];
162
- private redoStack: Array<{ prompt: string; cursorPos: number }> = [];
163
- private static readonly MAX_UNDO = 50;
194
+ public undoStack: Array<{ prompt: string; cursorPos: number }> = [];
195
+ public redoStack: Array<{ prompt: string; cursorPos: number }> = [];
196
+ public static readonly MAX_UNDO = 50;
164
197
 
165
198
  // ── Path completion (Tab on path-like token) ───────────────────────────────
166
199
  /** Current list of path completions cycling on repeated Tab presses. */
167
- private pathCompletions: string[] = [];
200
+ public pathCompletions: string[] = [];
168
201
  /** Index into pathCompletions for Tab cycling. */
169
- private pathCompletionIndex = -1;
202
+ public pathCompletionIndex = -1;
170
203
  /** The raw prefix that triggered path completion (e.g. 'src/in'). */
171
- private pathCompletionPrefix = '';
204
+ public pathCompletionPrefix = '';
172
205
  /** Start offset in prompt where the path token begins. */
173
- private pathCompletionStart = 0;
206
+ public pathCompletionStart = 0;
174
207
 
175
208
  constructor(
176
- private requestRender: () => void,
177
- private selection: SelectionManager,
178
- private getScrollTop: () => number,
179
- private getViewportHeight: () => number,
180
- private getHistory: () => InfiniteBuffer,
181
- private scroll: (delta: number) => void,
182
- private exitApp: () => void,
183
- private readonly uiServices: Pick<UiRuntimeServices,
209
+ public requestRender: () => void,
210
+ public selection: SelectionManager,
211
+ public getScrollTop: () => number,
212
+ public getViewportHeight: () => number,
213
+ public getHistory: () => InfiniteBuffer,
214
+ public scroll: (delta: number) => void,
215
+ public exitApp: () => void,
216
+ public readonly uiServices: Pick<UiRuntimeServices,
184
217
  'agents'
185
218
  | 'environment'
219
+ | 'platform'
186
220
  | 'providers'
187
221
  | 'sessions'
188
222
  | 'shell'
@@ -220,7 +254,7 @@ export class InputHandler {
220
254
  * initFeedContext — Build the long-lived InputFeedContext once via factory.
221
255
  * See feed-context-factory.ts for full field documentation.
222
256
  */
223
- private initFeedContext(): void {
257
+ public initFeedContext(): void {
224
258
  this.feedContext = buildInitialFeedContext(
225
259
  {
226
260
  prompt: this.prompt, cursorPos: this.cursorPos, commandMode: this.commandMode,
@@ -246,6 +280,7 @@ export class InputHandler {
246
280
  autocomplete: this.autocomplete,
247
281
  filePicker: this.filePicker,
248
282
  modelPicker: this.modelPicker,
283
+ onboardingWizard: this.onboardingWizard,
249
284
  processModal: this.processModal,
250
285
  liveTailModal: this.liveTailModal,
251
286
  agentDetailModal: this.agentDetailModal,
@@ -288,12 +323,16 @@ export class InputHandler {
288
323
  findMarkerAtPos: (pos: number) => this.findMarkerAtPos(pos),
289
324
  cleanupMarkerRegistry: (text: string) => this.cleanupMarkerRegistry(text),
290
325
  expandPrompt: (text: string) => this.expandPrompt(text),
326
+ openModelPickerWithTarget: (target: ModelPickerTarget, source?: 'settings' | 'onboarding') =>
327
+ this.openModelPickerWithTarget(target, source),
328
+ onModelPickerCommit: () => this.handleModelPickerCommit(),
329
+ onOnboardingAction: (action: OnboardingWizardAction) => { void this.handleOnboardingAction(action); },
291
330
  },
292
331
  );
293
332
  }
294
333
 
295
334
  /** Sync mutable handler fields back into feedContext after in-feed mutations. */
296
- private syncFeedContextMutableFields(): void {
335
+ public syncFeedContextMutableFields(): void {
297
336
  const h = this;
298
337
  syncFeedContextMutableFields({ prompt: h.prompt, cursorPos: h.cursorPos, commandMode: h.commandMode,
299
338
  panelFocused: h.panelFocused, indicatorFocused: h.indicatorFocused, helpOverlayActive: h.helpOverlayActive,
@@ -337,211 +376,46 @@ export class InputHandler {
337
376
  this.requestRender();
338
377
  }
339
378
 
340
- public registerPaste(content: string): string {
341
- const result = registerPaste({
342
- pasteRegistry: this.pasteRegistry,
343
- nextPasteId: this.nextPasteId,
344
- imageRegistry: this.imageRegistry,
345
- nextImageId: this.nextImageId,
346
- }, content, this.uiServices.environment.shellPaths.workingDirectory);
347
- this.nextPasteId = result.nextPasteId;
348
- this.nextImageId = result.nextImageId;
349
- return result.marker;
350
- }
351
-
352
- /**
353
- * expandPrompt - Replaces paste markers with actual content.
354
- * If image markers are present, returns ContentPart[] for multimodal delivery.
355
- * Otherwise returns a plain string.
356
- */
357
- private expandPrompt(text: string) {
358
- return expandPrompt(this.pasteRegistry, this.imageRegistry, text, this.uiServices.environment.shellPaths.workingDirectory);
359
- }
360
-
361
- /**
362
- * getImageAttachments - Returns a copy of the current image registry.
363
- * Callers can use this to attach images when building LLM messages.
364
- */
365
- public getImageAttachments(): Map<string, { data: string; mediaType: string }> {
366
- return new Map(this.imageRegistry);
367
- }
368
-
369
- /**
370
- * findMarkerAtPos - Returns the start/end of an atomic marker if pos is inside one.
371
- * Used to make backspace/delete/arrow treat markers as single units.
372
- */
373
- /**
374
- * cleanupMarkerRegistry - If the given marker text is an IMAGE marker,
375
- * parses its ID and removes it from imageRegistry.
376
- */
377
- private cleanupMarkerRegistry(markerText: string): void {
378
- cleanupMarkerRegistry(this.imageRegistry, markerText);
379
- }
380
-
381
- private findMarkerAtPos(pos: number): { start: number; end: number } | null {
382
- return findMarkerAtPos(this.prompt, pos);
383
- }
384
-
385
- private handleCopy(): void {
386
- handleCopy(this.selection, this.getHistory, this.requestRender, () => {
387
- this.lastCopyTime = Date.now();
388
- });
389
- }
390
-
391
- /**
392
- * handleBlockCopy - Ctrl+Y: Copy the content of the nearest code/tool block.
393
- */
394
- private handleBlockCopy(): void {
395
- handleBlockCopy(this.conversationManager, this.getScrollTop, this.requestRender, () => {
396
- this.lastBlockCopyTime = Date.now();
397
- });
398
- }
399
-
400
- /**
401
- * handleBookmark - Ctrl+B: Toggle bookmark on the nearest block.
402
- */
403
- private handleBookmark(): void {
404
- handleBookmark(this.conversationManager, this.getScrollTop, this.requestRender, this.uiServices.shell.bookmarkManager);
405
- }
406
-
407
- /**
408
- * handleBlockSave - Ctrl+S: Save nearest block content to a file.
409
- */
410
- private handleBlockSave(): void {
411
- handleBlockSave(this.conversationManager, this.getScrollTop, this.requestRender, this.uiServices.shell.bookmarkManager);
412
- }
413
-
414
- /**
415
- * executeBlockAction - Execute a block action ID on the nearest block.
416
- * Called when the user selects an action from the BlockActionsMenu.
417
- */
418
- private executeBlockAction(actionId: string): void {
419
- switch (actionId) {
420
- case 'copy': this.handleBlockCopy(); break;
421
- case 'bookmark': this.handleBookmark(); break;
422
- case 'toggle': this.handleBlockToggle(); break;
423
- case 'apply': this.handleDiffApply(); break;
424
- case 'rerun': this.handleBlockRerun(); break;
425
- }
426
- }
427
379
 
428
- /**
429
- * handleBlockRerun - Re-run the tool call for the nearest tool block.
430
- * Emits a tool-rerun event for the orchestrator to handle.
431
- */
432
- private handleBlockRerun(): void {
433
- handleBlockRerun(this.conversationManager, this.getScrollTop, this.requestRender);
434
- }
380
+ public openOnboardingWizard(
381
+ modeOrOptions: OnboardingWizardMode | OpenOnboardingWizardOptions = 'new',
382
+ ): void { openOnboardingWizardForHandler(this, modeOrOptions); }
383
+
384
+ public async hydrateOnboardingWizardFromRuntime(hydrationSerial: number): Promise<void> { await hydrateOnboardingWizardFromRuntimeForHandler(this, hydrationSerial); }
385
+ public registerPaste(content: string): string { return registerPasteForHandler(this, content); }
386
+ public expandPrompt(text: string) { return expandPromptForHandler(this, text); }
387
+ public getImageAttachments(): Map<string, { data: string; mediaType: string }> { return getImageAttachmentsForHandler(this); }
388
+ public cleanupMarkerRegistry(markerText: string): void { cleanupMarkerRegistryForHandler(this, markerText); }
389
+ public findMarkerAtPos(pos: number): { start: number; end: number } | null { return findMarkerAtPosForHandler(this, pos); }
390
+ public handleCopy(): void { handleCopyForHandler(this); }
391
+ public handleBlockCopy(): void { handleBlockCopyForHandler(this); }
392
+ public handleBookmark(): void { handleBookmarkForHandler(this); }
393
+ public handleBlockSave(): void { handleBlockSaveForHandler(this); }
394
+ public executeBlockAction(actionId: string): void { executeBlockActionForHandler(this, actionId); }
395
+ public handleBlockRerun(): void { handleBlockRerunForHandler(this); }
396
+ public handleBlockToggle(): void { handleBlockToggleForHandler(this); }
397
+ public handleDiffApply(): boolean { return handleDiffApplyForHandler(this); }
398
+ public handleCtrlC(): void { handleCtrlCForHandler(this); }
399
+ public modalOpened(name: string): void { modalOpenedForHandler(this, name); }
400
+ public clearModalStack(): void { clearModalStackForHandler(this); }
401
+ public handleEscape(): void { handleEscapeForHandler(this); }
402
+
403
+ public clearOnboardingPendingModelPickerTarget(): void { clearOnboardingPendingModelPickerTargetForHandler(this); }
404
+ public clearOnboardingModelPickerCancelState(): void { clearOnboardingModelPickerCancelStateForHandler(this); }
405
+ public restoreOnboardingModelPickerCancelState(): void { restoreOnboardingModelPickerCancelStateForHandler(this); }
406
+ public openModelPickerWithTarget(target: ModelPickerTarget, source: 'settings' | 'onboarding' = 'settings'): boolean { return openModelPickerWithTargetForHandler(this, target, source); }
407
+ public handleModelPickerCommit(): boolean { return handleModelPickerCommitForHandler(this); }
408
+ public async handleOnboardingAction(action: OnboardingWizardAction): Promise<void> { await handleOnboardingActionForHandler(this, action); }
409
+ public async refreshOnboardingHydration(options: { readonly preserveValues?: boolean; readonly targetStepId?: string } = {}): Promise<void> { await refreshOnboardingHydrationForHandler(this, options); }
410
+ public async handleOpenAiSubscriptionStart(): Promise<void> { await handleOpenAiSubscriptionStartForHandler(this); }
411
+ public async completeOpenAiSubscriptionFromListener(listener: Awaited<ReturnType<typeof createOAuthLocalListener>>, verifier: string, serial: number): Promise<void> { await completeOpenAiSubscriptionFromListenerForHandler(this, listener, verifier, serial); }
412
+ public async handleOpenAiSubscriptionFinish(): Promise<void> { await handleOpenAiSubscriptionFinishForHandler(this); }
413
+ public syncRuntimeFromOnboardingRequest(request: ReturnType<OnboardingWizardController['buildApplyRequest']>): void { syncRuntimeFromOnboardingRequestForHandler(this, request); }
414
+ public getOnboardingConfigValue(request: OnboardingApplyRequest, key: string): unknown { return getOnboardingConfigValueForHandler(this, request, key); }
415
+ public getOnboardingRuntimePosture(request: OnboardingApplyRequest): OnboardingRuntimePosture { return getOnboardingRuntimePostureForHandler(this, request); }
416
+ public async restartOnboardingExternalServicesIfNeeded(request: OnboardingApplyRequest): Promise<OnboardingVerificationItem[]> { return await restartOnboardingExternalServicesIfNeededForHandler(this, request); }
417
+ public verifyOnboardingRuntimePosture(request: OnboardingApplyRequest): OnboardingVerificationItem[] { return verifyOnboardingRuntimePostureForHandler(this, request); }
435
418
 
436
- /**
437
- * handleBlockToggle - Tab (non-command mode): Toggle collapse of nearest block.
438
- */
439
- private handleBlockToggle(): void {
440
- handleBlockToggle(this.conversationManager, this.getScrollTop, this.requestRender);
441
- }
442
-
443
- /**
444
- * handleDiffApply - Ctrl+A when a diff block is nearest: request approval and apply the diff.
445
- * Returns true if a diff was found and applied (so caller can skip default Ctrl+A).
446
- */
447
- private handleDiffApply(): boolean {
448
- return handleDiffApply(
449
- this.conversationManager,
450
- this.getScrollTop,
451
- this.commandContext,
452
- this.requestRender,
453
- () => `diff-apply-${Date.now()}`,
454
- 'write',
455
- );
456
- }
457
-
458
- /**
459
- * Handle Ctrl+C:
460
- * - If prompt has text: clear it
461
- * - If prompt is empty and LLM is thinking: cancel generation
462
- * - If prompt is empty and idle: show exit notice (double = exit)
463
- */
464
- private handleCtrlC(): void {
465
- handleCtrlC(
466
- this.prompt,
467
- () => this.saveUndoState(),
468
- (value) => { this.prompt = value; },
469
- (value) => { this.cursorPos = value; },
470
- this.commandContext?.cancelGeneration,
471
- this.exitApp,
472
- this.requestRender,
473
- this.lastCtrlCTime,
474
- (value) => { this.lastCtrlCTime = value; },
475
- (value) => { this.showExitNotice = value; },
476
- );
477
- }
478
-
479
- /**
480
- * Handle Escape:
481
- * - If prompt has text: clear it
482
- * - If prompt is empty: cancel generation (double-tap not needed)
483
- */
484
- /**
485
- * Record that a modal has been opened and push it onto the navigation stack.
486
- * Call this EVERY time a modal opens (except inside openModal()).
487
- *
488
- * @param name - The modal identifier (e.g. 'settings', 'help', 'process').
489
- */
490
- public modalOpened(name: string): void {
491
- modalOpened(this, name);
492
- }
493
-
494
- /**
495
- * Clear the modal navigation stack on non-modal user input (e.g. submit).
496
- */
497
- public clearModalStack(): void {
498
- clearModalStack(this.modalStack);
499
- }
500
-
501
- private handleEscape(): void {
502
- const result = handleEscape({
503
- helpOverlayActive: this.helpOverlayActive,
504
- shortcutsOverlayActive: this.shortcutsOverlayActive,
505
- bookmarkModal: this.bookmarkModal,
506
- agentDetailModal: this.agentDetailModal,
507
- liveTailModal: this.liveTailModal,
508
- settingsModal: this.settingsModal,
509
- sessionPickerModal: this.sessionPickerModal,
510
- profilePickerModal: this.profilePickerModal,
511
- contextInspectorModal: this.contextInspectorModal,
512
- processModal: this.processModal,
513
- modelPicker: this.modelPicker,
514
- filePicker: this.filePicker,
515
- blockActionsMenu: this.blockActionsMenu,
516
- selectionModal: this.selectionModal,
517
- commandMode: this.commandMode,
518
- modalStack: this.modalStack,
519
- modalReturnFocus: this.modalReturnFocus,
520
- panelFocused: this.panelFocused,
521
- indicatorFocused: this.indicatorFocused,
522
- prompt: this.prompt,
523
- cursorPos: this.cursorPos,
524
- requestRender: this.requestRender,
525
- saveUndoState: () => this.saveUndoState(),
526
- cancelGeneration: this.commandContext?.cancelGeneration,
527
- selectionCallback: this.selectionCallback,
528
- autocompleteReset: () => this.autocomplete?.reset(),
529
- autocompleteUpdate: (query: string) => this.autocomplete?.update(query),
530
- helpScrollOffset: this.helpScrollOffset,
531
- shortcutsScrollOffset: this.shortcutsScrollOffset,
532
- });
533
- this.prompt = result.prompt;
534
- this.cursorPos = result.cursorPos;
535
- this.commandMode = result.commandMode;
536
- this.helpOverlayActive = result.helpOverlayActive;
537
- this.helpScrollOffset = result.helpScrollOffset;
538
- this.shortcutsOverlayActive = result.shortcutsOverlayActive;
539
- this.shortcutsScrollOffset = result.shortcutsScrollOffset;
540
- this.selectionCallback = result.selectionCallback;
541
- this.panelFocused = result.panelFocused;
542
- this.indicatorFocused = result.indicatorFocused;
543
- this.modalReturnFocus = 'prompt';
544
- }
545
419
 
546
420
  /**
547
421
  * feed - Process raw stdin data through the tokenizer.
@@ -619,7 +493,7 @@ export class InputHandler {
619
493
  * handlePaste - Shared paste logic for Ctrl+V and middle-click.
620
494
  * Tries image clipboard first, falls back to text paste.
621
495
  */
622
- private handlePaste(): void {
496
+ public handlePaste(): void {
623
497
  const result = handleClipboardPaste({
624
498
  prompt: this.prompt,
625
499
  cursorPos: this.cursorPos,
@@ -638,7 +512,7 @@ export class InputHandler {
638
512
  }
639
513
 
640
514
  /** Content width for wrapping — set by main.ts via setContentWidth(). */
641
- private contentWidth = 76;
515
+ public contentWidth = 76;
642
516
 
643
517
  /** Set the content width used for wrapping calculations. Call from main.ts. */
644
518
  public setContentWidth(w: number): void {
@@ -650,7 +524,7 @@ export class InputHandler {
650
524
  * Uses the segment table to navigate visual lines, not raw \n lines.
651
525
  * Returns true if the cursor moved, false if at boundary.
652
526
  */
653
- private moveCursorVertical(direction: -1 | 1): boolean {
527
+ public moveCursorVertical(direction: -1 | 1): boolean {
654
528
  const result = moveCursorVertical(
655
529
  this.prompt,
656
530
  this.cursorPos,
@@ -707,14 +581,14 @@ export class InputHandler {
707
581
  * saveUndoState - Snapshot current prompt + cursor onto the undo stack.
708
582
  * Clears the redo stack because a new edit invalidates future states.
709
583
  */
710
- private saveUndoState(): void {
584
+ public saveUndoState(): void {
711
585
  saveUndoState(this.undoStack, this.redoStack, this.prompt, this.cursorPos, InputHandler.MAX_UNDO);
712
586
  }
713
587
 
714
588
  /**
715
589
  * handleUndo - Ctrl+Z: pop from undo stack, push current to redo stack.
716
590
  */
717
- private handleUndo(): void {
591
+ public handleUndo(): void {
718
592
  const state = undoPromptState(this.undoStack, this.redoStack, this.prompt, this.cursorPos);
719
593
  if (!state) return;
720
594
  this.prompt = state.prompt;
@@ -725,7 +599,7 @@ export class InputHandler {
725
599
  /**
726
600
  * handleRedo - Ctrl+Shift+Z: pop from redo stack, push current to undo stack.
727
601
  */
728
- private handleRedo(): void {
602
+ public handleRedo(): void {
729
603
  const state = redoPromptState(this.undoStack, this.redoStack, this.prompt, this.cursorPos);
730
604
  if (!state) return;
731
605
  this.prompt = state.prompt;
@@ -748,11 +622,11 @@ export class InputHandler {
748
622
  * Repeated Tab cycles through matches.
749
623
  * Returns true if path completion was performed.
750
624
  */
751
- private findPathToken(): { start: number; prefix: string } | null {
625
+ public findPathToken(): { start: number; prefix: string } | null {
752
626
  return findPathToken(this.prompt, this.cursorPos);
753
627
  }
754
628
 
755
- private handlePathCompletion(): boolean {
629
+ public handlePathCompletion(): boolean {
756
630
  const result = handlePathCompletion({
757
631
  prompt: this.prompt,
758
632
  cursorPos: this.cursorPos,
@@ -781,7 +655,7 @@ export class InputHandler {
781
655
  * Word-wrap a single line to fit within maxW columns.
782
656
  * Breaks at spaces; words wider than maxW are force-broken.
783
657
  */
784
- private cyclePanelTab(direction: 'next' | 'prev'): void {
658
+ public cyclePanelTab(direction: 'next' | 'prev'): void {
785
659
  const pm = this.uiServices.shell.panelManager;
786
660
  if (pm.isVisible()) {
787
661
  if (direction === 'next') pm.nextWorkspaceTab();
@@ -790,11 +664,11 @@ export class InputHandler {
790
664
  }
791
665
  }
792
666
 
793
- private handlePanelIntegrationAction(activePanel: Panel | null, key: string): void {
667
+ public handlePanelIntegrationAction(activePanel: Panel | null, key: string): void {
794
668
  runPanelIntegrationAction(this.uiServices.shell.panelManager, activePanel, key, this.commandContext);
795
669
  }
796
670
 
797
- private wordWrapLine(line: string, maxW: number): string[] {
671
+ public wordWrapLine(line: string, maxW: number): string[] {
798
672
  return wordWrapLine(line, maxW);
799
673
  }
800
674
  }
@@ -0,0 +1,105 @@
1
+ import type { InputToken } from '@pellux/goodvibes-sdk/platform/core/tokenizer';
2
+ import {
3
+ getOnboardingWizardVisibleFieldCount,
4
+ type OnboardingWizardAction,
5
+ type OnboardingWizardController,
6
+ } from './onboarding-wizard.ts';
7
+
8
+ type OnboardingRouteState = {
9
+ onboardingWizard: OnboardingWizardController;
10
+ getViewportHeight: () => number;
11
+ requestRender: () => void;
12
+ handleEscape: () => void;
13
+ openModelPickerWithTarget?: (
14
+ target: import('../model-picker.ts').ModelPickerTarget,
15
+ source?: 'settings' | 'onboarding',
16
+ ) => boolean;
17
+ onAction?: (action: OnboardingWizardAction) => void;
18
+ };
19
+
20
+ function activateSelection(state: OnboardingRouteState): void {
21
+ state.onboardingWizard.activateSelected();
22
+ const target = state.onboardingWizard.consumePendingModelPickerTarget();
23
+ if (target !== null) {
24
+ if (state.openModelPickerWithTarget) state.openModelPickerWithTarget(target, 'onboarding');
25
+ else state.onboardingWizard.clearPendingModelPickerTarget();
26
+ }
27
+ const action = state.onboardingWizard.consumePendingAction();
28
+ if (action !== null) state.onAction?.(action);
29
+ }
30
+
31
+ export function handleOnboardingWizardToken(state: OnboardingRouteState, token: InputToken): boolean {
32
+ if (!state.onboardingWizard.active) return false;
33
+
34
+ if (state.onboardingWizard.hydrationPending || state.onboardingWizard.hydrationError !== null) {
35
+ if (token.type === 'key' && token.logicalName === 'escape') state.handleEscape();
36
+ state.requestRender();
37
+ return true;
38
+ }
39
+
40
+ const visibleFields = getOnboardingWizardVisibleFieldCount(state.getViewportHeight());
41
+ const editing = state.onboardingWizard.isEditingTextField();
42
+
43
+ if (token.type === 'key') {
44
+ if (token.logicalName === 'escape') {
45
+ if (editing) state.onboardingWizard.cancelEdit();
46
+ else state.handleEscape();
47
+ return true;
48
+ }
49
+
50
+ if (editing) {
51
+ if (token.logicalName === 'enter') {
52
+ state.onboardingWizard.commitEdit();
53
+ } else if (token.logicalName === 'backspace') {
54
+ state.onboardingWizard.editBackspace();
55
+ } else if (token.logicalName === 'space') {
56
+ state.onboardingWizard.editChar(' ');
57
+ }
58
+ } else if (token.logicalName === 'left') {
59
+ state.onboardingWizard.prevStep();
60
+ } else if (token.logicalName === 'right') {
61
+ state.onboardingWizard.nextStep();
62
+ } else if (token.logicalName === 'tab') {
63
+ if (token.shift) state.onboardingWizard.prevStep();
64
+ else state.onboardingWizard.nextStep();
65
+ } else if (token.logicalName === 'up') {
66
+ state.onboardingWizard.moveSelection(-1, visibleFields);
67
+ } else if (token.logicalName === 'down') {
68
+ state.onboardingWizard.moveSelection(1, visibleFields);
69
+ } else if (token.logicalName === 'pageup') {
70
+ state.onboardingWizard.pageSelection(-1, visibleFields);
71
+ } else if (token.logicalName === 'pagedown') {
72
+ state.onboardingWizard.pageSelection(1, visibleFields);
73
+ } else if (token.logicalName === 'home') {
74
+ state.onboardingWizard.selectFirst(visibleFields);
75
+ } else if (token.logicalName === 'end') {
76
+ state.onboardingWizard.selectLast(visibleFields);
77
+ } else if (token.logicalName === 'enter' || token.logicalName === 'space') {
78
+ activateSelection(state);
79
+ } else if (token.logicalName === 'backspace') {
80
+ state.onboardingWizard.editBackspace();
81
+ }
82
+ } else if (token.type === 'text') {
83
+ if (editing) {
84
+ state.onboardingWizard.editChar(token.value);
85
+ } else if (token.value === 'h') {
86
+ state.onboardingWizard.prevStep();
87
+ } else if (token.value === 'l') {
88
+ state.onboardingWizard.nextStep();
89
+ } else if (token.value === 'k') {
90
+ state.onboardingWizard.moveSelection(-1, visibleFields);
91
+ } else if (token.value === 'j') {
92
+ state.onboardingWizard.moveSelection(1, visibleFields);
93
+ } else if (token.value === ' ') {
94
+ activateSelection(state);
95
+ } else if (/^[1-9]$/.test(token.value)) {
96
+ const stepIndex = Number(token.value) - 1;
97
+ if (stepIndex < state.onboardingWizard.steps.length) {
98
+ state.onboardingWizard.setStep(stepIndex);
99
+ }
100
+ }
101
+ }
102
+
103
+ state.requestRender();
104
+ return true;
105
+ }