@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
|
@@ -0,0 +1,642 @@
|
|
|
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';
|
|
3
|
+
import { countSelected, modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
|
|
4
|
+
import type { OnboardingWizardController } from './onboarding-wizard.ts';
|
|
5
|
+
import type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
|
|
6
|
+
|
|
7
|
+
export function buildOnboardingWizardSteps(controller: OnboardingWizardController): readonly OnboardingWizardStepDefinition[] {
|
|
8
|
+
if (controller.hydrationPending || controller.hydrationError !== null) return [buildLoadingStep(controller)];
|
|
9
|
+
|
|
10
|
+
const capabilities = controller.getCapabilitySelectionState();
|
|
11
|
+
const hasServers = capabilities.some((item) => item.id !== 'local-tui-only' && item.selected);
|
|
12
|
+
const wantsExternalServices = capabilities.some((item) => item.id === 'external-integrations' && item.selected);
|
|
13
|
+
const steps: OnboardingWizardStepDefinition[] = [
|
|
14
|
+
buildCapabilitiesStep(controller),
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
if (hasServers) {
|
|
18
|
+
steps.push(buildNetworkStep(controller));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (hasServers || controller.hasExistingAccessState()) {
|
|
22
|
+
steps.push(buildAccessStep(controller));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (wantsExternalServices) {
|
|
26
|
+
steps.push(buildExternalServicesStep(controller));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
steps.push(buildProviderAccessStep(controller));
|
|
30
|
+
steps.push(buildDefaultModelStep(controller));
|
|
31
|
+
steps.push(buildExperienceStep(controller));
|
|
32
|
+
steps.push(buildReviewStep(controller));
|
|
33
|
+
|
|
34
|
+
return steps;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildLoadingStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
38
|
+
const failed = controller.hydrationError !== null;
|
|
39
|
+
return {
|
|
40
|
+
id: 'loading',
|
|
41
|
+
title: failed ? 'Current settings unavailable' : 'Loading current settings',
|
|
42
|
+
shortLabel: 'Loading',
|
|
43
|
+
description: failed
|
|
44
|
+
? 'The wizard is locked because current runtime settings could not be collected. Close and reopen onboarding after fixing the reported issue.'
|
|
45
|
+
: 'Collecting the current daemon, listener, provider, subscription, auth, and surface settings before the wizard becomes editable.',
|
|
46
|
+
summaryTitle: failed ? 'Preload failed' : 'Preload required',
|
|
47
|
+
summaryLines: [
|
|
48
|
+
failed
|
|
49
|
+
? 'Editable fields remain locked to avoid applying defaults over existing configuration.'
|
|
50
|
+
: 'Editable fields are locked until runtime settings are loaded.',
|
|
51
|
+
failed
|
|
52
|
+
? controller.hydrationError ?? 'Unknown snapshot failure.'
|
|
53
|
+
: 'This prevents the wizard from applying defaults over existing configuration.',
|
|
54
|
+
],
|
|
55
|
+
fields: [
|
|
56
|
+
{
|
|
57
|
+
kind: 'status',
|
|
58
|
+
id: 'loading.runtime-snapshot',
|
|
59
|
+
label: failed ? 'Runtime settings snapshot failed' : 'Runtime settings snapshot',
|
|
60
|
+
hint: failed
|
|
61
|
+
? controller.hydrationError ?? 'The runtime snapshot did not complete.'
|
|
62
|
+
: 'Waiting for the current GoodVibes configuration and account state.',
|
|
63
|
+
defaultValue: failed ? 'Locked' : 'Loading',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function buildCapabilitiesStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
70
|
+
const capabilities = controller.getCapabilitySelectionState();
|
|
71
|
+
const selectedCount = countSelected(capabilities);
|
|
72
|
+
const fields: OnboardingWizardFieldDefinition[] = [
|
|
73
|
+
...capabilities.map((capability) => ({
|
|
74
|
+
kind: 'checklist' as const,
|
|
75
|
+
id: `capabilities.${capability.id}`,
|
|
76
|
+
capabilityId: capability.id,
|
|
77
|
+
label: capability.label,
|
|
78
|
+
hint: capability.detail,
|
|
79
|
+
defaultValue: capability.selected,
|
|
80
|
+
})),
|
|
81
|
+
{
|
|
82
|
+
kind: 'action',
|
|
83
|
+
id: 'capabilities.select-all',
|
|
84
|
+
action: 'select-all-capabilities',
|
|
85
|
+
label: 'Select all server-backed capabilities',
|
|
86
|
+
hint: 'Enable browser access, other-device LAN access, webhooks/events, and external integrations. Local TUI Only is turned off.',
|
|
87
|
+
defaultValue: 'Action',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
kind: 'action',
|
|
91
|
+
id: 'capabilities.clear',
|
|
92
|
+
action: 'clear-capabilities',
|
|
93
|
+
label: 'Use Local TUI Only (No Servers)',
|
|
94
|
+
hint: 'Clear all server-backed capabilities and keep GoodVibes in this terminal only.',
|
|
95
|
+
defaultValue: 'Action',
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
id: 'capabilities',
|
|
101
|
+
title: 'What should GoodVibes be able to do?',
|
|
102
|
+
shortLabel: 'Capabilities',
|
|
103
|
+
description: 'Choose the features this install should enable. Local TUI Only is selected when no server-backed capabilities are enabled.',
|
|
104
|
+
summaryTitle: 'Selected capabilities',
|
|
105
|
+
summaryLines: [
|
|
106
|
+
`${selectedCount}/${capabilities.length} option(s) selected`,
|
|
107
|
+
`Mode: ${controller.mode === 'edit' ? 'edit existing shell state' : controller.mode === 'reopen' ? 'reopen review flow' : 'new setup'}`,
|
|
108
|
+
controller.runtimeSnapshot?.collectionIssues.length
|
|
109
|
+
? `${controller.runtimeSnapshot.collectionIssues.length} runtime collection issue(s)`
|
|
110
|
+
: 'Runtime snapshot collected cleanly',
|
|
111
|
+
],
|
|
112
|
+
fields,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function buildProvidersStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
117
|
+
const providerAck = controller.runtimeDerived.reopenEditAcknowledgements.providers;
|
|
118
|
+
const activeSubscriptions = controller.runtimeSnapshot?.subscriptions.active ?? [];
|
|
119
|
+
const pendingSubscriptions = controller.runtimeSnapshot?.subscriptions.pending ?? [];
|
|
120
|
+
const openAiActive = activeSubscriptions.some((subscription) => subscription.provider === 'openai');
|
|
121
|
+
const openAiPending = pendingSubscriptions.some((subscription) => subscription.provider === 'openai');
|
|
122
|
+
const providerSecretCount = controller.runtimeSnapshot?.secrets.records.filter((record) => record.key.endsWith('_API_KEY') || record.key.endsWith('_TOKEN')).length ?? 0;
|
|
123
|
+
const openAiApiKeyConfigured = controller.runtimeSnapshot?.secrets.records.some((record) => record.key === 'OPENAI_API_KEY') ?? false;
|
|
124
|
+
const providerReviewField: OnboardingWizardAcknowledgementFieldDefinition = {
|
|
125
|
+
kind: 'acknowledgement',
|
|
126
|
+
id: 'providers.reviewed',
|
|
127
|
+
label: 'Confirm provider access review',
|
|
128
|
+
hint: providerAck.detail,
|
|
129
|
+
defaultValue: providerAck.accepted,
|
|
130
|
+
required: controller.mode !== 'new' && providerAck.required,
|
|
131
|
+
reason: providerAck.reason,
|
|
132
|
+
target: 'providers',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const fields: OnboardingWizardFieldDefinition[] = [
|
|
136
|
+
{
|
|
137
|
+
kind: 'status',
|
|
138
|
+
id: 'providers.openai-subscription',
|
|
139
|
+
label: 'OpenAI subscription status',
|
|
140
|
+
hint: openAiActive
|
|
141
|
+
? 'An OpenAI subscription session is already available.'
|
|
142
|
+
: openAiPending
|
|
143
|
+
? 'An OpenAI subscription login is pending.'
|
|
144
|
+
: 'No OpenAI subscription session was found in the current runtime state.',
|
|
145
|
+
defaultValue: openAiActive ? 'Active' : openAiPending ? 'Pending' : 'Not detected',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
kind: 'status',
|
|
149
|
+
id: 'providers.api-key-inventory',
|
|
150
|
+
label: 'Provider API key inventory',
|
|
151
|
+
hint: providerSecretCount > 0
|
|
152
|
+
? `${providerSecretCount} provider credential reference(s) were found. Values stay masked.`
|
|
153
|
+
: 'No provider API key references were detected in the current runtime state.',
|
|
154
|
+
defaultValue: providerSecretCount > 0 ? `${providerSecretCount} configured` : 'None detected',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
kind: 'masked',
|
|
158
|
+
id: 'providers.openai-api-key',
|
|
159
|
+
label: 'OpenAI API key',
|
|
160
|
+
hint: openAiApiKeyConfigured
|
|
161
|
+
? 'An OpenAI API key is already stored. Leave blank to keep it; enter a new key to replace it through the secret manager.'
|
|
162
|
+
: 'Optional: enter an OpenAI API key now. The value is stored through the secret manager, not in config.',
|
|
163
|
+
placeholder: openAiApiKeyConfigured ? 'already configured' : 'sk-...',
|
|
164
|
+
defaultValue: '',
|
|
165
|
+
},
|
|
166
|
+
...(openAiActive ? [] : [
|
|
167
|
+
{
|
|
168
|
+
kind: 'action' as const,
|
|
169
|
+
id: 'providers.openai-subscription-start',
|
|
170
|
+
action: 'start-openai-subscription' as const,
|
|
171
|
+
label: openAiPending ? 'Restart OpenAI subscription sign-in' : 'Start OpenAI subscription sign-in',
|
|
172
|
+
hint: 'Opens the OpenAI sign-in flow from the wizard and records pending login state here.',
|
|
173
|
+
defaultValue: openAiPending ? 'Restart' : 'Start',
|
|
174
|
+
},
|
|
175
|
+
...(openAiPending ? [
|
|
176
|
+
{
|
|
177
|
+
kind: 'text' as const,
|
|
178
|
+
id: 'providers.openai-authorization-url',
|
|
179
|
+
label: 'OpenAI authorization URL',
|
|
180
|
+
hint: 'If the browser did not open, use this URL to continue sign-in without leaving the wizard.',
|
|
181
|
+
placeholder: 'authorization URL appears after start',
|
|
182
|
+
defaultValue: '',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
kind: 'text' as const,
|
|
186
|
+
id: 'providers.openai-callback-code',
|
|
187
|
+
label: 'OpenAI callback code or URL',
|
|
188
|
+
hint: 'Paste the callback code or redirected URL after completing browser sign-in.',
|
|
189
|
+
placeholder: 'code or callback URL',
|
|
190
|
+
defaultValue: '',
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
kind: 'action' as const,
|
|
194
|
+
id: 'providers.openai-subscription-finish',
|
|
195
|
+
action: 'finish-openai-subscription' as const,
|
|
196
|
+
label: 'Finish OpenAI subscription sign-in',
|
|
197
|
+
hint: 'Completes the pending OpenAI subscription login using the code above.',
|
|
198
|
+
defaultValue: 'Finish',
|
|
199
|
+
},
|
|
200
|
+
] : []),
|
|
201
|
+
]),
|
|
202
|
+
providerReviewField,
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
id: 'provider-access',
|
|
207
|
+
title: 'AI provider access',
|
|
208
|
+
shortLabel: 'Providers',
|
|
209
|
+
description: 'Review subscription posture and optionally add an OpenAI API key directly through the wizard.',
|
|
210
|
+
summaryTitle: 'Provider access summary',
|
|
211
|
+
summaryLines: [
|
|
212
|
+
`OpenAI subscription: ${openAiActive ? 'active' : openAiPending ? 'pending' : 'not detected'}`,
|
|
213
|
+
`OpenAI API key: ${openAiApiKeyConfigured ? 'configured' : 'not detected'}`,
|
|
214
|
+
`Provider credential references: ${providerSecretCount}`,
|
|
215
|
+
`Review: ${controller.getFieldValueLabel(providerReviewField)}`,
|
|
216
|
+
],
|
|
217
|
+
fields,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function buildProviderAccessStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
222
|
+
return buildProvidersStep(controller);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function buildDefaultModelStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
226
|
+
const routing = controller.runtimeSnapshot?.providerRouting;
|
|
227
|
+
const primarySelectionField: OnboardingWizardModelPickerFieldDefinition = {
|
|
228
|
+
kind: 'modelPicker',
|
|
229
|
+
id: 'default-model.primary-model',
|
|
230
|
+
label: 'Default provider + model',
|
|
231
|
+
hint: 'Open the nested model picker for the shell’s main routing target.',
|
|
232
|
+
target: 'main',
|
|
233
|
+
defaultSelection: {
|
|
234
|
+
providerId: normalizeText(routing?.primaryProviderId),
|
|
235
|
+
modelId: normalizeText(routing?.primaryModelId),
|
|
236
|
+
enabled: true,
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
const reasoningField: OnboardingWizardRadioFieldDefinition = {
|
|
240
|
+
kind: 'radio',
|
|
241
|
+
id: 'default-model.reasoning',
|
|
242
|
+
label: 'Reasoning effort',
|
|
243
|
+
hint: 'Use the shell reasoning default that matches the current provider routing.',
|
|
244
|
+
options: REASONING_OPTIONS,
|
|
245
|
+
defaultValue: normalizeText(routing?.primaryReasoningEffort) || 'medium',
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
id: 'default-model',
|
|
250
|
+
title: 'Default model',
|
|
251
|
+
shortLabel: 'Model',
|
|
252
|
+
description: 'Choose the default model routing the shell should use after onboarding.',
|
|
253
|
+
summaryTitle: 'Default model summary',
|
|
254
|
+
summaryLines: [
|
|
255
|
+
`Main: ${modelSelectionLabel(controller.modelSelectionState.get('main') ?? primarySelectionField.defaultSelection)}`,
|
|
256
|
+
`Reasoning: ${controller.getFieldValueLabel(reasoningField)}`,
|
|
257
|
+
],
|
|
258
|
+
fields: [
|
|
259
|
+
primarySelectionField,
|
|
260
|
+
reasoningField,
|
|
261
|
+
],
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function buildExternalServicesStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
266
|
+
const enabledCount = EXTERNAL_SURFACE_SPECS
|
|
267
|
+
.filter((surface) => controller.getBooleanFieldValue(surface.enabledFieldId, surface.defaultEnabled(controller.runtimeSnapshot)))
|
|
268
|
+
.length;
|
|
269
|
+
const fields: OnboardingWizardFieldDefinition[] = [];
|
|
270
|
+
|
|
271
|
+
for (const surface of EXTERNAL_SURFACE_SPECS) {
|
|
272
|
+
const enabled = controller.getBooleanFieldValue(surface.enabledFieldId, surface.defaultEnabled(controller.runtimeSnapshot));
|
|
273
|
+
fields.push({
|
|
274
|
+
kind: 'checklist',
|
|
275
|
+
id: surface.enabledFieldId,
|
|
276
|
+
label: surface.label,
|
|
277
|
+
hint: surface.hint,
|
|
278
|
+
defaultValue: surface.defaultEnabled(controller.runtimeSnapshot),
|
|
279
|
+
});
|
|
280
|
+
|
|
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;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
fields.push({
|
|
296
|
+
kind: setupField.kind,
|
|
297
|
+
id: setupField.id,
|
|
298
|
+
label: setupField.label,
|
|
299
|
+
hint: setupField.hint,
|
|
300
|
+
placeholder: setupField.placeholder,
|
|
301
|
+
defaultValue: setupField.defaultValue(controller.runtimeSnapshot),
|
|
302
|
+
required: controller.isRequiredExternalSetupField(setupField.id),
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
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',
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
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',
|
|
322
|
+
summaryLines: [
|
|
323
|
+
`${enabledCount} external surface(s) selected`,
|
|
324
|
+
`Secret policy: ${controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure')}`,
|
|
325
|
+
'Secrets remain masked and policy-controlled.',
|
|
326
|
+
],
|
|
327
|
+
fields,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function buildAccessStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
332
|
+
const step = buildAccountsStep(controller);
|
|
333
|
+
return {
|
|
334
|
+
...step,
|
|
335
|
+
id: 'access',
|
|
336
|
+
title: 'Access and accounts',
|
|
337
|
+
shortLabel: 'Access',
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function buildExperienceStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
342
|
+
return {
|
|
343
|
+
id: 'experience',
|
|
344
|
+
title: 'Shell experience',
|
|
345
|
+
shortLabel: 'Experience',
|
|
346
|
+
description: 'Tune review noise, guidance, and permission posture for day-to-day use.',
|
|
347
|
+
summaryTitle: 'Experience posture',
|
|
348
|
+
summaryLines: [
|
|
349
|
+
`Human-in-the-Loop (HITL): ${controller.getStringFieldValue('experience.hitl', controller.runtimeSnapshot?.runtimeDefaults.behavior.hitlMode ?? 'balanced')}`,
|
|
350
|
+
`Guidance: ${controller.getStringFieldValue('experience.guidance', controller.runtimeSnapshot?.runtimeDefaults.behavior.guidanceMode ?? 'minimal')}`,
|
|
351
|
+
`Permissions: ${controller.getStringFieldValue('experience.permissions', controller.runtimeSnapshot?.runtimeDefaults.permissionsMode ?? 'prompt')}`,
|
|
352
|
+
],
|
|
353
|
+
fields: [
|
|
354
|
+
{
|
|
355
|
+
kind: 'radio',
|
|
356
|
+
id: 'experience.hitl',
|
|
357
|
+
label: 'Human-in-the-Loop (HITL) mode',
|
|
358
|
+
hint: 'Choose how much operational activity should be surfaced.',
|
|
359
|
+
options: HITL_MODE_OPTIONS,
|
|
360
|
+
defaultValue: controller.runtimeSnapshot?.runtimeDefaults.behavior.hitlMode ?? 'balanced',
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
kind: 'radio',
|
|
364
|
+
id: 'experience.guidance',
|
|
365
|
+
label: 'Guidance verbosity',
|
|
366
|
+
hint: 'Choose how much explanation the shell should provide.',
|
|
367
|
+
options: GUIDANCE_MODE_OPTIONS,
|
|
368
|
+
defaultValue: controller.runtimeSnapshot?.runtimeDefaults.behavior.guidanceMode ?? 'minimal',
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
kind: 'radio',
|
|
372
|
+
id: 'experience.permissions',
|
|
373
|
+
label: 'Permission posture',
|
|
374
|
+
hint: 'Choose how aggressively the shell should ask before powerful actions.',
|
|
375
|
+
options: PERMISSION_MODE_OPTIONS,
|
|
376
|
+
defaultValue: controller.runtimeSnapshot?.runtimeDefaults.permissionsMode ?? 'prompt',
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function buildNetworkStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
383
|
+
const bindSettings = controller.runtimeSnapshot?.bindSettings;
|
|
384
|
+
const browserEnabled = controller.shouldEnableBrowserSurface();
|
|
385
|
+
const listenerEnabled = controller.shouldExposeHttpListenerNetworkFields();
|
|
386
|
+
const listenerWillApply = controller.shouldEnableHttpListener();
|
|
387
|
+
const controlPlaneRemote = controller.shouldExposeControlPlaneNetwork();
|
|
388
|
+
const networkEnabled = {
|
|
389
|
+
controlPlane: controlPlaneRemote,
|
|
390
|
+
httpListener: listenerEnabled,
|
|
391
|
+
web: browserEnabled,
|
|
392
|
+
};
|
|
393
|
+
const mode = controller.getStringFieldValue('network.mode', controller.runtimeDerived.step1_5NetworkMode);
|
|
394
|
+
const custom = mode === 'custom';
|
|
395
|
+
const fields: OnboardingWizardFieldDefinition[] = [
|
|
396
|
+
{
|
|
397
|
+
kind: 'radio',
|
|
398
|
+
id: 'network.mode',
|
|
399
|
+
label: 'Network mode',
|
|
400
|
+
hint: 'Choose Local Network for the default LAN setup, or Custom to set IP addresses and ports.',
|
|
401
|
+
options: NETWORK_MODE_OPTIONS,
|
|
402
|
+
defaultValue: controller.runtimeDerived.step1_5NetworkMode,
|
|
403
|
+
},
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
if (custom) {
|
|
407
|
+
const sharedIpField: OnboardingWizardChecklistFieldDefinition = {
|
|
408
|
+
kind: 'checklist',
|
|
409
|
+
id: 'network.shared-ip',
|
|
410
|
+
label: 'Use the same IP address for all services',
|
|
411
|
+
hint: 'When included, browser, GoodVibes service, and webhook listener network bindings share one IP address.',
|
|
412
|
+
defaultValue: controller.getSharedIpDefault(networkEnabled),
|
|
413
|
+
};
|
|
414
|
+
const sharedIp = controller.getBooleanFieldValue(sharedIpField.id, sharedIpField.defaultValue);
|
|
415
|
+
fields.push(sharedIpField);
|
|
416
|
+
if (sharedIp) {
|
|
417
|
+
fields.push({
|
|
418
|
+
kind: 'text',
|
|
419
|
+
id: 'network.shared-ip-address',
|
|
420
|
+
label: 'Shared IP address',
|
|
421
|
+
hint: 'IP address used by each enabled service.',
|
|
422
|
+
placeholder: '0.0.0.0',
|
|
423
|
+
defaultValue: controller.getSharedIpHostDefault(networkEnabled),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (controlPlaneRemote) {
|
|
428
|
+
fields.push({
|
|
429
|
+
kind: 'text',
|
|
430
|
+
id: 'network.service-port',
|
|
431
|
+
label: 'GoodVibes service port',
|
|
432
|
+
hint: 'Port for the background service and control plane.',
|
|
433
|
+
placeholder: '3421',
|
|
434
|
+
defaultValue: String(bindSettings?.controlPlane.port ?? 3421),
|
|
435
|
+
});
|
|
436
|
+
if (!sharedIp) {
|
|
437
|
+
fields.push({
|
|
438
|
+
kind: 'text',
|
|
439
|
+
id: 'network.service-ip',
|
|
440
|
+
label: 'GoodVibes service IP address',
|
|
441
|
+
hint: 'IP address for the background service and control plane.',
|
|
442
|
+
placeholder: '0.0.0.0',
|
|
443
|
+
defaultValue: normalizeText(bindSettings?.controlPlane.host) || '0.0.0.0',
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (browserEnabled) {
|
|
449
|
+
fields.push({
|
|
450
|
+
kind: 'text',
|
|
451
|
+
id: 'network.browser-port',
|
|
452
|
+
label: 'Browser surface port',
|
|
453
|
+
hint: 'Port for browser access to GoodVibes.',
|
|
454
|
+
placeholder: '3423',
|
|
455
|
+
defaultValue: String(bindSettings?.web.port ?? 3423),
|
|
456
|
+
});
|
|
457
|
+
if (!sharedIp) {
|
|
458
|
+
fields.push({
|
|
459
|
+
kind: 'text',
|
|
460
|
+
id: 'network.browser-ip',
|
|
461
|
+
label: 'Browser surface IP address',
|
|
462
|
+
hint: 'IP address for browser access.',
|
|
463
|
+
placeholder: '0.0.0.0',
|
|
464
|
+
defaultValue: normalizeText(bindSettings?.web.host) || '0.0.0.0',
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (listenerEnabled) {
|
|
470
|
+
fields.push({
|
|
471
|
+
kind: 'text',
|
|
472
|
+
id: 'network.webhook-port',
|
|
473
|
+
label: 'HTTP listener port',
|
|
474
|
+
hint: 'Port for incoming webhooks and events.',
|
|
475
|
+
placeholder: '3422',
|
|
476
|
+
defaultValue: String(bindSettings?.httpListener.port ?? 3422),
|
|
477
|
+
});
|
|
478
|
+
if (!sharedIp) {
|
|
479
|
+
fields.push({
|
|
480
|
+
kind: 'text',
|
|
481
|
+
id: 'network.webhook-ip',
|
|
482
|
+
label: 'HTTP listener IP address',
|
|
483
|
+
hint: 'IP address for incoming webhooks and events.',
|
|
484
|
+
placeholder: '0.0.0.0',
|
|
485
|
+
defaultValue: normalizeText(bindSettings?.httpListener.host) || '0.0.0.0',
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
id: 'network',
|
|
493
|
+
title: 'Network setup',
|
|
494
|
+
shortLabel: 'Network',
|
|
495
|
+
description: 'Choose the LAN default or customize IP addresses and ports for the enabled browser, service, and listener surfaces.',
|
|
496
|
+
summaryTitle: 'Bind posture',
|
|
497
|
+
summaryLines: [
|
|
498
|
+
`Mode: ${custom ? 'custom' : 'local network default'}`,
|
|
499
|
+
`Browser surface: ${browserEnabled ? 'enabled' : 'not selected'}`,
|
|
500
|
+
`HTTP listener: ${listenerWillApply ? 'enabled' : listenerEnabled ? 'available for selected external apps' : 'not selected'}`,
|
|
501
|
+
],
|
|
502
|
+
fields,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function buildAccountsStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
507
|
+
const subscriptionsAck = controller.runtimeDerived.reopenEditAcknowledgements.subscriptions;
|
|
508
|
+
const authAck = controller.runtimeDerived.reopenEditAcknowledgements.auth;
|
|
509
|
+
const auth = controller.runtimeSnapshot?.auth.snapshot;
|
|
510
|
+
const needsAuthBootstrap = controller.requiresAuthBootstrap();
|
|
511
|
+
const needsExistingAuthAcknowledgement = controller.hasServerCapabilitiesSelected()
|
|
512
|
+
&& !needsAuthBootstrap
|
|
513
|
+
&& controller.hasAdminAuthUser();
|
|
514
|
+
const fields: OnboardingWizardFieldDefinition[] = [];
|
|
515
|
+
|
|
516
|
+
if (needsAuthBootstrap) {
|
|
517
|
+
const defaultAdminUsername = controller.getDefaultAdminUsername();
|
|
518
|
+
fields.push(
|
|
519
|
+
{
|
|
520
|
+
kind: 'text',
|
|
521
|
+
id: 'accounts.admin-username',
|
|
522
|
+
label: 'Local auth admin username',
|
|
523
|
+
hint: 'Required before any background service, browser surface, or listener is exposed.',
|
|
524
|
+
placeholder: defaultAdminUsername,
|
|
525
|
+
defaultValue: defaultAdminUsername,
|
|
526
|
+
required: true,
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
kind: 'masked',
|
|
530
|
+
id: 'accounts.admin-password',
|
|
531
|
+
label: 'Local auth admin password',
|
|
532
|
+
hint: controller.hasBootstrapCredentialPresent()
|
|
533
|
+
? 'Creates a new local admin, removes the bootstrap credential file, and retires the bootstrap admin before LAN/server settings are applied.'
|
|
534
|
+
: 'Creates the first local admin user and an initial session before LAN/server settings are applied.',
|
|
535
|
+
placeholder: 'password required',
|
|
536
|
+
defaultValue: '',
|
|
537
|
+
required: true,
|
|
538
|
+
},
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
fields.push(
|
|
543
|
+
{
|
|
544
|
+
kind: 'acknowledgement',
|
|
545
|
+
id: 'accounts.subscriptions',
|
|
546
|
+
label: 'Confirm stored subscription state',
|
|
547
|
+
hint: subscriptionsAck.detail,
|
|
548
|
+
defaultValue: subscriptionsAck.accepted,
|
|
549
|
+
required: controller.mode !== 'new' && subscriptionsAck.required,
|
|
550
|
+
reason: subscriptionsAck.reason,
|
|
551
|
+
target: 'subscriptions',
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
kind: 'acknowledgement',
|
|
555
|
+
id: 'accounts.auth',
|
|
556
|
+
label: 'Confirm local auth posture',
|
|
557
|
+
hint: authAck.detail,
|
|
558
|
+
defaultValue: authAck.accepted,
|
|
559
|
+
required: needsExistingAuthAcknowledgement || (controller.mode !== 'new' && authAck.required),
|
|
560
|
+
reason: authAck.reason,
|
|
561
|
+
target: 'auth',
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
kind: 'status',
|
|
565
|
+
id: 'accounts.bootstrap',
|
|
566
|
+
label: 'Bootstrap credential hint',
|
|
567
|
+
hint: needsAuthBootstrap
|
|
568
|
+
? 'The wizard will create local auth before applying network-accessible settings.'
|
|
569
|
+
: 'Masked auth state stays visible without leaking sensitive values.',
|
|
570
|
+
defaultValue: needsAuthBootstrap
|
|
571
|
+
? controller.hasBootstrapCredentialPresent() ? 'Bootstrap replacement required' : 'Local admin required'
|
|
572
|
+
: auth?.bootstrapCredentialPresent ? 'Configured' : 'Not detected',
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
kind: 'status',
|
|
576
|
+
id: 'accounts.user-store',
|
|
577
|
+
label: 'Local auth store path',
|
|
578
|
+
hint: 'Carry the current auth store location into edit/review mode.',
|
|
579
|
+
defaultValue: normalizeText(auth?.userStorePath) || 'No local auth store path',
|
|
580
|
+
},
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
id: 'access',
|
|
585
|
+
title: 'Subscriptions and auth review',
|
|
586
|
+
shortLabel: 'Accounts',
|
|
587
|
+
description: needsAuthBootstrap
|
|
588
|
+
? '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.',
|
|
590
|
+
summaryTitle: 'Stored account state',
|
|
591
|
+
summaryLines: [
|
|
592
|
+
`Subscriptions: ${controller.runtimeSnapshot?.subscriptions.active.length ?? 0} active / ${controller.runtimeSnapshot?.subscriptions.pending.length ?? 0} pending`,
|
|
593
|
+
`Auth: ${auth?.userCount ?? 0} users / ${auth?.sessionCount ?? 0} sessions`,
|
|
594
|
+
needsAuthBootstrap
|
|
595
|
+
? controller.hasBootstrapCredentialPresent()
|
|
596
|
+
? 'Bootstrap credentials will be replaced before network settings are applied'
|
|
597
|
+
: 'Local admin will be created before network settings are applied'
|
|
598
|
+
: auth?.bootstrapCredentialPresent ? 'Bootstrap credential file present' : 'No bootstrap credential file detected',
|
|
599
|
+
],
|
|
600
|
+
fields,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export function buildReviewStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
605
|
+
return {
|
|
606
|
+
id: 'review',
|
|
607
|
+
title: 'Review and completion',
|
|
608
|
+
shortLabel: 'Review',
|
|
609
|
+
description: 'Review the selected settings and apply them directly from the wizard.',
|
|
610
|
+
summaryTitle: 'Review posture',
|
|
611
|
+
summaryLines: [
|
|
612
|
+
`${controller.dirtyStepCount} dirty step(s)`,
|
|
613
|
+
`${controller.buildApplyRequest().operations.length} operation(s) ready to apply`,
|
|
614
|
+
`Pending picker: ${controller.pendingModelPickerTarget ?? 'none'}`,
|
|
615
|
+
controller.isEditingTextField() ? `Editing: ${controller.editingFieldId}` : 'Ready for apply/verify',
|
|
616
|
+
],
|
|
617
|
+
fields: [
|
|
618
|
+
{
|
|
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(),
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
kind: 'action',
|
|
634
|
+
id: 'review.apply',
|
|
635
|
+
action: 'apply',
|
|
636
|
+
label: 'Apply settings and verify',
|
|
637
|
+
hint: 'Persist the wizard settings, write completion markers, and verify the resulting runtime state.',
|
|
638
|
+
defaultValue: 'Ready',
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
};
|
|
642
|
+
}
|