@pellux/goodvibes-agent 0.1.71 → 0.1.72
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 +6 -0
- package/README.md +3 -3
- package/docs/README.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/runtime-connection.md +37 -0
- package/package.json +2 -2
- package/src/cli/config-overrides.ts +1 -5
- package/src/cli/entrypoint.ts +0 -6
- package/src/cli/help.ts +0 -43
- package/src/cli/index.ts +0 -2
- package/src/cli/management-commands.ts +1 -109
- package/src/cli/management.ts +1 -32
- package/src/cli/package-verification.ts +12 -4
- package/src/cli/parser.ts +0 -16
- package/src/cli/status.ts +1 -1
- package/src/cli/types.ts +0 -8
- package/src/panels/confirm-state.ts +1 -1
- package/src/version.ts +1 -1
- package/docs/deployment-and-services.md +0 -52
- package/src/cli/service-command.ts +0 -26
- package/src/cli/surface-command.ts +0 -247
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GoodVibes Agent will be recorded here.
|
|
4
4
|
|
|
5
|
+
## 0.1.72 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- Removed copied runtime lifecycle and transport words from the Agent CLI parser, command handler, detailed help, and package exports.
|
|
8
|
+
- Replaced package-facing deployment/service docs with runtime-connection docs focused on the external runtime prerequisite and Agent product boundary.
|
|
9
|
+
- Added regression coverage so hidden lifecycle words are treated as normal TUI prompts instead of supported Agent commands.
|
|
10
|
+
|
|
5
11
|
## 0.1.71 - 2026-05-31
|
|
6
12
|
|
|
7
13
|
- Stopped importing copied TUI slash-command modules that do not belong to the Agent product surface.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
GoodVibes Agent is the personal operator assistant TUI for GoodVibes. It is built for day-to-day operator work: chat, setup, local profiles, routines, skills, personas, isolated Agent Knowledge, status review, approvals, automation visibility, and explicit build delegation.
|
|
7
7
|
|
|
8
|
-
The Agent product connects to an already-running GoodVibes runtime. It does not install, start, stop, restart, or own runtime
|
|
8
|
+
The Agent product connects to an already-running GoodVibes runtime. It does not install, start, stop, restart, or own runtime hosting.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -87,7 +87,7 @@ Starting a routine records local usage and prints its steps; it does not spawn b
|
|
|
87
87
|
|
|
88
88
|
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`.
|
|
89
89
|
|
|
90
|
-
Agent reports unavailable, unauthenticated, or incompatible runtime state through `goodvibes-agent status`, `goodvibes-agent doctor`, and the TUI status
|
|
90
|
+
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.
|
|
91
91
|
|
|
92
92
|
## Product Boundary
|
|
93
93
|
|
|
@@ -102,7 +102,7 @@ GoodVibes TUI owns coding execution: file edits, git/worktree workflows, coding
|
|
|
102
102
|
Package-facing docs:
|
|
103
103
|
|
|
104
104
|
- [Getting Started](docs/getting-started.md)
|
|
105
|
-
- [
|
|
105
|
+
- [Runtime Connection](docs/runtime-connection.md)
|
|
106
106
|
- [Release And Publishing](docs/release-and-publishing.md)
|
|
107
107
|
|
|
108
108
|
The package-facing Agent documentation is limited to the docs listed above.
|
package/docs/README.md
CHANGED
|
@@ -5,7 +5,7 @@ These are the package-facing docs for GoodVibes Agent, the personal operator ass
|
|
|
5
5
|
Current package docs:
|
|
6
6
|
|
|
7
7
|
- [Getting Started](getting-started.md)
|
|
8
|
-
- [
|
|
8
|
+
- [Runtime Connection](runtime-connection.md)
|
|
9
9
|
- [Release And Publishing](release-and-publishing.md)
|
|
10
10
|
|
|
11
11
|
Important baseline constraints:
|
|
@@ -14,7 +14,7 @@ Important baseline constraints:
|
|
|
14
14
|
- Agent uses Bun and TypeScript-authored source.
|
|
15
15
|
- Agent depends on `@pellux/goodvibes-sdk@0.33.35`.
|
|
16
16
|
- Agent connects to an externally managed GoodVibes runtime.
|
|
17
|
-
- Agent does not start, stop, restart, install, uninstall, or own runtime
|
|
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
20
|
- 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.
|
package/docs/getting-started.md
CHANGED
|
@@ -99,7 +99,7 @@ Start the runtime from GoodVibes TUI or the owning host before using runtime-bac
|
|
|
99
99
|
|
|
100
100
|
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
101
|
|
|
102
|
-
|
|
102
|
+
Runtime-hosting commands are not part of GoodVibes Agent. Use `goodvibes-agent status`, `goodvibes-agent doctor`, and the Agent TUI status views for diagnostics.
|
|
103
103
|
|
|
104
104
|
## Current Product Notes
|
|
105
105
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Runtime Connection
|
|
2
|
+
|
|
3
|
+
GoodVibes Agent is a TUI client for an already-running GoodVibes runtime. The package exposes one executable:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
goodvibes-agent
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The installed command is backed by TypeScript-authored source with a Bun shebang. Package install smoke must verify:
|
|
10
|
+
|
|
11
|
+
- `goodvibes-agent --help`
|
|
12
|
+
- `goodvibes-agent --version`
|
|
13
|
+
- `goodvibes-agent status --json`
|
|
14
|
+
- `goodvibes-agent` launches the TUI in a real PTY
|
|
15
|
+
|
|
16
|
+
## Runtime Prerequisite
|
|
17
|
+
|
|
18
|
+
Start the GoodVibes runtime from the owning GoodVibes host before launching Agent. Agent expects the runtime to expose public operator routes and the isolated Agent Knowledge routes:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
http://127.0.0.1:3421
|
|
22
|
+
/api/goodvibes-agent/knowledge/status
|
|
23
|
+
/api/goodvibes-agent/knowledge/ask
|
|
24
|
+
/api/goodvibes-agent/knowledge/search
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If the runtime is unavailable, unauthenticated, or on an incompatible SDK version, Agent commands report actionable diagnostics without printing token values.
|
|
28
|
+
|
|
29
|
+
## Product Boundary
|
|
30
|
+
|
|
31
|
+
Agent owns the operator assistant TUI, local profiles, local memory/routines/skills/personas, isolated Agent Knowledge calls, companion chat, approvals/automation visibility, and explicit build delegation.
|
|
32
|
+
|
|
33
|
+
Agent does not host runtime connectivity. It does not provide commands to install, expose, start, stop, restart, or mutate the runtime host.
|
|
34
|
+
|
|
35
|
+
Agent Knowledge/Wiki is its own product segment. Agent uses `/api/goodvibes-agent/knowledge/*` only and must not fall back to default Knowledge/Wiki or other product-specific knowledge routes.
|
|
36
|
+
|
|
37
|
+
Normal assistant chat uses companion chat. Build/fix/review work is delegated explicitly to GoodVibes TUI through public runtime/session contracts, and WRFC is requested only when explicitly asked for delegated build/fix/review work.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.72",
|
|
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",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"CHANGELOG.md",
|
|
68
68
|
"docs/README.md",
|
|
69
69
|
"docs/getting-started.md",
|
|
70
|
-
"docs/
|
|
70
|
+
"docs/runtime-connection.md",
|
|
71
71
|
"docs/release-and-publishing.md"
|
|
72
72
|
],
|
|
73
73
|
"scripts": {
|
|
@@ -150,10 +150,6 @@ export function applyRuntimeCommandEndpointFlagOverrides(
|
|
|
150
150
|
flags: Pick<GoodVibesCliFlags, 'hostname' | 'port'>,
|
|
151
151
|
): readonly string[] {
|
|
152
152
|
if (flags.hostname === undefined && flags.port === undefined) return [];
|
|
153
|
-
if (command === '
|
|
154
|
-
if (command === 'listener') return applyRuntimeEndpointFlagOverrides(configManager, 'httpListener', flags);
|
|
155
|
-
if (command === 'control-plane' || command === 'pair' || command === 'serve') {
|
|
156
|
-
return applyRuntimeEndpointFlagOverrides(configManager, 'controlPlane', flags);
|
|
157
|
-
}
|
|
153
|
+
if (command === 'pair') return applyRuntimeEndpointFlagOverrides(configManager, 'controlPlane', flags);
|
|
158
154
|
return [];
|
|
159
155
|
}
|
package/src/cli/entrypoint.ts
CHANGED
|
@@ -88,12 +88,6 @@ export async function prepareShellCliRuntime(
|
|
|
88
88
|
process.exit(0);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
if (cli.command === 'serve') {
|
|
92
|
-
console.error(`${binary} connects to an already-running GoodVibes runtime and does not start or own runtime lifecycle.`);
|
|
93
|
-
console.error('Start or manage the runtime from GoodVibes TUI or host tooling, then run this Agent against it.');
|
|
94
|
-
process.exit(2);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
91
|
let ownership: ShellEntrypointOwnership;
|
|
98
92
|
try {
|
|
99
93
|
ownership = resolveShellEntrypointOwnership(
|
package/src/cli/help.ts
CHANGED
|
@@ -232,21 +232,6 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
232
232
|
summary: 'Inspect in-process runtime tasks. Agent blocks copied task submission; use run for one-shot work or delegate for explicit build/fix/review handoff.',
|
|
233
233
|
examples: ['tasks list', 'tasks show task-123', 'run "check provider readiness"', 'delegate "fix the failing tests"'],
|
|
234
234
|
},
|
|
235
|
-
surfaces: {
|
|
236
|
-
usage: ['surfaces [list]', 'surfaces check', 'surfaces show <surfaceId>'],
|
|
237
|
-
summary: 'Inspect advanced browser, channel, and runtime connection surfaces. Agent does not mutate runtime connection posture.',
|
|
238
|
-
examples: ['surfaces', 'surfaces check', 'surfaces show slack'],
|
|
239
|
-
},
|
|
240
|
-
listener: {
|
|
241
|
-
usage: ['listener test'],
|
|
242
|
-
summary: 'Check advanced inbound webhook readiness, network posture, auth, and enabled channel requirements.',
|
|
243
|
-
examples: ['listener test', 'listener test --json'],
|
|
244
|
-
},
|
|
245
|
-
'control-plane': {
|
|
246
|
-
usage: ['control-plane status'],
|
|
247
|
-
summary: 'Inspect advanced runtime API reachability, local auth, bootstrap credentials, and operator tokens.',
|
|
248
|
-
examples: ['control-plane status', 'control-plane status --json'],
|
|
249
|
-
},
|
|
250
235
|
bundle: {
|
|
251
236
|
usage: ['bundle export [path]', 'bundle inspect <path>', 'bundle import <path>'],
|
|
252
237
|
summary: 'Export, inspect, or import setup/profile/trust/support bundle data.',
|
|
@@ -257,31 +242,11 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
257
242
|
summary: 'Print companion pairing connection details and a QR code.',
|
|
258
243
|
examples: ['pair', 'qrcode'],
|
|
259
244
|
},
|
|
260
|
-
web: {
|
|
261
|
-
usage: ['web [--open]'],
|
|
262
|
-
summary: 'Show the configured browser surface URL, bind address, and enablement state.',
|
|
263
|
-
examples: ['web', 'web --open', 'web --hostname 0.0.0.0 --port 3423'],
|
|
264
|
-
},
|
|
265
|
-
service: {
|
|
266
|
-
usage: ['service status', 'service check'],
|
|
267
|
-
summary: 'Inspect the externally owned GoodVibes runtime service posture. Agent does not install, start, stop, restart, or uninstall the runtime.',
|
|
268
|
-
examples: ['service status', 'service check --json'],
|
|
269
|
-
},
|
|
270
245
|
completion: {
|
|
271
246
|
usage: ['completion <bash|zsh|fish>'],
|
|
272
247
|
summary: 'Generate shell completion scripts.',
|
|
273
248
|
examples: ['completion bash', 'completion zsh'],
|
|
274
249
|
},
|
|
275
|
-
serve: {
|
|
276
|
-
usage: ['serve [--hostname <host>] [--port <port>]', 'daemon [--hostname <host>] [--port <port>]'],
|
|
277
|
-
summary: 'Unavailable in GoodVibes Agent. Agent connects to an already-running GoodVibes runtime owned by GoodVibes TUI/host tooling.',
|
|
278
|
-
examples: [],
|
|
279
|
-
},
|
|
280
|
-
remote: {
|
|
281
|
-
usage: ['remote', 'bridge'],
|
|
282
|
-
summary: 'Inspect remote runner/node posture and bridge readiness.',
|
|
283
|
-
examples: ['remote', 'bridge'],
|
|
284
|
-
},
|
|
285
250
|
};
|
|
286
251
|
|
|
287
252
|
const HELP_ALIASES: Record<string, string> = {
|
|
@@ -295,16 +260,8 @@ const HELP_ALIASES: Record<string, string> = {
|
|
|
295
260
|
secret: 'secrets',
|
|
296
261
|
session: 'sessions',
|
|
297
262
|
task: 'tasks',
|
|
298
|
-
surface: 'surfaces',
|
|
299
|
-
webhook: 'listener',
|
|
300
|
-
controlplane: 'control-plane',
|
|
301
|
-
cp: 'control-plane',
|
|
302
263
|
qrcode: 'pair',
|
|
303
264
|
qr: 'pair',
|
|
304
|
-
daemon: 'serve',
|
|
305
|
-
server: 'serve',
|
|
306
|
-
services: 'service',
|
|
307
|
-
bridge: 'remote',
|
|
308
265
|
};
|
|
309
266
|
|
|
310
267
|
function normalizeHelpTopic(topic: string): string {
|
package/src/cli/index.ts
CHANGED
|
@@ -5,7 +5,5 @@ export * from './status.ts';
|
|
|
5
5
|
export * from './completion.ts';
|
|
6
6
|
export * from './config-overrides.ts';
|
|
7
7
|
export * from './endpoints.ts';
|
|
8
|
-
export * from './surface-command.ts';
|
|
9
|
-
export * from './service-command.ts';
|
|
10
8
|
export * from './bundle-command.ts';
|
|
11
9
|
export * from './management.ts';
|
|
@@ -8,9 +8,8 @@ import { inspectProviderAuth } from '@/runtime/index.ts';
|
|
|
8
8
|
import { getOrCreateCompanionToken, buildCompanionConnectionInfo, encodeConnectionPayload, formatConnectionBlock } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
9
9
|
import { generateQrMatrix, renderQrToString } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
10
10
|
import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
11
|
-
import { classifyBindPosture, isNetworkFacing } from './network-posture.ts';
|
|
12
11
|
import type { CliCommandRuntime } from './management.ts';
|
|
13
|
-
import { extractAuthorizationCode, formatJsonOrText, hasCommandFlag, openBrowser,
|
|
12
|
+
import { extractAuthorizationCode, formatJsonOrText, hasCommandFlag, openBrowser, urlHostForBindHost, withRuntimeServices, yesNo } from './management.ts';
|
|
14
13
|
import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../config/surface.ts';
|
|
15
14
|
|
|
16
15
|
export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<string> {
|
|
@@ -300,71 +299,6 @@ export async function handleTasks(runtime: CliCommandRuntime): Promise<string> {
|
|
|
300
299
|
});
|
|
301
300
|
}
|
|
302
301
|
|
|
303
|
-
export interface ControlPlaneStatusResult {
|
|
304
|
-
readonly enabled: unknown;
|
|
305
|
-
readonly hostMode: string;
|
|
306
|
-
readonly configuredHost: string;
|
|
307
|
-
readonly host: string;
|
|
308
|
-
readonly port: number;
|
|
309
|
-
readonly posture: ReturnType<typeof classifyBindPosture>;
|
|
310
|
-
readonly reachable: boolean;
|
|
311
|
-
readonly auth: ReturnType<typeof readAuthPaths>;
|
|
312
|
-
readonly service: {
|
|
313
|
-
readonly enabled: unknown;
|
|
314
|
-
readonly autostart: unknown;
|
|
315
|
-
readonly restartOnFailure: unknown;
|
|
316
|
-
};
|
|
317
|
-
readonly issues: readonly string[];
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
export async function buildControlPlaneStatusResult(runtime: CliCommandRuntime): Promise<ControlPlaneStatusResult> {
|
|
321
|
-
const binding = resolveRuntimeEndpointBinding(runtime.configManager, 'controlPlane');
|
|
322
|
-
const enabled = runtime.configManager.get('controlPlane.enabled');
|
|
323
|
-
const reachable = enabled === true ? await probeTcp(binding.host, binding.port) : false;
|
|
324
|
-
const auth = readAuthPaths(runtime);
|
|
325
|
-
const service = {
|
|
326
|
-
enabled: runtime.configManager.get('service.enabled'),
|
|
327
|
-
autostart: runtime.configManager.get('service.autostart'),
|
|
328
|
-
restartOnFailure: runtime.configManager.get('service.restartOnFailure'),
|
|
329
|
-
};
|
|
330
|
-
const issues: string[] = [];
|
|
331
|
-
if (enabled === true && !reachable) issues.push(`Runtime API is enabled but not reachable on ${binding.host}:${binding.port}.`);
|
|
332
|
-
if (enabled === true && service.enabled !== true) issues.push('Runtime API is enabled on the external runtime config, but Agent service ownership is disabled.');
|
|
333
|
-
if (enabled === true && service.autostart !== true) issues.push('Runtime API is enabled on the external runtime config, but autostart is off.');
|
|
334
|
-
if (enabled === true && service.restartOnFailure !== true) issues.push('Runtime API is enabled on the external runtime config, but restart-on-failure is off.');
|
|
335
|
-
if (isNetworkFacing(enabled, binding) && !auth.userStorePresent) issues.push('Network-facing Runtime API has no local auth user store.');
|
|
336
|
-
if (isNetworkFacing(enabled, binding) && auth.bootstrapCredentialPresent) issues.push('Network-facing Runtime API still has a bootstrap credential file.');
|
|
337
|
-
return {
|
|
338
|
-
enabled,
|
|
339
|
-
...binding,
|
|
340
|
-
posture: classifyBindPosture(binding),
|
|
341
|
-
reachable,
|
|
342
|
-
auth,
|
|
343
|
-
service,
|
|
344
|
-
issues,
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
export function formatControlPlaneStatus(runtime: CliCommandRuntime, value: ControlPlaneStatusResult): string {
|
|
349
|
-
return formatJsonOrText(runtime.cli)(value, [
|
|
350
|
-
'GoodVibes runtime API status',
|
|
351
|
-
` enabled: ${yesNo(value.enabled)}`,
|
|
352
|
-
` bind: ${value.hostMode} ${value.host}:${value.port}`,
|
|
353
|
-
` bind posture: ${value.posture.label}`,
|
|
354
|
-
` reachable: ${yesNo(value.reachable)}`,
|
|
355
|
-
` service: enabled=${yesNo(value.service.enabled)} autostart=${yesNo(value.service.autostart)} restartOnFailure=${yesNo(value.service.restartOnFailure)}`,
|
|
356
|
-
` local auth users: ${value.auth.userStorePresent ? 'present' : 'missing'}`,
|
|
357
|
-
` bootstrap credential: ${value.auth.bootstrapCredentialPresent ? 'present' : 'missing'}`,
|
|
358
|
-
` operator tokens: ${value.auth.operatorTokenPresent ? 'present' : 'missing'}`,
|
|
359
|
-
value.issues.length === 0 ? ' readiness: ready' : ' readiness: needs attention',
|
|
360
|
-
...value.issues.map((issue) => ` - ${issue}`),
|
|
361
|
-
].join('\n'));
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export async function renderControlPlaneStatus(runtime: CliCommandRuntime): Promise<string> {
|
|
365
|
-
return formatControlPlaneStatus(runtime, await buildControlPlaneStatusResult(runtime));
|
|
366
|
-
}
|
|
367
|
-
|
|
368
302
|
export async function renderPairing(runtime: CliCommandRuntime): Promise<string> {
|
|
369
303
|
const daemonHomeDir = join(runtime.homeDirectory, '.goodvibes', 'daemon');
|
|
370
304
|
const tokenRecord = getOrCreateCompanionToken(GOODVIBES_AGENT_PAIRING_SURFACE, { daemonHomeDir });
|
|
@@ -380,45 +314,3 @@ export async function renderPairing(runtime: CliCommandRuntime): Promise<string>
|
|
|
380
314
|
const qr = renderQrToString(generateQrMatrix(payload));
|
|
381
315
|
return [formatConnectionBlock(info, payload), '', qr].join('\n');
|
|
382
316
|
}
|
|
383
|
-
|
|
384
|
-
export async function renderRemote(runtime: CliCommandRuntime, label: 'remote' | 'bridge'): Promise<string> {
|
|
385
|
-
return await withRuntimeServices(runtime, (services) => {
|
|
386
|
-
const pools = services.remoteRunnerRegistry.listPools?.() ?? [];
|
|
387
|
-
const contracts = services.remoteRunnerRegistry.listContracts();
|
|
388
|
-
const artifacts = services.remoteRunnerRegistry.listArtifacts();
|
|
389
|
-
const value = {
|
|
390
|
-
pools: pools.length,
|
|
391
|
-
contracts: contracts.length,
|
|
392
|
-
artifacts: artifacts.length,
|
|
393
|
-
remoteFetchPrivateHosts: runtime.configManager.get('network.remoteFetch.allowPrivateHosts'),
|
|
394
|
-
};
|
|
395
|
-
return formatJsonOrText(runtime.cli)(value, [
|
|
396
|
-
`GoodVibes ${label} status`,
|
|
397
|
-
` runner pools: ${value.pools}`,
|
|
398
|
-
` contracts: ${value.contracts}`,
|
|
399
|
-
` review artifacts: ${value.artifacts}`,
|
|
400
|
-
` private-host remote fetch: ${yesNo(value.remoteFetchPrivateHosts)}`,
|
|
401
|
-
].join('\n'));
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
export function renderWeb(runtime: CliCommandRuntime): string {
|
|
406
|
-
const binding = resolveRuntimeEndpointBinding(runtime.configManager, 'web');
|
|
407
|
-
const publicBaseUrl = String(runtime.configManager.get('web.publicBaseUrl') ?? '');
|
|
408
|
-
const hasEndpointOverride = runtime.cli.flags.hostname !== undefined || runtime.cli.flags.port !== undefined;
|
|
409
|
-
const url = !hasEndpointOverride && publicBaseUrl
|
|
410
|
-
? publicBaseUrl
|
|
411
|
-
: `http://${urlHostForBindHost(binding.host)}:${binding.port}`;
|
|
412
|
-
const value = {
|
|
413
|
-
enabled: runtime.configManager.get('web.enabled'),
|
|
414
|
-
...binding,
|
|
415
|
-
url,
|
|
416
|
-
};
|
|
417
|
-
return formatJsonOrText(runtime.cli)(value, [
|
|
418
|
-
'GoodVibes web',
|
|
419
|
-
` enabled: ${yesNo(value.enabled)}`,
|
|
420
|
-
` bind: ${value.hostMode} ${value.host}:${value.port}`,
|
|
421
|
-
` url: ${value.url}`,
|
|
422
|
-
...(runtime.cli.flags.open ? [` open: ${openBrowser(value.url)}`] : []),
|
|
423
|
-
].join('\n'));
|
|
424
|
-
}
|
package/src/cli/management.ts
CHANGED
|
@@ -28,10 +28,8 @@ import { classifyProviderSetup } from './provider-classification.ts';
|
|
|
28
28
|
import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
29
29
|
import { applyRuntimeEndpointFlagOverrides } from './config-overrides.ts';
|
|
30
30
|
import type { RuntimeEndpointId } from './endpoints.ts';
|
|
31
|
-
import { handleServiceCommand } from './service-command.ts';
|
|
32
31
|
import { handleBundleCommand } from './bundle-command.ts';
|
|
33
|
-
import {
|
|
34
|
-
import { buildControlPlaneStatusResult, formatControlPlaneStatus, handleSecrets, handleSessions, handleTasks, renderPairing, renderRemote, renderSubscriptions, renderWeb } from './management-commands.ts';
|
|
32
|
+
import { handleSecrets, handleSessions, handleTasks, renderPairing, renderSubscriptions } from './management-commands.ts';
|
|
35
33
|
import { handleAgentKnowledgeCommand, handleAgentKnowledgeShortcutCommand, handleCompatCommand, handleDelegateCommand } from './agent-knowledge-command.ts';
|
|
36
34
|
import { handleProfilesCommand } from './profiles-command.ts';
|
|
37
35
|
import { handleRoutinesCommand } from './routines-command.ts';
|
|
@@ -672,14 +670,6 @@ export async function handleGoodVibesCliCommand(runtime: CliCommandRuntime): Pro
|
|
|
672
670
|
switch (runtime.cli.command) {
|
|
673
671
|
case 'run':
|
|
674
672
|
return { handled: true, exitCode: await runNonInteractiveAgent(runtime) };
|
|
675
|
-
case 'web':
|
|
676
|
-
console.log(renderWeb(runtime));
|
|
677
|
-
return { handled: true, exitCode: 0 };
|
|
678
|
-
case 'service': {
|
|
679
|
-
const result = await handleServiceCommand(runtime);
|
|
680
|
-
console.log(result.output);
|
|
681
|
-
return { handled: true, exitCode: result.exitCode };
|
|
682
|
-
}
|
|
683
673
|
case 'providers': {
|
|
684
674
|
const output = await renderProviders(runtime);
|
|
685
675
|
console.log(output);
|
|
@@ -747,21 +737,6 @@ export async function handleGoodVibesCliCommand(runtime: CliCommandRuntime): Pro
|
|
|
747
737
|
if (output) console.log(output);
|
|
748
738
|
return { handled: true, exitCode: exitCodeForText(output) };
|
|
749
739
|
}
|
|
750
|
-
case 'surfaces': {
|
|
751
|
-
const result = await handleSurfacesCommand(runtime);
|
|
752
|
-
console.log(result.output);
|
|
753
|
-
return { handled: true, exitCode: result.exitCode };
|
|
754
|
-
}
|
|
755
|
-
case 'listener': {
|
|
756
|
-
const result = await buildListenerTestResult(runtime);
|
|
757
|
-
console.log(formatListenerTestResult(runtime, result));
|
|
758
|
-
return { handled: true, exitCode: result.issues.length > 0 ? 1 : 0 };
|
|
759
|
-
}
|
|
760
|
-
case 'control-plane': {
|
|
761
|
-
const result = await buildControlPlaneStatusResult(runtime);
|
|
762
|
-
console.log(formatControlPlaneStatus(runtime, result));
|
|
763
|
-
return { handled: true, exitCode: result.issues.length > 0 ? 1 : 0 };
|
|
764
|
-
}
|
|
765
740
|
case 'pair':
|
|
766
741
|
console.log(await renderPairing(runtime));
|
|
767
742
|
return { handled: true, exitCode: 0 };
|
|
@@ -770,12 +745,6 @@ export async function handleGoodVibesCliCommand(runtime: CliCommandRuntime): Pro
|
|
|
770
745
|
console.log(result.output);
|
|
771
746
|
return { handled: true, exitCode: result.exitCode };
|
|
772
747
|
}
|
|
773
|
-
case 'remote':
|
|
774
|
-
console.log(await renderRemote(runtime, 'remote'));
|
|
775
|
-
return { handled: true, exitCode: 0 };
|
|
776
|
-
case 'bridge':
|
|
777
|
-
console.log(await renderRemote(runtime, 'bridge'));
|
|
778
|
-
return { handled: true, exitCode: 0 };
|
|
779
748
|
default:
|
|
780
749
|
return { handled: false, exitCode: 0 };
|
|
781
750
|
}
|
|
@@ -39,7 +39,7 @@ const REQUIRED_TARBALL_PATHS = [
|
|
|
39
39
|
'tsconfig.json',
|
|
40
40
|
'docs/README.md',
|
|
41
41
|
'docs/getting-started.md',
|
|
42
|
-
'docs/
|
|
42
|
+
'docs/runtime-connection.md',
|
|
43
43
|
'docs/release-and-publishing.md',
|
|
44
44
|
] as const;
|
|
45
45
|
const FORBIDDEN_TARBALL_PREFIXES = ['.github/', 'src/test/', 'src/.test/', '.goodvibes/', 'vendor/'] as const;
|
|
@@ -53,7 +53,7 @@ const PACKAGE_FACING_TEXT_PATHS = [
|
|
|
53
53
|
'CHANGELOG.md',
|
|
54
54
|
'docs/README.md',
|
|
55
55
|
'docs/getting-started.md',
|
|
56
|
-
'docs/
|
|
56
|
+
'docs/runtime-connection.md',
|
|
57
57
|
'docs/release-and-publishing.md',
|
|
58
58
|
] as const;
|
|
59
59
|
const PACKAGE_FACING_FORBIDDEN_TEXT = [
|
|
@@ -75,8 +75,15 @@ const PACKAGE_FACING_FORBIDDEN_TEXT = [
|
|
|
75
75
|
['capabilities', ' command'].join(''),
|
|
76
76
|
['near', '-fork'].join(''),
|
|
77
77
|
['goodvibes-agent', 'serve'].join(' '),
|
|
78
|
-
['goodvibes-agent', 'service
|
|
79
|
-
['goodvibes-agent', '
|
|
78
|
+
['goodvibes-agent', 'service'].join(' '),
|
|
79
|
+
['goodvibes-agent', 'services'].join(' '),
|
|
80
|
+
['goodvibes-agent', 'surfaces'].join(' '),
|
|
81
|
+
['goodvibes-agent', 'surface'].join(' '),
|
|
82
|
+
['goodvibes-agent', 'listener'].join(' '),
|
|
83
|
+
['goodvibes-agent', 'control-plane'].join(' '),
|
|
84
|
+
['goodvibes-agent', 'remote'].join(' '),
|
|
85
|
+
['goodvibes-agent', 'bridge'].join(' '),
|
|
86
|
+
['goodvibes-agent', 'web'].join(' '),
|
|
80
87
|
'Every plan must have a multi-agent execution strategy',
|
|
81
88
|
'NEVER skip WRFC',
|
|
82
89
|
'ALWAYS work in parallel when implementing a plan',
|
|
@@ -92,6 +99,7 @@ const PACKAGE_FACING_REQUIRED_TEXT: readonly {
|
|
|
92
99
|
{ path: 'README.md', required: ['/api/goodvibes-agent/knowledge'] },
|
|
93
100
|
{ path: 'docs/README.md', required: ['/api/goodvibes-agent/knowledge'] },
|
|
94
101
|
{ path: 'docs/getting-started.md', required: ['/api/goodvibes-agent/knowledge'] },
|
|
102
|
+
{ path: 'docs/runtime-connection.md', required: ['/api/goodvibes-agent/knowledge'] },
|
|
95
103
|
{ path: 'docs/release-and-publishing.md', required: ['/api/goodvibes-agent/knowledge'] },
|
|
96
104
|
];
|
|
97
105
|
|
package/src/cli/parser.ts
CHANGED
|
@@ -11,12 +11,6 @@ const COMMAND_ALIASES: Readonly<Record<string, GoodVibesCliCommand>> = {
|
|
|
11
11
|
run: 'run',
|
|
12
12
|
exec: 'run',
|
|
13
13
|
e: 'run',
|
|
14
|
-
serve: 'serve',
|
|
15
|
-
daemon: 'serve',
|
|
16
|
-
server: 'serve',
|
|
17
|
-
web: 'web',
|
|
18
|
-
service: 'service',
|
|
19
|
-
services: 'service',
|
|
20
14
|
status: 'status',
|
|
21
15
|
doctor: 'doctor',
|
|
22
16
|
onboarding: 'onboarding',
|
|
@@ -51,18 +45,8 @@ const COMMAND_ALIASES: Readonly<Record<string, GoodVibesCliCommand>> = {
|
|
|
51
45
|
pair: 'pair',
|
|
52
46
|
qrcode: 'pair',
|
|
53
47
|
qr: 'pair',
|
|
54
|
-
surfaces: 'surfaces',
|
|
55
|
-
surface: 'surfaces',
|
|
56
|
-
listener: 'listener',
|
|
57
|
-
'http-listener': 'listener',
|
|
58
|
-
webhook: 'listener',
|
|
59
|
-
'control-plane': 'control-plane',
|
|
60
|
-
controlplane: 'control-plane',
|
|
61
|
-
cp: 'control-plane',
|
|
62
48
|
bundle: 'bundle',
|
|
63
49
|
bundles: 'bundle',
|
|
64
|
-
remote: 'remote',
|
|
65
|
-
bridge: 'bridge',
|
|
66
50
|
completion: 'completion',
|
|
67
51
|
completions: 'completion',
|
|
68
52
|
help: 'help',
|
package/src/cli/status.ts
CHANGED
|
@@ -163,7 +163,7 @@ export function buildCliDoctorFindings(options: CliStatusOptions): readonly CliD
|
|
|
163
163
|
summary: issue,
|
|
164
164
|
cause: 'The service lifecycle inspection found a mismatch between configured service/surface state and observed host state.',
|
|
165
165
|
impact: 'Runtime API, listener, or web availability may not match the configuration.',
|
|
166
|
-
action: 'Use
|
|
166
|
+
action: 'Use Agent status and doctor diagnostics here, then manage the runtime from GoodVibes TUI or your host tooling.',
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
}
|
package/src/cli/types.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
export type GoodVibesCliCommand =
|
|
2
2
|
| 'tui'
|
|
3
3
|
| 'run'
|
|
4
|
-
| 'serve'
|
|
5
|
-
| 'web'
|
|
6
|
-
| 'service'
|
|
7
4
|
| 'status'
|
|
8
5
|
| 'doctor'
|
|
9
6
|
| 'onboarding'
|
|
@@ -22,12 +19,7 @@ export type GoodVibesCliCommand =
|
|
|
22
19
|
| 'sessions'
|
|
23
20
|
| 'tasks'
|
|
24
21
|
| 'pair'
|
|
25
|
-
| 'surfaces'
|
|
26
|
-
| 'listener'
|
|
27
|
-
| 'control-plane'
|
|
28
22
|
| 'bundle'
|
|
29
|
-
| 'remote'
|
|
30
|
-
| 'bridge'
|
|
31
23
|
| 'completion'
|
|
32
24
|
| 'help'
|
|
33
25
|
| 'version'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// useConfirmState<T> — reusable inline y/n confirmation helper
|
|
3
3
|
//
|
|
4
4
|
// Pattern (chosen over ConfirmableListPanel base class):
|
|
5
|
-
// - Composable:
|
|
5
|
+
// - Composable: each panel holds a ConfirmState field, not a new base class
|
|
6
6
|
// - Identical y/n UX everywhere: y confirms, n/Esc cancels, any other key
|
|
7
7
|
// is absorbed (does nothing) while confirm is active
|
|
8
8
|
// - Render: caller calls renderConfirmLines(width, state) to get the two
|
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.72';
|
|
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 {
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# Deployment And Services
|
|
2
|
-
|
|
3
|
-
GoodVibes Agent is a client/operator TUI. It connects to an already-running GoodVibes runtime and does not own runtime or listener deployment.
|
|
4
|
-
|
|
5
|
-
## Runtime Ownership
|
|
6
|
-
|
|
7
|
-
Agent must not:
|
|
8
|
-
|
|
9
|
-
- start an embedded runtime
|
|
10
|
-
- start an embedded HTTP listener
|
|
11
|
-
- install or uninstall OS services
|
|
12
|
-
- start, stop, or restart runtime services
|
|
13
|
-
- enable web, listener, control-plane, or channel surface posture
|
|
14
|
-
|
|
15
|
-
Those operations belong to GoodVibes TUI or the owning runtime host. Agent reports external runtime readiness but does not configure that host.
|
|
16
|
-
|
|
17
|
-
## Agent Runtime
|
|
18
|
-
|
|
19
|
-
The installed package exposes one executable:
|
|
20
|
-
|
|
21
|
-
```sh
|
|
22
|
-
goodvibes-agent
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
The executable is backed by TypeScript-authored source with a Bun shebang. Package install smoke must verify:
|
|
26
|
-
|
|
27
|
-
- `goodvibes-agent --help`
|
|
28
|
-
- `goodvibes-agent --version`
|
|
29
|
-
- `goodvibes-agent status --json`
|
|
30
|
-
- `goodvibes-agent` launches the TUI in a real PTY
|
|
31
|
-
- `goodvibes-agent smoke --json` when that command is available in the baseline being tested
|
|
32
|
-
|
|
33
|
-
## External Runtime Connection
|
|
34
|
-
|
|
35
|
-
Agent reads configuration and tokens, then connects to an already-running GoodVibes runtime. The default local control-plane URL is normally:
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
http://127.0.0.1:3421
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
If the runtime is unavailable, unauthenticated, or on an incompatible SDK version, Agent commands should report actionable diagnostics without printing token values.
|
|
42
|
-
|
|
43
|
-
## Release Rule
|
|
44
|
-
|
|
45
|
-
Only publish Agent releases that preserve the Agent product policy:
|
|
46
|
-
|
|
47
|
-
- serial/proactive assistant by default
|
|
48
|
-
- local memory/routines/skills/personas until shared registries are stable
|
|
49
|
-
- Agent knowledge routes only for Agent wiki calls
|
|
50
|
-
- companion chat for normal assistant chat
|
|
51
|
-
- explicit delegation to GoodVibes TUI for build/fix/review work
|
|
52
|
-
- WRFC only when explicitly requested for delegated build/fix/review work
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { CliCommandRuntime } from './management.ts';
|
|
2
|
-
import { buildCliServicePosture, formatCliServicePosture } from './service-posture.ts';
|
|
3
|
-
import type { CliCommandOutput } from './types.ts';
|
|
4
|
-
|
|
5
|
-
export async function handleServiceCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
|
|
6
|
-
const [sub = 'status'] = runtime.cli.commandArgs;
|
|
7
|
-
const json = runtime.cli.flags.outputFormat === 'json';
|
|
8
|
-
if (sub === 'status' || sub === 'check') {
|
|
9
|
-
const posture = await buildCliServicePosture(runtime, { probe: sub === 'check' });
|
|
10
|
-
return {
|
|
11
|
-
output: formatCliServicePosture(posture, json),
|
|
12
|
-
exitCode: sub === 'check' && posture.issues.length > 0 ? 1 : 0,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
if (sub === 'install' || sub === 'start' || sub === 'restart' || sub === 'stop' || sub === 'uninstall') {
|
|
16
|
-
const text = 'GoodVibes Agent connects to an existing GoodVibes runtime and does not manage runtime lifecycle. Use GoodVibes TUI or host tooling for service mutations.';
|
|
17
|
-
return {
|
|
18
|
-
output: json ? JSON.stringify({ ok: false, kind: 'daemon_lifecycle_external', action: sub, error: text }, null, 2) : text,
|
|
19
|
-
exitCode: 2,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
return {
|
|
23
|
-
output: `Usage: ${runtime.cli.binary} service [status|check]`,
|
|
24
|
-
exitCode: 2,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import type { ConfigKey } from '../config/index.ts';
|
|
2
|
-
import {
|
|
3
|
-
GOODVIBES_NTFY_AGENT_TOPIC,
|
|
4
|
-
GOODVIBES_NTFY_CHAT_TOPIC,
|
|
5
|
-
GOODVIBES_NTFY_REMOTE_TOPIC,
|
|
6
|
-
resolveGoodVibesNtfyTopics,
|
|
7
|
-
} from '@pellux/goodvibes-sdk/platform/integrations';
|
|
8
|
-
import { getMissingSurfaceFeatureFlags } from '../runtime/surface-feature-flags.ts';
|
|
9
|
-
import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
10
|
-
import { classifyBindPosture, isNetworkFacing } from './network-posture.ts';
|
|
11
|
-
import type { CliCommandRuntime } from './management.ts';
|
|
12
|
-
import {
|
|
13
|
-
formatJsonOrText,
|
|
14
|
-
isPresentConfigValue,
|
|
15
|
-
probeTcp,
|
|
16
|
-
readAuthPaths,
|
|
17
|
-
yesNo,
|
|
18
|
-
} from './management.ts';
|
|
19
|
-
|
|
20
|
-
export const SURFACE_CONFIGS = [
|
|
21
|
-
['slack', 'Slack', ['surfaces.slack.signingSecret', 'surfaces.slack.botToken']],
|
|
22
|
-
['discord', 'Discord', ['surfaces.discord.publicKey', 'surfaces.discord.botToken', 'surfaces.discord.applicationId']],
|
|
23
|
-
['telegram', 'Telegram', ['surfaces.telegram.botToken']],
|
|
24
|
-
['webhook', 'Webhook', ['surfaces.webhook.secret']],
|
|
25
|
-
['ntfy', 'ntfy', ['surfaces.ntfy.baseUrl']],
|
|
26
|
-
['googleChat', 'Google Chat', ['surfaces.googleChat.webhookUrl']],
|
|
27
|
-
['signal', 'Signal', ['surfaces.signal.bridgeUrl', 'surfaces.signal.account']],
|
|
28
|
-
['whatsapp', 'WhatsApp', ['surfaces.whatsapp.accessToken', 'surfaces.whatsapp.phoneNumberId']],
|
|
29
|
-
['imessage', 'iMessage', ['surfaces.imessage.bridgeUrl', 'surfaces.imessage.account']],
|
|
30
|
-
['msteams', 'Microsoft Teams', ['surfaces.msteams.appId', 'surfaces.msteams.appPassword']],
|
|
31
|
-
['bluebubbles', 'BlueBubbles', ['surfaces.bluebubbles.serverUrl', 'surfaces.bluebubbles.password']],
|
|
32
|
-
['mattermost', 'Mattermost', ['surfaces.mattermost.baseUrl', 'surfaces.mattermost.botToken']],
|
|
33
|
-
['matrix', 'Matrix', ['surfaces.matrix.homeserverUrl', 'surfaces.matrix.accessToken', 'surfaces.matrix.userId']],
|
|
34
|
-
] as const;
|
|
35
|
-
|
|
36
|
-
export async function handleSurfacesCommand(runtime: CliCommandRuntime): Promise<{ readonly output: string; readonly exitCode: number }> {
|
|
37
|
-
const config = runtime.configManager;
|
|
38
|
-
const [sub = 'list', ...rest] = runtime.cli.commandArgs;
|
|
39
|
-
const target = rest[0];
|
|
40
|
-
if (sub === 'enable' || sub === 'disable') {
|
|
41
|
-
if (!target) return { output: `Usage: goodvibes-agent surfaces ${sub} <web|listener|control-plane|surfaceId>`, exitCode: 2 };
|
|
42
|
-
const text = [
|
|
43
|
-
'GoodVibes Agent does not mutate runtime, listener, web, or channel surface posture.',
|
|
44
|
-
'Configure those surfaces from GoodVibes TUI or the external GoodVibes runtime host, then use `goodvibes-agent surfaces check` for read-only diagnostics.',
|
|
45
|
-
].join(' ');
|
|
46
|
-
return {
|
|
47
|
-
output: formatJsonOrText(runtime.cli)({
|
|
48
|
-
ok: false,
|
|
49
|
-
kind: 'daemon_lifecycle_external',
|
|
50
|
-
action: `surfaces.${sub}`,
|
|
51
|
-
target,
|
|
52
|
-
error: text,
|
|
53
|
-
}, text),
|
|
54
|
-
exitCode: 2,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
if (sub !== 'list' && sub !== 'status' && sub !== 'check' && sub !== 'show') {
|
|
58
|
-
return { output: 'Usage: goodvibes-agent surfaces [list|check|show <surfaceId>]', exitCode: 2 };
|
|
59
|
-
}
|
|
60
|
-
const controlPlane = resolveRuntimeEndpointBinding(config, 'controlPlane');
|
|
61
|
-
const web = resolveRuntimeEndpointBinding(config, 'web');
|
|
62
|
-
const httpListener = resolveRuntimeEndpointBinding(config, 'httpListener');
|
|
63
|
-
const includeProbe = sub === 'check';
|
|
64
|
-
const targetExternalSurface = target && SURFACE_CONFIGS.some(([id]) => id === target);
|
|
65
|
-
const shouldProbeControlPlane = includeProbe && !target;
|
|
66
|
-
const shouldProbeWeb = includeProbe && !target;
|
|
67
|
-
const shouldProbeListener = includeProbe && (!target || targetExternalSurface);
|
|
68
|
-
const [controlPlaneReachable, webReachable, listenerReachable] = includeProbe
|
|
69
|
-
? await Promise.all([
|
|
70
|
-
shouldProbeControlPlane ? probeTcp(controlPlane.host, controlPlane.port) : Promise.resolve(undefined),
|
|
71
|
-
shouldProbeWeb ? probeTcp(web.host, web.port) : Promise.resolve(undefined),
|
|
72
|
-
shouldProbeListener ? probeTcp(httpListener.host, httpListener.port) : Promise.resolve(undefined),
|
|
73
|
-
])
|
|
74
|
-
: [undefined, undefined, undefined];
|
|
75
|
-
const externalSurfaces = SURFACE_CONFIGS.map(([id, label, requiredKeys]) => {
|
|
76
|
-
const enabled = config.get(`surfaces.${id}.enabled` as ConfigKey);
|
|
77
|
-
const missing = requiredKeys.filter((key) => !isPresentConfigValue(config.get(key as ConfigKey)));
|
|
78
|
-
const missingFeatureFlags = enabled === true ? getMissingSurfaceFeatureFlags(config, id) : [];
|
|
79
|
-
return {
|
|
80
|
-
id,
|
|
81
|
-
label,
|
|
82
|
-
enabled,
|
|
83
|
-
ready: !enabled || (missing.length === 0 && missingFeatureFlags.length === 0),
|
|
84
|
-
missing,
|
|
85
|
-
missingFeatureFlags,
|
|
86
|
-
};
|
|
87
|
-
});
|
|
88
|
-
const filteredSurfaces = target ? externalSurfaces.filter((surface) => surface.id === target) : externalSurfaces;
|
|
89
|
-
if (target && filteredSurfaces.length === 0) return { output: `Unknown surface: ${target}`, exitCode: 1 };
|
|
90
|
-
const ntfyTopics = resolveGoodVibesNtfyTopics({
|
|
91
|
-
chatTopic: String(config.get('surfaces.ntfy.chatTopic' as ConfigKey) || GOODVIBES_NTFY_CHAT_TOPIC),
|
|
92
|
-
agentTopic: String(config.get('surfaces.ntfy.agentTopic' as ConfigKey) || GOODVIBES_NTFY_AGENT_TOPIC),
|
|
93
|
-
remoteTopic: String(config.get('surfaces.ntfy.remoteTopic' as ConfigKey) || GOODVIBES_NTFY_REMOTE_TOPIC),
|
|
94
|
-
});
|
|
95
|
-
const readinessIssues: string[] = [];
|
|
96
|
-
if (shouldProbeControlPlane && config.get('controlPlane.enabled') === true && !controlPlaneReachable) {
|
|
97
|
-
readinessIssues.push(`Control plane is enabled but not reachable on ${controlPlane.host}:${controlPlane.port}.`);
|
|
98
|
-
}
|
|
99
|
-
if (shouldProbeWeb && config.get('web.enabled') === true && !webReachable) {
|
|
100
|
-
readinessIssues.push(`Web surface is enabled but not reachable on ${web.host}:${web.port}.`);
|
|
101
|
-
}
|
|
102
|
-
if (shouldProbeListener && config.get('danger.httpListener') === true && !listenerReachable) {
|
|
103
|
-
readinessIssues.push(`HTTP listener is enabled but not reachable on ${httpListener.host}:${httpListener.port}.`);
|
|
104
|
-
}
|
|
105
|
-
for (const surface of filteredSurfaces) {
|
|
106
|
-
if (surface.enabled !== true) continue;
|
|
107
|
-
if (config.get('danger.httpListener') !== true) {
|
|
108
|
-
readinessIssues.push(`${surface.label} is enabled but the HTTP listener is disabled.`);
|
|
109
|
-
}
|
|
110
|
-
if (surface.missing.length > 0) {
|
|
111
|
-
readinessIssues.push(`${surface.label} is enabled but missing ${surface.missing.join(', ')}.`);
|
|
112
|
-
}
|
|
113
|
-
if (surface.missingFeatureFlags.length > 0) {
|
|
114
|
-
readinessIssues.push(`${surface.label} is enabled but feature gates are disabled: ${surface.missingFeatureFlags.join(', ')}.`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const value = {
|
|
118
|
-
controlPlane: {
|
|
119
|
-
enabled: config.get('controlPlane.enabled'),
|
|
120
|
-
hostMode: controlPlane.hostMode,
|
|
121
|
-
configuredHost: controlPlane.configuredHost,
|
|
122
|
-
host: controlPlane.host,
|
|
123
|
-
port: controlPlane.port,
|
|
124
|
-
reachable: controlPlaneReachable,
|
|
125
|
-
},
|
|
126
|
-
web: {
|
|
127
|
-
enabled: config.get('web.enabled'),
|
|
128
|
-
hostMode: web.hostMode,
|
|
129
|
-
configuredHost: web.configuredHost,
|
|
130
|
-
host: web.host,
|
|
131
|
-
port: web.port,
|
|
132
|
-
reachable: webReachable,
|
|
133
|
-
},
|
|
134
|
-
httpListener: {
|
|
135
|
-
enabled: config.get('danger.httpListener'),
|
|
136
|
-
hostMode: httpListener.hostMode,
|
|
137
|
-
configuredHost: httpListener.configuredHost,
|
|
138
|
-
host: httpListener.host,
|
|
139
|
-
port: httpListener.port,
|
|
140
|
-
reachable: listenerReachable,
|
|
141
|
-
},
|
|
142
|
-
surfaces: filteredSurfaces,
|
|
143
|
-
readinessIssues,
|
|
144
|
-
};
|
|
145
|
-
const output = formatJsonOrText(runtime.cli)(value, [
|
|
146
|
-
'GoodVibes Agent surfaces',
|
|
147
|
-
` control-plane: ${yesNo(value.controlPlane.enabled)} (${value.controlPlane.hostMode} ${value.controlPlane.host}:${value.controlPlane.port})${includeProbe ? ` reachable=${yesNo(value.controlPlane.reachable)}` : ''}`,
|
|
148
|
-
` web: ${yesNo(value.web.enabled)} (${value.web.hostMode} ${value.web.host}:${value.web.port})${includeProbe ? ` reachable=${yesNo(value.web.reachable)}` : ''}`,
|
|
149
|
-
` http-listener: ${yesNo(value.httpListener.enabled)} (${value.httpListener.hostMode} ${value.httpListener.host}:${value.httpListener.port})${includeProbe ? ` reachable=${yesNo(value.httpListener.reachable)}` : ''}`,
|
|
150
|
-
'',
|
|
151
|
-
'External surfaces:',
|
|
152
|
-
...value.surfaces.map((surface) => ` ${surface.label.padEnd(16)} enabled=${yesNo(surface.enabled)} ready=${yesNo(surface.ready)}${surface.enabled && surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}${surface.enabled && surface.missingFeatureFlags.length > 0 ? ` featureGates=${surface.missingFeatureFlags.join(',')}` : ''}`),
|
|
153
|
-
...(filteredSurfaces.some((surface) => surface.id === 'ntfy') ? [
|
|
154
|
-
'',
|
|
155
|
-
'ntfy inbound topics:',
|
|
156
|
-
` chat: ${ntfyTopics.chatTopic}`,
|
|
157
|
-
` agent: ${ntfyTopics.agentTopic}`,
|
|
158
|
-
` runtime-only remote: ${ntfyTopics.remoteTopic}`,
|
|
159
|
-
` default delivery topic: ${String(config.get('surfaces.ntfy.topic') || '(none)')}`,
|
|
160
|
-
] : []),
|
|
161
|
-
...(includeProbe ? [
|
|
162
|
-
readinessIssues.length === 0 ? 'Readiness: ready' : 'Readiness: needs attention',
|
|
163
|
-
...readinessIssues.map((issue) => ` - ${issue}`),
|
|
164
|
-
] : []),
|
|
165
|
-
].join('\n'));
|
|
166
|
-
return { output, exitCode: includeProbe && readinessIssues.length > 0 ? 1 : 0 };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export interface ListenerTestResult {
|
|
170
|
-
readonly enabled: unknown;
|
|
171
|
-
readonly hostMode: string;
|
|
172
|
-
readonly configuredHost: string;
|
|
173
|
-
readonly host: string;
|
|
174
|
-
readonly port: number;
|
|
175
|
-
readonly posture: ReturnType<typeof classifyBindPosture>;
|
|
176
|
-
readonly reachable: boolean;
|
|
177
|
-
readonly service: {
|
|
178
|
-
readonly enabled: unknown;
|
|
179
|
-
readonly autostart: unknown;
|
|
180
|
-
readonly restartOnFailure: unknown;
|
|
181
|
-
};
|
|
182
|
-
readonly auth: ReturnType<typeof readAuthPaths>;
|
|
183
|
-
readonly surfaces: readonly {
|
|
184
|
-
readonly id: string;
|
|
185
|
-
readonly label: string;
|
|
186
|
-
readonly enabled: unknown;
|
|
187
|
-
readonly ready: boolean;
|
|
188
|
-
readonly missing: readonly string[];
|
|
189
|
-
readonly missingFeatureFlags: readonly string[];
|
|
190
|
-
}[];
|
|
191
|
-
readonly issues: readonly string[];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export async function buildListenerTestResult(runtime: CliCommandRuntime): Promise<ListenerTestResult> {
|
|
195
|
-
const enabled = runtime.configManager.get('danger.httpListener');
|
|
196
|
-
const binding = resolveRuntimeEndpointBinding(runtime.configManager, 'httpListener');
|
|
197
|
-
const posture = classifyBindPosture(binding);
|
|
198
|
-
const reachable = enabled === true ? await probeTcp(binding.host, binding.port) : false;
|
|
199
|
-
const auth = readAuthPaths(runtime);
|
|
200
|
-
const service = {
|
|
201
|
-
enabled: runtime.configManager.get('service.enabled'),
|
|
202
|
-
autostart: runtime.configManager.get('service.autostart'),
|
|
203
|
-
restartOnFailure: runtime.configManager.get('service.restartOnFailure'),
|
|
204
|
-
};
|
|
205
|
-
const surfaces = SURFACE_CONFIGS.map(([id, label, requiredKeys]) => {
|
|
206
|
-
const surfaceEnabled = runtime.configManager.get(`surfaces.${id}.enabled` as ConfigKey);
|
|
207
|
-
const missing = requiredKeys.filter((key) => !isPresentConfigValue(runtime.configManager.get(key as ConfigKey)));
|
|
208
|
-
const missingFeatureFlags = surfaceEnabled === true ? getMissingSurfaceFeatureFlags(runtime.configManager, id) : [];
|
|
209
|
-
return {
|
|
210
|
-
id,
|
|
211
|
-
label,
|
|
212
|
-
enabled: surfaceEnabled,
|
|
213
|
-
ready: surfaceEnabled !== true || (missing.length === 0 && missingFeatureFlags.length === 0),
|
|
214
|
-
missing,
|
|
215
|
-
missingFeatureFlags,
|
|
216
|
-
};
|
|
217
|
-
}).filter((surface) => surface.enabled === true);
|
|
218
|
-
const issues: string[] = [];
|
|
219
|
-
if (enabled !== true) issues.push('HTTP listener is disabled.');
|
|
220
|
-
if (enabled === true && service.enabled !== true) issues.push('HTTP listener is enabled on the external runtime config, but Agent service ownership is disabled.');
|
|
221
|
-
if (enabled === true && service.autostart !== true) issues.push('HTTP listener is enabled on the external runtime config, but autostart is off.');
|
|
222
|
-
if (enabled === true && service.restartOnFailure !== true) issues.push('HTTP listener is enabled on the external runtime config, but restart-on-failure is off.');
|
|
223
|
-
if (isNetworkFacing(enabled, binding) && !auth.userStorePresent) issues.push('Network-facing listener has no local auth user store.');
|
|
224
|
-
if (isNetworkFacing(enabled, binding) && auth.bootstrapCredentialPresent) issues.push('Network-facing listener still has a bootstrap credential file.');
|
|
225
|
-
for (const surface of surfaces) {
|
|
226
|
-
if (surface.missing.length > 0) issues.push(`${surface.label} is enabled but missing ${surface.missing.join(', ')}.`);
|
|
227
|
-
if (surface.missingFeatureFlags.length > 0) issues.push(`${surface.label} is enabled but feature gates are disabled: ${surface.missingFeatureFlags.join(', ')}.`);
|
|
228
|
-
}
|
|
229
|
-
return { enabled, ...binding, posture, reachable, service, auth, surfaces, issues };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export function formatListenerTestResult(runtime: CliCommandRuntime, value: ListenerTestResult): string {
|
|
233
|
-
return formatJsonOrText(runtime.cli)(value, [
|
|
234
|
-
'GoodVibes Agent listener test',
|
|
235
|
-
` enabled: ${yesNo(value.enabled)}`,
|
|
236
|
-
` endpoint: ${value.hostMode} ${value.host}:${value.port}`,
|
|
237
|
-
` bind posture: ${value.posture.label}`,
|
|
238
|
-
` reachable: ${yesNo(value.reachable)}`,
|
|
239
|
-
` service: enabled=${yesNo(value.service.enabled)} autostart=${yesNo(value.service.autostart)} restartOnFailure=${yesNo(value.service.restartOnFailure)}`,
|
|
240
|
-
` local auth users: ${value.auth.userStorePresent ? 'present' : 'missing'}`,
|
|
241
|
-
` bootstrap credential: ${value.auth.bootstrapCredentialPresent ? 'present' : 'missing'}`,
|
|
242
|
-
value.surfaces.length === 0 ? ' enabled webhook surfaces: none' : ' enabled webhook surfaces:',
|
|
243
|
-
...value.surfaces.map((surface) => ` ${surface.label}: ready=${yesNo(surface.ready)}${surface.missing.length > 0 ? ` missing=${surface.missing.join(',')}` : ''}${surface.missingFeatureFlags.length > 0 ? ` featureGates=${surface.missingFeatureFlags.join(',')}` : ''}`),
|
|
244
|
-
value.issues.length === 0 ? ' readiness: ready' : ' readiness: needs attention',
|
|
245
|
-
...value.issues.map((issue) => ` - ${issue}`),
|
|
246
|
-
].join('\n'));
|
|
247
|
-
}
|