@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 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 connectivity or service lifecycle.
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 surfaces. It does not provide runtime lifecycle setup commands.
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
- - [Deployment And Services](docs/deployment-and-services.md)
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
- - [Deployment And Services](deployment-and-services.md)
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 connectivity or service lifecycle.
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.
@@ -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
- Agent lifecycle commands that would start or mutate runtime posture are blocked intentionally. Use `goodvibes-agent status`, `goodvibes-agent doctor`, and read-only surface checks for diagnostics.
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.71",
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/deployment-and-services.md",
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 === 'web') return applyRuntimeEndpointFlagOverrides(configManager, 'web', flags);
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
  }
@@ -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, probeTcp, readAuthPaths, urlHostForBindHost, withRuntimeServices, yesNo } from './management.ts';
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
- }
@@ -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 { buildListenerTestResult, formatListenerTestResult, handleSurfacesCommand } from './surface-command.ts';
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/deployment-and-services.md',
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/deployment-and-services.md',
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 start'].join(' '),
79
- ['goodvibes-agent', 'surfaces enable'].join(' '),
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 goodvibes-agent service check for read-only detail, then manage the runtime from GoodVibes TUI or your host tooling.',
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: any panel holds a ConfirmState field, not a new base class
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.71';
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
- }