@pellux/goodvibes-tui 0.19.27 → 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.
- package/CHANGELOG.md +5 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/cli/bundle-command.ts +3 -2
- package/src/cli/entrypoint.ts +2 -2
- package/src/cli/help.ts +1 -1
- package/src/cli/status.ts +9 -9
- package/src/cli/tui-startup.ts +4 -4
- package/src/input/handler-interactions.ts +14 -1
- package/src/input/handler-onboarding.ts +18 -82
- package/src/input/handler.ts +1 -1
- package/src/input/onboarding/handler-onboarding-routes.ts +31 -15
- package/src/input/onboarding/onboarding-wizard-apply.ts +0 -17
- package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -2
- package/src/input/onboarding/onboarding-wizard-rules.ts +18 -5
- package/src/input/onboarding/onboarding-wizard-state.ts +8 -2
- package/src/input/onboarding/onboarding-wizard-steps.ts +118 -59
- package/src/input/onboarding/onboarding-wizard-types.ts +5 -0
- package/src/input/onboarding/onboarding-wizard.ts +70 -5
- package/src/main.ts +2 -1
- package/src/renderer/onboarding/onboarding-wizard.ts +115 -48
- package/src/runtime/onboarding/apply.ts +9 -82
- package/src/runtime/onboarding/markers.ts +41 -55
- package/src/runtime/onboarding/state.ts +6 -6
- package/src/runtime/onboarding/types.ts +20 -26
- package/src/runtime/onboarding/verify.ts +2 -64
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/mgd34msu/goodvibes-tui)
|
|
6
6
|
|
|
7
7
|
A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.28",
|
|
4
4
|
"description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.ts",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"@anthropic-ai/vertex-sdk": "^0.16.0",
|
|
92
92
|
"@ast-grep/napi": "^0.42.0",
|
|
93
93
|
"@aws/bedrock-token-generator": "^1.1.0",
|
|
94
|
-
"@pellux/goodvibes-sdk": "^0.25.
|
|
94
|
+
"@pellux/goodvibes-sdk": "^0.25.2",
|
|
95
95
|
"bash-language-server": "^5.6.0",
|
|
96
96
|
"fuse.js": "^7.1.0",
|
|
97
97
|
"graphql": "^16.13.2",
|
|
@@ -5,6 +5,7 @@ import { createShellPathService } from '@pellux/goodvibes-sdk/platform/runtime/s
|
|
|
5
5
|
import { listProviderRuntimeSnapshots } from '@pellux/goodvibes-sdk/platform/providers/runtime-snapshot';
|
|
6
6
|
import { createRuntimeServices } from '../runtime/services.ts';
|
|
7
7
|
import { createRuntimeStore } from '../runtime/store/index.ts';
|
|
8
|
+
import { getOnboardingCheckMarkerPath } from '../runtime/onboarding/index.ts';
|
|
8
9
|
import { CONFIG_SCHEMA } from '../config/index.ts';
|
|
9
10
|
import { SecretsManager } from '../config/secrets.ts';
|
|
10
11
|
import type { ConfigKey } from '../config/index.ts';
|
|
@@ -178,8 +179,8 @@ export async function handleBundleCommand(runtime: CliCommandRuntime): Promise<C
|
|
|
178
179
|
},
|
|
179
180
|
secrets: await secrets.inspect(),
|
|
180
181
|
onboarding: {
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
userMarker: existsSync(getOnboardingCheckMarkerPath(shellPaths, 'user')),
|
|
183
|
+
projectMarker: existsSync(getOnboardingCheckMarkerPath(shellPaths, 'project')),
|
|
183
184
|
},
|
|
184
185
|
};
|
|
185
186
|
const targetPath = shellPaths.resolveWorkspacePath(outputPath);
|
package/src/cli/entrypoint.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { ConfigManager } from '../config/index.ts';
|
|
4
|
-
import {
|
|
4
|
+
import { readOnboardingCheckMarkers } from '../runtime/onboarding/index.ts';
|
|
5
5
|
import { GlobalNetworkTransportInstaller } from '@pellux/goodvibes-sdk/platform/runtime/network/index';
|
|
6
6
|
import { createShellPathService } from '@pellux/goodvibes-sdk/platform/runtime/shell-paths';
|
|
7
7
|
import { configureActivityLogger } from '@pellux/goodvibes-sdk/platform/utils/logger';
|
|
@@ -125,7 +125,7 @@ export async function prepareShellCliRuntime(
|
|
|
125
125
|
const userStorePath = shellPaths.resolveUserPath('tui', 'auth-users.json');
|
|
126
126
|
const bootstrapCredentialPath = shellPaths.resolveUserPath('tui', 'auth-bootstrap.txt');
|
|
127
127
|
const operatorTokenPath = join(bootstrapHomeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
|
|
128
|
-
const onboardingMarkers =
|
|
128
|
+
const onboardingMarkers = readOnboardingCheckMarkers(shellPaths);
|
|
129
129
|
const service = await buildCliServicePosture({
|
|
130
130
|
configManager,
|
|
131
131
|
workingDirectory: bootstrapWorkingDir,
|
package/src/cli/help.ts
CHANGED
|
@@ -124,7 +124,7 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
124
124
|
},
|
|
125
125
|
onboarding: {
|
|
126
126
|
usage: ['onboarding', 'setup', 'onboarding status'],
|
|
127
|
-
summary: 'Open the setup wizard
|
|
127
|
+
summary: 'Open the setup wizard, or inspect whether onboarding has already been shown for this user.',
|
|
128
128
|
examples: ['onboarding', 'onboarding status'],
|
|
129
129
|
},
|
|
130
130
|
status: {
|
package/src/cli/status.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
|
|
2
|
-
import type {
|
|
2
|
+
import type { OnboardingCheckMarkersState } from '../runtime/onboarding/index.ts';
|
|
3
3
|
import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
4
4
|
import { isNetworkFacing } from './network-posture.ts';
|
|
5
5
|
import type { GoodVibesCliOutputFormat } from './types.ts';
|
|
@@ -9,7 +9,7 @@ export interface CliStatusOptions {
|
|
|
9
9
|
readonly configManager: Pick<ConfigManager, 'get'>;
|
|
10
10
|
readonly workingDirectory: string;
|
|
11
11
|
readonly homeDirectory: string;
|
|
12
|
-
readonly onboardingMarkers?:
|
|
12
|
+
readonly onboardingMarkers?: OnboardingCheckMarkersState;
|
|
13
13
|
readonly auth?: CliAuthStatus;
|
|
14
14
|
readonly service?: CliServicePosture;
|
|
15
15
|
readonly doctor?: boolean;
|
|
@@ -63,7 +63,7 @@ export interface CliStatusSnapshot {
|
|
|
63
63
|
readonly web: ReturnType<typeof resolveRuntimeEndpointBinding> & { readonly enabled: unknown };
|
|
64
64
|
};
|
|
65
65
|
readonly onboarding: {
|
|
66
|
-
readonly
|
|
66
|
+
readonly checked: boolean;
|
|
67
67
|
readonly scope: string;
|
|
68
68
|
readonly updatedAt: number | null;
|
|
69
69
|
};
|
|
@@ -167,13 +167,13 @@ export function buildCliDoctorFindings(options: CliStatusOptions): readonly CliD
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
if (!marker?.
|
|
170
|
+
if (!marker?.exists) {
|
|
171
171
|
findings.push({
|
|
172
172
|
id: 'onboarding-incomplete',
|
|
173
173
|
area: 'onboarding',
|
|
174
174
|
severity: 'warning',
|
|
175
|
-
summary: 'Onboarding has not been
|
|
176
|
-
cause: 'No
|
|
175
|
+
summary: 'Onboarding has not been shown for this user.',
|
|
176
|
+
cause: 'No global user onboarding check marker was found.',
|
|
177
177
|
impact: 'Important service, network, provider, auth, or permission choices may still be implicit defaults.',
|
|
178
178
|
action: 'Run /onboarding in the TUI or goodvibes onboarding status to review setup state.',
|
|
179
179
|
});
|
|
@@ -277,7 +277,7 @@ export function buildCliStatusSnapshot(options: CliStatusOptions): CliStatusSnap
|
|
|
277
277
|
web: { enabled: config.get('web.enabled'), ...webBinding },
|
|
278
278
|
},
|
|
279
279
|
onboarding: {
|
|
280
|
-
|
|
280
|
+
checked: Boolean(marker?.exists),
|
|
281
281
|
scope: marker?.scope ?? 'none',
|
|
282
282
|
updatedAt: marker?.payload?.updatedAt ?? null,
|
|
283
283
|
},
|
|
@@ -344,7 +344,7 @@ export function renderCliStatus(options: CliStatusOptions): string {
|
|
|
344
344
|
bindLine('web', webEnabled, webBinding),
|
|
345
345
|
'',
|
|
346
346
|
'Onboarding:',
|
|
347
|
-
`
|
|
347
|
+
` checked: ${marker?.exists ? 'yes' : 'no'}`,
|
|
348
348
|
` scope: ${marker?.scope ?? 'none'}`,
|
|
349
349
|
` updatedAt: ${marker?.payload ? new Date(marker.payload.updatedAt).toISOString() : 'n/a'}`,
|
|
350
350
|
];
|
|
@@ -372,7 +372,7 @@ export function renderOnboardingCliStatus(options: CliStatusOptions): string {
|
|
|
372
372
|
const marker = options.onboardingMarkers?.effective;
|
|
373
373
|
return [
|
|
374
374
|
'GoodVibes onboarding status',
|
|
375
|
-
`
|
|
375
|
+
` checked: ${marker?.exists ? 'yes' : 'no'}`,
|
|
376
376
|
` scope: ${marker?.scope ?? 'none'}`,
|
|
377
377
|
` source: ${marker?.payload?.source ?? 'n/a'}`,
|
|
378
378
|
` mode: ${marker?.payload?.mode ?? 'n/a'}`,
|
package/src/cli/tui-startup.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CommandContext, CommandRegistry } from '../input/command-registry.ts';
|
|
2
2
|
import type { InputHandler } from '../input/handler.ts';
|
|
3
|
-
import {
|
|
3
|
+
import { readOnboardingCheckMarker } from '../runtime/onboarding/index.ts';
|
|
4
4
|
import type { GoodVibesCliParseResult } from './types.ts';
|
|
5
5
|
|
|
6
6
|
export function applyInitialTuiCliState(options: {
|
|
@@ -8,11 +8,11 @@ export function applyInitialTuiCliState(options: {
|
|
|
8
8
|
readonly input: InputHandler;
|
|
9
9
|
readonly commandRegistry: CommandRegistry;
|
|
10
10
|
readonly commandContext: CommandContext;
|
|
11
|
-
readonly shellPaths: Parameters<typeof
|
|
11
|
+
readonly shellPaths: Parameters<typeof readOnboardingCheckMarker>[0];
|
|
12
12
|
readonly render: () => void;
|
|
13
13
|
}): void {
|
|
14
14
|
const { cli, input, commandRegistry, commandContext, shellPaths, render } = options;
|
|
15
|
-
const
|
|
15
|
+
const globalOnboardingMarker = readOnboardingCheckMarker(shellPaths, 'user');
|
|
16
16
|
if (cli.command === 'onboarding') {
|
|
17
17
|
input.openOnboardingWizard({ mode: 'edit', reset: true });
|
|
18
18
|
} else if (cli.command === 'sessions' && cli.commandArgs[0] === 'resume') {
|
|
@@ -20,7 +20,7 @@ export function applyInitialTuiCliState(options: {
|
|
|
20
20
|
if (target) {
|
|
21
21
|
void commandRegistry.execute('session', ['resume', target], commandContext).then(() => render());
|
|
22
22
|
}
|
|
23
|
-
} else if (!
|
|
23
|
+
} else if (!globalOnboardingMarker.exists) {
|
|
24
24
|
input.openOnboardingWizard({ mode: 'new', reset: true });
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildProviderAccountSnapshot } from '@pellux/goodvibes-sdk/platform/runtime/provider-accounts/registry';
|
|
2
2
|
import type { OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
|
|
3
|
-
import { collectOnboardingSnapshot } from '../runtime/onboarding/index.ts';
|
|
3
|
+
import { collectOnboardingSnapshot, readOnboardingCheckMarker, writeOnboardingCheckMarker } from '../runtime/onboarding/index.ts';
|
|
4
4
|
import { cleanupMarkerRegistry, expandPrompt, findMarkerAtPos, handleBlockCopy, handleBlockRerun, handleBlockSave, handleBlockToggle, handleBookmark, handleClipboardPaste, handleCopy, handleCtrlC, handleDiffApply, registerPaste } from './handler-content-actions.ts';
|
|
5
5
|
import { clearModalStack, handleEscape, modalOpened } from './handler-modal-stack.ts';
|
|
6
6
|
import { openOnboardingWizardState, type OpenOnboardingWizardOptions } from './handler-ui-state.ts';
|
|
@@ -11,6 +11,19 @@ export function openOnboardingWizardForHandler(
|
|
|
11
11
|
modeOrOptions: OnboardingWizardMode | OpenOnboardingWizardOptions = 'new',
|
|
12
12
|
): void {
|
|
13
13
|
const options = typeof modeOrOptions === 'string' ? { mode: modeOrOptions } : modeOrOptions;
|
|
14
|
+
const userMarker = readOnboardingCheckMarker(handler.uiServices.environment.shellPaths, 'user');
|
|
15
|
+
if (!userMarker.payload) {
|
|
16
|
+
try {
|
|
17
|
+
writeOnboardingCheckMarker(handler.uiServices.environment.shellPaths, {
|
|
18
|
+
scope: 'user',
|
|
19
|
+
source: 'wizard',
|
|
20
|
+
mode: options.mode ?? 'new',
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
handler.commandContext?.print?.(`Onboarding check marker could not be written: ${message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
14
27
|
if (!handler.modalStack.includes('onboarding')) handler.modalOpened('onboarding');
|
|
15
28
|
handler.clearOnboardingModelPickerCancelState();
|
|
16
29
|
openOnboardingWizardState(handler.onboardingWizard, options);
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
3
1
|
import { createOAuthLocalListener } from '@pellux/goodvibes-sdk/platform/config/oauth-local-listener';
|
|
4
2
|
import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibes-sdk/platform/config/openai-codex-auth';
|
|
5
3
|
import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils/open-external';
|
|
6
4
|
import { buildProviderAccountSnapshot } from '@pellux/goodvibes-sdk/platform/runtime/provider-accounts/registry';
|
|
7
5
|
import { OnboardingWizardController, type OnboardingWizardAction } from './onboarding/onboarding-wizard.ts';
|
|
8
|
-
import { applyOnboardingRequest, collectOnboardingSnapshot,
|
|
9
|
-
import type {
|
|
6
|
+
import { applyOnboardingRequest, collectOnboardingSnapshot, verifyOnboardingRequest } from '../runtime/onboarding/index.ts';
|
|
7
|
+
import type { OnboardingApplyRequest, OnboardingVerificationItem } from '../runtime/onboarding/index.ts';
|
|
10
8
|
import type { ModelPickerTarget } from './model-picker.ts';
|
|
11
9
|
import { captureOnboardingWizardSnapshot, restoreOnboardingWizardSnapshot } from './handler-ui-state.ts';
|
|
12
10
|
import type { InputHandler } from './handler.ts';
|
|
13
11
|
|
|
14
|
-
interface CompletionMarkerSnapshot {
|
|
15
|
-
readonly path: string;
|
|
16
|
-
readonly previous: string | null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
12
|
export interface OnboardingRuntimePosture {
|
|
20
13
|
readonly serviceEnabled: boolean;
|
|
21
14
|
readonly serviceAutostart: boolean;
|
|
@@ -38,44 +31,6 @@ function extractAuthorizationCode(input: string): string | null {
|
|
|
38
31
|
}
|
|
39
32
|
}
|
|
40
33
|
|
|
41
|
-
function splitCompletionMarkerOperations(request: OnboardingApplyRequest): {
|
|
42
|
-
readonly settingsRequest: OnboardingApplyRequest;
|
|
43
|
-
readonly markerRequest: OnboardingApplyRequest;
|
|
44
|
-
} {
|
|
45
|
-
const markerOperations = request.operations.filter((operation) => operation.kind === 'set-completion-marker');
|
|
46
|
-
const settingsOperations = request.operations.filter((operation) => operation.kind !== 'set-completion-marker');
|
|
47
|
-
return {
|
|
48
|
-
settingsRequest: { ...request, operations: settingsOperations },
|
|
49
|
-
markerRequest: { ...request, operations: markerOperations },
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function snapshotCompletionMarkers(
|
|
54
|
-
shellPaths: OnboardingShellPaths,
|
|
55
|
-
operations: readonly OnboardingApplyOperation[],
|
|
56
|
-
): readonly CompletionMarkerSnapshot[] {
|
|
57
|
-
return operations
|
|
58
|
-
.filter((operation) => operation.kind === 'set-completion-marker')
|
|
59
|
-
.map((operation) => {
|
|
60
|
-
const path = getOnboardingCompletionMarkerPath(shellPaths, operation.scope);
|
|
61
|
-
return {
|
|
62
|
-
path,
|
|
63
|
-
previous: existsSync(path) ? readFileSync(path, 'utf-8') : null,
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function restoreCompletionMarkers(snapshots: readonly CompletionMarkerSnapshot[]): void {
|
|
69
|
-
for (const snapshot of snapshots) {
|
|
70
|
-
if (snapshot.previous === null) {
|
|
71
|
-
rmSync(snapshot.path, { force: true });
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
mkdirSync(dirname(snapshot.path), { recursive: true });
|
|
75
|
-
writeFileSync(snapshot.path, snapshot.previous, 'utf-8');
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
34
|
function isLoopbackHostValue(value: string | null | undefined): boolean {
|
|
80
35
|
const normalized = (value ?? '').trim().toLowerCase();
|
|
81
36
|
if (normalized.length === 0) return false;
|
|
@@ -161,7 +116,7 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
|
|
|
161
116
|
const blockers = handler.onboardingWizard.getBlockingFieldLabels();
|
|
162
117
|
if (blockers.length > 0) {
|
|
163
118
|
handler.commandContext?.print?.([
|
|
164
|
-
'Onboarding needs
|
|
119
|
+
'Onboarding needs these fields before applying.',
|
|
165
120
|
...blockers.map((label) => ` ${label}`),
|
|
166
121
|
].join('\n'));
|
|
167
122
|
handler.requestRender();
|
|
@@ -169,7 +124,6 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
|
|
|
169
124
|
}
|
|
170
125
|
|
|
171
126
|
const request = handler.onboardingWizard.buildApplyRequest();
|
|
172
|
-
const { settingsRequest, markerRequest } = splitCompletionMarkerOperations(request);
|
|
173
127
|
const deps = {
|
|
174
128
|
config: handler.uiServices.platform.configManager,
|
|
175
129
|
secrets: handler.uiServices.platform.secretsManager,
|
|
@@ -181,12 +135,12 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
|
|
|
181
135
|
let verificationItems: readonly OnboardingVerificationItem[] = [];
|
|
182
136
|
handler.onboardingApplyPending = true;
|
|
183
137
|
try {
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
verificationItems =
|
|
138
|
+
const applied = await applyOnboardingRequest(deps, request);
|
|
139
|
+
const verification = await verifyOnboardingRequest(deps, request);
|
|
140
|
+
verificationItems = verification.items;
|
|
187
141
|
appliedErrors = [
|
|
188
|
-
...
|
|
189
|
-
...
|
|
142
|
+
...applied.errors.map((error) => `apply ${error.kind}: ${error.message}`),
|
|
143
|
+
...verification.items
|
|
190
144
|
.filter((item) => item.status !== 'pass')
|
|
191
145
|
.map((item) => `verify ${item.id}: ${item.message}`),
|
|
192
146
|
];
|
|
@@ -194,29 +148,11 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
|
|
|
194
148
|
if (appliedErrors.length === 0) {
|
|
195
149
|
const activationVerification = await handler.restartOnboardingExternalServicesIfNeeded(request);
|
|
196
150
|
const runtimeVerification = [...activationVerification, ...handler.verifyOnboardingRuntimePosture(request)];
|
|
197
|
-
verificationItems = [...
|
|
151
|
+
verificationItems = [...verification.items, ...runtimeVerification];
|
|
198
152
|
appliedErrors = runtimeVerification
|
|
199
153
|
.filter((item) => item.status === 'fail')
|
|
200
154
|
.map((item) => `verify ${item.id}: ${item.message}`);
|
|
201
155
|
}
|
|
202
|
-
|
|
203
|
-
if (appliedErrors.length === 0 && markerRequest.operations.length > 0) {
|
|
204
|
-
const markerSnapshots = snapshotCompletionMarkers(deps.shellPaths, markerRequest.operations);
|
|
205
|
-
const markerApplied = await applyOnboardingRequest(deps, markerRequest);
|
|
206
|
-
const finalVerification = await verifyOnboardingRequest(deps, request);
|
|
207
|
-
const runtimeVerification = handler.verifyOnboardingRuntimePosture(request);
|
|
208
|
-
verificationItems = [...finalVerification.items, ...runtimeVerification];
|
|
209
|
-
appliedErrors = [
|
|
210
|
-
...markerApplied.errors.map((error) => `apply ${error.kind}: ${error.message}`),
|
|
211
|
-
...finalVerification.items
|
|
212
|
-
.filter((item) => item.status !== 'pass')
|
|
213
|
-
.map((item) => `verify ${item.id}: ${item.message}`),
|
|
214
|
-
...runtimeVerification
|
|
215
|
-
.filter((item) => item.status === 'fail')
|
|
216
|
-
.map((item) => `verify ${item.id}: ${item.message}`),
|
|
217
|
-
];
|
|
218
|
-
if (appliedErrors.length > 0) restoreCompletionMarkers(markerSnapshots);
|
|
219
|
-
}
|
|
220
156
|
} catch (error) {
|
|
221
157
|
handler.commandContext?.print?.([
|
|
222
158
|
'Onboarding apply did not complete.',
|
|
@@ -625,7 +561,7 @@ export function verifyOnboardingRuntimePostureForHandler(handler: InputHandler,
|
|
|
625
561
|
}
|
|
626
562
|
|
|
627
563
|
const auth = handler.uiServices.platform.localUserAuthManager.inspect();
|
|
628
|
-
const
|
|
564
|
+
const hasLocalAuth = auth.users.length > 0;
|
|
629
565
|
const items: OnboardingVerificationItem[] = [];
|
|
630
566
|
|
|
631
567
|
items.push({
|
|
@@ -638,19 +574,19 @@ export function verifyOnboardingRuntimePostureForHandler(handler: InputHandler,
|
|
|
638
574
|
});
|
|
639
575
|
items.push({
|
|
640
576
|
id: 'runtime:auth-posture',
|
|
641
|
-
status:
|
|
642
|
-
message:
|
|
643
|
-
? 'Local
|
|
644
|
-
: 'Network-capable surfaces require local
|
|
577
|
+
status: hasLocalAuth && !auth.bootstrapCredentialPresent ? 'pass' : 'fail',
|
|
578
|
+
message: hasLocalAuth && !auth.bootstrapCredentialPresent
|
|
579
|
+
? 'Local auth is configured and bootstrap credentials are not present.'
|
|
580
|
+
: 'Network-capable surfaces require local auth with no bootstrap credential file.',
|
|
645
581
|
target: 'auth',
|
|
646
582
|
});
|
|
647
583
|
if (posture.remoteExposure) {
|
|
648
584
|
items.push({
|
|
649
585
|
id: 'runtime:remote-auth-gate',
|
|
650
|
-
status:
|
|
651
|
-
message:
|
|
652
|
-
? 'Remote-capable bind settings have local
|
|
653
|
-
: 'Remote-capable bind settings cannot be applied without local
|
|
586
|
+
status: hasLocalAuth ? 'pass' : 'fail',
|
|
587
|
+
message: hasLocalAuth
|
|
588
|
+
? 'Remote-capable bind settings have local auth available.'
|
|
589
|
+
: 'Remote-capable bind settings cannot be applied without local auth.',
|
|
654
590
|
target: 'auth',
|
|
655
591
|
});
|
|
656
592
|
}
|
package/src/input/handler.ts
CHANGED
|
@@ -31,7 +31,7 @@ import { OnboardingWizardController, type OnboardingWizardAction, type Onboardin
|
|
|
31
31
|
import {
|
|
32
32
|
applyOnboardingRequest,
|
|
33
33
|
collectOnboardingSnapshot,
|
|
34
|
-
|
|
34
|
+
getOnboardingCheckMarkerPath,
|
|
35
35
|
verifyOnboardingRequest,
|
|
36
36
|
} from '../runtime/onboarding/index.ts';
|
|
37
37
|
import type {
|
|
@@ -28,6 +28,20 @@ function activateSelection(state: OnboardingRouteState): void {
|
|
|
28
28
|
if (action !== null) state.onAction?.(action);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function isEnterKey(token: InputToken): boolean {
|
|
32
|
+
return token.type === 'key' && (token.logicalName === 'enter' || token.logicalName === 'return');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getKeyTextInput(token: Extract<InputToken, { type: 'key' }>): string | null {
|
|
36
|
+
if (token.ctrl || token.meta) return null;
|
|
37
|
+
if (token.logicalName === 'space') return ' ';
|
|
38
|
+
if (token.logicalName.length !== 1) return null;
|
|
39
|
+
if (token.shift && token.logicalName >= 'a' && token.logicalName <= 'z') {
|
|
40
|
+
return token.logicalName.toUpperCase();
|
|
41
|
+
}
|
|
42
|
+
return token.logicalName;
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
export function handleOnboardingWizardToken(state: OnboardingRouteState, token: InputToken): boolean {
|
|
32
46
|
if (!state.onboardingWizard.active) return false;
|
|
33
47
|
|
|
@@ -48,12 +62,13 @@ export function handleOnboardingWizardToken(state: OnboardingRouteState, token:
|
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
if (editing) {
|
|
51
|
-
if (token
|
|
65
|
+
if (isEnterKey(token)) {
|
|
52
66
|
state.onboardingWizard.commitEdit();
|
|
53
67
|
} else if (token.logicalName === 'backspace') {
|
|
54
68
|
state.onboardingWizard.editBackspace();
|
|
55
|
-
} else
|
|
56
|
-
|
|
69
|
+
} else {
|
|
70
|
+
const textInput = getKeyTextInput(token);
|
|
71
|
+
if (textInput !== null) state.onboardingWizard.editChar(textInput);
|
|
57
72
|
}
|
|
58
73
|
} else if (token.logicalName === 'left') {
|
|
59
74
|
state.onboardingWizard.prevStep();
|
|
@@ -74,22 +89,23 @@ export function handleOnboardingWizardToken(state: OnboardingRouteState, token:
|
|
|
74
89
|
state.onboardingWizard.selectFirst(visibleFields);
|
|
75
90
|
} else if (token.logicalName === 'end') {
|
|
76
91
|
state.onboardingWizard.selectLast(visibleFields);
|
|
77
|
-
} else
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
} else {
|
|
93
|
+
const textInput = getKeyTextInput(token);
|
|
94
|
+
if (textInput !== null && state.onboardingWizard.beginSelectedTextInput(textInput)) {
|
|
95
|
+
state.requestRender();
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
if (isEnterKey(token) || token.logicalName === 'space') {
|
|
99
|
+
activateSelection(state);
|
|
100
|
+
} else if (token.logicalName === 'backspace') {
|
|
101
|
+
state.onboardingWizard.editBackspace();
|
|
102
|
+
}
|
|
81
103
|
}
|
|
82
104
|
} else if (token.type === 'text') {
|
|
83
105
|
if (editing) {
|
|
84
106
|
state.onboardingWizard.editChar(token.value);
|
|
85
|
-
} else if (token.value
|
|
86
|
-
|
|
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);
|
|
107
|
+
} else if (state.onboardingWizard.beginSelectedTextInput(token.value)) {
|
|
108
|
+
// Direct typing into selected inputs behaves like a real form field.
|
|
93
109
|
} else if (token.value === ' ') {
|
|
94
110
|
activateSelection(state);
|
|
95
111
|
} else if (/^[1-9]$/.test(token.value)) {
|
|
@@ -131,23 +131,6 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
|
|
|
131
131
|
acknowledge('subscriptions', 'accounts.subscriptions');
|
|
132
132
|
acknowledge('auth', 'accounts.auth');
|
|
133
133
|
|
|
134
|
-
if (controller.getBooleanFieldValue('review.project-marker', true)) {
|
|
135
|
-
operations.push({
|
|
136
|
-
kind: 'set-completion-marker',
|
|
137
|
-
scope: 'project',
|
|
138
|
-
completed: true,
|
|
139
|
-
payload: { source: 'wizard', mode: controller.mode },
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
if (controller.getBooleanFieldValue('review.user-marker', controller.defaultReviewUserMarker())) {
|
|
143
|
-
operations.push({
|
|
144
|
-
kind: 'set-completion-marker',
|
|
145
|
-
scope: 'user',
|
|
146
|
-
completed: true,
|
|
147
|
-
payload: { source: 'wizard', mode: controller.mode },
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
134
|
return {
|
|
152
135
|
mode: controller.mode,
|
|
153
136
|
source: 'wizard',
|
|
@@ -213,6 +213,5 @@ export function getOnboardingWizardBodyRows(viewportHeight: number): number {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
export function getOnboardingWizardVisibleFieldCount(viewportHeight: number): number {
|
|
216
|
-
return Math.max(1,
|
|
216
|
+
return Math.max(1, getOnboardingWizardBodyRows(viewportHeight) - 5);
|
|
217
217
|
}
|
|
218
|
-
|
|
@@ -30,10 +30,6 @@ export function getSharedIpHostDefault(
|
|
|
30
30
|
return hosts[0] ?? '0.0.0.0';
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export function defaultReviewUserMarker(controller: OnboardingWizardController): boolean {
|
|
34
|
-
return controller.mode === 'new';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
33
|
export function toggleCapability(controller: OnboardingWizardController, capabilityId: OnboardingStep1CapabilityId): void {
|
|
38
34
|
if (capabilityId === 'local-tui-only') {
|
|
39
35
|
for (const capability of controller.getCurrentCapabilities()) {
|
|
@@ -69,6 +65,18 @@ export function selectLocalTuiOnly(controller: OnboardingWizardController): void
|
|
|
69
65
|
}
|
|
70
66
|
}
|
|
71
67
|
|
|
68
|
+
export function selectAllExternalSurfaces(controller: OnboardingWizardController): void {
|
|
69
|
+
for (const surface of EXTERNAL_SURFACE_SPECS) {
|
|
70
|
+
controller.toggleState.set(surface.enabledFieldId, true);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function clearExternalSurfaces(controller: OnboardingWizardController): void {
|
|
75
|
+
for (const surface of EXTERNAL_SURFACE_SPECS) {
|
|
76
|
+
controller.toggleState.set(surface.enabledFieldId, false);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
72
80
|
export function setCapabilityValue(controller: OnboardingWizardController, capabilityId: OnboardingStep1CapabilityId, selected: boolean): void {
|
|
73
81
|
if (capabilityId === 'local-tui-only') {
|
|
74
82
|
if (selected) {
|
|
@@ -172,7 +180,7 @@ export function shouldExposeControlPlaneNetwork(controller: OnboardingWizardCont
|
|
|
172
180
|
|
|
173
181
|
export function requiresAuthBootstrap(controller: OnboardingWizardController): boolean {
|
|
174
182
|
return controller.hasServerCapabilitiesSelected()
|
|
175
|
-
&& (!controller.
|
|
183
|
+
&& (!controller.hasLocalAuthUser() || controller.hasBootstrapCredentialPresent());
|
|
176
184
|
}
|
|
177
185
|
|
|
178
186
|
export function hasAdminAuthUser(controller: OnboardingWizardController): boolean {
|
|
@@ -180,6 +188,11 @@ export function hasAdminAuthUser(controller: OnboardingWizardController): boolea
|
|
|
180
188
|
.some((user) => user.roles.includes('admin'));
|
|
181
189
|
}
|
|
182
190
|
|
|
191
|
+
export function hasLocalAuthUser(controller: OnboardingWizardController): boolean {
|
|
192
|
+
return (controller.runtimeSnapshot?.auth.snapshot.userCount ?? 0) > 0
|
|
193
|
+
|| (controller.runtimeSnapshot?.auth.snapshot.users ?? []).length > 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
183
196
|
export function hasBootstrapCredentialPresent(controller: OnboardingWizardController): boolean {
|
|
184
197
|
return controller.runtimeSnapshot?.auth.snapshot.bootstrapCredentialPresent === true;
|
|
185
198
|
}
|
|
@@ -313,14 +313,20 @@ export function isFieldDirtyByDefinition(controller: OnboardingWizardController,
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
export function isFieldSatisfied(controller: OnboardingWizardController, field: OnboardingWizardFieldDefinition): boolean {
|
|
316
|
-
if (field.kind === 'checklist'
|
|
317
|
-
|
|
316
|
+
if (field.kind === 'checklist') {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (field.kind === 'acknowledgement') {
|
|
321
|
+
if (!field.required) return true;
|
|
318
322
|
return Boolean(controller.getFieldValue(field));
|
|
319
323
|
}
|
|
320
324
|
|
|
321
325
|
if (field.kind === 'radio') return true;
|
|
322
326
|
|
|
323
327
|
if (field.kind === 'text' || field.kind === 'masked') {
|
|
328
|
+
const required = field.required === true || controller.isRequiredExternalSetupField(field.id);
|
|
329
|
+
if (!required) return true;
|
|
324
330
|
return normalizeText(controller.getFieldValue(field) as string).length > 0;
|
|
325
331
|
}
|
|
326
332
|
|