@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,400 @@
|
|
|
1
|
+
import type { SecretStorageReview } from '../../config/secrets.ts';
|
|
2
|
+
import type { LocalAuthSnapshot } from '@pellux/goodvibes-sdk/platform/security/user-auth';
|
|
3
|
+
import { readOnboardingRuntimeState } from './state.ts';
|
|
4
|
+
import type {
|
|
5
|
+
OnboardingAcknowledgementSnapshot,
|
|
6
|
+
OnboardingConfigSnapshot,
|
|
7
|
+
OnboardingProviderRoutingSnapshot,
|
|
8
|
+
OnboardingRuntimeDefaultsSnapshot,
|
|
9
|
+
OnboardingServiceState,
|
|
10
|
+
OnboardingSnapshotCollectionIssue,
|
|
11
|
+
OnboardingSnapshotCollectionIssueArea,
|
|
12
|
+
OnboardingSnapshotDependencies,
|
|
13
|
+
OnboardingSnapshotState,
|
|
14
|
+
OnboardingSurfaceRecord,
|
|
15
|
+
} from './types.ts';
|
|
16
|
+
|
|
17
|
+
function sortUnique(values: readonly string[]): string[] {
|
|
18
|
+
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildConfigSnapshot(
|
|
22
|
+
config: OnboardingSnapshotDependencies['config'],
|
|
23
|
+
): OnboardingConfigSnapshot {
|
|
24
|
+
return {
|
|
25
|
+
display: config.getCategory('display'),
|
|
26
|
+
provider: config.getCategory('provider'),
|
|
27
|
+
behavior: config.getCategory('behavior'),
|
|
28
|
+
storage: config.getCategory('storage'),
|
|
29
|
+
permissions: config.getCategory('permissions'),
|
|
30
|
+
helper: config.getCategory('helper'),
|
|
31
|
+
tools: {
|
|
32
|
+
llmEnabled: config.get('tools.llmEnabled'),
|
|
33
|
+
llmProvider: config.get('tools.llmProvider'),
|
|
34
|
+
llmModel: config.get('tools.llmModel'),
|
|
35
|
+
},
|
|
36
|
+
danger: config.getCategory('danger'),
|
|
37
|
+
controlPlane: config.getCategory('controlPlane'),
|
|
38
|
+
httpListener: config.getCategory('httpListener'),
|
|
39
|
+
web: config.getCategory('web'),
|
|
40
|
+
network: config.getCategory('network'),
|
|
41
|
+
surfaces: config.getCategory('surfaces'),
|
|
42
|
+
service: config.getCategory('service'),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildProviderRoutingSnapshot(
|
|
47
|
+
config: OnboardingConfigSnapshot,
|
|
48
|
+
): OnboardingProviderRoutingSnapshot {
|
|
49
|
+
return {
|
|
50
|
+
primaryProviderId: config.provider.provider,
|
|
51
|
+
primaryModelId: config.provider.model,
|
|
52
|
+
primaryReasoningEffort: config.provider.reasoningEffort,
|
|
53
|
+
embeddingProviderId: config.provider.embeddingProvider,
|
|
54
|
+
systemPromptFile: config.provider.systemPromptFile,
|
|
55
|
+
helperEnabled: config.helper.enabled,
|
|
56
|
+
helperProviderId: config.helper.globalProvider,
|
|
57
|
+
helperModelId: config.helper.globalModel,
|
|
58
|
+
toolLlmEnabled: config.tools.llmEnabled,
|
|
59
|
+
toolProviderId: config.tools.llmProvider,
|
|
60
|
+
toolModelId: config.tools.llmModel,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildRuntimeDefaultsSnapshot(
|
|
65
|
+
config: OnboardingConfigSnapshot,
|
|
66
|
+
): OnboardingRuntimeDefaultsSnapshot {
|
|
67
|
+
return {
|
|
68
|
+
providerReasoningEffort: config.provider.reasoningEffort,
|
|
69
|
+
permissionsMode: config.permissions.mode,
|
|
70
|
+
behavior: config.behavior,
|
|
71
|
+
display: config.display,
|
|
72
|
+
secretStoragePolicy: config.storage.secretPolicy,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function buildServicesSnapshot(
|
|
77
|
+
services: OnboardingSnapshotDependencies['services'],
|
|
78
|
+
): Promise<{
|
|
79
|
+
snapshot: {
|
|
80
|
+
total: number;
|
|
81
|
+
oauthProviderIds: readonly string[];
|
|
82
|
+
services: readonly OnboardingServiceState[];
|
|
83
|
+
};
|
|
84
|
+
issues: readonly OnboardingSnapshotCollectionIssue[];
|
|
85
|
+
}> {
|
|
86
|
+
let configs: ReturnType<OnboardingSnapshotDependencies['services']['getAll']>;
|
|
87
|
+
try {
|
|
88
|
+
configs = services.getAll();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
snapshot: {
|
|
92
|
+
total: 0,
|
|
93
|
+
oauthProviderIds: [],
|
|
94
|
+
services: [],
|
|
95
|
+
},
|
|
96
|
+
issues: [
|
|
97
|
+
{
|
|
98
|
+
area: 'services',
|
|
99
|
+
message: error instanceof Error ? error.message : String(error),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const names = Object.keys(configs).sort((left, right) => left.localeCompare(right));
|
|
106
|
+
const entryResults = await Promise.all(names.map(async (name) => {
|
|
107
|
+
const config = configs[name]!;
|
|
108
|
+
try {
|
|
109
|
+
const inspection = await services.inspect(name);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
entry: {
|
|
113
|
+
name,
|
|
114
|
+
providerId: config.providerId ?? config.name,
|
|
115
|
+
baseUrl: config.baseUrl ?? null,
|
|
116
|
+
authType: config.authType,
|
|
117
|
+
tokenKey: config.tokenKey,
|
|
118
|
+
oauthConfigured: Boolean(config.oauth),
|
|
119
|
+
hasPrimaryCredential: inspection?.hasPrimaryCredential ?? false,
|
|
120
|
+
hasPasswordCredential: inspection?.hasPasswordCredential ?? false,
|
|
121
|
+
hasWebhookUrl: inspection?.hasWebhookUrl ?? false,
|
|
122
|
+
hasSigningSecret: inspection?.hasSigningSecret ?? false,
|
|
123
|
+
hasPublicKey: inspection?.hasPublicKey ?? false,
|
|
124
|
+
} satisfies OnboardingServiceState,
|
|
125
|
+
issue: null,
|
|
126
|
+
};
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return {
|
|
129
|
+
entry: {
|
|
130
|
+
name,
|
|
131
|
+
providerId: config.providerId ?? config.name,
|
|
132
|
+
baseUrl: config.baseUrl ?? null,
|
|
133
|
+
authType: config.authType,
|
|
134
|
+
tokenKey: config.tokenKey,
|
|
135
|
+
oauthConfigured: Boolean(config.oauth),
|
|
136
|
+
hasPrimaryCredential: false,
|
|
137
|
+
hasPasswordCredential: false,
|
|
138
|
+
hasWebhookUrl: false,
|
|
139
|
+
hasSigningSecret: false,
|
|
140
|
+
hasPublicKey: false,
|
|
141
|
+
} satisfies OnboardingServiceState,
|
|
142
|
+
issue: {
|
|
143
|
+
area: 'services',
|
|
144
|
+
message: `${name}: ${error instanceof Error ? error.message : String(error)}`,
|
|
145
|
+
} satisfies OnboardingSnapshotCollectionIssue,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}));
|
|
149
|
+
|
|
150
|
+
const entries = entryResults.map((result) => result.entry);
|
|
151
|
+
const issues: OnboardingSnapshotCollectionIssue[] = [];
|
|
152
|
+
for (const result of entryResults) {
|
|
153
|
+
if (result.issue) issues.push(result.issue);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
snapshot: {
|
|
158
|
+
total: entries.length,
|
|
159
|
+
oauthProviderIds: sortUnique(entries.filter((entry) => entry.oauthConfigured).map((entry) => entry.providerId)),
|
|
160
|
+
services: entries,
|
|
161
|
+
},
|
|
162
|
+
issues,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function buildFallbackSecretReview(
|
|
167
|
+
config: OnboardingConfigSnapshot,
|
|
168
|
+
): SecretStorageReview {
|
|
169
|
+
return {
|
|
170
|
+
policy: config.storage.secretPolicy,
|
|
171
|
+
secureAvailable: false,
|
|
172
|
+
storedKeys: 0,
|
|
173
|
+
envBackedKeys: 0,
|
|
174
|
+
secureKeys: 0,
|
|
175
|
+
plaintextKeys: 0,
|
|
176
|
+
warnings: [],
|
|
177
|
+
locations: [],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function buildFallbackAuthSnapshot(): LocalAuthSnapshot {
|
|
182
|
+
return {
|
|
183
|
+
userStorePath: '',
|
|
184
|
+
bootstrapCredentialPath: '',
|
|
185
|
+
persisted: false,
|
|
186
|
+
bootstrapCredentialPresent: false,
|
|
187
|
+
userCount: 0,
|
|
188
|
+
sessionCount: 0,
|
|
189
|
+
users: [],
|
|
190
|
+
sessions: [],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function buildConfiguredSurfaceKinds(
|
|
195
|
+
surfaces: OnboardingConfigSnapshot['surfaces'],
|
|
196
|
+
): string[] {
|
|
197
|
+
const enabledKinds: string[] = [];
|
|
198
|
+
|
|
199
|
+
for (const [kind, value] of Object.entries(surfaces)) {
|
|
200
|
+
if (!value || typeof value !== 'object') continue;
|
|
201
|
+
if ('enabled' in value && value.enabled === true) enabledKinds.push(kind);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return enabledKinds.sort((left, right) => left.localeCompare(right));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function sortSurfaceRecords(
|
|
208
|
+
records: readonly OnboardingSurfaceRecord[],
|
|
209
|
+
): OnboardingSurfaceRecord[] {
|
|
210
|
+
return [...records].sort((left, right) => {
|
|
211
|
+
const kindOrder = left.kind.localeCompare(right.kind);
|
|
212
|
+
if (kindOrder !== 0) return kindOrder;
|
|
213
|
+
|
|
214
|
+
const labelOrder = left.label.localeCompare(right.label);
|
|
215
|
+
if (labelOrder !== 0) return labelOrder;
|
|
216
|
+
|
|
217
|
+
return left.id.localeCompare(right.id);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function loadOptionalSnapshot<T>(
|
|
222
|
+
area: OnboardingSnapshotCollectionIssueArea,
|
|
223
|
+
loader: (() => Promise<T>) | undefined,
|
|
224
|
+
fallback: T,
|
|
225
|
+
): Promise<{
|
|
226
|
+
value: T;
|
|
227
|
+
issue: OnboardingSnapshotCollectionIssue | null;
|
|
228
|
+
}> {
|
|
229
|
+
if (!loader) return { value: fallback, issue: null };
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
return {
|
|
233
|
+
value: await loader(),
|
|
234
|
+
issue: null,
|
|
235
|
+
};
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return {
|
|
238
|
+
value: fallback,
|
|
239
|
+
issue: {
|
|
240
|
+
area,
|
|
241
|
+
message: error instanceof Error ? error.message : String(error),
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function buildAcknowledgementSnapshot(
|
|
248
|
+
deps: OnboardingSnapshotDependencies,
|
|
249
|
+
): {
|
|
250
|
+
snapshot: OnboardingAcknowledgementSnapshot;
|
|
251
|
+
issue: OnboardingSnapshotCollectionIssue | null;
|
|
252
|
+
} {
|
|
253
|
+
const scope = deps.acknowledgementScope ?? 'project';
|
|
254
|
+
if (!deps.shellPaths) {
|
|
255
|
+
return {
|
|
256
|
+
snapshot: {
|
|
257
|
+
scope,
|
|
258
|
+
exists: false,
|
|
259
|
+
updatedAt: null,
|
|
260
|
+
source: null,
|
|
261
|
+
accepted: {},
|
|
262
|
+
},
|
|
263
|
+
issue: null,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const state = readOnboardingRuntimeState(deps.shellPaths, scope);
|
|
268
|
+
if (state.parseError) {
|
|
269
|
+
return {
|
|
270
|
+
snapshot: {
|
|
271
|
+
scope,
|
|
272
|
+
exists: true,
|
|
273
|
+
updatedAt: null,
|
|
274
|
+
source: null,
|
|
275
|
+
accepted: {},
|
|
276
|
+
},
|
|
277
|
+
issue: {
|
|
278
|
+
area: 'acknowledgements',
|
|
279
|
+
message: state.parseError,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
snapshot: {
|
|
286
|
+
scope,
|
|
287
|
+
exists: state.exists,
|
|
288
|
+
updatedAt: state.payload?.updatedAt ?? null,
|
|
289
|
+
source: state.payload?.source ?? null,
|
|
290
|
+
...(state.payload?.mode ? { mode: state.payload.mode } : {}),
|
|
291
|
+
...(state.payload?.workspaceRoot ? { workspaceRoot: state.payload.workspaceRoot } : {}),
|
|
292
|
+
accepted: state.payload?.acknowledgements ?? {},
|
|
293
|
+
},
|
|
294
|
+
issue: null,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export async function collectOnboardingSnapshot(
|
|
299
|
+
deps: OnboardingSnapshotDependencies,
|
|
300
|
+
): Promise<OnboardingSnapshotState> {
|
|
301
|
+
const capturedAt = deps.clock?.() ?? Date.now();
|
|
302
|
+
const config = buildConfigSnapshot(deps.config);
|
|
303
|
+
const providerRouting = buildProviderRoutingSnapshot(config);
|
|
304
|
+
const runtimeDefaults = buildRuntimeDefaultsSnapshot(config);
|
|
305
|
+
const acknowledgementResult = buildAcknowledgementSnapshot(deps);
|
|
306
|
+
|
|
307
|
+
const [
|
|
308
|
+
subscriptionsActiveResult,
|
|
309
|
+
subscriptionsPendingResult,
|
|
310
|
+
authSnapshotResult,
|
|
311
|
+
servicesResult,
|
|
312
|
+
secretReviewResult,
|
|
313
|
+
secretRecordsResult,
|
|
314
|
+
surfaceResult,
|
|
315
|
+
providerAccountsResult,
|
|
316
|
+
] = await Promise.all([
|
|
317
|
+
loadOptionalSnapshot(
|
|
318
|
+
'subscriptions-active',
|
|
319
|
+
() => Promise.resolve(deps.subscriptions.list()),
|
|
320
|
+
[] as ReturnType<OnboardingSnapshotDependencies['subscriptions']['list']>,
|
|
321
|
+
),
|
|
322
|
+
loadOptionalSnapshot(
|
|
323
|
+
'subscriptions-pending',
|
|
324
|
+
() => Promise.resolve(deps.subscriptions.listPending()),
|
|
325
|
+
[] as ReturnType<OnboardingSnapshotDependencies['subscriptions']['listPending']>,
|
|
326
|
+
),
|
|
327
|
+
loadOptionalSnapshot(
|
|
328
|
+
'auth',
|
|
329
|
+
() => Promise.resolve(deps.auth.inspect()),
|
|
330
|
+
buildFallbackAuthSnapshot(),
|
|
331
|
+
),
|
|
332
|
+
buildServicesSnapshot(deps.services),
|
|
333
|
+
loadOptionalSnapshot(
|
|
334
|
+
'secrets-review',
|
|
335
|
+
() => deps.secrets.inspect(),
|
|
336
|
+
buildFallbackSecretReview(config),
|
|
337
|
+
),
|
|
338
|
+
loadOptionalSnapshot(
|
|
339
|
+
'secrets-records',
|
|
340
|
+
() => deps.secrets.listDetailed(),
|
|
341
|
+
[] as const,
|
|
342
|
+
),
|
|
343
|
+
loadOptionalSnapshot(
|
|
344
|
+
'surfaces',
|
|
345
|
+
deps.surfaces ? () => Promise.resolve(deps.surfaces!.list()) : undefined,
|
|
346
|
+
[] as readonly OnboardingSurfaceRecord[],
|
|
347
|
+
),
|
|
348
|
+
loadOptionalSnapshot(
|
|
349
|
+
'provider-accounts',
|
|
350
|
+
deps.providerAccounts ? () => deps.providerAccounts!.loadSnapshot() : undefined,
|
|
351
|
+
null,
|
|
352
|
+
),
|
|
353
|
+
]);
|
|
354
|
+
|
|
355
|
+
const collectionIssues: OnboardingSnapshotCollectionIssue[] = [];
|
|
356
|
+
if (acknowledgementResult.issue) collectionIssues.push(acknowledgementResult.issue);
|
|
357
|
+
collectionIssues.push(...servicesResult.issues);
|
|
358
|
+
if (subscriptionsActiveResult.issue) collectionIssues.push(subscriptionsActiveResult.issue);
|
|
359
|
+
if (subscriptionsPendingResult.issue) collectionIssues.push(subscriptionsPendingResult.issue);
|
|
360
|
+
if (authSnapshotResult.issue) collectionIssues.push(authSnapshotResult.issue);
|
|
361
|
+
if (secretReviewResult.issue) collectionIssues.push(secretReviewResult.issue);
|
|
362
|
+
if (secretRecordsResult.issue) collectionIssues.push(secretRecordsResult.issue);
|
|
363
|
+
if (surfaceResult.issue) collectionIssues.push(surfaceResult.issue);
|
|
364
|
+
if (providerAccountsResult.issue) collectionIssues.push(providerAccountsResult.issue);
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
capturedAt,
|
|
368
|
+
config,
|
|
369
|
+
providerRouting,
|
|
370
|
+
runtimeDefaults,
|
|
371
|
+
acknowledgements: acknowledgementResult.snapshot,
|
|
372
|
+
services: servicesResult.snapshot,
|
|
373
|
+
subscriptions: {
|
|
374
|
+
active: subscriptionsActiveResult.value,
|
|
375
|
+
pending: subscriptionsPendingResult.value,
|
|
376
|
+
activeProviderIds: sortUnique(subscriptionsActiveResult.value.map((entry) => entry.provider)),
|
|
377
|
+
pendingProviderIds: sortUnique(subscriptionsPendingResult.value.map((entry) => entry.provider)),
|
|
378
|
+
},
|
|
379
|
+
secrets: {
|
|
380
|
+
review: secretReviewResult.value,
|
|
381
|
+
records: secretRecordsResult.value,
|
|
382
|
+
},
|
|
383
|
+
auth: {
|
|
384
|
+
snapshot: authSnapshotResult.value,
|
|
385
|
+
},
|
|
386
|
+
bindSettings: {
|
|
387
|
+
daemonEnabled: Boolean(config.danger.daemon),
|
|
388
|
+
httpListenerEnabled: Boolean(config.danger.httpListener),
|
|
389
|
+
controlPlane: config.controlPlane,
|
|
390
|
+
httpListener: config.httpListener,
|
|
391
|
+
web: config.web,
|
|
392
|
+
},
|
|
393
|
+
surfaces: {
|
|
394
|
+
configuredEnabledKinds: buildConfiguredSurfaceKinds(config.surfaces),
|
|
395
|
+
records: sortSurfaceRecords(surfaceResult.value),
|
|
396
|
+
},
|
|
397
|
+
providerAccounts: providerAccountsResult.value,
|
|
398
|
+
collectionIssues,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import type {
|
|
4
|
+
OnboardingAcknowledgementRuntimeState,
|
|
5
|
+
OnboardingAcknowledgementTarget,
|
|
6
|
+
OnboardingCompletionMarkerScope,
|
|
7
|
+
OnboardingMode,
|
|
8
|
+
OnboardingShellPaths,
|
|
9
|
+
} from './types.ts';
|
|
10
|
+
|
|
11
|
+
const ONBOARDING_RUNTIME_STATE_FILE = 'onboarding-state.json';
|
|
12
|
+
|
|
13
|
+
export interface OnboardingRuntimeStateRecord {
|
|
14
|
+
readonly scope: OnboardingCompletionMarkerScope;
|
|
15
|
+
readonly path: string;
|
|
16
|
+
readonly exists: boolean;
|
|
17
|
+
readonly payload: OnboardingAcknowledgementRuntimeState | null;
|
|
18
|
+
readonly parseError?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface WriteOnboardingAcknowledgementStateOptions {
|
|
22
|
+
readonly scope?: OnboardingCompletionMarkerScope;
|
|
23
|
+
readonly target: OnboardingAcknowledgementTarget;
|
|
24
|
+
readonly acknowledged: boolean;
|
|
25
|
+
readonly updatedAt?: number;
|
|
26
|
+
readonly source: string;
|
|
27
|
+
readonly mode?: OnboardingMode;
|
|
28
|
+
readonly workspaceRoot?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveStatePath(
|
|
32
|
+
shellPaths: OnboardingShellPaths,
|
|
33
|
+
scope: OnboardingCompletionMarkerScope,
|
|
34
|
+
): string {
|
|
35
|
+
return scope === 'project'
|
|
36
|
+
? shellPaths.resolveProjectPath('tui', ONBOARDING_RUNTIME_STATE_FILE)
|
|
37
|
+
: shellPaths.resolveUserPath('tui', ONBOARDING_RUNTIME_STATE_FILE);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
41
|
+
return typeof value === 'object' && value !== null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isOnboardingMode(value: unknown): value is OnboardingAcknowledgementRuntimeState['mode'] {
|
|
45
|
+
return value === 'new' || value === 'edit' || value === 'reopen';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isAcknowledgementTarget(value: string): value is OnboardingAcknowledgementTarget {
|
|
49
|
+
return value === 'providers' || value === 'subscriptions' || value === 'auth';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isRuntimeStatePayload(value: unknown): value is OnboardingAcknowledgementRuntimeState {
|
|
53
|
+
if (!isObject(value)) return false;
|
|
54
|
+
if (value.version !== 1) return false;
|
|
55
|
+
if (typeof value.updatedAt !== 'number' || !Number.isFinite(value.updatedAt)) return false;
|
|
56
|
+
if (typeof value.source !== 'string') return false;
|
|
57
|
+
if (value.mode !== undefined && !isOnboardingMode(value.mode)) return false;
|
|
58
|
+
if (value.workspaceRoot !== undefined && typeof value.workspaceRoot !== 'string') return false;
|
|
59
|
+
if (!isObject(value.acknowledgements)) return false;
|
|
60
|
+
|
|
61
|
+
return Object.entries(value.acknowledgements).every(([key, entry]) => isAcknowledgementTarget(key) && typeof entry === 'boolean');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getOnboardingRuntimeStatePath(
|
|
65
|
+
shellPaths: OnboardingShellPaths,
|
|
66
|
+
scope: OnboardingCompletionMarkerScope = 'project',
|
|
67
|
+
): string {
|
|
68
|
+
return resolveStatePath(shellPaths, scope);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function readOnboardingRuntimeState(
|
|
72
|
+
shellPaths: OnboardingShellPaths,
|
|
73
|
+
scope: OnboardingCompletionMarkerScope = 'project',
|
|
74
|
+
): OnboardingRuntimeStateRecord {
|
|
75
|
+
const path = resolveStatePath(shellPaths, scope);
|
|
76
|
+
if (!existsSync(path)) {
|
|
77
|
+
return {
|
|
78
|
+
scope,
|
|
79
|
+
path,
|
|
80
|
+
exists: false,
|
|
81
|
+
payload: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8')) as unknown;
|
|
87
|
+
if (!isRuntimeStatePayload(parsed)) {
|
|
88
|
+
return {
|
|
89
|
+
scope,
|
|
90
|
+
path,
|
|
91
|
+
exists: true,
|
|
92
|
+
payload: null,
|
|
93
|
+
parseError: 'Invalid onboarding runtime state payload.',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
scope,
|
|
99
|
+
path,
|
|
100
|
+
exists: true,
|
|
101
|
+
payload: parsed,
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
scope,
|
|
106
|
+
path,
|
|
107
|
+
exists: true,
|
|
108
|
+
payload: null,
|
|
109
|
+
parseError: error instanceof Error ? error.message : String(error),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function writeOnboardingAcknowledgementState(
|
|
115
|
+
shellPaths: OnboardingShellPaths,
|
|
116
|
+
options: WriteOnboardingAcknowledgementStateOptions,
|
|
117
|
+
): OnboardingRuntimeStateRecord {
|
|
118
|
+
const scope = options.scope ?? 'project';
|
|
119
|
+
const path = resolveStatePath(shellPaths, scope);
|
|
120
|
+
const existing = readOnboardingRuntimeState(shellPaths, scope);
|
|
121
|
+
const updatedAt = options.updatedAt ?? Date.now();
|
|
122
|
+
const payload: OnboardingAcknowledgementRuntimeState = {
|
|
123
|
+
version: 1,
|
|
124
|
+
updatedAt,
|
|
125
|
+
source: options.source,
|
|
126
|
+
...(options.mode ? { mode: options.mode } : {}),
|
|
127
|
+
...(options.workspaceRoot ?? shellPaths.workingDirectory
|
|
128
|
+
? { workspaceRoot: options.workspaceRoot ?? shellPaths.workingDirectory }
|
|
129
|
+
: {}),
|
|
130
|
+
acknowledgements: {
|
|
131
|
+
...(existing.payload?.acknowledgements ?? {}),
|
|
132
|
+
[options.target]: options.acknowledged,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
137
|
+
writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
138
|
+
|
|
139
|
+
return readOnboardingRuntimeState(shellPaths, scope);
|
|
140
|
+
}
|