@pellux/goodvibes-tui 0.19.23 → 0.19.25
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 +21 -0
- package/README.md +5 -5
- package/bin/goodvibes +5 -0
- package/bin/goodvibes-daemon +5 -0
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/cli/completion.ts +89 -0
- package/src/cli/config-overrides.ts +159 -0
- package/src/cli/endpoints.ts +63 -0
- package/src/cli/entrypoint.ts +155 -0
- package/src/cli/help.ts +122 -0
- package/src/cli/index.ts +8 -0
- package/src/cli/management-commands.ts +576 -0
- package/src/cli/management.ts +693 -0
- package/src/cli/parser.ts +367 -0
- package/src/cli/status.ts +112 -0
- package/src/cli/tui-startup.ts +32 -0
- package/src/cli/types.ts +63 -0
- package/src/cli-flags.ts +17 -55
- package/src/config/index.ts +1 -1
- package/src/config/secrets.ts +44 -0
- package/src/core/conversation.ts +36 -13
- 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/panels/panel-manager.ts +6 -2
- 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/renderer/panel-composite.ts +42 -5
- package/src/renderer/panel-workspace-bar.ts +5 -1
- 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
package/src/config/secrets.ts
CHANGED
|
@@ -13,9 +13,38 @@ import {
|
|
|
13
13
|
SecretsManager as SdkSecretsManager,
|
|
14
14
|
type SecretsManagerOptions as SdkSecretsManagerOptions,
|
|
15
15
|
} from '@pellux/goodvibes-sdk/platform/config/secrets';
|
|
16
|
+
import { isSecretRefInput } from '@pellux/goodvibes-sdk/platform/config/secret-refs';
|
|
16
17
|
|
|
17
18
|
export type SecretsManagerOptions = Omit<SdkSecretsManagerOptions, 'surfaceRoot'>;
|
|
18
19
|
|
|
20
|
+
const RAW_SECRET_LITERAL_PREFIX = '__GOODVIBES_LITERAL_V1__';
|
|
21
|
+
|
|
22
|
+
function isGoodVibesSecretRefInput(value: string): boolean {
|
|
23
|
+
const normalized = value.trim();
|
|
24
|
+
return normalized.startsWith('goodvibes://secrets/') && isSecretRefInput(normalized);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function shouldStoreAsLiteral(value: string): boolean {
|
|
28
|
+
return value.startsWith(RAW_SECRET_LITERAL_PREFIX)
|
|
29
|
+
|| (isSecretRefInput(value) && !isGoodVibesSecretRefInput(value));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function encodeLiteralSecret(value: string): string {
|
|
33
|
+
return `${RAW_SECRET_LITERAL_PREFIX}${Buffer.from(JSON.stringify({ value }), 'utf-8').toString('base64url')}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function decodeLiteralSecret(value: string): string | null {
|
|
37
|
+
if (!value.startsWith(RAW_SECRET_LITERAL_PREFIX)) return null;
|
|
38
|
+
try {
|
|
39
|
+
const decoded = Buffer.from(value.slice(RAW_SECRET_LITERAL_PREFIX.length), 'base64url').toString('utf-8');
|
|
40
|
+
const parsed = JSON.parse(decoded) as unknown;
|
|
41
|
+
if (!parsed || typeof parsed !== 'object' || typeof (parsed as { value?: unknown }).value !== 'string') return null;
|
|
42
|
+
return (parsed as { value: string }).value;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
19
48
|
export class SecretsManager extends SdkSecretsManager {
|
|
20
49
|
constructor(options: SecretsManagerOptions) {
|
|
21
50
|
super({
|
|
@@ -23,4 +52,19 @@ export class SecretsManager extends SdkSecretsManager {
|
|
|
23
52
|
surfaceRoot: 'tui',
|
|
24
53
|
});
|
|
25
54
|
}
|
|
55
|
+
|
|
56
|
+
override async get(key: string): Promise<string | null> {
|
|
57
|
+
const envValue = process.env[key];
|
|
58
|
+
if (envValue !== undefined && shouldStoreAsLiteral(envValue)) {
|
|
59
|
+
return decodeLiteralSecret(envValue) ?? envValue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const value = await super.get(key);
|
|
63
|
+
if (value === null) return null;
|
|
64
|
+
return decodeLiteralSecret(value) ?? value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override async set(key: string, value: string, options?: Parameters<SdkSecretsManager['set']>[2]): Promise<void> {
|
|
68
|
+
await super.set(key, shouldStoreAsLiteral(value) ? encodeLiteralSecret(value) : value, options);
|
|
69
|
+
}
|
|
26
70
|
}
|
package/src/core/conversation.ts
CHANGED
|
@@ -74,6 +74,13 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
74
74
|
private errorLineRegistry: number[] = [];
|
|
75
75
|
/** Streaming block start line in history buffer (for incremental streaming update). */
|
|
76
76
|
private streamingStartLine = -1;
|
|
77
|
+
/**
|
|
78
|
+
* Message index at the time of the last clearDisplay() call.
|
|
79
|
+
* rebuildHistory() renders only messages at or after this index, so the
|
|
80
|
+
* display stays blank for messages added before the clear while LLM history
|
|
81
|
+
* is fully preserved. Reset to 0 on resetAll() or rebuildHistory() width change.
|
|
82
|
+
*/
|
|
83
|
+
private _displayFromMessageIndex = 0;
|
|
77
84
|
|
|
78
85
|
public suppressSplash: boolean = false;
|
|
79
86
|
public splashOptions: SplashOptions = {};
|
|
@@ -215,6 +222,7 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
215
222
|
this.messageLineRegistry = [];
|
|
216
223
|
this.errorLineRegistry = [];
|
|
217
224
|
this.streamingStartLine = -1;
|
|
225
|
+
this._displayFromMessageIndex = 0; // full reset — show everything on next render
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
/**
|
|
@@ -293,20 +301,27 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
293
301
|
this.lastRenderedWidth = width;
|
|
294
302
|
this.dirty = false;
|
|
295
303
|
|
|
304
|
+
const snapshot = this.getMessageSnapshot();
|
|
305
|
+
// When _displayFromMessageIndex > 0, clearDisplay() was called. Only render
|
|
306
|
+
// messages added after the clear — the pre-clear history stays off-screen.
|
|
307
|
+
// On a full rebuild (e.g. width change), reset the display-start to 0 so the
|
|
308
|
+
// user can scroll back to the full history if needed.
|
|
309
|
+
const displayStart = this._displayFromMessageIndex;
|
|
310
|
+
const visibleSnapshot = displayStart > 0 ? snapshot.slice(displayStart) : snapshot;
|
|
311
|
+
|
|
296
312
|
// Tool messages ARE rendered (as collapsed blocks); this filter is only
|
|
297
313
|
// for determining whether to show the splash screen (tool-only messages
|
|
298
314
|
// don't count as visible conversation content for splash purposes).
|
|
299
|
-
const
|
|
300
|
-
const displayMessages = snapshot.filter(
|
|
315
|
+
const displayMessages = visibleSnapshot.filter(
|
|
301
316
|
(m) => m.role !== 'tool' && m.role !== 'system',
|
|
302
317
|
);
|
|
303
318
|
|
|
304
|
-
if (displayMessages.length === 0 && !this.suppressSplash) {
|
|
319
|
+
if (displayMessages.length === 0 && displayStart === 0 && !this.suppressSplash) {
|
|
305
320
|
this.addSplashScreen(width);
|
|
306
321
|
return;
|
|
307
322
|
}
|
|
308
323
|
|
|
309
|
-
this.appendMessages(
|
|
324
|
+
this.appendMessages(visibleSnapshot, width);
|
|
310
325
|
this.appendedUpTo = snapshot.length;
|
|
311
326
|
}
|
|
312
327
|
|
|
@@ -509,19 +524,27 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
509
524
|
|
|
510
525
|
/**
|
|
511
526
|
* clearDisplay - Clear the visual history buffer without touching the LLM context messages.
|
|
512
|
-
* The next render will show a blank conversation area.
|
|
527
|
+
* The next render will show a blank conversation area. Subsequent message additions
|
|
528
|
+
* rebuild the display incrementally from that point forward.
|
|
529
|
+
*
|
|
530
|
+
* Contract:
|
|
531
|
+
* - getDisplayBlocks() returns an empty array immediately after this call.
|
|
532
|
+
* - getMessageSnapshot() is unaffected — full LLM history is preserved.
|
|
533
|
+
* - resetAll() (which clears both display and messages) continues to work.
|
|
534
|
+
* - rebuildHistory() can be called by callers that need a full display rebuild.
|
|
513
535
|
*/
|
|
514
536
|
public clearDisplay(): void {
|
|
515
537
|
this.history.clear();
|
|
516
|
-
this.
|
|
517
|
-
this.
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
this.
|
|
538
|
+
this.blockRegistry = [];
|
|
539
|
+
this.messageLineRegistry = [];
|
|
540
|
+
this.errorLineRegistry = [];
|
|
541
|
+
// Advance _displayFromMessageIndex to exclude all current messages from display.
|
|
542
|
+
// rebuildHistory() will only render messages added AFTER this point.
|
|
543
|
+
this._displayFromMessageIndex = this.getMessageSnapshot().length;
|
|
544
|
+
this.appendedUpTo = this._displayFromMessageIndex;
|
|
521
545
|
this.dirty = false;
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
this.appendedUpTo = snapshot.length;
|
|
546
|
+
// Do NOT re-render here — display stays blank until the next message is added.
|
|
547
|
+
// The lastRenderedWidth is kept so subsequent appends use the correct width.
|
|
525
548
|
}
|
|
526
549
|
}
|
|
527
550
|
|
package/src/daemon/cli.ts
CHANGED
|
@@ -25,7 +25,15 @@ import {
|
|
|
25
25
|
persistProviders,
|
|
26
26
|
} from '@pellux/goodvibes-sdk/platform/discovery/index';
|
|
27
27
|
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
parseGoodVibesCli,
|
|
30
|
+
renderGoodVibesDaemonHelp,
|
|
31
|
+
renderGoodVibesVersion,
|
|
32
|
+
applyRuntimeConfigOverrides,
|
|
33
|
+
applyRuntimeConfigValue,
|
|
34
|
+
applyRuntimeFeatureFlagOverrides,
|
|
35
|
+
applyRuntimeEndpointFlagOverrides,
|
|
36
|
+
} from '../cli/index.ts';
|
|
29
37
|
type DaemonCliOwnership = {
|
|
30
38
|
readonly workingDirectory: string;
|
|
31
39
|
readonly homeDirectory: string;
|
|
@@ -39,13 +47,17 @@ type DaemonCliTokens = {
|
|
|
39
47
|
};
|
|
40
48
|
|
|
41
49
|
function getLocalNetworkIp(): string {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (const
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
try {
|
|
51
|
+
const nets = networkInterfaces();
|
|
52
|
+
for (const name of Object.keys(nets)) {
|
|
53
|
+
for (const net of nets[name] ?? []) {
|
|
54
|
+
if (net.family === 'IPv4' && !net.internal) {
|
|
55
|
+
return net.address;
|
|
56
|
+
}
|
|
47
57
|
}
|
|
48
58
|
}
|
|
59
|
+
} catch {
|
|
60
|
+
return 'localhost';
|
|
49
61
|
}
|
|
50
62
|
return 'localhost';
|
|
51
63
|
}
|
|
@@ -82,7 +94,22 @@ function readDaemonCliTokens(env: NodeJS.ProcessEnv): DaemonCliTokens {
|
|
|
82
94
|
async function main(): Promise<void> {
|
|
83
95
|
// Parse CLI flags first so --daemon-home and --working-dir env vars are set
|
|
84
96
|
// before resolveDaemonCliOwnership() reads them.
|
|
85
|
-
const
|
|
97
|
+
const cli = parseGoodVibesCli(process.argv.slice(2), 'goodvibes-daemon');
|
|
98
|
+
if (cli.errors.length > 0) {
|
|
99
|
+
console.error(cli.errors.join('\n'));
|
|
100
|
+
console.error('');
|
|
101
|
+
console.error(renderGoodVibesDaemonHelp('goodvibes-daemon'));
|
|
102
|
+
process.exit(2);
|
|
103
|
+
}
|
|
104
|
+
if (cli.flags.help || cli.command === 'help') {
|
|
105
|
+
console.log(renderGoodVibesDaemonHelp('goodvibes-daemon'));
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
if (cli.flags.version || cli.command === 'version') {
|
|
109
|
+
console.log(renderGoodVibesVersion('goodvibes-daemon'));
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
const cliFlags = cli.flags;
|
|
86
113
|
if (cliFlags.daemonHome !== undefined) {
|
|
87
114
|
process.env['GOODVIBES_DAEMON_HOME'] = cliFlags.daemonHome;
|
|
88
115
|
logger.info('daemon: --daemon-home flag applied', { daemonHome: cliFlags.daemonHome });
|
|
@@ -96,15 +123,36 @@ async function main(): Promise<void> {
|
|
|
96
123
|
const config = new ConfigManager({ workingDir, homeDir: homeDirectory, surfaceRoot: 'tui' });
|
|
97
124
|
new GlobalNetworkTransportInstaller().install(config);
|
|
98
125
|
|
|
99
|
-
|
|
126
|
+
const overrideErrors = applyRuntimeConfigOverrides(config, cliFlags.configOverrides);
|
|
127
|
+
if (overrideErrors.length > 0) {
|
|
128
|
+
console.error(overrideErrors.join('\n'));
|
|
129
|
+
process.exit(2);
|
|
130
|
+
}
|
|
131
|
+
applyRuntimeFeatureFlagOverrides(config, {
|
|
132
|
+
enableFeatures: cliFlags.enableFeatures,
|
|
133
|
+
disableFeatures: cliFlags.disableFeatures,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Apply remaining CLI flags before the provider registry is constructed.
|
|
137
|
+
// These are runtime-only overrides; they must not rewrite settings.json.
|
|
100
138
|
if (cliFlags.provider !== undefined) {
|
|
101
|
-
config
|
|
139
|
+
applyRuntimeConfigValue(config, 'provider.provider', cliFlags.provider);
|
|
102
140
|
logger.info('daemon: --provider flag applied', { provider: cliFlags.provider });
|
|
103
141
|
}
|
|
104
142
|
if (cliFlags.model !== undefined) {
|
|
105
|
-
config
|
|
143
|
+
applyRuntimeConfigValue(config, 'provider.model', cliFlags.model);
|
|
106
144
|
logger.info('daemon: --model flag applied', { model: cliFlags.model });
|
|
107
145
|
}
|
|
146
|
+
const endpointOverrideErrors = applyRuntimeEndpointFlagOverrides(config, 'controlPlane', cliFlags);
|
|
147
|
+
if (endpointOverrideErrors.length > 0) {
|
|
148
|
+
console.error(endpointOverrideErrors.join('\n'));
|
|
149
|
+
process.exit(2);
|
|
150
|
+
}
|
|
151
|
+
if (cliFlags.port !== undefined) logger.info('daemon: --port flag applied', { port: cliFlags.port });
|
|
152
|
+
if (cliFlags.hostname !== undefined) {
|
|
153
|
+
process.env['GOODVIBES_DAEMON_HOST'] = cliFlags.hostname;
|
|
154
|
+
logger.info('daemon: --hostname flag applied', { hostname: cliFlags.hostname });
|
|
155
|
+
}
|
|
108
156
|
const runtimeBus = new RuntimeEventBus();
|
|
109
157
|
const runtimeStore = createRuntimeStore();
|
|
110
158
|
const runtimeServices = createRuntimeServices({
|
|
@@ -209,7 +257,10 @@ async function main(): Promise<void> {
|
|
|
209
257
|
// Print companion connection info + QR code to stdout.
|
|
210
258
|
// Use the config-driven control plane port, not a hardcoded default.
|
|
211
259
|
const daemonPort = config.get('controlPlane.port');
|
|
212
|
-
const
|
|
260
|
+
const configuredDaemonHost = String(process.env.GOODVIBES_DAEMON_HOST ?? getLocalNetworkIp());
|
|
261
|
+
const daemonHost = configuredDaemonHost === '0.0.0.0' || configuredDaemonHost === '::'
|
|
262
|
+
? getLocalNetworkIp()
|
|
263
|
+
: configuredDaemonHost;
|
|
213
264
|
const daemonUrl = `http://${daemonHost}:${daemonPort}`;
|
|
214
265
|
const bootstrapPassword = readBootstrapPassword(userAuth.getBootstrapCredentialPath());
|
|
215
266
|
const connectionInfo = buildCompanionConnectionInfo({
|
|
@@ -9,6 +9,8 @@ import type { SelectionItem, SelectionResult, SelectionAction } from './selectio
|
|
|
9
9
|
import type { FileUndoManager } from '@pellux/goodvibes-sdk/platform/state/file-undo';
|
|
10
10
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
11
11
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
12
|
+
import type { OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
|
|
13
|
+
import type { OpenOnboardingWizardOptions } from './handler-ui-state.ts';
|
|
12
14
|
import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge/knowledge-api';
|
|
13
15
|
import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks/hook-api';
|
|
14
16
|
import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp/mcp-api';
|
|
@@ -72,6 +74,7 @@ export interface CommandUiActions {
|
|
|
72
74
|
|
|
73
75
|
export interface CommandShellUiOpeners {
|
|
74
76
|
reloadSystemPrompt?: () => string;
|
|
77
|
+
openOnboardingWizard?: (modeOrOptions?: OnboardingWizardMode | OpenOnboardingWizardOptions) => void;
|
|
75
78
|
openModelPicker?: () => void;
|
|
76
79
|
openProviderPicker?: () => void;
|
|
77
80
|
openContextInspector?: () => void;
|
|
@@ -2,24 +2,29 @@ import { estimateConversationTokens } from '@pellux/goodvibes-sdk/platform/core/
|
|
|
2
2
|
import { evaluateSessionMaintenance, formatSessionMaintenanceLines, getGuidanceMode } from '@pellux/goodvibes-sdk/platform/runtime/session-maintenance';
|
|
3
3
|
import { dismissGuidance, evaluateContextualGuidance, formatGuidanceItems, resetGuidance } from '@pellux/goodvibes-sdk/platform/runtime/guidance';
|
|
4
4
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
5
|
-
import {
|
|
5
|
+
import { requireProviderApi, requireReadModels, requireSessionMemoryStore, requireShellPaths } from './runtime-services.ts';
|
|
6
6
|
|
|
7
7
|
export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void {
|
|
8
8
|
registry.register({
|
|
9
9
|
name: 'welcome',
|
|
10
10
|
aliases: ['guide'],
|
|
11
|
-
description: 'Open the
|
|
11
|
+
description: 'Open the product entry surface for the onboarding wizard, security, marketplace, remote, and operator workflows',
|
|
12
12
|
usage: '[open|print]',
|
|
13
13
|
handler(args, ctx) {
|
|
14
14
|
const sub = args[0] ?? 'open';
|
|
15
15
|
if (sub === 'open' || sub === 'panel') {
|
|
16
|
-
|
|
16
|
+
if (ctx.openOnboardingWizard) {
|
|
17
|
+
ctx.openOnboardingWizard({ mode: 'edit' });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
ctx.print('Use /onboarding to open the setup wizard.');
|
|
17
21
|
return;
|
|
18
22
|
}
|
|
19
23
|
if (sub === 'print') {
|
|
20
24
|
ctx.print([
|
|
21
25
|
'Welcome To GoodVibes',
|
|
22
|
-
' /
|
|
26
|
+
' /onboarding - open the onboarding wizard with current settings preloaded',
|
|
27
|
+
' /setup onboarding - open the same onboarding wizard from setup workflows',
|
|
23
28
|
' /health review - unified startup, service, and sandbox posture',
|
|
24
29
|
' /sandbox review - inspect VM isolation posture',
|
|
25
30
|
' /marketplace open - browse curated plugins, skills, hook packs, and policy packs',
|
|
@@ -11,6 +11,16 @@ import { BUILTIN_SECRET_PROVIDER_SOURCES, describeSecretRef, isSecretRefInput, r
|
|
|
11
11
|
import { openCommandPanel, requireBookmarkManager, requireProviderApi, requireSecretsManager } from './runtime-services.ts';
|
|
12
12
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
|
|
13
13
|
|
|
14
|
+
function isGoodVibesSecretRefInput(value: string): boolean {
|
|
15
|
+
const normalized = value.trim();
|
|
16
|
+
return normalized.startsWith('goodvibes://secrets/') && isSecretRefInput(normalized);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isMalformedGoodVibesSecretRefInput(value: string): boolean {
|
|
20
|
+
const normalized = value.trim();
|
|
21
|
+
return normalized.startsWith('goodvibes://') && !isGoodVibesSecretRefInput(normalized);
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
function toggleBlocks(typeFilter: string, collapsed: boolean, ctx: CommandContext): void {
|
|
15
25
|
const VALID_TYPES = ['all', 'thinking', 'tool', 'code'] as const;
|
|
16
26
|
if (!VALID_TYPES.includes(typeFilter as typeof VALID_TYPES[number])) {
|
|
@@ -157,11 +167,11 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
157
167
|
...BUILTIN_SECRET_PROVIDER_SOURCES.map((source) => ` ${source}`),
|
|
158
168
|
'',
|
|
159
169
|
'Examples:',
|
|
160
|
-
' /secrets link OPENAI_API_KEY
|
|
161
|
-
' /secrets link SLACK_BOT_TOKEN
|
|
162
|
-
' /secrets link SLACK_BOT_TOKEN vaultwarden
|
|
163
|
-
' /secrets link STRIPE_TOKEN bws
|
|
164
|
-
' /secrets link OPENAI_API_KEY
|
|
170
|
+
' /secrets link OPENAI_API_KEY goodvibes://secrets/env/OPENAI_API_KEY',
|
|
171
|
+
' /secrets link SLACK_BOT_TOKEN goodvibes://secrets/bitwarden?item=GoodVibes%20Slack&field=password&sessionEnv=BW_SESSION',
|
|
172
|
+
' /secrets link SLACK_BOT_TOKEN goodvibes://secrets/vaultwarden?item=GoodVibes%20Slack&field=password&server=https%3A%2F%2Fvault.example.test',
|
|
173
|
+
' /secrets link STRIPE_TOKEN goodvibes://secrets/bws/00000000-0000-0000-0000-000000000000?field=value&accessTokenEnv=BWS_ACCESS_TOKEN',
|
|
174
|
+
' /secrets link OPENAI_API_KEY goodvibes://secrets/1password?vault=Private&item=GoodVibes%20OpenAI&field=API%20Key',
|
|
165
175
|
].join('\n'));
|
|
166
176
|
return;
|
|
167
177
|
}
|
|
@@ -171,7 +181,7 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
171
181
|
ctx.print('[secrets] Usage: /secrets test <secret-ref>');
|
|
172
182
|
return;
|
|
173
183
|
}
|
|
174
|
-
if (!
|
|
184
|
+
if (!isGoodVibesSecretRefInput(refText)) {
|
|
175
185
|
ctx.print('[secrets] Invalid secret reference. Use /secrets providers for examples.');
|
|
176
186
|
return;
|
|
177
187
|
}
|
|
@@ -192,7 +202,11 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
192
202
|
return;
|
|
193
203
|
}
|
|
194
204
|
const value = rawValueParts.join(' ');
|
|
195
|
-
if (sub === 'link' && !
|
|
205
|
+
if (sub === 'link' && !isGoodVibesSecretRefInput(value)) {
|
|
206
|
+
ctx.print('[secrets] Invalid secret reference. Use /secrets providers for examples.');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (sub === 'set' && isMalformedGoodVibesSecretRefInput(value)) {
|
|
196
210
|
ctx.print('[secrets] Invalid secret reference. Use /secrets providers for examples.');
|
|
197
211
|
return;
|
|
198
212
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { dirname, join
|
|
2
|
-
import {
|
|
3
|
-
import type { CommandRegistry
|
|
1
|
+
import { dirname, join } from 'path';
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
4
|
import type { ConfigKey } from '../../config/index.ts';
|
|
5
5
|
import { CONFIG_SCHEMA } from '../../config/index.ts';
|
|
6
6
|
import { listHookPointContracts } from '@pellux/goodvibes-sdk/platform/hooks/index';
|
|
7
|
-
import { isRunningInWsl } from '@pellux/goodvibes-sdk/platform/runtime/sandbox/manager';
|
|
8
7
|
import { renderQemuWrapperTemplate } from '@pellux/goodvibes-sdk/platform/runtime/sandbox/qemu-wrapper-template';
|
|
9
8
|
import type { SetupTransferBundle } from './local-setup-transfer.ts';
|
|
10
9
|
import {
|
|
@@ -15,20 +14,29 @@ import {
|
|
|
15
14
|
parseSetupLink,
|
|
16
15
|
} from './local-setup-transfer.ts';
|
|
17
16
|
import { buildSetupReviewSnapshot, exportSetupSupportBundle, renderSetupSandboxReview } from './local-setup-review.ts';
|
|
18
|
-
import { requirePanelManager, requireShellPaths } from './runtime-services.ts';
|
|
17
|
+
import { openOnboardingWizard, requirePanelManager, requireShellPaths } from './runtime-services.ts';
|
|
19
18
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
|
|
20
19
|
|
|
20
|
+
type SetupSnapshot = Awaited<ReturnType<typeof buildSetupReviewSnapshot>>;
|
|
21
|
+
|
|
21
22
|
export function registerLocalSetupCommands(registry: CommandRegistry): void {
|
|
22
23
|
registry.register({
|
|
23
24
|
name: 'setup',
|
|
24
25
|
aliases: ['startup'],
|
|
25
|
-
description: '
|
|
26
|
+
description: 'Launch the onboarding wizard and review startup readiness, service posture, and sandbox bring-up',
|
|
26
27
|
usage: '[review|doctor|services|hooks|remote|sandbox|onboarding|support-bundle <dir>|export <path>|transfer <export|inspect|import> <path>|link <surface> [target]|open-link <uri>]',
|
|
27
28
|
async handler(args, ctx) {
|
|
28
|
-
const shellPaths = requireShellPaths(ctx);
|
|
29
29
|
const sub = args[0] ?? 'review';
|
|
30
|
-
|
|
30
|
+
let shellPaths: ReturnType<typeof requireShellPaths> | null = null;
|
|
31
|
+
let snapshotPromise: Promise<SetupSnapshot> | null = null;
|
|
32
|
+
const getShellPaths = () => (shellPaths ??= requireShellPaths(ctx));
|
|
33
|
+
const getSnapshot = async (): Promise<SetupSnapshot> => {
|
|
34
|
+
snapshotPromise ??= buildSetupReviewSnapshot(ctx);
|
|
35
|
+
return snapshotPromise;
|
|
36
|
+
};
|
|
37
|
+
|
|
31
38
|
if (sub === 'review') {
|
|
39
|
+
const snapshot = await getSnapshot();
|
|
32
40
|
ctx.print([
|
|
33
41
|
'Startup Readiness Review',
|
|
34
42
|
` session: ${snapshot.sessionId}`,
|
|
@@ -58,6 +66,7 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
|
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
if (sub === 'doctor') {
|
|
69
|
+
const snapshot = await getSnapshot();
|
|
61
70
|
ctx.print([
|
|
62
71
|
'Startup Doctor',
|
|
63
72
|
...snapshot.issues.map((issue) => ` [${issue.severity.toUpperCase()}] ${issue.area}: ${issue.message}`),
|
|
@@ -75,6 +84,7 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
|
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
if (sub === 'services') {
|
|
87
|
+
const snapshot = await getSnapshot();
|
|
78
88
|
ctx.print([
|
|
79
89
|
'Startup Services',
|
|
80
90
|
` configured: ${snapshot.serviceCount}`,
|
|
@@ -91,6 +101,7 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
|
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
if (sub === 'hooks') {
|
|
104
|
+
const snapshot = await getSnapshot();
|
|
94
105
|
const contracts = listHookPointContracts();
|
|
95
106
|
ctx.print([
|
|
96
107
|
'Startup Hooks',
|
|
@@ -102,6 +113,7 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
|
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
if (sub === 'remote') {
|
|
116
|
+
const snapshot = await getSnapshot();
|
|
105
117
|
const runners = ctx.ops.remoteRuntime?.listContracts() ?? [];
|
|
106
118
|
ctx.print([
|
|
107
119
|
'Startup Remote',
|
|
@@ -112,40 +124,19 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
|
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
if (sub === 'sandbox') {
|
|
127
|
+
const snapshot = await getSnapshot();
|
|
115
128
|
ctx.print(renderSetupSandboxReview(ctx, snapshot));
|
|
116
129
|
return;
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
if (sub === 'onboarding') {
|
|
120
|
-
ctx
|
|
121
|
-
|
|
122
|
-
` providers: ${snapshot.providerCount > 0 ? '[ready]' : '[needs setup]'}`,
|
|
123
|
-
` services: ${(snapshot.serviceCount > 0 || snapshot.oauthProviderCount > 0 || snapshot.builtinSubscriptionProviderCount > 0) ? '[ready]' : '[optional]'}`,
|
|
124
|
-
` subscriptions: ${snapshot.activeSubscriptionCount > 0 ? '[ready]' : (snapshot.oauthProviderCount + snapshot.builtinSubscriptionProviderCount) > 0 ? '[available]' : '[optional]'}`,
|
|
125
|
-
` hooks: ${(snapshot.managedHookCount + snapshot.managedHookChainCount) > 0 ? '[ready]' : '[optional]'}`,
|
|
126
|
-
` remote: ${snapshot.remoteRunnerCount > 0 ? '[ready]' : '[optional]'}`,
|
|
127
|
-
` sandbox: ${`${ctx.platform.configManager.get('sandbox.vmBackend')}` === 'local' ? '[local default]' : (snapshot.sandboxSecureModeReady ? '[qemu ready]' : '[host blocked]')}`,
|
|
128
|
-
` plugins: ${snapshot.pluginCount > 0 ? '[ready]' : '[optional]'}`,
|
|
129
|
-
` skills: ${snapshot.skillCount > 0 ? '[ready]' : '[optional]'}`,
|
|
130
|
-
'',
|
|
131
|
-
'Recommended next commands:',
|
|
132
|
-
' /health review',
|
|
133
|
-
' /provider',
|
|
134
|
-
' /services doctor',
|
|
135
|
-
' /subscription review',
|
|
136
|
-
' /hooks scaffold <name> <match> <type>',
|
|
137
|
-
' /setup sandbox',
|
|
138
|
-
' /sandbox recommend',
|
|
139
|
-
' /sandbox qemu bootstrap .goodvibes/tui/sandbox 20',
|
|
140
|
-
...(process.platform === 'win32' && !isRunningInWsl() ? [' Run GoodVibes inside WSL before enabling QEMU sandboxing'] : []),
|
|
141
|
-
' /remote setup',
|
|
142
|
-
' /plugin browse',
|
|
143
|
-
' /skills browse',
|
|
144
|
-
].join('\n'));
|
|
133
|
+
openOnboardingWizard(ctx, { mode: 'edit', reset: true });
|
|
134
|
+
ctx.print('Opening onboarding wizard.');
|
|
145
135
|
return;
|
|
146
136
|
}
|
|
147
137
|
|
|
148
138
|
if (sub === 'support-bundle') {
|
|
139
|
+
const snapshot = await getSnapshot();
|
|
149
140
|
const dirArg = args[1];
|
|
150
141
|
if (!dirArg) {
|
|
151
142
|
ctx.print('Usage: /setup support-bundle <dir>');
|
|
@@ -167,12 +158,13 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
|
|
|
167
158
|
}
|
|
168
159
|
|
|
169
160
|
if (sub === 'export') {
|
|
161
|
+
const snapshot = await getSnapshot();
|
|
170
162
|
const pathArg = args[1];
|
|
171
163
|
if (!pathArg) {
|
|
172
164
|
ctx.print('Usage: /setup export <path>');
|
|
173
165
|
return;
|
|
174
166
|
}
|
|
175
|
-
const targetPath =
|
|
167
|
+
const targetPath = getShellPaths().resolveWorkspacePath(pathArg);
|
|
176
168
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
177
169
|
writeFileSync(targetPath, JSON.stringify(snapshot, null, 2) + '\n', 'utf-8');
|
|
178
170
|
ctx.print(`Exported startup review to ${targetPath}`);
|
|
@@ -186,8 +178,9 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
|
|
|
186
178
|
ctx.print('Usage: /setup transfer <export|inspect|import> <path>');
|
|
187
179
|
return;
|
|
188
180
|
}
|
|
189
|
-
const targetPath =
|
|
181
|
+
const targetPath = getShellPaths().resolveWorkspacePath(pathArg);
|
|
190
182
|
if (mode === 'export') {
|
|
183
|
+
const snapshot = await getSnapshot();
|
|
191
184
|
const bundle = buildSetupTransferBundle(ctx, snapshot);
|
|
192
185
|
ctx.print(`Exported setup transfer bundle to ${exportSetupTransferBundle(ctx, pathArg, bundle)}`);
|
|
193
186
|
return;
|
|
@@ -210,17 +203,17 @@ export function registerLocalSetupCommands(registry: CommandRegistry): void {
|
|
|
210
203
|
}
|
|
211
204
|
}
|
|
212
205
|
if (bundle.services) {
|
|
213
|
-
const servicesPath =
|
|
206
|
+
const servicesPath = getShellPaths().resolveProjectPath('tui', 'services.json');
|
|
214
207
|
mkdirSync(dirname(servicesPath), { recursive: true });
|
|
215
208
|
writeFileSync(servicesPath, JSON.stringify(bundle.services, null, 2) + '\n', 'utf-8');
|
|
216
209
|
}
|
|
217
210
|
if (bundle.ecosystem?.plugins) {
|
|
218
|
-
const pluginsPath =
|
|
211
|
+
const pluginsPath = getShellPaths().resolveProjectPath('tui', 'ecosystem', 'plugins.json');
|
|
219
212
|
mkdirSync(dirname(pluginsPath), { recursive: true });
|
|
220
213
|
writeFileSync(pluginsPath, JSON.stringify(bundle.ecosystem.plugins, null, 2) + '\n', 'utf-8');
|
|
221
214
|
}
|
|
222
215
|
if (bundle.ecosystem?.skills) {
|
|
223
|
-
const skillsPath =
|
|
216
|
+
const skillsPath = getShellPaths().resolveProjectPath('tui', 'ecosystem', 'skills.json');
|
|
224
217
|
mkdirSync(dirname(skillsPath), { recursive: true });
|
|
225
218
|
writeFileSync(skillsPath, JSON.stringify(bundle.ecosystem.skills, null, 2) + '\n', 'utf-8');
|
|
226
219
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
+
import { openOnboardingWizard } from './runtime-services.ts';
|
|
3
|
+
|
|
4
|
+
export function registerOnboardingRuntimeCommands(registry: CommandRegistry): void {
|
|
5
|
+
registry.register({
|
|
6
|
+
name: 'onboarding',
|
|
7
|
+
description: 'Open the onboarding wizard with current settings preloaded for review and editing',
|
|
8
|
+
usage: '',
|
|
9
|
+
handler(_args, ctx) {
|
|
10
|
+
openOnboardingWizard(ctx, { mode: 'edit', reset: true });
|
|
11
|
+
ctx.print('Opening onboarding wizard.');
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -99,6 +99,15 @@ export function openCommandPanel(
|
|
|
99
99
|
showPanel(panelId, pane);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
export function openOnboardingWizard(
|
|
103
|
+
context: Pick<CommandContext, 'openOnboardingWizard'>,
|
|
104
|
+
modeOrOptions?: import('../onboarding/onboarding-wizard.ts').OnboardingWizardMode
|
|
105
|
+
| import('../handler-ui-state.ts').OpenOnboardingWizardOptions,
|
|
106
|
+
): void {
|
|
107
|
+
const openWizard = requireContextValue(context.openOnboardingWizard, 'openOnboardingWizard');
|
|
108
|
+
openWizard(modeOrOptions);
|
|
109
|
+
}
|
|
110
|
+
|
|
102
111
|
export function requireKeybindingsManager(context: CommandContext) {
|
|
103
112
|
return requireContextValue(context.workspace.keybindingsManager, 'workspace.keybindingsManager');
|
|
104
113
|
}
|
package/src/input/commands.ts
CHANGED
|
@@ -53,6 +53,7 @@ import { registerLocalAuthRuntimeCommands } from './commands/local-auth-runtime.
|
|
|
53
53
|
import { registerIntelligenceRuntimeCommands } from './commands/intelligence-runtime.ts';
|
|
54
54
|
import { registerConversationRuntimeCommands } from './commands/conversation-runtime.ts';
|
|
55
55
|
import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
|
|
56
|
+
import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime.ts';
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* registerBuiltinCommands - Register all built-in slash commands into the registry.
|
|
@@ -100,6 +101,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
|
100
101
|
registerIntelligenceRuntimeCommands(registry);
|
|
101
102
|
registerConversationRuntimeCommands(registry);
|
|
102
103
|
registerQrcodeRuntimeCommands(registry);
|
|
104
|
+
registerOnboardingRuntimeCommands(registry);
|
|
103
105
|
registerLocalRuntimeCommands(registry);
|
|
104
106
|
registerSessionWorkflowCommands(registry);
|
|
105
107
|
registerDiscoveryRuntimeCommands(registry);
|
|
@@ -31,10 +31,12 @@ import type { BookmarkModal } from './bookmark-modal.ts';
|
|
|
31
31
|
import type { SettingsModal } from './settings-modal.ts';
|
|
32
32
|
import type { SessionPickerModal } from './session-picker-modal.ts';
|
|
33
33
|
import type { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
34
|
+
import type { OnboardingWizardController } from './onboarding/onboarding-wizard.ts';
|
|
34
35
|
import type { WrappedPromptInfo } from './handler-prompt-buffer.ts';
|
|
35
36
|
import type { Panel } from '../panels/types.ts';
|
|
36
37
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
37
38
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
39
|
+
import type { ModelPickerTarget } from './model-picker.ts';
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* Initial mutable scalar values for InputFeedContext.
|
|
@@ -77,7 +79,7 @@ export interface FeedContextMutableInit {
|
|
|
77
79
|
* `profilePickerModal` — modal objects constructed once
|
|
78
80
|
* - `filePicker`, `modelPicker`, `processModal`, `liveTailModal`,
|
|
79
81
|
* `agentDetailModal`, `contextInspectorModal`, `blockActionsMenu`,
|
|
80
|
-
* `searchManager`, `historySearch` — service objects constructed once
|
|
82
|
+
* `searchManager`, `historySearch`, `onboardingWizard` — service objects constructed once
|
|
81
83
|
* - `panelManager`, `keybindingsManager` — from uiServices, stable
|
|
82
84
|
* - `modalStack` — reference to the handler's shared array
|
|
83
85
|
* - `getHistory`, `getViewportHeight`, `getScrollTop`, `scroll`, `exitApp` — callbacks
|
|
@@ -104,6 +106,7 @@ export interface FeedContextStableRefs {
|
|
|
104
106
|
autocomplete: AutocompleteEngine | null;
|
|
105
107
|
filePicker: FilePickerModal;
|
|
106
108
|
modelPicker: ModelPickerModal;
|
|
109
|
+
onboardingWizard: OnboardingWizardController;
|
|
107
110
|
processModal: ProcessModal;
|
|
108
111
|
liveTailModal: LiveTailModal;
|
|
109
112
|
agentDetailModal: AgentDetailModal;
|
|
@@ -148,6 +151,9 @@ export interface FeedContextClosures {
|
|
|
148
151
|
findMarkerAtPos: (pos: number) => { start: number; end: number } | null;
|
|
149
152
|
cleanupMarkerRegistry: (text: string) => void;
|
|
150
153
|
expandPrompt: (text: string) => string | import('@pellux/goodvibes-sdk/platform/providers/interface').ContentPart[];
|
|
154
|
+
openModelPickerWithTarget: (target: ModelPickerTarget, source?: 'settings' | 'onboarding') => boolean;
|
|
155
|
+
onModelPickerCommit: () => boolean;
|
|
156
|
+
onOnboardingAction: (action: import('./onboarding/onboarding-wizard.ts').OnboardingWizardAction) => void;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
/**
|
|
@@ -233,4 +239,5 @@ export function syncFeedContextMutableFields(
|
|
|
233
239
|
ctx.nextImageId = fields.nextImageId;
|
|
234
240
|
ctx.mouseDownRow = fields.mouseDownRow;
|
|
235
241
|
ctx.mouseDownCol = fields.mouseDownCol;
|
|
242
|
+
ctx.contentWidth = fields.contentWidth;
|
|
236
243
|
}
|