@pellux/goodvibes-tui 0.19.27 → 0.19.29
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 +11 -0
- package/README.md +3 -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/surface-command.ts +46 -11
- package/src/cli/tui-startup.ts +4 -4
- package/src/daemon/cli.ts +7 -0
- package/src/input/handler-interactions.ts +14 -1
- package/src/input/handler-onboarding.ts +161 -118
- package/src/input/handler.ts +1 -1
- package/src/input/onboarding/handler-onboarding-routes.ts +35 -15
- package/src/input/onboarding/onboarding-wizard-apply.ts +35 -25
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -5
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +93 -5
- package/src/input/onboarding/onboarding-wizard-helpers.ts +2 -3
- package/src/input/onboarding/onboarding-wizard-rules.ts +40 -8
- package/src/input/onboarding/onboarding-wizard-state.ts +19 -8
- package/src/input/onboarding/onboarding-wizard-steps.ts +226 -93
- package/src/input/onboarding/onboarding-wizard-types.ts +15 -0
- package/src/input/onboarding/onboarding-wizard.ts +123 -6
- package/src/input/settings-modal-types.ts +2 -1
- package/src/input/settings-modal.ts +4 -0
- package/src/main.ts +35 -27
- package/src/renderer/compositor.ts +3 -3
- package/src/renderer/onboarding/onboarding-wizard.ts +141 -57
- package/src/renderer/settings-modal-helpers.ts +9 -0
- package/src/renderer/settings-modal.ts +3 -0
- package/src/runtime/bootstrap.ts +15 -0
- package/src/runtime/onboarding/apply.ts +45 -90
- package/src/runtime/onboarding/derivation.ts +7 -7
- package/src/runtime/onboarding/markers.ts +41 -55
- package/src/runtime/onboarding/snapshot.ts +1 -0
- package/src/runtime/onboarding/state.ts +6 -6
- package/src/runtime/onboarding/types.ts +24 -27
- package/src/runtime/onboarding/verify.ts +3 -65
- package/src/runtime/surface-feature-flags.ts +67 -0
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.19.29] — 2026-04-24
|
|
8
|
+
|
|
9
|
+
### Changes
|
|
10
|
+
- 617b8860 feat: improve onboarding and surface setup
|
|
11
|
+
- 5e39b0f8 docs: add onboarding wizard WIP notice to README top
|
|
12
|
+
|
|
13
|
+
## [0.19.28] — 2026-04-24
|
|
14
|
+
|
|
15
|
+
### Changes
|
|
16
|
+
- 89221c1 fix: mark onboarding checked when opened
|
|
17
|
+
|
|
7
18
|
## [0.19.27] — 2026-04-24
|
|
8
19
|
|
|
9
20
|
### Changes
|
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
> **ATTENTION:** Currently updating the Onboarding Wizard - Expect problems with starting the TUI until this work is complete!
|
|
2
|
+
|
|
1
3
|
# goodvibes-tui
|
|
2
4
|
|
|
3
5
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
6
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/mgd34msu/goodvibes-tui)
|
|
6
8
|
|
|
7
9
|
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
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.29",
|
|
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.4",
|
|
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'}`,
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { ConfigKey } from '../config/index.ts';
|
|
2
|
+
import {
|
|
3
|
+
GOODVIBES_NTFY_AGENT_TOPIC,
|
|
4
|
+
GOODVIBES_NTFY_CHAT_TOPIC,
|
|
5
|
+
GOODVIBES_NTFY_REMOTE_TOPIC,
|
|
6
|
+
resolveGoodVibesNtfyTopics,
|
|
7
|
+
} from '@pellux/goodvibes-sdk/platform/integrations/ntfy';
|
|
8
|
+
import { enableFeatureFlags, getMissingSurfaceFeatureFlags, getServerSurfaceFeatureFlags } from '../runtime/surface-feature-flags.ts';
|
|
2
9
|
import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
3
10
|
import { classifyBindPosture, isNetworkFacing } from './network-posture.ts';
|
|
4
11
|
import type { CliCommandRuntime } from './management.ts';
|
|
@@ -18,7 +25,7 @@ export const SURFACE_CONFIGS = [
|
|
|
18
25
|
['discord', 'Discord', ['surfaces.discord.publicKey', 'surfaces.discord.botToken', 'surfaces.discord.applicationId']],
|
|
19
26
|
['telegram', 'Telegram', ['surfaces.telegram.botToken']],
|
|
20
27
|
['webhook', 'Webhook', ['surfaces.webhook.secret']],
|
|
21
|
-
['ntfy', 'ntfy', ['surfaces.ntfy.baseUrl'
|
|
28
|
+
['ntfy', 'ntfy', ['surfaces.ntfy.baseUrl']],
|
|
22
29
|
['googleChat', 'Google Chat', ['surfaces.googleChat.webhookUrl']],
|
|
23
30
|
['signal', 'Signal', ['surfaces.signal.bridgeUrl', 'surfaces.signal.account']],
|
|
24
31
|
['whatsapp', 'WhatsApp', ['surfaces.whatsapp.accessToken', 'surfaces.whatsapp.phoneNumberId']],
|
|
@@ -39,6 +46,7 @@ export async function handleSurfacesCommand(runtime: CliCommandRuntime): Promise
|
|
|
39
46
|
if (target === 'web') {
|
|
40
47
|
runtime.configManager.setDynamic('web.enabled', enabled);
|
|
41
48
|
if (enabled) {
|
|
49
|
+
enableFeatureFlags(runtime.configManager, getServerSurfaceFeatureFlags({ serverBacked: true, web: true }));
|
|
42
50
|
runtime.configManager.setDynamic('danger.daemon', true);
|
|
43
51
|
runtime.configManager.setDynamic('controlPlane.enabled', true);
|
|
44
52
|
const webError = applyTargetEndpointFlagsOrDefault(runtime, 'web');
|
|
@@ -71,6 +79,7 @@ export async function handleSurfacesCommand(runtime: CliCommandRuntime): Promise
|
|
|
71
79
|
else if (SURFACE_CONFIGS.some(([id]) => id === target)) {
|
|
72
80
|
runtime.configManager.setDynamic(`surfaces.${target}.enabled` as ConfigKey, enabled);
|
|
73
81
|
if (enabled) {
|
|
82
|
+
enableFeatureFlags(runtime.configManager, getServerSurfaceFeatureFlags({ serverBacked: true, externalSurfaces: [target] }));
|
|
74
83
|
runtime.configManager.setDynamic('danger.httpListener', true);
|
|
75
84
|
enableEndpointLanDefault(runtime.configManager, 'httpListener');
|
|
76
85
|
}
|
|
@@ -88,34 +97,45 @@ export async function handleSurfacesCommand(runtime: CliCommandRuntime): Promise
|
|
|
88
97
|
const web = resolveRuntimeEndpointBinding(config, 'web');
|
|
89
98
|
const httpListener = resolveRuntimeEndpointBinding(config, 'httpListener');
|
|
90
99
|
const includeProbe = sub === 'check';
|
|
100
|
+
const targetExternalSurface = target && SURFACE_CONFIGS.some(([id]) => id === target);
|
|
101
|
+
const shouldProbeControlPlane = includeProbe && !target;
|
|
102
|
+
const shouldProbeWeb = includeProbe && !target;
|
|
103
|
+
const shouldProbeListener = includeProbe && (!target || targetExternalSurface);
|
|
91
104
|
const [controlPlaneReachable, webReachable, listenerReachable] = includeProbe
|
|
92
105
|
? await Promise.all([
|
|
93
|
-
probeTcp(controlPlane.host, controlPlane.port),
|
|
94
|
-
probeTcp(web.host, web.port),
|
|
95
|
-
probeTcp(httpListener.host, httpListener.port),
|
|
106
|
+
shouldProbeControlPlane ? probeTcp(controlPlane.host, controlPlane.port) : Promise.resolve(undefined),
|
|
107
|
+
shouldProbeWeb ? probeTcp(web.host, web.port) : Promise.resolve(undefined),
|
|
108
|
+
shouldProbeListener ? probeTcp(httpListener.host, httpListener.port) : Promise.resolve(undefined),
|
|
96
109
|
])
|
|
97
110
|
: [undefined, undefined, undefined];
|
|
98
111
|
const externalSurfaces = SURFACE_CONFIGS.map(([id, label, requiredKeys]) => {
|
|
99
112
|
const enabled = config.get(`surfaces.${id}.enabled` as ConfigKey);
|
|
100
113
|
const missing = requiredKeys.filter((key) => !isPresentConfigValue(config.get(key as ConfigKey)));
|
|
114
|
+
const missingFeatureFlags = enabled === true ? getMissingSurfaceFeatureFlags(config, id) : [];
|
|
101
115
|
return {
|
|
102
116
|
id,
|
|
103
117
|
label,
|
|
104
118
|
enabled,
|
|
105
|
-
ready: !enabled || missing.length === 0,
|
|
119
|
+
ready: !enabled || (missing.length === 0 && missingFeatureFlags.length === 0),
|
|
106
120
|
missing,
|
|
121
|
+
missingFeatureFlags,
|
|
107
122
|
};
|
|
108
123
|
});
|
|
109
124
|
const filteredSurfaces = target ? externalSurfaces.filter((surface) => surface.id === target) : externalSurfaces;
|
|
110
125
|
if (target && filteredSurfaces.length === 0) return { output: `Unknown surface: ${target}`, exitCode: 1 };
|
|
126
|
+
const ntfyTopics = resolveGoodVibesNtfyTopics({
|
|
127
|
+
chatTopic: String(config.get('surfaces.ntfy.chatTopic' as ConfigKey) || GOODVIBES_NTFY_CHAT_TOPIC),
|
|
128
|
+
agentTopic: String(config.get('surfaces.ntfy.agentTopic' as ConfigKey) || GOODVIBES_NTFY_AGENT_TOPIC),
|
|
129
|
+
remoteTopic: String(config.get('surfaces.ntfy.remoteTopic' as ConfigKey) || GOODVIBES_NTFY_REMOTE_TOPIC),
|
|
130
|
+
});
|
|
111
131
|
const readinessIssues: string[] = [];
|
|
112
|
-
if (
|
|
132
|
+
if (shouldProbeControlPlane && config.get('controlPlane.enabled') === true && !controlPlaneReachable) {
|
|
113
133
|
readinessIssues.push(`Control plane is enabled but not reachable on ${controlPlane.host}:${controlPlane.port}.`);
|
|
114
134
|
}
|
|
115
|
-
if (
|
|
135
|
+
if (shouldProbeWeb && config.get('web.enabled') === true && !webReachable) {
|
|
116
136
|
readinessIssues.push(`Web surface is enabled but not reachable on ${web.host}:${web.port}.`);
|
|
117
137
|
}
|
|
118
|
-
if (
|
|
138
|
+
if (shouldProbeListener && config.get('danger.httpListener') === true && !listenerReachable) {
|
|
119
139
|
readinessIssues.push(`HTTP listener is enabled but not reachable on ${httpListener.host}:${httpListener.port}.`);
|
|
120
140
|
}
|
|
121
141
|
for (const surface of filteredSurfaces) {
|
|
@@ -126,6 +146,9 @@ export async function handleSurfacesCommand(runtime: CliCommandRuntime): Promise
|
|
|
126
146
|
if (surface.missing.length > 0) {
|
|
127
147
|
readinessIssues.push(`${surface.label} is enabled but missing ${surface.missing.join(', ')}.`);
|
|
128
148
|
}
|
|
149
|
+
if (surface.missingFeatureFlags.length > 0) {
|
|
150
|
+
readinessIssues.push(`${surface.label} is enabled but feature gates are disabled: ${surface.missingFeatureFlags.join(', ')}.`);
|
|
151
|
+
}
|
|
129
152
|
}
|
|
130
153
|
const value = {
|
|
131
154
|
controlPlane: {
|
|
@@ -162,7 +185,15 @@ export async function handleSurfacesCommand(runtime: CliCommandRuntime): Promise
|
|
|
162
185
|
` http-listener: ${yesNo(value.httpListener.enabled)} (${value.httpListener.hostMode} ${value.httpListener.host}:${value.httpListener.port})${includeProbe ? ` reachable=${yesNo(value.httpListener.reachable)}` : ''}`,
|
|
163
186
|
'',
|
|
164
187
|
'External surfaces:',
|
|
165
|
-
...value.surfaces.map((surface) => ` ${surface.label.padEnd(16)} enabled=${yesNo(surface.enabled)} ready=${yesNo(surface.ready)}${surface.enabled && surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}`),
|
|
188
|
+
...value.surfaces.map((surface) => ` ${surface.label.padEnd(16)} enabled=${yesNo(surface.enabled)} ready=${yesNo(surface.ready)}${surface.enabled && surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}${surface.enabled && surface.missingFeatureFlags.length > 0 ? ` featureGates=${surface.missingFeatureFlags.join(',')}` : ''}`),
|
|
189
|
+
...(filteredSurfaces.some((surface) => surface.id === 'ntfy') ? [
|
|
190
|
+
'',
|
|
191
|
+
'ntfy inbound topics:',
|
|
192
|
+
` chat: ${ntfyTopics.chatTopic}`,
|
|
193
|
+
` agent: ${ntfyTopics.agentTopic}`,
|
|
194
|
+
` daemon-only remote: ${ntfyTopics.remoteTopic}`,
|
|
195
|
+
` default delivery topic: ${String(config.get('surfaces.ntfy.topic') || '(none)')}`,
|
|
196
|
+
] : []),
|
|
166
197
|
...(includeProbe ? [
|
|
167
198
|
readinessIssues.length === 0 ? 'Readiness: ready' : 'Readiness: needs attention',
|
|
168
199
|
...readinessIssues.map((issue) => ` - ${issue}`),
|
|
@@ -191,6 +222,7 @@ export interface ListenerTestResult {
|
|
|
191
222
|
readonly enabled: unknown;
|
|
192
223
|
readonly ready: boolean;
|
|
193
224
|
readonly missing: readonly string[];
|
|
225
|
+
readonly missingFeatureFlags: readonly string[];
|
|
194
226
|
}[];
|
|
195
227
|
readonly issues: readonly string[];
|
|
196
228
|
}
|
|
@@ -209,12 +241,14 @@ export async function buildListenerTestResult(runtime: CliCommandRuntime): Promi
|
|
|
209
241
|
const surfaces = SURFACE_CONFIGS.map(([id, label, requiredKeys]) => {
|
|
210
242
|
const surfaceEnabled = runtime.configManager.get(`surfaces.${id}.enabled` as ConfigKey);
|
|
211
243
|
const missing = requiredKeys.filter((key) => !isPresentConfigValue(runtime.configManager.get(key as ConfigKey)));
|
|
244
|
+
const missingFeatureFlags = surfaceEnabled === true ? getMissingSurfaceFeatureFlags(runtime.configManager, id) : [];
|
|
212
245
|
return {
|
|
213
246
|
id,
|
|
214
247
|
label,
|
|
215
248
|
enabled: surfaceEnabled,
|
|
216
|
-
ready: surfaceEnabled !== true || missing.length === 0,
|
|
249
|
+
ready: surfaceEnabled !== true || (missing.length === 0 && missingFeatureFlags.length === 0),
|
|
217
250
|
missing,
|
|
251
|
+
missingFeatureFlags,
|
|
218
252
|
};
|
|
219
253
|
}).filter((surface) => surface.enabled === true);
|
|
220
254
|
const issues: string[] = [];
|
|
@@ -226,6 +260,7 @@ export async function buildListenerTestResult(runtime: CliCommandRuntime): Promi
|
|
|
226
260
|
if (isNetworkFacing(enabled, binding) && auth.bootstrapCredentialPresent) issues.push('Network-facing listener still has a bootstrap credential file.');
|
|
227
261
|
for (const surface of surfaces) {
|
|
228
262
|
if (surface.missing.length > 0) issues.push(`${surface.label} is enabled but missing ${surface.missing.join(', ')}.`);
|
|
263
|
+
if (surface.missingFeatureFlags.length > 0) issues.push(`${surface.label} is enabled but feature gates are disabled: ${surface.missingFeatureFlags.join(', ')}.`);
|
|
229
264
|
}
|
|
230
265
|
return { enabled, ...binding, posture, reachable, service, auth, surfaces, issues };
|
|
231
266
|
}
|
|
@@ -241,7 +276,7 @@ export function formatListenerTestResult(runtime: CliCommandRuntime, value: List
|
|
|
241
276
|
` local auth users: ${value.auth.userStorePresent ? 'present' : 'missing'}`,
|
|
242
277
|
` bootstrap credential: ${value.auth.bootstrapCredentialPresent ? 'present' : 'missing'}`,
|
|
243
278
|
value.surfaces.length === 0 ? ' enabled webhook surfaces: none' : ' enabled webhook surfaces:',
|
|
244
|
-
...value.surfaces.map((surface) => ` ${surface.label}: ready=${yesNo(surface.ready)}${surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}`),
|
|
279
|
+
...value.surfaces.map((surface) => ` ${surface.label}: ready=${yesNo(surface.ready)}${surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}${surface.missingFeatureFlags.length > 0 ? ` featureGates=${surface.missingFeatureFlags.join(',')}` : ''}`),
|
|
245
280
|
value.issues.length === 0 ? ' readiness: ready' : ' readiness: needs attention',
|
|
246
281
|
...value.issues.map((issue) => ` - ${issue}`),
|
|
247
282
|
].join('\n'));
|
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
|
|
package/src/daemon/cli.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
4
|
import { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
|
|
5
5
|
import { RuntimeEventBus } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
|
|
6
|
+
import { createFeatureFlagManager } from '@pellux/goodvibes-sdk/platform/runtime/feature-flags/index';
|
|
7
|
+
import type { FlagState } from '@pellux/goodvibes-sdk/platform/runtime/feature-flags/types';
|
|
6
8
|
import { createRuntimeStore } from '../runtime/store/index.ts';
|
|
7
9
|
import { createRuntimeServices } from '../runtime/services.ts';
|
|
8
10
|
import { DaemonServer } from '@pellux/goodvibes-sdk/platform/daemon/server';
|
|
@@ -155,8 +157,13 @@ async function main(): Promise<void> {
|
|
|
155
157
|
}
|
|
156
158
|
const runtimeBus = new RuntimeEventBus();
|
|
157
159
|
const runtimeStore = createRuntimeStore();
|
|
160
|
+
const featureFlags = createFeatureFlagManager();
|
|
161
|
+
featureFlags.loadFromConfig({
|
|
162
|
+
flags: (config.getCategory('featureFlags') as Record<string, FlagState>) ?? {},
|
|
163
|
+
});
|
|
158
164
|
const runtimeServices = createRuntimeServices({
|
|
159
165
|
configManager: config,
|
|
166
|
+
featureFlags,
|
|
160
167
|
runtimeBus,
|
|
161
168
|
runtimeStore,
|
|
162
169
|
getConversationTitle: () => 'goodvibes daemon',
|
|
@@ -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);
|