@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
package/src/cli/types.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export type GoodVibesCliCommand =
|
|
2
|
+
| 'tui'
|
|
3
|
+
| 'run'
|
|
4
|
+
| 'serve'
|
|
5
|
+
| 'web'
|
|
6
|
+
| 'service'
|
|
7
|
+
| 'status'
|
|
8
|
+
| 'doctor'
|
|
9
|
+
| 'onboarding'
|
|
10
|
+
| 'models'
|
|
11
|
+
| 'providers'
|
|
12
|
+
| 'auth'
|
|
13
|
+
| 'subscription'
|
|
14
|
+
| 'secrets'
|
|
15
|
+
| 'sessions'
|
|
16
|
+
| 'tasks'
|
|
17
|
+
| 'pair'
|
|
18
|
+
| 'surfaces'
|
|
19
|
+
| 'listener'
|
|
20
|
+
| 'control-plane'
|
|
21
|
+
| 'bundle'
|
|
22
|
+
| 'remote'
|
|
23
|
+
| 'bridge'
|
|
24
|
+
| 'completion'
|
|
25
|
+
| 'help'
|
|
26
|
+
| 'version'
|
|
27
|
+
| 'unknown';
|
|
28
|
+
|
|
29
|
+
export type GoodVibesCliOutputFormat = 'text' | 'json' | 'stream-json';
|
|
30
|
+
|
|
31
|
+
export interface CliCommandOutput {
|
|
32
|
+
readonly output: string;
|
|
33
|
+
readonly exitCode: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface GoodVibesCliFlags {
|
|
37
|
+
readonly provider: string | undefined;
|
|
38
|
+
readonly model: string | undefined;
|
|
39
|
+
readonly daemonHome: string | undefined;
|
|
40
|
+
readonly workingDir: string | undefined;
|
|
41
|
+
readonly help: boolean;
|
|
42
|
+
readonly version: boolean;
|
|
43
|
+
readonly prompt: string | undefined;
|
|
44
|
+
readonly print: boolean;
|
|
45
|
+
readonly outputFormat: GoodVibesCliOutputFormat;
|
|
46
|
+
readonly configOverrides: readonly string[];
|
|
47
|
+
readonly enableFeatures: readonly string[];
|
|
48
|
+
readonly disableFeatures: readonly string[];
|
|
49
|
+
readonly noAltScreen: boolean;
|
|
50
|
+
readonly port: number | undefined;
|
|
51
|
+
readonly hostname: string | undefined;
|
|
52
|
+
readonly open: boolean;
|
|
53
|
+
readonly continueLast: boolean;
|
|
54
|
+
readonly resume: string | undefined;
|
|
55
|
+
readonly session: string | undefined;
|
|
56
|
+
readonly fork: boolean;
|
|
57
|
+
readonly rawOutput: boolean;
|
|
58
|
+
readonly acceptRawOutputRisk: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface GoodVibesCliParseResult {
|
|
62
|
+
readonly binary: string;
|
|
63
|
+
readonly command: GoodVibesCliCommand;
|
|
64
|
+
readonly rawCommand: string | undefined;
|
|
65
|
+
readonly commandArgs: readonly string[];
|
|
66
|
+
readonly positionals: readonly string[];
|
|
67
|
+
readonly flags: GoodVibesCliFlags;
|
|
68
|
+
readonly errors: readonly string[];
|
|
69
|
+
}
|
package/src/cli-flags.ts
CHANGED
|
@@ -1,58 +1,21 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// Compatibility wrapper for older imports. New CLI code lives in src/cli.
|
|
2
|
+
export type { GoodVibesCliFlags as CliFlags } from './cli/types.ts';
|
|
3
|
+
export {
|
|
4
|
+
applyRuntimeConfigOverrides,
|
|
5
|
+
applyRuntimeConfigValue,
|
|
6
|
+
applyRuntimeCommandEndpointFlagOverrides,
|
|
7
|
+
applyRuntimeEndpointFlagOverrides,
|
|
8
|
+
applyRuntimeFeatureFlagOverrides,
|
|
9
|
+
handleGoodVibesCliCommand,
|
|
10
|
+
parseGoodVibesCli,
|
|
11
|
+
renderGoodVibesCommandHelp,
|
|
12
|
+
renderGoodVibesHelp,
|
|
13
|
+
renderGoodVibesVersion,
|
|
14
|
+
} from './cli/index.ts';
|
|
4
15
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
readonly model: string | undefined;
|
|
8
|
-
readonly daemonHome: string | undefined;
|
|
9
|
-
readonly workingDir: string | undefined;
|
|
10
|
-
};
|
|
16
|
+
import { parseGoodVibesCli } from './cli/parser.ts';
|
|
17
|
+
import type { GoodVibesCliFlags } from './cli/types.ts';
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
* @param argv - argv array (pass `process.argv.slice(2)`)
|
|
16
|
-
* @param binary - binary name shown in the --help usage line (e.g. "goodvibes" or "goodvibes-daemon")
|
|
17
|
-
*/
|
|
18
|
-
export function parseCliFlags(argv: readonly string[], binary = 'goodvibes'): CliFlags {
|
|
19
|
-
let provider: string | undefined;
|
|
20
|
-
let model: string | undefined;
|
|
21
|
-
let daemonHome: string | undefined;
|
|
22
|
-
let workingDir: string | undefined;
|
|
23
|
-
|
|
24
|
-
for (let i = 0; i < argv.length; i++) {
|
|
25
|
-
const arg = argv[i];
|
|
26
|
-
if (arg === '--help' || arg === '-h') {
|
|
27
|
-
// eslint-disable-next-line no-console
|
|
28
|
-
console.log([
|
|
29
|
-
`Usage: ${binary} [options]`,
|
|
30
|
-
'',
|
|
31
|
-
'Options:',
|
|
32
|
-
' --provider <id> Override the provider from settings.json at startup',
|
|
33
|
-
' --model <registryKey> Override the model from settings.json at startup',
|
|
34
|
-
' Format: provider:modelId (e.g. inception:mercury-2)',
|
|
35
|
-
' If provider:modelId format is used, --provider is inferred',
|
|
36
|
-
' --daemon-home=<path> Override daemon home (precedence: flag > GOODVIBES_DAEMON_HOME env > ~/.goodvibes/daemon)',
|
|
37
|
-
' --working-dir=<path> Override working directory (precedence: flag > GOODVIBES_WORKING_DIR env > <cwd>)',
|
|
38
|
-
' --help, -h Show this help message',
|
|
39
|
-
].join('\n'));
|
|
40
|
-
process.exit(0);
|
|
41
|
-
}
|
|
42
|
-
if (arg === '--provider' && argv[i + 1] !== undefined) {
|
|
43
|
-
provider = argv[++i];
|
|
44
|
-
} else if (arg === '--model' && argv[i + 1] !== undefined) {
|
|
45
|
-
model = argv[++i];
|
|
46
|
-
// Infer provider from registryKey format (provider:modelId) if --provider not given
|
|
47
|
-
if (typeof model === 'string' && model.includes(':') && provider === undefined) {
|
|
48
|
-
provider = model.split(':')[0];
|
|
49
|
-
}
|
|
50
|
-
} else if (arg.startsWith('--daemon-home=')) {
|
|
51
|
-
daemonHome = arg.slice('--daemon-home='.length);
|
|
52
|
-
} else if (arg.startsWith('--working-dir=')) {
|
|
53
|
-
workingDir = arg.slice('--working-dir='.length);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return { provider, model, daemonHome, workingDir };
|
|
19
|
+
export function parseCliFlags(argv: readonly string[], binary = 'goodvibes'): GoodVibesCliFlags {
|
|
20
|
+
return parseGoodVibesCli(argv, binary).flags;
|
|
58
21
|
}
|
package/src/config/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
export { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
|
|
10
10
|
export type { DeepReadonly } from '@pellux/goodvibes-sdk/platform/config/manager';
|
|
11
|
-
export type { GoodVibesConfig, ConfigKey, ConfigValue, ConfigSetting, PermissionMode, PermissionAction, PermissionsToolConfig, NotificationsConfig } from '@pellux/goodvibes-sdk/platform/config/schema';
|
|
11
|
+
export type { GoodVibesConfig, ConfigKey, ConfigValue, ConfigSetting, PermissionMode, PermissionAction, PermissionsToolConfig, NotificationsConfig, PersistedFlagState } from '@pellux/goodvibes-sdk/platform/config/schema';
|
|
12
12
|
export { DEFAULT_CONFIG, CONFIG_SCHEMA } from '@pellux/goodvibes-sdk/platform/config/schema';
|
|
13
13
|
export { ConfigError } from '@pellux/goodvibes-sdk/platform/types/errors';
|
|
14
14
|
|
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/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
|
}
|