@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +5 -5
  3. package/bin/goodvibes +5 -0
  4. package/bin/goodvibes-daemon +5 -0
  5. package/docs/foundation-artifacts/operator-contract.json +1 -1
  6. package/package.json +2 -2
  7. package/src/cli/completion.ts +89 -0
  8. package/src/cli/config-overrides.ts +159 -0
  9. package/src/cli/endpoints.ts +63 -0
  10. package/src/cli/entrypoint.ts +155 -0
  11. package/src/cli/help.ts +122 -0
  12. package/src/cli/index.ts +8 -0
  13. package/src/cli/management-commands.ts +576 -0
  14. package/src/cli/management.ts +693 -0
  15. package/src/cli/parser.ts +367 -0
  16. package/src/cli/status.ts +112 -0
  17. package/src/cli/tui-startup.ts +32 -0
  18. package/src/cli/types.ts +63 -0
  19. package/src/cli-flags.ts +17 -55
  20. package/src/config/index.ts +1 -1
  21. package/src/config/secrets.ts +44 -0
  22. package/src/core/conversation.ts +36 -13
  23. package/src/daemon/cli.ts +62 -11
  24. package/src/input/command-registry.ts +3 -0
  25. package/src/input/commands/guidance-runtime.ts +9 -4
  26. package/src/input/commands/local-runtime.ts +21 -7
  27. package/src/input/commands/local-setup.ts +31 -38
  28. package/src/input/commands/onboarding-runtime.ts +14 -0
  29. package/src/input/commands/runtime-services.ts +9 -0
  30. package/src/input/commands.ts +2 -0
  31. package/src/input/feed-context-factory.ts +8 -1
  32. package/src/input/handler-feed.ts +13 -8
  33. package/src/input/handler-interactions.ts +266 -0
  34. package/src/input/handler-modal-stack.ts +23 -3
  35. package/src/input/handler-modal-token-routes.ts +23 -1
  36. package/src/input/handler-onboarding.ts +696 -0
  37. package/src/input/handler-picker-routes.ts +15 -7
  38. package/src/input/handler-ui-state.ts +58 -0
  39. package/src/input/handler.ts +120 -246
  40. package/src/input/onboarding/handler-onboarding-routes.ts +105 -0
  41. package/src/input/onboarding/onboarding-wizard-apply.ts +211 -0
  42. package/src/input/onboarding/onboarding-wizard-constants.ts +148 -0
  43. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +712 -0
  44. package/src/input/onboarding/onboarding-wizard-helpers.ts +218 -0
  45. package/src/input/onboarding/onboarding-wizard-rules.ts +224 -0
  46. package/src/input/onboarding/onboarding-wizard-state.ts +354 -0
  47. package/src/input/onboarding/onboarding-wizard-steps.ts +642 -0
  48. package/src/input/onboarding/onboarding-wizard-types.ts +170 -0
  49. package/src/input/onboarding/onboarding-wizard.ts +594 -0
  50. package/src/main.ts +32 -39
  51. package/src/panels/builtin/operations.ts +0 -10
  52. package/src/panels/index.ts +0 -1
  53. package/src/panels/panel-manager.ts +6 -2
  54. package/src/renderer/conversation-overlays.ts +6 -0
  55. package/src/renderer/help-overlay.ts +1 -1
  56. package/src/renderer/onboarding/onboarding-wizard.ts +533 -0
  57. package/src/renderer/panel-composite.ts +42 -5
  58. package/src/renderer/panel-workspace-bar.ts +5 -1
  59. package/src/runtime/bootstrap-core.ts +1 -0
  60. package/src/runtime/bootstrap.ts +123 -0
  61. package/src/runtime/onboarding/apply.ts +685 -0
  62. package/src/runtime/onboarding/derivation.ts +495 -0
  63. package/src/runtime/onboarding/index.ts +7 -0
  64. package/src/runtime/onboarding/markers.ts +161 -0
  65. package/src/runtime/onboarding/snapshot.ts +400 -0
  66. package/src/runtime/onboarding/state.ts +140 -0
  67. package/src/runtime/onboarding/types.ts +402 -0
  68. package/src/runtime/onboarding/verify.ts +233 -0
  69. package/src/runtime/ui-services.ts +16 -0
  70. package/src/shell/ui-openers.ts +12 -2
  71. package/src/version.ts +1 -1
  72. package/src/panels/welcome-panel.ts +0 -64
