@pellux/goodvibes-agent 0.1.91 → 0.1.93
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 +8 -0
- package/README.md +2 -0
- package/docs/README.md +1 -0
- package/docs/getting-started.md +2 -0
- package/docs/runtime-connection.md +14 -0
- package/package.json +1 -1
- package/src/cli/bundle-command.ts +3 -3
- package/src/cli/completion.ts +1 -3
- package/src/cli/config-overrides.ts +48 -0
- package/src/cli/entrypoint.ts +25 -0
- package/src/cli/help.ts +7 -5
- package/src/cli/management-commands.ts +19 -17
- package/src/cli/management.ts +8 -6
- package/src/cli/parser.ts +7 -0
- package/src/cli/types.ts +1 -0
- package/src/cli-flags.ts +1 -0
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GoodVibes Agent will be recorded here.
|
|
4
4
|
|
|
5
|
+
## 0.1.93 - 2026-06-01
|
|
6
|
+
|
|
7
|
+
- 7f802e5 Add external runtime URL override
|
|
8
|
+
|
|
9
|
+
## 0.1.92 - 2026-06-01
|
|
10
|
+
|
|
11
|
+
- f52b62e Fix copied CLI executable guidance
|
|
12
|
+
|
|
5
13
|
## 0.1.91 - 2026-06-01
|
|
6
14
|
|
|
7
15
|
- efbb82a Expand Agent Knowledge CLI management
|
package/README.md
CHANGED
|
@@ -89,6 +89,8 @@ Starting a routine records local usage and prints its steps; it does not spawn b
|
|
|
89
89
|
|
|
90
90
|
Start or restart the GoodVibes runtime from GoodVibes TUI or the owning host before launching Agent. Agent status and companion/knowledge routes connect to that external runtime, normally on `http://127.0.0.1:3421`.
|
|
91
91
|
|
|
92
|
+
Use `--runtime-url http://host:port` for a one-off launch, or set `GOODVIBES_AGENT_RUNTIME_URL=http://host:port` when the runtime API is not on the default local port. The legacy `GOODVIBES_AGENT_BASE_URL` env var is also accepted as an alias. These only change the external runtime connection target; Agent still does not host or start the runtime.
|
|
93
|
+
|
|
92
94
|
Agent reports unavailable, unauthenticated, or incompatible runtime state through `goodvibes-agent status`, `goodvibes-agent doctor`, and the TUI status views. It does not provide runtime hosting commands.
|
|
93
95
|
|
|
94
96
|
## Product Boundary
|
package/docs/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Important baseline constraints:
|
|
|
17
17
|
- Agent does not start, stop, restart, install, uninstall, or own runtime hosting.
|
|
18
18
|
- Agent Knowledge/Wiki uses only `/api/goodvibes-agent/knowledge/*`; there is no default Knowledge/Wiki or non-Agent product fallback.
|
|
19
19
|
- Agent supports isolated runtime homes with `GOODVIBES_AGENT_HOME=<path>` and named profile homes with `goodvibes-agent profiles create <name> --template <starter> --yes` plus `--agent-profile <name>`.
|
|
20
|
+
- Agent supports external runtime URL overrides with `--runtime-url http://host:port` or `GOODVIBES_AGENT_RUNTIME_URL=http://host:port`; these do not make Agent own runtime hosting.
|
|
20
21
|
- Agent ships starter profile templates for household, research, travel, operations, and personal productivity local state; `profiles templates export/import` and `/agent-profile guide` support local custom starters.
|
|
21
22
|
- Local personas, routines, and Agent skills are stored under the Agent home and are injected only into the serial Agent conversation.
|
|
22
23
|
- Normal assistant chat is not coding-session delegation.
|
package/docs/getting-started.md
CHANGED
|
@@ -97,6 +97,8 @@ Start the runtime from GoodVibes TUI or the owning host before using runtime-bac
|
|
|
97
97
|
- `/api/goodvibes-agent/knowledge/search`
|
|
98
98
|
- `/api/goodvibes-agent/knowledge/ingest/url`
|
|
99
99
|
|
|
100
|
+
If the runtime API is not on `http://127.0.0.1:3421`, use `goodvibes-agent --runtime-url http://host:port status` for a one-off check or set `GOODVIBES_AGENT_RUNTIME_URL=http://host:port` before launching the TUI.
|
|
101
|
+
|
|
100
102
|
Agent Knowledge/Wiki is an Agent-owned product segment. Agent commands must not fall back to default Knowledge/Wiki or other product-specific knowledge spaces.
|
|
101
103
|
|
|
102
104
|
Runtime-hosting commands are not part of GoodVibes Agent. Use `goodvibes-agent status`, `goodvibes-agent doctor`, and the Agent TUI status views for diagnostics.
|
|
@@ -24,6 +24,20 @@ http://127.0.0.1:3421
|
|
|
24
24
|
/api/goodvibes-agent/knowledge/search
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
If the runtime API is on a different host or port, use a one-off override:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
goodvibes-agent --runtime-url http://127.0.0.1:3421 status
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For a persistent shell/session override, set:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
export GOODVIBES_AGENT_RUNTIME_URL=http://127.0.0.1:3421
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`GOODVIBES_AGENT_BASE_URL` is accepted as a legacy alias. These values only select the external runtime API root; they do not enable runtime hosting inside Agent.
|
|
40
|
+
|
|
27
41
|
If the runtime is unavailable, unauthenticated, or on an incompatible SDK version, Agent commands report actionable diagnostics without printing token values.
|
|
28
42
|
|
|
29
43
|
## Product Boundary
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.93",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
|
|
6
6
|
"type": "module",
|
|
@@ -125,7 +125,7 @@ export async function handleBundleCommand(runtime: CliCommandRuntime): Promise<C
|
|
|
125
125
|
|
|
126
126
|
if (sub === 'inspect') {
|
|
127
127
|
const path = rest[0];
|
|
128
|
-
if (!path) return { output:
|
|
128
|
+
if (!path) return { output: `Usage: ${runtime.cli.binary} bundle inspect <path>`, exitCode: 2 };
|
|
129
129
|
const sourcePath = shellPaths.resolveWorkspacePath(path);
|
|
130
130
|
const parsed = readJsonFile(sourcePath);
|
|
131
131
|
if (!parsed.ok) return { output: `Invalid bundle JSON: ${parsed.error}`, exitCode: 1 };
|
|
@@ -199,7 +199,7 @@ export async function handleBundleCommand(runtime: CliCommandRuntime): Promise<C
|
|
|
199
199
|
|
|
200
200
|
if (sub === 'import') {
|
|
201
201
|
const path = rest[0];
|
|
202
|
-
if (!path) return { output:
|
|
202
|
+
if (!path) return { output: `Usage: ${runtime.cli.binary} bundle import <path>`, exitCode: 2 };
|
|
203
203
|
const sourcePath = shellPaths.resolveWorkspacePath(path);
|
|
204
204
|
const parsed = readJsonFile(sourcePath);
|
|
205
205
|
if (!parsed.ok) return { output: `Invalid bundle JSON: ${parsed.error}`, exitCode: 1 };
|
|
@@ -223,5 +223,5 @@ export async function handleBundleCommand(runtime: CliCommandRuntime): Promise<C
|
|
|
223
223
|
};
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
return { output:
|
|
226
|
+
return { output: `Usage: ${runtime.cli.binary} bundle export [path]|inspect <path>|import <path>`, exitCode: 2 };
|
|
227
227
|
}
|
package/src/cli/completion.ts
CHANGED
|
@@ -19,12 +19,9 @@ const COMMANDS = [
|
|
|
19
19
|
'subscription',
|
|
20
20
|
'secrets',
|
|
21
21
|
'sessions',
|
|
22
|
-
'tasks',
|
|
23
22
|
'pair',
|
|
24
23
|
'qrcode',
|
|
25
24
|
'bundle',
|
|
26
|
-
'remote',
|
|
27
|
-
'bridge',
|
|
28
25
|
'completion',
|
|
29
26
|
'version',
|
|
30
27
|
'help',
|
|
@@ -36,6 +33,7 @@ const OPTIONS = [
|
|
|
36
33
|
'--model',
|
|
37
34
|
'--provider',
|
|
38
35
|
'--agent-profile',
|
|
36
|
+
'--runtime-url',
|
|
39
37
|
'--cd',
|
|
40
38
|
'--working-dir',
|
|
41
39
|
'--config',
|
|
@@ -21,6 +21,38 @@ function parseConfigOverrideValue(value: string): unknown {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function parseRuntimeUrl(rawValue: string, source: string): { readonly host: string; readonly port: number } {
|
|
25
|
+
const trimmed = rawValue.trim();
|
|
26
|
+
if (trimmed.length === 0) {
|
|
27
|
+
throw new ConfigError(`${source} requires a non-empty http://host:port value.`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const value = /^[a-z][a-z\d+.-]*:\/\//i.test(trimmed) ? trimmed : `http://${trimmed}`;
|
|
31
|
+
let url: URL;
|
|
32
|
+
try {
|
|
33
|
+
url = new URL(value);
|
|
34
|
+
} catch {
|
|
35
|
+
throw new ConfigError(`${source} must be a valid http://host:port URL.`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (url.protocol !== 'http:') {
|
|
39
|
+
throw new ConfigError(`${source} must use http:// because Agent connects to the local GoodVibes runtime API.`);
|
|
40
|
+
}
|
|
41
|
+
if (!url.hostname) {
|
|
42
|
+
throw new ConfigError(`${source} must include a hostname.`);
|
|
43
|
+
}
|
|
44
|
+
if ((url.pathname && url.pathname !== '/') || url.search || url.hash) {
|
|
45
|
+
throw new ConfigError(`${source} must point at the runtime root, not a path, query, or hash.`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const port = url.port ? Number.parseInt(url.port, 10) : 3421;
|
|
49
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
50
|
+
throw new ConfigError(`${source} port must be from 1 to 65535.`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { host: url.hostname, port };
|
|
54
|
+
}
|
|
55
|
+
|
|
24
56
|
function getRuntimeConfig(configManager: ConfigManager): GoodVibesConfig {
|
|
25
57
|
const mutable = configManager as unknown as { config?: GoodVibesConfig };
|
|
26
58
|
if (!mutable.config || typeof mutable.config !== 'object') {
|
|
@@ -93,6 +125,22 @@ export function applyRuntimeConfigOverrides(
|
|
|
93
125
|
return errors;
|
|
94
126
|
}
|
|
95
127
|
|
|
128
|
+
export function applyRuntimeUrlOverride(
|
|
129
|
+
configManager: ConfigManager,
|
|
130
|
+
rawValue: string,
|
|
131
|
+
source = '--runtime-url',
|
|
132
|
+
): readonly string[] {
|
|
133
|
+
try {
|
|
134
|
+
const parsed = parseRuntimeUrl(rawValue, source);
|
|
135
|
+
applyRuntimeConfigValue(configManager, 'controlPlane.hostMode', hostModeForHostname(parsed.host));
|
|
136
|
+
applyRuntimeConfigValue(configManager, 'controlPlane.host', parsed.host);
|
|
137
|
+
applyRuntimeConfigValue(configManager, 'controlPlane.port', parsed.port);
|
|
138
|
+
return [];
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return [error instanceof Error ? error.message : `Invalid ${source}`];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
96
144
|
export function applyRuntimeFeatureFlagOverrides(
|
|
97
145
|
configManager: ConfigManager,
|
|
98
146
|
options: {
|
package/src/cli/entrypoint.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
applyRuntimeConfigOverrides,
|
|
12
12
|
applyRuntimeConfigValue,
|
|
13
13
|
applyRuntimeFeatureFlagOverrides,
|
|
14
|
+
applyRuntimeUrlOverride,
|
|
14
15
|
buildCliStatusSnapshot,
|
|
15
16
|
handleGoodVibesCliCommand,
|
|
16
17
|
parseGoodVibesCli,
|
|
@@ -31,6 +32,14 @@ type ShellEntrypointOwnership = {
|
|
|
31
32
|
readonly homeDirectory: string;
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
function readEnvRuntimeUrl(): { readonly source: string; readonly value: string } | null {
|
|
36
|
+
const runtimeUrl = process.env['GOODVIBES_AGENT_RUNTIME_URL'];
|
|
37
|
+
if (runtimeUrl !== undefined) return { source: 'GOODVIBES_AGENT_RUNTIME_URL', value: runtimeUrl };
|
|
38
|
+
const legacyBaseUrl = process.env['GOODVIBES_AGENT_BASE_URL'];
|
|
39
|
+
if (legacyBaseUrl !== undefined) return { source: 'GOODVIBES_AGENT_BASE_URL', value: legacyBaseUrl };
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
export type ShellEntrypointRoots = {
|
|
35
44
|
readonly defaultWorkingDirectory: string;
|
|
36
45
|
readonly homeDirectory: string;
|
|
@@ -112,6 +121,15 @@ export async function prepareShellCliRuntime(
|
|
|
112
121
|
});
|
|
113
122
|
new GlobalNetworkTransportInstaller().install(configManager);
|
|
114
123
|
|
|
124
|
+
const envRuntimeUrl = readEnvRuntimeUrl();
|
|
125
|
+
const envRuntimeUrlErrors = envRuntimeUrl
|
|
126
|
+
? applyRuntimeUrlOverride(configManager, envRuntimeUrl.value, envRuntimeUrl.source)
|
|
127
|
+
: [];
|
|
128
|
+
if (envRuntimeUrlErrors.length > 0) {
|
|
129
|
+
console.error(envRuntimeUrlErrors.join('\n'));
|
|
130
|
+
process.exit(2);
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
const overrideErrors = applyRuntimeConfigOverrides(configManager, cli.flags.configOverrides);
|
|
116
134
|
if (overrideErrors.length > 0) {
|
|
117
135
|
console.error(overrideErrors.join('\n'));
|
|
@@ -128,6 +146,13 @@ export async function prepareShellCliRuntime(
|
|
|
128
146
|
const model = cli.flags.model ?? getModelIdFromProviderModel(currentModel);
|
|
129
147
|
applyRuntimeConfigValue(configManager, 'provider.model', formatProviderModel(provider, model));
|
|
130
148
|
}
|
|
149
|
+
if (cli.flags.runtimeUrl !== undefined) {
|
|
150
|
+
const runtimeUrlErrors = applyRuntimeUrlOverride(configManager, cli.flags.runtimeUrl);
|
|
151
|
+
if (runtimeUrlErrors.length > 0) {
|
|
152
|
+
console.error(runtimeUrlErrors.join('\n'));
|
|
153
|
+
process.exit(2);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
131
156
|
const endpointOverrideErrors = applyRuntimeCommandEndpointFlagOverrides(configManager, cli.command, cli.flags);
|
|
132
157
|
if (endpointOverrideErrors.length > 0) {
|
|
133
158
|
console.error(endpointOverrideErrors.join('\n'));
|
package/src/cli/help.ts
CHANGED
|
@@ -57,6 +57,7 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
|
|
|
57
57
|
' -m, --model <registryKey> Override model. provider:model infers --provider',
|
|
58
58
|
' --provider <id> Override provider',
|
|
59
59
|
' --agent-profile <name> Use an isolated Agent profile home',
|
|
60
|
+
' --runtime-url <url> External GoodVibes runtime API root',
|
|
60
61
|
' -C, --cd <dir> Set working directory for this launch',
|
|
61
62
|
' --working-dir <dir> Alias for --cd',
|
|
62
63
|
' -c, --config <key=value> Override a config value for this launch',
|
|
@@ -82,6 +83,7 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
|
|
|
82
83
|
` ${binary} onboarding`,
|
|
83
84
|
` ${binary} onboarding status`,
|
|
84
85
|
` ${binary} status`,
|
|
86
|
+
` ${binary} --runtime-url http://127.0.0.1:3421 status`,
|
|
85
87
|
` ${binary} models current`,
|
|
86
88
|
` ${binary} models use openai:gpt-5.2`,
|
|
87
89
|
` ${binary} providers inspect openai`,
|
|
@@ -125,14 +127,14 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
125
127
|
examples: ['onboarding', 'onboarding status'],
|
|
126
128
|
},
|
|
127
129
|
status: {
|
|
128
|
-
usage: ['status', 'status --json'],
|
|
130
|
+
usage: ['status', 'status --json', '--runtime-url http://127.0.0.1:3421 status'],
|
|
129
131
|
summary: 'Print Agent config, provider, auth, runtime connection, and onboarding posture.',
|
|
130
|
-
examples: ['status', 'status --json'],
|
|
132
|
+
examples: ['status', 'status --json', '--runtime-url http://127.0.0.1:3421 status'],
|
|
131
133
|
},
|
|
132
134
|
doctor: {
|
|
133
|
-
usage: ['doctor', 'doctor --json'],
|
|
135
|
+
usage: ['doctor', 'doctor --json', '--runtime-url http://127.0.0.1:3421 doctor'],
|
|
134
136
|
summary: 'Print status plus actionable setup warnings with cause, impact, and next action.',
|
|
135
|
-
examples: ['doctor', 'doctor --json'],
|
|
137
|
+
examples: ['doctor', 'doctor --json', '--runtime-url http://127.0.0.1:3421 doctor'],
|
|
136
138
|
},
|
|
137
139
|
providers: {
|
|
138
140
|
usage: ['providers [list]', 'providers current', 'providers inspect <provider>', 'providers use <provider> [modelRegistryKey]'],
|
|
@@ -222,7 +224,7 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
222
224
|
],
|
|
223
225
|
},
|
|
224
226
|
subscription: {
|
|
225
|
-
usage: ['subscription list', 'subscription providers', 'subscription inspect <provider>', 'subscription login <provider> start
|
|
227
|
+
usage: ['subscription list', 'subscription providers', 'subscription inspect <provider>', 'subscription login <provider> start [--open]', 'subscription login <provider> finish <code-or-url>', 'subscription logout <provider>'],
|
|
226
228
|
summary: 'Manage OAuth/subscription-backed provider sessions such as OpenAI subscription access.',
|
|
227
229
|
examples: ['subscription providers', 'subscription login openai start --open', 'subscription inspect openai'],
|
|
228
230
|
},
|
|
@@ -14,6 +14,7 @@ import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../config/surface.ts';
|
|
|
14
14
|
|
|
15
15
|
export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<string> {
|
|
16
16
|
return await withRuntimeServices(runtime, async (services) => {
|
|
17
|
+
const binary = runtime.cli.binary;
|
|
17
18
|
const [sub = 'list', ...rest] = runtime.cli.commandArgs;
|
|
18
19
|
const subscriptions = services.subscriptionManager.list();
|
|
19
20
|
const pending = services.subscriptionManager.listPending();
|
|
@@ -26,7 +27,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
26
27
|
}
|
|
27
28
|
if (sub === 'inspect' || sub === 'show') {
|
|
28
29
|
const provider = rest[0];
|
|
29
|
-
if (!provider) return
|
|
30
|
+
if (!provider) return `Usage: ${binary} subscription inspect <provider>`;
|
|
30
31
|
const resolved = getSubscriptionProviderConfig(provider, services.serviceRegistry.get(provider));
|
|
31
32
|
if (!resolved && !services.subscriptionManager.get(provider) && !services.subscriptionManager.getPending(provider)) {
|
|
32
33
|
return `No stored or available subscription provider named ${provider}.`;
|
|
@@ -59,7 +60,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
59
60
|
if (sub === 'login' || sub === 'start') {
|
|
60
61
|
const provider = sub === 'start' ? rest[0] : rest[0];
|
|
61
62
|
const mode = sub === 'start' ? 'start' : rest[1]?.toLowerCase();
|
|
62
|
-
if (!provider || mode !== 'start') return
|
|
63
|
+
if (!provider || mode !== 'start') return `Usage: ${binary} subscription login <provider> start [--open]`;
|
|
63
64
|
const resolved = getSubscriptionProviderConfig(provider, services.serviceRegistry.get(provider));
|
|
64
65
|
if (!resolved) return `No subscription provider found: ${provider}`;
|
|
65
66
|
if (provider === 'openai' && resolved.source === 'builtin') {
|
|
@@ -78,7 +79,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
78
79
|
` state: ${started.state}`,
|
|
79
80
|
` redirectUri: ${started.redirectUri}`,
|
|
80
81
|
...(openResult ? [` open: ${openResult}`] : []),
|
|
81
|
-
` next:
|
|
82
|
+
` next: ${binary} subscription login ${provider} finish <code-or-url>`,
|
|
82
83
|
' authorizationUrl:',
|
|
83
84
|
` ${started.authorizationUrl}`,
|
|
84
85
|
].join('\n');
|
|
@@ -91,7 +92,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
91
92
|
` state: ${started.pending.state}`,
|
|
92
93
|
` redirectUri: ${started.pending.redirectUri}`,
|
|
93
94
|
...(openResult ? [` open: ${openResult}`] : []),
|
|
94
|
-
` next:
|
|
95
|
+
` next: ${binary} subscription login ${provider} finish <code-or-url>`,
|
|
95
96
|
' authorizationUrl:',
|
|
96
97
|
` ${started.authorizationUrl}`,
|
|
97
98
|
].join('\n');
|
|
@@ -99,7 +100,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
99
100
|
if (sub === 'finish' || (sub === 'login' && rest[1]?.toLowerCase() === 'finish')) {
|
|
100
101
|
const provider = sub === 'finish' ? rest[0] : rest[0];
|
|
101
102
|
const codeInput = sub === 'finish' ? rest[1] : rest[2];
|
|
102
|
-
if (!provider || !codeInput) return
|
|
103
|
+
if (!provider || !codeInput) return `Usage: ${binary} subscription login <provider> finish <code-or-url>`;
|
|
103
104
|
const resolved = getSubscriptionProviderConfig(provider, services.serviceRegistry.get(provider));
|
|
104
105
|
if (!resolved) return `No subscription provider found: ${provider}`;
|
|
105
106
|
const code = extractAuthorizationCode(codeInput);
|
|
@@ -127,7 +128,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
127
128
|
}
|
|
128
129
|
if (sub === 'refresh') {
|
|
129
130
|
const provider = rest[0];
|
|
130
|
-
if (!provider) return
|
|
131
|
+
if (!provider) return `Usage: ${binary} subscription refresh <provider>`;
|
|
131
132
|
const resolved = getSubscriptionProviderConfig(provider, services.serviceRegistry.get(provider));
|
|
132
133
|
if (!resolved) return `No subscription provider found: ${provider}`;
|
|
133
134
|
const record = await services.subscriptionManager.refreshOAuthToken(provider, resolved.oauth);
|
|
@@ -135,12 +136,12 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
|
|
|
135
136
|
}
|
|
136
137
|
if (sub === 'logout' || sub === 'remove') {
|
|
137
138
|
const provider = rest[0];
|
|
138
|
-
if (!provider) return
|
|
139
|
+
if (!provider) return `Usage: ${binary} subscription logout <provider>`;
|
|
139
140
|
const removed = services.subscriptionManager.logout(provider);
|
|
140
141
|
return removed ? `Subscription removed: ${provider}` : `No stored subscription session existed for ${provider}.`;
|
|
141
142
|
}
|
|
142
143
|
if (sub !== 'list' && sub !== 'status' && sub !== 'review') {
|
|
143
|
-
return
|
|
144
|
+
return `Usage: ${binary} subscription [list|providers|inspect <provider>|login <provider> start|finish <code-or-url>|refresh <provider>|logout <provider>]`;
|
|
144
145
|
}
|
|
145
146
|
const value = {
|
|
146
147
|
subscriptions: subscriptions.map((sub) => ({
|
|
@@ -180,7 +181,7 @@ export async function handleSecrets(runtime: CliCommandRuntime): Promise<string>
|
|
|
180
181
|
if (sub === 'test') {
|
|
181
182
|
const ref = rest.join(' ').trim();
|
|
182
183
|
if (!ref || !ref.startsWith('goodvibes://secrets/') || !isSecretRefInput(ref)) {
|
|
183
|
-
return
|
|
184
|
+
return `Usage: ${runtime.cli.binary} secrets test goodvibes://secrets/<source>/...`;
|
|
184
185
|
}
|
|
185
186
|
const resolved = await resolveSecretRef(ref, { resolveLocalSecret: (key) => secrets.get(key) });
|
|
186
187
|
const value = { ref: describeSecretRef(ref), resolved: Boolean(resolved.value) };
|
|
@@ -191,7 +192,7 @@ export async function handleSecrets(runtime: CliCommandRuntime): Promise<string>
|
|
|
191
192
|
const values = rest.filter((arg) => !arg.startsWith('--'));
|
|
192
193
|
const [key, ...rawValueParts] = values;
|
|
193
194
|
const value = rawValueParts.join(' ');
|
|
194
|
-
if (!key || !value) return `Usage:
|
|
195
|
+
if (!key || !value) return `Usage: ${runtime.cli.binary} secrets ${sub} <KEY> <value> [--user|--project] [--secure|--plaintext]`;
|
|
195
196
|
if (sub === 'link' && (!value.startsWith('goodvibes://secrets/') || !isSecretRefInput(value))) {
|
|
196
197
|
return 'Invalid secret reference. Use goodvibes://secrets/<source>/...';
|
|
197
198
|
}
|
|
@@ -203,7 +204,7 @@ export async function handleSecrets(runtime: CliCommandRuntime): Promise<string>
|
|
|
203
204
|
}
|
|
204
205
|
if (sub === 'delete') {
|
|
205
206
|
const key = rest.find((arg) => !arg.startsWith('--'));
|
|
206
|
-
if (!key) return
|
|
207
|
+
if (!key) return `Usage: ${runtime.cli.binary} secrets delete <KEY> [--user|--project] [--secure|--plaintext]`;
|
|
207
208
|
const flags = new Set(rest.filter((arg) => arg.startsWith('--')));
|
|
208
209
|
await secrets.delete(key, {
|
|
209
210
|
scope: flags.has('--user') ? 'user' : flags.has('--project') ? 'project' : undefined,
|
|
@@ -226,6 +227,7 @@ export async function handleSecrets(runtime: CliCommandRuntime): Promise<string>
|
|
|
226
227
|
|
|
227
228
|
export async function handleSessions(runtime: CliCommandRuntime): Promise<string | null> {
|
|
228
229
|
return await withRuntimeServices(runtime, (services) => {
|
|
230
|
+
const binary = runtime.cli.binary;
|
|
229
231
|
const [sub = 'list', ...rest] = runtime.cli.commandArgs;
|
|
230
232
|
const sessions = services.sessionManager.list();
|
|
231
233
|
if (sub === 'list') {
|
|
@@ -237,7 +239,7 @@ export async function handleSessions(runtime: CliCommandRuntime): Promise<string
|
|
|
237
239
|
}
|
|
238
240
|
if (sub === 'show' || sub === 'info') {
|
|
239
241
|
const target = rest.join(' ').trim();
|
|
240
|
-
if (!target) return
|
|
242
|
+
if (!target) return `Usage: ${binary} sessions show <id|name>`;
|
|
241
243
|
const found = sessions.find((session) => session.name === target || session.name.startsWith(target) || session.title.toLowerCase() === target.toLowerCase());
|
|
242
244
|
if (!found) return `Session not found: ${target}`;
|
|
243
245
|
return formatJsonOrText(runtime.cli)(found, [
|
|
@@ -252,7 +254,7 @@ export async function handleSessions(runtime: CliCommandRuntime): Promise<string
|
|
|
252
254
|
if (sub === 'export') {
|
|
253
255
|
const target = rest[0];
|
|
254
256
|
const outputPath = rest[1];
|
|
255
|
-
if (!target) return
|
|
257
|
+
if (!target) return `Usage: ${binary} sessions export <id|name> [path]`;
|
|
256
258
|
const found = sessions.find((session) => session.name === target || session.name.startsWith(target) || session.title.toLowerCase() === target.toLowerCase());
|
|
257
259
|
if (!found) return `Session not found: ${target}`;
|
|
258
260
|
const data = services.sessionManager.load(found.name);
|
|
@@ -267,9 +269,9 @@ export async function handleSessions(runtime: CliCommandRuntime): Promise<string
|
|
|
267
269
|
}
|
|
268
270
|
if (sub === 'resume') {
|
|
269
271
|
const target = rest.join(' ').trim();
|
|
270
|
-
return target ? null :
|
|
272
|
+
return target ? null : `Usage: ${binary} sessions resume <id|name>`;
|
|
271
273
|
}
|
|
272
|
-
return
|
|
274
|
+
return `Usage: ${binary} sessions list|show <id>|export <id> [path]|resume <id>`;
|
|
273
275
|
});
|
|
274
276
|
}
|
|
275
277
|
|
|
@@ -291,11 +293,11 @@ export async function handleTasks(runtime: CliCommandRuntime): Promise<string> {
|
|
|
291
293
|
: ['GoodVibes tasks', ...tasks.map((task) => ` ${task.id} ${task.status} ${task.kind} ${task.title}`)].join('\n');
|
|
292
294
|
}
|
|
293
295
|
if (sub === 'show') {
|
|
294
|
-
if (!rest[0]) return
|
|
296
|
+
if (!rest[0]) return `Usage: ${runtime.cli.binary} tasks show <taskId>`;
|
|
295
297
|
const task = tasks.find((candidate) => candidate.id === rest[0]);
|
|
296
298
|
return task ? JSON.stringify(task, null, 2) : `Unknown task: ${rest[0] ?? ''}`;
|
|
297
299
|
}
|
|
298
|
-
return
|
|
300
|
+
return `Usage: ${runtime.cli.binary} tasks list|show <taskId>`;
|
|
299
301
|
});
|
|
300
302
|
}
|
|
301
303
|
|
package/src/cli/management.ts
CHANGED
|
@@ -244,7 +244,7 @@ export function readAuthPaths(runtime: CliCommandRuntime) {
|
|
|
244
244
|
export async function runNonInteractiveAgent(runtime: CliCommandRuntime): Promise<number> {
|
|
245
245
|
const prompt = runtime.cli.flags.prompt ?? runtime.cli.positionals.join(' ').trim();
|
|
246
246
|
if (!prompt) {
|
|
247
|
-
console.error(
|
|
247
|
+
console.error(`Usage: ${runtime.cli.binary} run|exec [prompt]`);
|
|
248
248
|
return 2;
|
|
249
249
|
}
|
|
250
250
|
|
|
@@ -329,6 +329,7 @@ export async function runNonInteractiveAgent(runtime: CliCommandRuntime): Promis
|
|
|
329
329
|
|
|
330
330
|
async function renderProviders(runtime: CliCommandRuntime): Promise<string> {
|
|
331
331
|
return await withRuntimeServices(runtime, async (services) => {
|
|
332
|
+
const binary = runtime.cli.binary;
|
|
332
333
|
const [sub = 'list', ...rest] = runtime.cli.commandArgs;
|
|
333
334
|
const snapshots = await listProviderRuntimeSnapshots(services.providerRegistry);
|
|
334
335
|
const current = services.providerRegistry.getCurrentModel();
|
|
@@ -354,7 +355,7 @@ async function renderProviders(runtime: CliCommandRuntime): Promise<string> {
|
|
|
354
355
|
}
|
|
355
356
|
if (sub === 'use' || sub === 'set') {
|
|
356
357
|
const provider = rest[0];
|
|
357
|
-
if (!provider) return
|
|
358
|
+
if (!provider) return `Usage: ${binary} providers use <provider> [modelRegistryKey]`;
|
|
358
359
|
const providerModels = services.providerRegistry
|
|
359
360
|
.getSelectableModels()
|
|
360
361
|
.filter((model) => model.provider === provider || model.registryKey.startsWith(`${provider}:`));
|
|
@@ -377,7 +378,7 @@ async function renderProviders(runtime: CliCommandRuntime): Promise<string> {
|
|
|
377
378
|
}
|
|
378
379
|
if (sub === 'inspect' || sub === 'show') {
|
|
379
380
|
const provider = rest[0];
|
|
380
|
-
if (!provider) return
|
|
381
|
+
if (!provider) return `Usage: ${binary} providers inspect <provider>`;
|
|
381
382
|
const snapshot = snapshots.find((candidate) => candidate.providerId === provider);
|
|
382
383
|
if (!snapshot) return `No provider found: ${provider}`;
|
|
383
384
|
const setup = classifyProviderSetup({
|
|
@@ -404,7 +405,7 @@ async function renderProviders(runtime: CliCommandRuntime): Promise<string> {
|
|
|
404
405
|
` detail: ${snapshot.runtime.auth?.detail ?? snapshot.runtime.notes?.join('; ') ?? ''}`,
|
|
405
406
|
].join('\n'));
|
|
406
407
|
}
|
|
407
|
-
if (sub !== 'list') return
|
|
408
|
+
if (sub !== 'list') return `Usage: ${binary} providers [list|current|inspect <provider>|use <provider> [modelRegistryKey]]`;
|
|
408
409
|
const value = snapshots.map((snapshot) => ({
|
|
409
410
|
...classifyProviderSetup({
|
|
410
411
|
providerId: snapshot.providerId,
|
|
@@ -433,6 +434,7 @@ async function renderProviders(runtime: CliCommandRuntime): Promise<string> {
|
|
|
433
434
|
|
|
434
435
|
async function renderModels(runtime: CliCommandRuntime): Promise<string> {
|
|
435
436
|
return await withRuntimeServices(runtime, async (services) => {
|
|
437
|
+
const binary = runtime.cli.binary;
|
|
436
438
|
const [subOrFilter, ...rest] = runtime.cli.commandArgs;
|
|
437
439
|
const current = services.providerRegistry.getCurrentModel().registryKey;
|
|
438
440
|
const providerSnapshots = await listProviderRuntimeSnapshots(services.providerRegistry);
|
|
@@ -469,7 +471,7 @@ async function renderModels(runtime: CliCommandRuntime): Promise<string> {
|
|
|
469
471
|
}
|
|
470
472
|
if (subOrFilter === 'use' || subOrFilter === 'set') {
|
|
471
473
|
const modelKey = rest[0];
|
|
472
|
-
if (!modelKey) return
|
|
474
|
+
if (!modelKey) return `Usage: ${binary} models use <registryKey>`;
|
|
473
475
|
const model = services.providerRegistry
|
|
474
476
|
.getSelectableModels()
|
|
475
477
|
.find((candidate) => candidate.registryKey === modelKey || candidate.id === modelKey);
|
|
@@ -485,7 +487,7 @@ async function renderModels(runtime: CliCommandRuntime): Promise<string> {
|
|
|
485
487
|
}
|
|
486
488
|
if (subOrFilter === 'pin' || subOrFilter === 'unpin') {
|
|
487
489
|
const modelKey = rest[0];
|
|
488
|
-
if (!modelKey) return `Usage:
|
|
490
|
+
if (!modelKey) return `Usage: ${binary} models ${subOrFilter} <registryKey>`;
|
|
489
491
|
if (subOrFilter === 'pin') await services.favoritesStore.pinModel(modelKey);
|
|
490
492
|
else await services.favoritesStore.unpinModel(modelKey);
|
|
491
493
|
return `Model ${subOrFilter === 'pin' ? 'pinned' : 'unpinned'}: ${modelKey}`;
|
package/src/cli/parser.ts
CHANGED
|
@@ -59,6 +59,7 @@ function createDefaultFlags(): GoodVibesCliFlags {
|
|
|
59
59
|
model: undefined,
|
|
60
60
|
agentProfile: undefined,
|
|
61
61
|
daemonHome: undefined,
|
|
62
|
+
runtimeUrl: undefined,
|
|
62
63
|
workingDir: undefined,
|
|
63
64
|
help: false,
|
|
64
65
|
version: false,
|
|
@@ -276,6 +277,12 @@ export function parseGoodVibesCli(
|
|
|
276
277
|
if (consumed.value !== undefined) flags = withFlag(flags, 'daemonHome', consumed.value);
|
|
277
278
|
continue;
|
|
278
279
|
}
|
|
280
|
+
if (name === '--runtime-url' || name === '--runtime') {
|
|
281
|
+
const consumed = getValue(argv, index, inlineValue, name, errors);
|
|
282
|
+
index = consumed.nextIndex;
|
|
283
|
+
if (consumed.value !== undefined) flags = withFlag(flags, 'runtimeUrl', consumed.value);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
279
286
|
if (name === '--working-dir' || name === '--cd' || name === '-C') {
|
|
280
287
|
const consumed = getValue(argv, index, inlineValue, name, errors);
|
|
281
288
|
index = consumed.nextIndex;
|
package/src/cli/types.ts
CHANGED
|
@@ -37,6 +37,7 @@ export interface GoodVibesCliFlags {
|
|
|
37
37
|
readonly model: string | undefined;
|
|
38
38
|
readonly agentProfile: string | undefined;
|
|
39
39
|
readonly daemonHome: string | undefined;
|
|
40
|
+
readonly runtimeUrl: string | undefined;
|
|
40
41
|
readonly workingDir: string | undefined;
|
|
41
42
|
readonly help: boolean;
|
|
42
43
|
readonly version: boolean;
|
package/src/cli-flags.ts
CHANGED
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '0.1.
|
|
9
|
+
let _version = '0.1.93';
|
|
10
10
|
let _sdkVersion = '0.33.35';
|
|
11
11
|
try {
|
|
12
12
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
|