@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.
- package/CHANGELOG.md +13 -0
- package/README.md +5 -5
- package/bin/goodvibes +10 -0
- package/bin/goodvibes-daemon +10 -0
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +3 -2
- package/src/cli/bundle-command.ts +225 -0
- package/src/cli/completion.ts +90 -0
- package/src/cli/config-overrides.ts +159 -0
- package/src/cli/endpoints.ts +63 -0
- package/src/cli/entrypoint.ts +169 -0
- package/src/cli/help.ts +301 -0
- package/src/cli/index.ts +11 -0
- package/src/cli/management-commands.ts +426 -0
- package/src/cli/management.ts +719 -0
- package/src/cli/network-posture.ts +46 -0
- package/src/cli/package-verification.ts +119 -0
- package/src/cli/parser.ts +369 -0
- package/src/cli/provider-classification.ts +107 -0
- package/src/cli/redaction.ts +105 -0
- package/src/cli/service-command.ts +45 -0
- package/src/cli/service-posture.ts +247 -0
- package/src/cli/status.ts +382 -0
- package/src/cli/surface-command.ts +248 -0
- package/src/cli/tui-startup.ts +32 -0
- package/src/cli/types.ts +69 -0
- package/src/cli-flags.ts +18 -55
- package/src/config/index.ts +1 -1
- package/src/config/secrets.ts +44 -0
- package/src/daemon/cli.ts +62 -11
- package/src/input/command-registry.ts +3 -0
- package/src/input/commands/guidance-runtime.ts +9 -4
- package/src/input/commands/local-runtime.ts +21 -7
- package/src/input/commands/local-setup.ts +31 -38
- package/src/input/commands/onboarding-runtime.ts +14 -0
- package/src/input/commands/runtime-services.ts +9 -0
- package/src/input/commands.ts +2 -0
- package/src/input/feed-context-factory.ts +8 -1
- package/src/input/handler-feed.ts +13 -8
- package/src/input/handler-interactions.ts +266 -0
- package/src/input/handler-modal-stack.ts +23 -3
- package/src/input/handler-modal-token-routes.ts +23 -1
- package/src/input/handler-onboarding.ts +696 -0
- package/src/input/handler-picker-routes.ts +15 -7
- package/src/input/handler-ui-state.ts +58 -0
- package/src/input/handler.ts +120 -246
- package/src/input/onboarding/handler-onboarding-routes.ts +105 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +211 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +148 -0
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +712 -0
- package/src/input/onboarding/onboarding-wizard-helpers.ts +218 -0
- package/src/input/onboarding/onboarding-wizard-rules.ts +224 -0
- package/src/input/onboarding/onboarding-wizard-state.ts +354 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +642 -0
- package/src/input/onboarding/onboarding-wizard-types.ts +170 -0
- package/src/input/onboarding/onboarding-wizard.ts +594 -0
- package/src/main.ts +32 -39
- package/src/panels/builtin/operations.ts +0 -10
- package/src/panels/index.ts +0 -1
- package/src/renderer/conversation-overlays.ts +6 -0
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/onboarding/onboarding-wizard.ts +533 -0
- package/src/runtime/bootstrap-core.ts +1 -0
- package/src/runtime/bootstrap.ts +123 -0
- package/src/runtime/onboarding/apply.ts +685 -0
- package/src/runtime/onboarding/derivation.ts +495 -0
- package/src/runtime/onboarding/index.ts +7 -0
- package/src/runtime/onboarding/markers.ts +161 -0
- package/src/runtime/onboarding/snapshot.ts +400 -0
- package/src/runtime/onboarding/state.ts +140 -0
- package/src/runtime/onboarding/types.ts +402 -0
- package/src/runtime/onboarding/verify.ts +233 -0
- package/src/runtime/ui-services.ts +16 -0
- package/src/shell/ui-openers.ts +12 -2
- package/src/version.ts +1 -1
- package/src/panels/welcome-panel.ts +0 -64
package/src/input/handler.ts
CHANGED
|
@@ -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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
178
|
+
public inputHistory: InputHistory | null = null;
|
|
146
179
|
public filePicker: FilePickerModal;
|
|
147
180
|
public historySearch: HistorySearch = new HistorySearch(() => this.inputHistory?.getEntries() ?? []);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
154
|
-
|
|
186
|
+
public mouseDownRow = -1;
|
|
187
|
+
public mouseDownCol = -1;
|
|
155
188
|
|
|
156
189
|
/** Pasted images: maps marker IDs to base64 image data. */
|
|
157
|
-
|
|
158
|
-
|
|
190
|
+
public imageRegistry = new Map<string, { data: string; mediaType: string }>();
|
|
191
|
+
public nextImageId = 1;
|
|
159
192
|
|
|
160
193
|
// ── Undo / Redo ────────────────────────────────────────────────────────────
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
200
|
+
public pathCompletions: string[] = [];
|
|
168
201
|
/** Index into pathCompletions for Tab cycling. */
|
|
169
|
-
|
|
202
|
+
public pathCompletionIndex = -1;
|
|
170
203
|
/** The raw prefix that triggered path completion (e.g. 'src/in'). */
|
|
171
|
-
|
|
204
|
+
public pathCompletionPrefix = '';
|
|
172
205
|
/** Start offset in prompt where the path token begins. */
|
|
173
|
-
|
|
206
|
+
public pathCompletionStart = 0;
|
|
174
207
|
|
|
175
208
|
constructor(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
625
|
+
public findPathToken(): { start: number; prefix: string } | null {
|
|
752
626
|
return findPathToken(this.prompt, this.cursorPos);
|
|
753
627
|
}
|
|
754
628
|
|
|
755
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|