@pellux/goodvibes-agent 0.1.71 → 0.1.73

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.
@@ -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
- }