@pellux/goodvibes-tui 0.19.26 → 0.19.28

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.
@@ -1,8 +1,8 @@
1
1
  import { NETWORK_MODE_OPTIONS, REASONING_OPTIONS, HITL_MODE_OPTIONS, GUIDANCE_MODE_OPTIONS, PERMISSION_MODE_OPTIONS, SECRET_POLICY_OPTIONS } from './onboarding-wizard-constants.ts';
2
- import { EXTERNAL_SURFACE_SPECS } from './onboarding-wizard-external-surfaces.ts';
2
+ import { EXTERNAL_SURFACE_SPECS, type ExternalSurfaceSpec } from './onboarding-wizard-external-surfaces.ts';
3
3
  import { countSelected, modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
4
4
  import type { OnboardingWizardController } from './onboarding-wizard.ts';
5
- import type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
5
+ import type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardExternalSurfaceStepId, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
6
6
 
7
7
  export function buildOnboardingWizardSteps(controller: OnboardingWizardController): readonly OnboardingWizardStepDefinition[] {
8
8
  if (controller.hydrationPending || controller.hydrationError !== null) return [buildLoadingStep(controller)];
@@ -24,6 +24,9 @@ export function buildOnboardingWizardSteps(controller: OnboardingWizardControlle
24
24
 
25
25
  if (wantsExternalServices) {
26
26
  steps.push(buildExternalServicesStep(controller));
27
+ for (const surface of getSelectedExternalSurfaceSpecs(controller)) {
28
+ steps.push(buildExternalSurfaceStep(controller, surface));
29
+ }
27
30
  }
28
31
 
29
32
  steps.push(buildProviderAccessStep(controller));
@@ -98,9 +101,9 @@ export function buildCapabilitiesStep(controller: OnboardingWizardController): O
98
101
 
99
102
  return {
100
103
  id: 'capabilities',
101
- title: 'What should GoodVibes be able to do?',
104
+ title: 'Choose GoodVibes capabilities',
102
105
  shortLabel: 'Capabilities',
103
- description: 'Choose the features this install should enable. Local TUI Only is selected when no server-backed capabilities are enabled.',
106
+ description: 'Select one or more capabilities. Local TUI Only means no browser, daemon, listener, or network setup; any other choice turns on service mode and autostart.',
104
107
  summaryTitle: 'Selected capabilities',
105
108
  summaryLines: [
106
109
  `${selectedCount}/${capabilities.length} option(s) selected`,
@@ -269,7 +272,6 @@ export function buildExternalServicesStep(controller: OnboardingWizardController
269
272
  const fields: OnboardingWizardFieldDefinition[] = [];
270
273
 
271
274
  for (const surface of EXTERNAL_SURFACE_SPECS) {
272
- const enabled = controller.getBooleanFieldValue(surface.enabledFieldId, surface.defaultEnabled(controller.runtimeSnapshot));
273
275
  fields.push({
274
276
  kind: 'checklist',
275
277
  id: surface.enabledFieldId,
@@ -277,54 +279,114 @@ export function buildExternalServicesStep(controller: OnboardingWizardController
277
279
  hint: surface.hint,
278
280
  defaultValue: surface.defaultEnabled(controller.runtimeSnapshot),
279
281
  });
282
+ }
280
283
 
281
- if (!enabled) continue;
282
- for (const setupField of surface.fields) {
283
- if (setupField.kind === 'radio') {
284
- fields.push({
285
- kind: 'radio',
286
- id: setupField.id,
287
- label: setupField.label,
288
- hint: setupField.hint,
289
- options: setupField.options ?? [],
290
- defaultValue: setupField.defaultValue(controller.runtimeSnapshot),
291
- });
292
- continue;
284
+ fields.push(
285
+ {
286
+ kind: 'action',
287
+ id: 'external-services.select-all',
288
+ action: 'select-all-external-surfaces',
289
+ label: 'Select all external surfaces',
290
+ hint: 'Enable every supported external surface so each one gets a setup screen.',
291
+ defaultValue: 'Action',
292
+ },
293
+ {
294
+ kind: 'action',
295
+ id: 'external-services.clear',
296
+ action: 'clear-external-surfaces',
297
+ label: 'Clear all external surfaces',
298
+ hint: 'Disable all external surfaces. The HTTP listener can still be enabled separately by webhook/event capabilities.',
299
+ defaultValue: 'Action',
300
+ },
301
+ {
302
+ kind: 'radio',
303
+ id: 'external-services.secret-policy',
304
+ label: 'Secret storage policy',
305
+ hint: 'Choose how selected surface secrets should be stored. Secret values are never shown in the wizard.',
306
+ options: SECRET_POLICY_OPTIONS,
307
+ defaultValue: controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure',
308
+ },
309
+ );
310
+
311
+ return {
312
+ id: 'external-services',
313
+ title: 'Choose external surfaces',
314
+ shortLabel: 'Services',
315
+ description: 'Select the apps and integration surfaces GoodVibes should prepare. Setup fields are not shown here; each selected surface opens as its own screen.',
316
+ summaryTitle: 'External surfaces',
317
+ summaryLines: [
318
+ `${enabledCount} external surface(s) selected`,
319
+ `Secret policy: ${controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure')}`,
320
+ enabledCount > 0 ? 'Selected surfaces appear as separate setup screens.' : 'No external surfaces selected.',
321
+ ],
322
+ fields,
323
+ };
324
+ }
325
+
326
+ function getSelectedExternalSurfaceSpecs(controller: OnboardingWizardController): readonly ExternalSurfaceSpec[] {
327
+ return EXTERNAL_SURFACE_SPECS.filter((surface) => (
328
+ controller.getBooleanFieldValue(surface.enabledFieldId, surface.defaultEnabled(controller.runtimeSnapshot))
329
+ ));
330
+ }
331
+
332
+ function buildExternalSurfaceStep(
333
+ controller: OnboardingWizardController,
334
+ surface: ExternalSurfaceSpec,
335
+ ): OnboardingWizardStepDefinition {
336
+ let requiredCount = 0;
337
+ let requiredCompleteCount = 0;
338
+ const setupFields = surface.fields.map((setupField): OnboardingWizardFieldDefinition => {
339
+ const required = controller.isRequiredExternalSetupField(setupField.id);
340
+ if (required) {
341
+ requiredCount += 1;
342
+ if (normalizeText(setupField.defaultValue(controller.runtimeSnapshot)).length > 0
343
+ || normalizeText(controller.getStringFieldValue(setupField.id, '')).length > 0) {
344
+ requiredCompleteCount += 1;
293
345
  }
346
+ }
294
347
 
295
- fields.push({
296
- kind: setupField.kind,
348
+ const hint = required
349
+ ? `${setupField.hint} Required because ${surface.label} is selected.`
350
+ : setupField.hint;
351
+
352
+ if (setupField.kind === 'radio') {
353
+ return {
354
+ kind: 'radio',
297
355
  id: setupField.id,
298
356
  label: setupField.label,
299
- hint: setupField.hint,
300
- placeholder: setupField.placeholder,
357
+ hint,
358
+ options: setupField.options ?? [],
301
359
  defaultValue: setupField.defaultValue(controller.runtimeSnapshot),
302
- required: controller.isRequiredExternalSetupField(setupField.id),
303
- });
360
+ };
304
361
  }
305
- }
306
362
 
307
- fields.push({
308
- kind: 'radio',
309
- id: 'external-services.secret-policy',
310
- label: 'Secret storage policy',
311
- hint: 'Choose how external integration secrets should be stored.',
312
- options: SECRET_POLICY_OPTIONS,
313
- defaultValue: controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure',
363
+ return {
364
+ kind: setupField.kind,
365
+ id: setupField.id,
366
+ label: setupField.label,
367
+ hint,
368
+ placeholder: setupField.placeholder,
369
+ defaultValue: setupField.defaultValue(controller.runtimeSnapshot),
370
+ required,
371
+ };
314
372
  });
373
+ const title = `${surface.label.replace(/ surface$/i, '')} setup`;
374
+ const requiredSummary = requiredCount === 0
375
+ ? 'Required setup: none'
376
+ : `Required setup: ${requiredCompleteCount}/${requiredCount}`;
315
377
 
316
378
  return {
317
- id: 'external-services',
318
- title: 'External apps and services',
319
- shortLabel: 'Services',
320
- description: 'Select each external surface this install should prepare and enter its setup values directly in the wizard. Sensitive fields remain masked.',
321
- summaryTitle: 'External service summary',
379
+ id: `external-surface:${surface.id}` as OnboardingWizardExternalSurfaceStepId,
380
+ title,
381
+ shortLabel: surface.label.replace(/ surface$/i, ''),
382
+ description: `Configure ${surface.label}. This screen only appears because that surface is selected on the previous screen.`,
383
+ summaryTitle: `${surface.label} setup`,
322
384
  summaryLines: [
323
- `${enabledCount} external surface(s) selected`,
385
+ requiredSummary,
324
386
  `Secret policy: ${controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure')}`,
325
- 'Secrets remain masked and policy-controlled.',
387
+ 'Leave optional fields blank to keep defaults or existing values.',
326
388
  ],
327
- fields,
389
+ fields: setupFields,
328
390
  };
329
391
  }
330
392
 
@@ -510,7 +572,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
510
572
  const needsAuthBootstrap = controller.requiresAuthBootstrap();
511
573
  const needsExistingAuthAcknowledgement = controller.hasServerCapabilitiesSelected()
512
574
  && !needsAuthBootstrap
513
- && controller.hasAdminAuthUser();
575
+ && controller.hasLocalAuthUser();
514
576
  const fields: OnboardingWizardFieldDefinition[] = [];
515
577
 
516
578
  if (needsAuthBootstrap) {
@@ -563,13 +625,17 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
563
625
  {
564
626
  kind: 'status',
565
627
  id: 'accounts.bootstrap',
566
- label: 'Bootstrap credential hint',
628
+ label: 'Local auth readiness',
567
629
  hint: needsAuthBootstrap
568
630
  ? 'The wizard will create local auth before applying network-accessible settings.'
569
- : 'Masked auth state stays visible without leaking sensitive values.',
631
+ : controller.hasAdminAuthUser()
632
+ ? 'An existing local auth admin user was detected and will be kept.'
633
+ : controller.hasLocalAuthUser()
634
+ ? 'Existing local auth users were detected and will be kept.'
635
+ : 'No server-backed capability is selected, so local auth is not required.',
570
636
  defaultValue: needsAuthBootstrap
571
637
  ? controller.hasBootstrapCredentialPresent() ? 'Bootstrap replacement required' : 'Local admin required'
572
- : auth?.bootstrapCredentialPresent ? 'Configured' : 'Not detected',
638
+ : controller.hasAdminAuthUser() ? 'Admin detected' : controller.hasLocalAuthUser() ? 'Local auth detected' : 'Not required',
573
639
  },
574
640
  {
575
641
  kind: 'status',
@@ -586,7 +652,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
586
652
  shortLabel: 'Accounts',
587
653
  description: needsAuthBootstrap
588
654
  ? 'Create wizard-owned local auth before any LAN, browser, service, or listener settings are applied.'
589
- : 'Require explicit acknowledgement for existing subscription or local-auth state when reopening the wizard in edit mode.',
655
+ : 'Review existing subscription and local auth state. Existing local auth is kept unless you change it elsewhere.',
590
656
  summaryTitle: 'Stored account state',
591
657
  summaryLines: [
592
658
  `Subscriptions: ${controller.runtimeSnapshot?.subscriptions.active.length ?? 0} active / ${controller.runtimeSnapshot?.subscriptions.pending.length ?? 0} pending`,
@@ -595,7 +661,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
595
661
  ? controller.hasBootstrapCredentialPresent()
596
662
  ? 'Bootstrap credentials will be replaced before network settings are applied'
597
663
  : 'Local admin will be created before network settings are applied'
598
- : auth?.bootstrapCredentialPresent ? 'Bootstrap credential file present' : 'No bootstrap credential file detected',
664
+ : controller.hasLocalAuthUser() ? 'Existing local auth will be kept' : 'Local auth is not required for this setup',
599
665
  ],
600
666
  fields,
601
667
  };
@@ -604,7 +670,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
604
670
  export function buildReviewStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
605
671
  return {
606
672
  id: 'review',
607
- title: 'Review and completion',
673
+ title: 'Review and apply',
608
674
  shortLabel: 'Review',
609
675
  description: 'Review the selected settings and apply them directly from the wizard.',
610
676
  summaryTitle: 'Review posture',
@@ -616,25 +682,18 @@ export function buildReviewStep(controller: OnboardingWizardController): Onboard
616
682
  ],
617
683
  fields: [
618
684
  {
619
- kind: 'checklist',
620
- id: 'review.project-marker',
621
- label: 'Write project completion marker',
622
- hint: 'Project scope keeps the workspace-specific completion state close to the repo.',
623
- defaultValue: true,
624
- },
625
- {
626
- kind: 'checklist',
627
- id: 'review.user-marker',
628
- label: 'Write user completion marker',
629
- hint: 'User scope keeps the shell-level completion marker available outside this workspace.',
630
- defaultValue: controller.defaultReviewUserMarker(),
685
+ kind: 'status',
686
+ id: 'review.global-marker',
687
+ label: 'Global onboarding check',
688
+ hint: 'Opening this wizard marks onboarding as shown for this user account, so new projects do not reopen it automatically.',
689
+ defaultValue: 'Already marked as shown',
631
690
  },
632
691
  {
633
692
  kind: 'action',
634
693
  id: 'review.apply',
635
694
  action: 'apply',
636
695
  label: 'Apply settings and verify',
637
- hint: 'Persist the wizard settings, write completion markers, and verify the resulting runtime state.',
696
+ hint: 'Persist the wizard settings and verify the resulting runtime state. The global onboarding check was already recorded when the wizard opened.',
638
697
  defaultValue: 'Ready',
639
698
  },
640
699
  ],
@@ -16,12 +16,15 @@ import type { ConfigKey } from '../../config/index.ts';
16
16
 
17
17
  export type OnboardingWizardMode = OnboardingMode;
18
18
 
19
+ export type OnboardingWizardExternalSurfaceStepId = `external-surface:${string}`;
20
+
19
21
  export type OnboardingWizardStepId =
20
22
  | 'loading'
21
23
  | 'capabilities'
22
24
  | 'network'
23
25
  | 'access'
24
26
  | 'external-services'
27
+ | OnboardingWizardExternalSurfaceStepId
25
28
  | 'provider-access'
26
29
  | 'default-model'
27
30
  | 'experience'
@@ -41,6 +44,8 @@ export type OnboardingWizardAction =
41
44
  | 'apply'
42
45
  | 'select-all-capabilities'
43
46
  | 'clear-capabilities'
47
+ | 'select-all-external-surfaces'
48
+ | 'clear-external-surfaces'
44
49
  | 'start-openai-subscription'
45
50
  | 'finish-openai-subscription';
46
51
 
@@ -4,11 +4,39 @@ import { STEP_ORDER } from './onboarding-wizard-constants.ts';
4
4
  import { buildOnboardingApplyRequest } from './onboarding-wizard-apply.ts';
5
5
  import { buildOnboardingWizardSteps } from './onboarding-wizard-steps.ts';
6
6
  import { buildDefaultDerivedState, clamp, cloneSelection, getRuntimeDerivedState, maskValue, modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
7
- import { defaultReviewUserMarker as defaultReviewUserMarkerForController, getBooleanFieldValue as getBooleanFieldValueForController, getDefaultAdminUsername as getDefaultAdminUsernameForController, getNumberFieldValue as getNumberFieldValueForController, getPortFieldValue as getPortFieldValueForController, getSelectedSecretMedium as getSelectedSecretMediumForController, getSharedIpDefault as getSharedIpDefaultForController, getSharedIpHostDefault as getSharedIpHostDefaultForController, getStringFieldValue as getStringFieldValueForController, hasAdminAuthUser as hasAdminAuthUserForController, hasBootstrapCredentialPresent as hasBootstrapCredentialPresentForController, hasSelectedInboundExternalSurface as hasSelectedInboundExternalSurfaceForController, hasServerCapabilitiesSelected as hasServerCapabilitiesSelectedForController, isCapabilitySelected as isCapabilitySelectedForController, isRequiredExternalSetupField as isRequiredExternalSetupFieldForController, parseIntegerFieldValue as parseIntegerFieldValueForController, requiresAuthBootstrap as requiresAuthBootstrapForController, selectAllServerCapabilities as selectAllServerCapabilitiesForController, selectLocalTuiOnly as selectLocalTuiOnlyForController, setCapabilityValue as setCapabilityValueForController, shouldEnableBrowserSurface as shouldEnableBrowserSurfaceForController, shouldEnableHttpListener as shouldEnableHttpListenerForController, shouldExposeControlPlaneNetwork as shouldExposeControlPlaneNetworkForController, shouldExposeHttpListenerNetworkFields as shouldExposeHttpListenerNetworkFieldsForController, toggleCapability as toggleCapabilityForController } from './onboarding-wizard-rules.ts';
7
+ import {
8
+ clearExternalSurfaces as clearExternalSurfacesForController,
9
+ getBooleanFieldValue as getBooleanFieldValueForController,
10
+ getDefaultAdminUsername as getDefaultAdminUsernameForController,
11
+ getNumberFieldValue as getNumberFieldValueForController,
12
+ getPortFieldValue as getPortFieldValueForController,
13
+ getSelectedSecretMedium as getSelectedSecretMediumForController,
14
+ getSharedIpDefault as getSharedIpDefaultForController,
15
+ getSharedIpHostDefault as getSharedIpHostDefaultForController,
16
+ getStringFieldValue as getStringFieldValueForController,
17
+ hasAdminAuthUser as hasAdminAuthUserForController,
18
+ hasBootstrapCredentialPresent as hasBootstrapCredentialPresentForController,
19
+ hasLocalAuthUser as hasLocalAuthUserForController,
20
+ hasSelectedInboundExternalSurface as hasSelectedInboundExternalSurfaceForController,
21
+ hasServerCapabilitiesSelected as hasServerCapabilitiesSelectedForController,
22
+ isCapabilitySelected as isCapabilitySelectedForController,
23
+ isRequiredExternalSetupField as isRequiredExternalSetupFieldForController,
24
+ parseIntegerFieldValue as parseIntegerFieldValueForController,
25
+ requiresAuthBootstrap as requiresAuthBootstrapForController,
26
+ selectAllExternalSurfaces as selectAllExternalSurfacesForController,
27
+ selectAllServerCapabilities as selectAllServerCapabilitiesForController,
28
+ selectLocalTuiOnly as selectLocalTuiOnlyForController,
29
+ setCapabilityValue as setCapabilityValueForController,
30
+ shouldEnableBrowserSurface as shouldEnableBrowserSurfaceForController,
31
+ shouldEnableHttpListener as shouldEnableHttpListenerForController,
32
+ shouldExposeControlPlaneNetwork as shouldExposeControlPlaneNetworkForController,
33
+ shouldExposeHttpListenerNetworkFields as shouldExposeHttpListenerNetworkFieldsForController,
34
+ toggleCapability as toggleCapabilityForController,
35
+ } from './onboarding-wizard-rules.ts';
8
36
  import { ensureSelectionVisible as ensureSelectionVisibleForController, getBlockingFieldLabels as getBlockingFieldLabelsForController, getCapabilitySelectionState as getCapabilitySelectionStateForController, getCompletedFieldCount as getCompletedFieldCountForController, getCompletedToggleCount as getCompletedToggleCountForController, getCurrentCapabilities as getCurrentCapabilitiesForController, getFieldById as getFieldByIdForController, getFieldValidationError as getFieldValidationErrorForController, getStepFieldCount as getStepFieldCountForController, getToggleFieldCount as getToggleFieldCountForController, hasExistingAccessState as hasExistingAccessStateForController, isFieldDirty as isFieldDirtyForController, isFieldDirtyByDefinition as isFieldDirtyByDefinitionForController, isFieldSatisfied as isFieldSatisfiedForController, isStepDirty as isStepDirtyForController, recalculateDirtyState as recalculateDirtyStateForController, reconcileStateWithCurrentDefinitions as reconcileStateWithCurrentDefinitionsForController, reconcileStepCursor as reconcileStepCursorForController, resetValuesFromCurrentDefinitions as resetValuesFromCurrentDefinitionsForController } from './onboarding-wizard-state.ts';
9
37
  import type { MutableModelSelectionMap, OnboardingWizardAction, OnboardingWizardFieldDefinition, OnboardingWizardFieldWindow, OnboardingWizardMode, OnboardingWizardModelSelection, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStepDefinition, OnboardingWizardStepId } from './onboarding-wizard-types.ts';
10
38
 
11
- export type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardAction, OnboardingWizardActionFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardFieldDefinition, OnboardingWizardFieldKind, OnboardingWizardFieldWindow, OnboardingWizardMaskedFieldDefinition, OnboardingWizardMode, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardModelSelection, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStatusFieldDefinition, OnboardingWizardStepDefinition, OnboardingWizardStepId, OnboardingWizardTextFieldDefinition } from './onboarding-wizard-types.ts';
39
+ export type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardAction, OnboardingWizardActionFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardExternalSurfaceStepId, OnboardingWizardFieldDefinition, OnboardingWizardFieldKind, OnboardingWizardFieldWindow, OnboardingWizardMaskedFieldDefinition, OnboardingWizardMode, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardModelSelection, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStatusFieldDefinition, OnboardingWizardStepDefinition, OnboardingWizardStepId, OnboardingWizardTextFieldDefinition } from './onboarding-wizard-types.ts';
12
40
  export { getOnboardingWizardBodyRows, getOnboardingWizardVisibleFieldCount } from './onboarding-wizard-helpers.ts';
13
41
 
14
42
  export class OnboardingWizardController {
@@ -40,7 +68,9 @@ export class OnboardingWizardController {
40
68
  public runtimeDerived: OnboardingStepDerivationState = buildDefaultDerivedState();
41
69
 
42
70
  public get steps(): readonly OnboardingWizardStepDefinition[] {
43
- return buildOnboardingWizardSteps(this);
71
+ const steps = buildOnboardingWizardSteps(this);
72
+ this.ensureNavigationStateLength(steps.length);
73
+ return steps;
44
74
  }
45
75
 
46
76
  public get currentStep(): OnboardingWizardStepDefinition {
@@ -59,6 +89,11 @@ export class OnboardingWizardController {
59
89
  this.resetValuesFromCurrentDefinitions();
60
90
  }
61
91
 
92
+ private ensureNavigationStateLength(stepCount: number): void {
93
+ while (this.scrollOffsets.length < stepCount) this.scrollOffsets.push(0);
94
+ while (this.selectedFieldIndices.length < stepCount) this.selectedFieldIndices.push(0);
95
+ }
96
+
62
97
  public open(mode: OnboardingWizardMode = 'new'): void {
63
98
  this.mode = mode;
64
99
  this.active = true;
@@ -159,7 +194,9 @@ export class OnboardingWizardController {
159
194
  this.dirtyStepIds.clear();
160
195
  for (const stepId of snapshot.dirtyStepIds) this.dirtyStepIds.add(stepId);
161
196
 
162
- for (let index = 0; index < this.scrollOffsets.length; index += 1) {
197
+ const navigationLength = Math.max(this.steps.length, snapshot.scrollOffsets.length, this.scrollOffsets.length);
198
+ this.ensureNavigationStateLength(navigationLength);
199
+ for (let index = 0; index < navigationLength; index += 1) {
163
200
  this.scrollOffsets[index] = snapshot.scrollOffsets[index] ?? 0;
164
201
  this.selectedFieldIndices[index] = snapshot.selectedFieldIndices[index] ?? 0;
165
202
  }
@@ -372,6 +409,22 @@ export class OnboardingWizardController {
372
409
  return;
373
410
  }
374
411
 
412
+ if (field.action === 'select-all-external-surfaces') {
413
+ this.selectAllExternalSurfaces();
414
+ this.pendingAction = null;
415
+ this.pendingModelPickerTarget = null;
416
+ this.recalculateDirtyState();
417
+ return;
418
+ }
419
+
420
+ if (field.action === 'clear-external-surfaces') {
421
+ this.clearExternalSurfaces();
422
+ this.pendingAction = null;
423
+ this.pendingModelPickerTarget = null;
424
+ this.recalculateDirtyState();
425
+ return;
426
+ }
427
+
375
428
  this.pendingAction = field.action;
376
429
  this.pendingModelPickerTarget = null;
377
430
  return;
@@ -391,6 +444,14 @@ export class OnboardingWizardController {
391
444
  this.editBuffer = this.textState.get(field.id) ?? field.defaultValue;
392
445
  }
393
446
 
447
+ public beginSelectedTextInput(initialText: string): boolean {
448
+ const field = this.getSelectedField();
449
+ if (!field || (field.kind !== 'text' && field.kind !== 'masked')) return false;
450
+ this.beginEdit(field.id);
451
+ this.editBuffer = initialText;
452
+ return true;
453
+ }
454
+
394
455
  public commitEdit(): void {
395
456
  const fieldId = this.editingFieldId;
396
457
  if (fieldId === null) return;
@@ -498,11 +559,13 @@ export class OnboardingWizardController {
498
559
 
499
560
  if (field.kind === 'text') {
500
561
  const value = normalizeText(this.getFieldValue(field) as string);
562
+ if (value.length === 0 && (field.required === true || this.isRequiredExternalSetupField(field.id))) return 'Missing';
501
563
  return value.length > 0 ? value : field.placeholder;
502
564
  }
503
565
 
504
566
  if (field.kind === 'masked') {
505
567
  const value = normalizeText(this.getFieldValue(field) as string);
568
+ if (value.length === 0 && (field.required === true || this.isRequiredExternalSetupField(field.id))) return 'Missing';
506
569
  return value.length > 0 ? maskValue(value) : field.placeholder;
507
570
  }
508
571
 
@@ -548,10 +611,11 @@ export class OnboardingWizardController {
548
611
  return getSharedIpHostDefaultForController(this, enabled);
549
612
  }
550
613
 
551
- public defaultReviewUserMarker(): boolean { return defaultReviewUserMarkerForController(this); }
552
614
  public toggleCapability(capabilityId: OnboardingStep1CapabilityId): void { toggleCapabilityForController(this, capabilityId); }
553
615
  public selectAllServerCapabilities(): void { selectAllServerCapabilitiesForController(this); }
554
616
  public selectLocalTuiOnly(): void { selectLocalTuiOnlyForController(this); }
617
+ public selectAllExternalSurfaces(): void { selectAllExternalSurfacesForController(this); }
618
+ public clearExternalSurfaces(): void { clearExternalSurfacesForController(this); }
555
619
  public setCapabilityValue(capabilityId: OnboardingStep1CapabilityId, selected: boolean): void { setCapabilityValueForController(this, capabilityId, selected); }
556
620
  public isCapabilitySelected(capabilityId: OnboardingStep1CapabilityId): boolean { return isCapabilitySelectedForController(this, capabilityId); }
557
621
  public hasServerCapabilitiesSelected(): boolean { return hasServerCapabilitiesSelectedForController(this); }
@@ -564,6 +628,7 @@ export class OnboardingWizardController {
564
628
  public shouldExposeControlPlaneNetwork(): boolean { return shouldExposeControlPlaneNetworkForController(this); }
565
629
  public requiresAuthBootstrap(): boolean { return requiresAuthBootstrapForController(this); }
566
630
  public hasAdminAuthUser(): boolean { return hasAdminAuthUserForController(this); }
631
+ public hasLocalAuthUser(): boolean { return hasLocalAuthUserForController(this); }
567
632
  public hasBootstrapCredentialPresent(): boolean { return hasBootstrapCredentialPresentForController(this); }
568
633
  public getDefaultAdminUsername(): string { return getDefaultAdminUsernameForController(this); }
569
634
  public getBooleanFieldValue(fieldId: string, fallback: boolean): boolean { return getBooleanFieldValueForController(this, fieldId, fallback); }
package/src/main.ts CHANGED
@@ -258,7 +258,8 @@ async function main() {
258
258
  stdout.removeListener('resize', resizeHandler);
259
259
  process.removeListener('SIGINT', sigintHandler);
260
260
  process.removeListener('unhandledRejection', unhandledRejectionHandler);
261
- stdout.write(PASTE_DISABLE + KEYBOARD_EXT_DISABLE + MOUSE_DISABLE + CURSOR_SHOW + (cli.flags.noAltScreen ? '' : ALT_SCREEN_EXIT));
261
+ const exitScreen = cli.flags.noAltScreen ? CLEAR_SCREEN : CLEAR_SCREEN + ALT_SCREEN_EXIT;
262
+ stdout.write(PASTE_DISABLE + KEYBOARD_EXT_DISABLE + MOUSE_DISABLE + CURSOR_SHOW + exitScreen);
262
263
  stdin.setRawMode(false);
263
264
  process.exit(0);
264
265
  };