@@ -0,0 +1,367 @@
1
+ import type {
2
+ GoodVibesCliCommand,
3
+ GoodVibesCliFlags,
4
+ GoodVibesCliOutputFormat,
5
+ GoodVibesCliParseResult,
6
+ } from './types.ts';
7
+
8
+ const COMMAND_ALIASES: Readonly<Record<string, GoodVibesCliCommand>> = {
9
+ tui: 'tui',
10
+ app: 'tui',
11
+ run: 'run',
12
+ exec: 'run',
13
+ e: 'run',
14
+ serve: 'serve',
15
+ daemon: 'serve',
16
+ server: 'serve',
17
+ web: 'web',
18
+ status: 'status',
19
+ doctor: 'doctor',
20
+ onboarding: 'onboarding',
21
+ setup: 'onboarding',
22
+ models: 'models',
23
+ model: 'models',
24
+ providers: 'providers',
25
+ provider: 'providers',
26
+ auth: 'auth',
27
+ subscription: 'subscription',
28
+ subscriptions: 'subscription',
29
+ secrets: 'secrets',
30
+ secret: 'secrets',
31
+ sessions: 'sessions',
32
+ session: 'sessions',
33
+ tasks: 'tasks',
34
+ task: 'tasks',
35
+ pair: 'pair',
36
+ qrcode: 'pair',
37
+ qr: 'pair',
38
+ surfaces: 'surfaces',
39
+ surface: 'surfaces',
40
+ listener: 'listener',
41
+ 'http-listener': 'listener',
42
+ webhook: 'listener',
43
+ 'control-plane': 'control-plane',
44
+ controlplane: 'control-plane',
45
+ cp: 'control-plane',
46
+ bundle: 'bundle',
47
+ bundles: 'bundle',
48
+ remote: 'remote',
49
+ bridge: 'bridge',
50
+ completion: 'completion',
51
+ completions: 'completion',
52
+ help: 'help',
53
+ version: 'version',
54
+ };
55
+
56
+ function createDefaultFlags(): GoodVibesCliFlags {
57
+ return {
58
+ provider: undefined,
59
+ model: undefined,
60
+ daemonHome: undefined,
61
+ workingDir: undefined,
62
+ help: false,
63
+ version: false,
64
+ prompt: undefined,
65
+ print: false,
66
+ outputFormat: 'text',
67
+ configOverrides: [],
68
+ enableFeatures: [],
69
+ disableFeatures: [],
70
+ noAltScreen: false,
71
+ port: undefined,
72
+ hostname: undefined,
73
+ open: false,
74
+ continueLast: false,
75
+ resume: undefined,
76
+ session: undefined,
77
+ fork: false,
78
+ rawOutput: false,
79
+ acceptRawOutputRisk: false,
80
+ };
81
+ }
82
+
83
+ function splitOption(token: string): { readonly name: string; readonly value: string | undefined } {
84
+ const index = token.indexOf('=');
85
+ if (index < 0) return { name: token, value: undefined };
86
+ return {
87
+ name: token.slice(0, index),
88
+ value: token.slice(index + 1),
89
+ };
90
+ }
91
+
92
+ function getValue(
93
+ argv: readonly string[],
94
+ index: number,
95
+ inlineValue: string | undefined,
96
+ optionName: string,
97
+ errors: string[],
98
+ ): { readonly value: string | undefined; readonly nextIndex: number } {
99
+ if (inlineValue !== undefined) return { value: inlineValue, nextIndex: index };
100
+ const next = argv[index + 1];
101
+ if (next === undefined || (next.startsWith('-') && next !== '-')) {
102
+ errors.push(`${optionName} requires a value.`);
103
+ return { value: undefined, nextIndex: index };
104
+ }
105
+ return { value: next, nextIndex: index + 1 };
106
+ }
107
+
108
+ function getOptionalValue(
109
+ argv: readonly string[],
110
+ index: number,
111
+ inlineValue: string | undefined,
112
+ ): { readonly value: string | undefined; readonly nextIndex: number } {
113
+ if (inlineValue !== undefined) return { value: inlineValue, nextIndex: index };
114
+ const next = argv[index + 1];
115
+ if (next === undefined || next.startsWith('-')) return { value: undefined, nextIndex: index };
116
+ return { value: next, nextIndex: index + 1 };
117
+ }
118
+
119
+ function parsePort(value: string | undefined, optionName: string, errors: string[]): number | undefined {
120
+ if (value === undefined) return undefined;
121
+ if (!/^\d+$/.test(value)) {
122
+ errors.push(`${optionName} must be a port number from 1 to 65535.`);
123
+ return undefined;
124
+ }
125
+ const port = Number.parseInt(value, 10);
126
+ if (port < 1 || port > 65535) {
127
+ errors.push(`${optionName} must be a port number from 1 to 65535.`);
128
+ return undefined;
129
+ }
130
+ return port;
131
+ }
132
+
133
+ function normalizeOutputFormat(value: string | undefined, errors: string[]): GoodVibesCliOutputFormat {
134
+ if (value === 'text' || value === 'json' || value === 'stream-json') return value;
135
+ errors.push('--output-format must be one of: text, json, stream-json.');
136
+ return 'text';
137
+ }
138
+
139
+ function inferProviderFromModel(model: string, currentProvider: string | undefined): string | undefined {
140
+ if (currentProvider !== undefined) return currentProvider;
141
+ if (model.includes(':')) return model.split(':')[0];
142
+ if (model.includes('/')) return model.split('/')[0];
143
+ return undefined;
144
+ }
145
+
146
+ function withFlag<K extends keyof GoodVibesCliFlags>(
147
+ flags: GoodVibesCliFlags,
148
+ key: K,
149
+ value: GoodVibesCliFlags[K],
150
+ ): GoodVibesCliFlags {
151
+ return { ...flags, [key]: value };
152
+ }
153
+
154
+ function appendFlagArray<K extends 'configOverrides' | 'enableFeatures' | 'disableFeatures'>(
155
+ flags: GoodVibesCliFlags,
156
+ key: K,
157
+ value: string,
158
+ ): GoodVibesCliFlags {
159
+ return {
160
+ ...flags,
161
+ [key]: [...flags[key], value],
162
+ };
163
+ }
164
+
165
+ export function parseGoodVibesCli(
166
+ argv: readonly string[],
167
+ binary = 'goodvibes',
168
+ ): GoodVibesCliParseResult {
169
+ let flags = createDefaultFlags();
170
+ let command: GoodVibesCliCommand = 'tui';
171
+ let rawCommand: string | undefined;
172
+ const commandArgs: string[] = [];
173
+ const positionals: string[] = [];
174
+ const errors: string[] = [];
175
+ let sawCommand = false;
176
+ let passthrough = false;
177
+
178
+ for (let index = 0; index < argv.length; index += 1) {
179
+ const token = argv[index]!;
180
+
181
+ if (passthrough) {
182
+ if (sawCommand) commandArgs.push(token);
183
+ else positionals.push(token);
184
+ continue;
185
+ }
186
+
187
+ if (token === '--') {
188
+ passthrough = true;
189
+ continue;
190
+ }
191
+
192
+ if (!token.startsWith('-') || token === '-') {
193
+ if (!sawCommand) {
194
+ const normalized = COMMAND_ALIASES[token.toLowerCase()];
195
+ if (normalized) {
196
+ command = normalized;
197
+ rawCommand = token;
198
+ sawCommand = true;
199
+ continue;
200
+ }
201
+ }
202
+ positionals.push(token);
203
+ if (sawCommand) commandArgs.push(token);
204
+ continue;
205
+ }
206
+
207
+ const { name, value: inlineValue } = splitOption(token);
208
+
209
+ if (name === '--help' || name === '-h') {
210
+ flags = withFlag(flags, 'help', true);
211
+ continue;
212
+ }
213
+ if (name === '--version' || name === '-v') {
214
+ flags = withFlag(flags, 'version', true);
215
+ continue;
216
+ }
217
+ if (name === '--print') {
218
+ flags = withFlag(flags, 'print', true);
219
+ if (!sawCommand) command = 'run';
220
+ continue;
221
+ }
222
+ if (name === '--json') {
223
+ flags = withFlag(flags, 'outputFormat', 'json');
224
+ continue;
225
+ }
226
+ if (name === '--no-alt-screen') {
227
+ flags = withFlag(flags, 'noAltScreen', true);
228
+ continue;
229
+ }
230
+ if (name === '--open') {
231
+ flags = withFlag(flags, 'open', true);
232
+ continue;
233
+ }
234
+ if (name === '--continue') {
235
+ flags = withFlag(flags, 'continueLast', true);
236
+ continue;
237
+ }
238
+ if (name === '--fork') {
239
+ flags = withFlag(flags, 'fork', true);
240
+ continue;
241
+ }
242
+ if (name === '--raw-output') {
243
+ flags = withFlag(flags, 'rawOutput', true);
244
+ continue;
245
+ }
246
+ if (name === '--accept-raw-output-risk') {
247
+ flags = withFlag(flags, 'acceptRawOutputRisk', true);
248
+ continue;
249
+ }
250
+
251
+ if (name === '--provider') {
252
+ const consumed = getValue(argv, index, inlineValue, name, errors);
253
+ index = consumed.nextIndex;
254
+ if (consumed.value !== undefined) flags = withFlag(flags, 'provider', consumed.value);
255
+ continue;
256
+ }
257
+ if (name === '--model' || name === '-m') {
258
+ const consumed = getValue(argv, index, inlineValue, name, errors);
259
+ index = consumed.nextIndex;
260
+ if (consumed.value !== undefined) {
261
+ flags = withFlag(flags, 'model', consumed.value);
262
+ flags = withFlag(flags, 'provider', inferProviderFromModel(consumed.value, flags.provider));
263
+ }
264
+ continue;
265
+ }
266
+ if (name === '--daemon-home') {
267
+ const consumed = getValue(argv, index, inlineValue, name, errors);
268
+ index = consumed.nextIndex;
269
+ if (consumed.value !== undefined) flags = withFlag(flags, 'daemonHome', consumed.value);
270
+ continue;
271
+ }
272
+ if (name === '--working-dir' || name === '--cd' || name === '-C') {
273
+ const consumed = getValue(argv, index, inlineValue, name, errors);
274
+ index = consumed.nextIndex;
275
+ if (consumed.value !== undefined) flags = withFlag(flags, 'workingDir', consumed.value);
276
+ continue;
277
+ }
278
+ if (name === '--prompt' || name === '-p') {
279
+ const consumed = getValue(argv, index, inlineValue, name, errors);
280
+ index = consumed.nextIndex;
281
+ if (consumed.value !== undefined) {
282
+ flags = withFlag(flags, 'prompt', consumed.value);
283
+ if (!sawCommand) command = 'run';
284
+ }
285
+ continue;
286
+ }
287
+ if (name === '--output-format' || name === '--output' || name === '-o') {
288
+ const consumed = getValue(argv, index, inlineValue, name, errors);
289
+ index = consumed.nextIndex;
290
+ flags = withFlag(flags, 'outputFormat', normalizeOutputFormat(consumed.value, errors));
291
+ continue;
292
+ }
293
+ if (name === '--config') {
294
+ const consumed = getValue(argv, index, inlineValue, name, errors);
295
+ index = consumed.nextIndex;
296
+ if (consumed.value !== undefined) flags = appendFlagArray(flags, 'configOverrides', consumed.value);
297
+ continue;
298
+ }
299
+ if (name === '-c') {
300
+ const consumed = getValue(argv, index, inlineValue, name, errors);
301
+ index = consumed.nextIndex;
302
+ if (consumed.value !== undefined) flags = appendFlagArray(flags, 'configOverrides', consumed.value);
303
+ continue;
304
+ }
305
+ if (name === '--enable') {
306
+ const consumed = getValue(argv, index, inlineValue, name, errors);
307
+ index = consumed.nextIndex;
308
+ if (consumed.value !== undefined) flags = appendFlagArray(flags, 'enableFeatures', consumed.value);
309
+ continue;
310
+ }
311
+ if (name === '--disable') {
312
+ const consumed = getValue(argv, index, inlineValue, name, errors);
313
+ index = consumed.nextIndex;
314
+ if (consumed.value !== undefined) flags = appendFlagArray(flags, 'disableFeatures', consumed.value);
315
+ continue;
316
+ }
317
+ if (name === '--port') {
318
+ const consumed = getValue(argv, index, inlineValue, name, errors);
319
+ index = consumed.nextIndex;
320
+ flags = withFlag(flags, 'port', parsePort(consumed.value, name, errors));
321
+ continue;
322
+ }
323
+ if (name === '--hostname' || name === '--host') {
324
+ const consumed = getValue(argv, index, inlineValue, name, errors);
325
+ index = consumed.nextIndex;
326
+ if (consumed.value !== undefined) flags = withFlag(flags, 'hostname', consumed.value);
327
+ continue;
328
+ }
329
+ if (name === '--resume' || name === '-r') {
330
+ const consumed = getOptionalValue(argv, index, inlineValue);
331
+ index = consumed.nextIndex;
332
+ flags = withFlag(flags, 'resume', consumed.value ?? 'latest');
333
+ continue;
334
+ }
335
+ if (name === '--session' || name === '-s') {
336
+ const consumed = getValue(argv, index, inlineValue, name, errors);
337
+ index = consumed.nextIndex;
338
+ if (consumed.value !== undefined) flags = withFlag(flags, 'session', consumed.value);
339
+ continue;
340
+ }
341
+
342
+ if (sawCommand) {
343
+ commandArgs.push(token);
344
+ continue;
345
+ }
346
+
347
+ errors.push(`Unknown option: ${name}`);
348
+ }
349
+
350
+ if (flags.prompt === undefined && (command === 'run' || flags.print) && positionals.length > 0) {
351
+ flags = withFlag(flags, 'prompt', positionals.join(' '));
352
+ }
353
+
354
+ if (rawCommand !== undefined && command === 'unknown') {
355
+ errors.push(`Unknown command: ${rawCommand}`);
356
+ }
357
+
358
+ return {
359
+ binary,
360
+ command,
361
+ rawCommand,
362
+ commandArgs,
363
+ positionals,
364
+ flags,
365
+ errors,
366
+ };
367
+ }
@@ -0,0 +1,112 @@
1
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
2
+ import type { OnboardingCompletionMarkersState } from '../runtime/onboarding/index.ts';
3
+ import { resolveRuntimeEndpointBinding } from './endpoints.ts';
4
+
5
+ export interface CliStatusOptions {
6
+ readonly configManager: Pick<ConfigManager, 'get'>;
7
+ readonly workingDirectory: string;
8
+ readonly homeDirectory: string;
9
+ readonly onboardingMarkers?: OnboardingCompletionMarkersState;
10
+ readonly auth?: CliAuthStatus;
11
+ readonly doctor?: boolean;
12
+ }
13
+
14
+ export interface CliAuthStatus {
15
+ readonly userStorePath: string;
16
+ readonly userStorePresent: boolean;
17
+ readonly bootstrapCredentialPath: string;
18
+ readonly bootstrapCredentialPresent: boolean;
19
+ readonly operatorTokenPath: string;
20
+ readonly operatorTokenPresent: boolean;
21
+ }
22
+
23
+ function yesNo(value: unknown): string {
24
+ return value === true ? 'yes' : 'no';
25
+ }
26
+
27
+ function bindLine(label: string, enabled: unknown, binding: { readonly hostMode: string; readonly host: string; readonly port: number }): string {
28
+ return ` ${label}: ${yesNo(enabled)} (${binding.hostMode} ${binding.host}:${binding.port})`;
29
+ }
30
+
31
+ export function renderCliStatus(options: CliStatusOptions): string {
32
+ const config = options.configManager;
33
+ const serviceEnabled = config.get('service.enabled');
34
+ const serviceAutostart = config.get('service.autostart');
35
+ const restartOnFailure = config.get('service.restartOnFailure');
36
+ const daemonEnabled = config.get('danger.daemon');
37
+ const listenerEnabled = config.get('danger.httpListener');
38
+ const webEnabled = config.get('web.enabled');
39
+ const controlPlaneEnabled = config.get('controlPlane.enabled');
40
+ const controlPlaneBinding = resolveRuntimeEndpointBinding(config, 'controlPlane');
41
+ const httpListenerBinding = resolveRuntimeEndpointBinding(config, 'httpListener');
42
+ const webBinding = resolveRuntimeEndpointBinding(config, 'web');
43
+ const marker = options.onboardingMarkers?.effective;
44
+ const warnings: string[] = [];
45
+
46
+ if ((daemonEnabled || controlPlaneEnabled || listenerEnabled || webEnabled) && !serviceEnabled) {
47
+ warnings.push('server-backed surfaces are enabled but service.enabled is off');
48
+ }
49
+ if (serviceEnabled && !serviceAutostart) warnings.push('service.enabled is on but service.autostart is off');
50
+ if (serviceEnabled && !restartOnFailure) warnings.push('service.enabled is on but service.restartOnFailure is off');
51
+ if (!marker?.payload) warnings.push('onboarding has not been completed for this user/project');
52
+
53
+ const lines = [
54
+ options.doctor ? 'GoodVibes doctor' : 'GoodVibes status',
55
+ ` workingDir: ${options.workingDirectory}`,
56
+ ` homeDir: ${options.homeDirectory}`,
57
+ '',
58
+ 'Provider:',
59
+ ` provider: ${String(config.get('provider.provider'))}`,
60
+ ` model: ${String(config.get('provider.model'))}`,
61
+ ` reasoning: ${String(config.get('provider.reasoningEffort'))}`,
62
+ '',
63
+ 'Auth:',
64
+ ` permissions: ${String(config.get('permissions.mode'))}`,
65
+ ` secretPolicy: ${String(config.get('storage.secretPolicy'))}`,
66
+ options.auth
67
+ ? ` localUsers: ${options.auth.userStorePresent ? 'present' : 'missing'} (${options.auth.userStorePath})`
68
+ : ' localUsers: unknown',
69
+ options.auth
70
+ ? ` bootstrapCredential: ${options.auth.bootstrapCredentialPresent ? 'present' : 'missing'} (${options.auth.bootstrapCredentialPath})`
71
+ : ' bootstrapCredential: unknown',
72
+ options.auth
73
+ ? ` operatorTokens: ${options.auth.operatorTokenPresent ? 'present' : 'missing'} (${options.auth.operatorTokenPath})`
74
+ : ' operatorTokens: unknown',
75
+ '',
76
+ 'Service:',
77
+ ` enabled: ${yesNo(serviceEnabled)}`,
78
+ ` autostart: ${yesNo(serviceAutostart)}`,
79
+ ` restartOnFailure: ${yesNo(restartOnFailure)}`,
80
+ '',
81
+ 'Surfaces:',
82
+ bindLine('controlPlane', controlPlaneEnabled, controlPlaneBinding),
83
+ bindLine('httpListener', listenerEnabled, httpListenerBinding),
84
+ bindLine('web', webEnabled, webBinding),
85
+ '',
86
+ 'Onboarding:',
87
+ ` completed: ${marker?.payload ? 'yes' : 'no'}`,
88
+ ` scope: ${marker?.scope ?? 'none'}`,
89
+ ` updatedAt: ${marker?.payload ? new Date(marker.payload.updatedAt).toISOString() : 'n/a'}`,
90
+ ];
91
+
92
+ if (options.doctor) {
93
+ lines.push('', 'Warnings:');
94
+ if (warnings.length === 0) lines.push(' none');
95
+ else lines.push(...warnings.map((warning) => ` - ${warning}`));
96
+ }
97
+
98
+ return lines.join('\n');
99
+ }
100
+
101
+ export function renderOnboardingCliStatus(options: CliStatusOptions): string {
102
+ const marker = options.onboardingMarkers?.effective;
103
+ return [
104
+ 'GoodVibes onboarding status',
105
+ ` completed: ${marker?.payload ? 'yes' : 'no'}`,
106
+ ` scope: ${marker?.scope ?? 'none'}`,
107
+ ` source: ${marker?.payload?.source ?? 'n/a'}`,
108
+ ` mode: ${marker?.payload?.mode ?? 'n/a'}`,
109
+ ` updatedAt: ${marker?.payload ? new Date(marker.payload.updatedAt).toISOString() : 'n/a'}`,
110
+ ` workingDir: ${options.workingDirectory}`,
111
+ ].join('\n');
112
+ }
@@ -0,0 +1,32 @@
1
+ import type { CommandContext, CommandRegistry } from '../input/command-registry.ts';
2
+ import type { InputHandler } from '../input/handler.ts';
3
+ import { readOnboardingCompletionMarkers } from '../runtime/onboarding/index.ts';
4
+ import type { GoodVibesCliParseResult } from './types.ts';
5
+
6
+ export function applyInitialTuiCliState(options: {
7
+ readonly cli: GoodVibesCliParseResult;
8
+ readonly input: InputHandler;
9
+ readonly commandRegistry: CommandRegistry;
10
+ readonly commandContext: CommandContext;
11
+ readonly shellPaths: Parameters<typeof readOnboardingCompletionMarkers>[0];
12
+ readonly render: () => void;
13
+ }): void {
14
+ const { cli, input, commandRegistry, commandContext, shellPaths, render } = options;
15
+ const onboardingMarkers = readOnboardingCompletionMarkers(shellPaths);
16
+ if (cli.command === 'onboarding') {
17
+ input.openOnboardingWizard({ mode: 'edit', reset: true });
18
+ } else if (cli.command === 'sessions' && cli.commandArgs[0] === 'resume') {
19
+ const target = cli.commandArgs.slice(1).join(' ').trim();
20
+ if (target) {
21
+ void commandRegistry.execute('session', ['resume', target], commandContext).then(() => render());
22
+ }
23
+ } else if (!onboardingMarkers.effective?.payload) {
24
+ input.openOnboardingWizard({ mode: 'new', reset: true });
25
+ }
26
+
27
+ const seededPrompt = cli.flags.prompt ?? (cli.rawCommand === undefined && cli.positionals.length > 0 ? cli.positionals.join(' ') : undefined);
28
+ if (seededPrompt) {
29
+ input.prompt = seededPrompt;
30
+ input.cursorPos = seededPrompt.length;
31
+ }
32
+ }
@@ -0,0 +1,63 @@
1
+ export type GoodVibesCliCommand =
2
+ | 'tui'
3
+ | 'run'
4
+ | 'serve'
5
+ | 'web'
6
+ | 'status'
7
+ | 'doctor'
8
+ | 'onboarding'
9
+ | 'models'
10
+ | 'providers'
11
+ | 'auth'
12
+ | 'subscription'
13
+ | 'secrets'
14
+ | 'sessions'
15
+ | 'tasks'
16
+ | 'pair'
17
+ | 'surfaces'
18
+ | 'listener'
19
+ | 'control-plane'
20
+ | 'bundle'
21
+ | 'remote'
22
+ | 'bridge'
23
+ | 'completion'
24
+ | 'help'
25
+ | 'version'
26
+ | 'unknown';
27
+
28
+ export type GoodVibesCliOutputFormat = 'text' | 'json' | 'stream-json';
29
+
30
+ export interface GoodVibesCliFlags {
31
+ readonly provider: string | undefined;
32
+ readonly model: string | undefined;
33
+ readonly daemonHome: string | undefined;
34
+ readonly workingDir: string | undefined;
35
+ readonly help: boolean;
36
+ readonly version: boolean;
37
+ readonly prompt: string | undefined;
38
+ readonly print: boolean;
39
+ readonly outputFormat: GoodVibesCliOutputFormat;
40
+ readonly configOverrides: readonly string[];
41
+ readonly enableFeatures: readonly string[];
42
+ readonly disableFeatures: readonly string[];
43
+ readonly noAltScreen: boolean;
44
+ readonly port: number | undefined;
45
+ readonly hostname: string | undefined;
46
+ readonly open: boolean;
47
+ readonly continueLast: boolean;
48
+ readonly resume: string | undefined;
49
+ readonly session: string | undefined;
50
+ readonly fork: boolean;
51
+ readonly rawOutput: boolean;
52
+ readonly acceptRawOutputRisk: boolean;
53
+ }
54
+
55
+ export interface GoodVibesCliParseResult {
56
+ readonly binary: string;
57
+ readonly command: GoodVibesCliCommand;
58
+ readonly rawCommand: string | undefined;
59
+ readonly commandArgs: readonly string[];
60
+ readonly positionals: readonly string[];
61
+ readonly flags: GoodVibesCliFlags;
62
+ readonly errors: readonly string[];
63
+ }
package/src/cli-flags.ts CHANGED
@@ -1,58 +1,20 @@
1
- // ---------------------------------------------------------------------------
2
- // Shared CLI flag parsing for TUI shell and daemon entrypoints.
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
+ renderGoodVibesHelp,
12
+ renderGoodVibesVersion,
13
+ } from './cli/index.ts';
4
14
 
5
- export type CliFlags = {
6
- readonly provider: string | undefined;
7
- readonly model: string | undefined;
8
- readonly daemonHome: string | undefined;
9
- readonly workingDir: string | undefined;
10
- };
15
+ import { parseGoodVibesCli } from './cli/parser.ts';
16
+ import type { GoodVibesCliFlags } from './cli/types.ts';
11
17
 
12
- /**
13
- * Parse `--provider` / `--model` / `--daemon-home` / `--working-dir` / `--help` flags from an argv slice.
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 };
18
+ export function parseCliFlags(argv: readonly string[], binary = 'goodvibes'): GoodVibesCliFlags {
19
+ return parseGoodVibesCli(argv, binary).flags;
58
20
  }
@@ -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