@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 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.
@@ -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.91",
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: 'Usage: goodvibes bundle inspect <path>', exitCode: 2 };
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: 'Usage: goodvibes bundle import <path>', exitCode: 2 };
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: 'Usage: goodvibes bundle export [path]|inspect <path>|import <path>', exitCode: 2 };
226
+ return { output: `Usage: ${runtime.cli.binary} bundle export [path]|inspect <path>|import <path>`, exitCode: 2 };
227
227
  }
@@ -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: {
@@ -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|finish', 'subscription logout <provider>'],
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 'Usage: goodvibes subscription inspect <provider>';
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 'Usage: goodvibes subscription login <provider> start [--open]';
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: goodvibes subscription login ${provider} finish <code-or-url>`,
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: goodvibes subscription login ${provider} finish <code-or-url>`,
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 'Usage: goodvibes subscription login <provider> finish <code-or-url>';
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 'Usage: goodvibes subscription refresh <provider>';
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 'Usage: goodvibes subscription logout <provider>';
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 'Usage: goodvibes subscription [list|providers|inspect <provider>|login <provider> start|finish <code-or-url>|refresh <provider>|logout <provider>]';
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 'Usage: goodvibes secrets test goodvibes://secrets/<source>/...';
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: goodvibes secrets ${sub} <KEY> <value> [--user|--project] [--secure|--plaintext]`;
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 'Usage: goodvibes secrets delete <KEY> [--user|--project] [--secure|--plaintext]';
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 'Usage: goodvibes sessions show <id|name>';
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 'Usage: goodvibes sessions export <id|name> [path]';
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 : 'Usage: goodvibes sessions resume <id|name>';
272
+ return target ? null : `Usage: ${binary} sessions resume <id|name>`;
271
273
  }
272
- return 'Usage: goodvibes sessions list|show <id>|export <id> [path]|resume <id>';
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 'Usage: goodvibes tasks show <taskId>';
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 'Usage: goodvibes tasks list|show <taskId>';
300
+ return `Usage: ${runtime.cli.binary} tasks list|show <taskId>`;
299
301
  });
300
302
  }
301
303
 
@@ -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('Usage: goodvibes run|exec [prompt]');
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 'Usage: goodvibes providers use <provider> [modelRegistryKey]';
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 'Usage: goodvibes providers inspect <provider>';
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 'Usage: goodvibes providers [list|current|inspect <provider>|use <provider> [modelRegistryKey]]';
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 'Usage: goodvibes models use <registryKey>';
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: goodvibes models ${subOrFilter} <registryKey>`;
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
@@ -6,6 +6,7 @@ export {
6
6
  applyRuntimeCommandEndpointFlagOverrides,
7
7
  applyRuntimeEndpointFlagOverrides,
8
8
  applyRuntimeFeatureFlagOverrides,
9
+ applyRuntimeUrlOverride,
9
10
  handleGoodVibesCliCommand,
10
11
  parseGoodVibesCli,
11
12
  renderGoodVibesCommandHelp,
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.91';
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 {