@pellux/goodvibes-agent 0.1.56 → 0.1.58
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/.goodvibes/GOODVIBES.md +1 -1
- package/CHANGELOG.md +18 -9
- package/README.md +3 -3
- package/docs/README.md +1 -1
- package/docs/getting-started.md +3 -3
- package/docs/release-and-publishing.md +2 -2
- package/package.json +1 -3
- package/src/agent/routine-schedule-args.ts +219 -0
- package/src/agent/routine-schedule-format.ts +173 -0
- package/src/agent/routine-schedule-promotion.ts +3 -811
- package/src/agent/routine-schedule-receipts.ts +502 -0
- package/src/cli/agent-knowledge-command.ts +6 -6
- package/src/cli/help.ts +3 -25
- package/src/cli/package-verification.ts +23 -16
- package/src/cli/redaction.ts +4 -1
- package/src/cli/routines-command.ts +10 -6
- package/src/cli/service-posture.ts +47 -280
- package/src/cli/status.ts +0 -1
- package/src/cli/tui-startup.ts +23 -0
- package/src/config/secret-config.ts +0 -2
- package/src/input/agent-workspace-categories.ts +219 -0
- package/src/input/agent-workspace-editors.ts +143 -0
- package/src/input/agent-workspace-snapshot.ts +265 -0
- package/src/input/agent-workspace-types.ts +142 -0
- package/src/input/agent-workspace.ts +22 -766
- package/src/input/commands/agent-runtime-profile-runtime.ts +1 -1
- package/src/input/commands/delegation-runtime.ts +1 -1
- package/src/input/commands/experience-runtime.ts +3 -4
- package/src/input/commands/guidance-runtime.ts +1 -2
- package/src/input/commands/health-runtime.ts +3 -65
- package/src/input/commands/knowledge.ts +7 -7
- package/src/input/commands/local-setup-review.ts +0 -61
- package/src/input/commands/local-setup-transfer.ts +0 -3
- package/src/input/commands/local-setup.ts +2 -15
- package/src/input/commands/planning-runtime.ts +4 -1
- package/src/input/commands/platform-access-runtime.ts +1 -10
- package/src/input/commands/platform-services-runtime.ts +0 -1
- package/src/input/commands/recall-query.ts +1 -1
- package/src/input/commands/routines-runtime.ts +10 -6
- package/src/input/commands/schedule-runtime.ts +10 -6
- package/src/input/commands/session-workflow.ts +1 -1
- package/src/input/commands/tasks-runtime.ts +1 -14
- package/src/input/commands.ts +0 -4
- package/src/input/handler-onboarding.ts +10 -120
- package/src/input/onboarding/onboarding-wizard-apply.ts +5 -196
- package/src/input/onboarding/onboarding-wizard-constants.ts +8 -119
- package/src/input/onboarding/onboarding-wizard-helpers.ts +2 -53
- package/src/input/onboarding/onboarding-wizard-rules.ts +2 -236
- package/src/input/onboarding/onboarding-wizard-state.ts +1 -69
- package/src/input/onboarding/onboarding-wizard-steps.ts +584 -737
- package/src/input/onboarding/onboarding-wizard-types.ts +8 -26
- package/src/input/onboarding/onboarding-wizard.ts +4 -109
- package/src/input/settings-modal-agent-policy.ts +10 -0
- package/src/input/settings-modal-types.ts +2 -4
- package/src/input/settings-modal.ts +3 -1
- package/src/input/submission-router.ts +0 -1
- package/src/main.ts +13 -12
- package/src/panels/approval-panel.ts +1 -2
- package/src/panels/builtin/operations.ts +1 -2
- package/src/panels/knowledge-panel.ts +2 -2
- package/src/panels/project-planning-panel.ts +4 -1
- package/src/panels/provider-health-domains.ts +0 -22
- package/src/panels/provider-health-panel.ts +1 -5
- package/src/panels/session-browser-panel.ts +0 -5
- package/src/panels/tasks-panel.ts +2 -64
- package/src/renderer/agent-workspace.ts +1 -1
- package/src/renderer/help-overlay.ts +1 -2
- package/src/renderer/semantic-diff.ts +1 -1
- package/src/renderer/settings-modal-helpers.ts +0 -16
- package/src/renderer/settings-modal.ts +3 -5
- package/src/runtime/bootstrap-hook-bridge.ts +0 -3
- package/src/runtime/bootstrap-shell.ts +2 -1
- package/src/runtime/bootstrap.ts +1 -1
- package/src/runtime/index.ts +0 -1
- package/src/runtime/onboarding/derivation.ts +1 -28
- package/src/runtime/onboarding/snapshot.ts +0 -1
- package/src/runtime/onboarding/types.ts +1 -4
- package/src/runtime/services.ts +4 -23
- package/src/runtime/ui-read-models.ts +4 -3
- package/src/shell/service-settings-sync.ts +15 -244
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/wrfc-agent-guard.ts +3 -3
- package/src/verification/live-verifier.ts +11 -5
- package/src/verification/verification-ledger.ts +3 -6
- package/src/version.ts +1 -1
- package/src/input/commands/agent-externalized-tui.ts +0 -73
- package/src/input/commands/cloudflare-runtime.ts +0 -385
- package/src/input/handler-onboarding-cloudflare.ts +0 -322
- package/src/input/onboarding/onboarding-runtime-status.ts +0 -87
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +0 -494
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +0 -199
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +0 -130
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +0 -762
- package/src/runtime/cloudflare-control-plane.ts +0 -350
- package/src/runtime/sandbox-public-gaps.ts +0 -358
|
@@ -2,18 +2,22 @@ import { createShellPathService } from '@/runtime/index.ts';
|
|
|
2
2
|
import { AgentRoutineRegistry, type AgentRoutineRecord } from '../agent/routine-registry.ts';
|
|
3
3
|
import {
|
|
4
4
|
buildRoutineSchedulePreview,
|
|
5
|
+
promoteRoutineToDaemonSchedule,
|
|
6
|
+
resolveAgentDaemonConnection,
|
|
7
|
+
} from '../agent/routine-schedule-promotion.ts';
|
|
8
|
+
import { parseRoutineSchedulePromotionArgs } from '../agent/routine-schedule-args.ts';
|
|
9
|
+
import {
|
|
5
10
|
formatRoutineScheduleCorrelation,
|
|
6
|
-
formatRoutineScheduleReceipt,
|
|
7
|
-
formatRoutineScheduleReceipts,
|
|
8
11
|
formatRoutineScheduleFailure,
|
|
9
12
|
formatRoutineSchedulePreview,
|
|
13
|
+
formatRoutineScheduleReceipt,
|
|
14
|
+
formatRoutineScheduleReceipts,
|
|
10
15
|
formatRoutineScheduleSuccess,
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
} from '../agent/routine-schedule-format.ts';
|
|
17
|
+
import {
|
|
13
18
|
reconcileRoutineScheduleReceipts,
|
|
14
|
-
resolveAgentDaemonConnection,
|
|
15
19
|
RoutineScheduleReceiptStore,
|
|
16
|
-
} from '../agent/routine-schedule-
|
|
20
|
+
} from '../agent/routine-schedule-receipts.ts';
|
|
17
21
|
import type { CliCommandOutput } from './types.ts';
|
|
18
22
|
import type { CliCommandRuntime } from './management.ts';
|
|
19
23
|
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { accessSync, closeSync, constants, existsSync, openSync, readSync, realpathSync, statSync } from 'node:fs';
|
|
1
|
+
import { closeSync, existsSync, openSync, readSync, statSync } from 'node:fs';
|
|
3
2
|
import net from 'node:net';
|
|
4
|
-
import {
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { PlatformServiceManager } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
7
|
-
import type { ManagedServiceStatus } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
3
|
+
import { isAbsolute, join } from 'node:path';
|
|
8
4
|
import type { ConfigManager } from '../config/index.ts';
|
|
9
5
|
import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
10
6
|
import type { RuntimeEndpointBinding, RuntimeEndpointId } from './endpoints.ts';
|
|
@@ -36,6 +32,21 @@ export interface CliServiceLogPosture {
|
|
|
36
32
|
readonly readError?: string;
|
|
37
33
|
}
|
|
38
34
|
|
|
35
|
+
export interface CliExternalDaemonLifecyclePosture {
|
|
36
|
+
readonly platform: 'manual';
|
|
37
|
+
readonly path: string;
|
|
38
|
+
readonly installed: false;
|
|
39
|
+
readonly autostart: false;
|
|
40
|
+
readonly running: false;
|
|
41
|
+
readonly logPath?: string;
|
|
42
|
+
readonly commandPreview: string;
|
|
43
|
+
readonly suggestedCommands: readonly string[];
|
|
44
|
+
readonly lastAction: 'status';
|
|
45
|
+
readonly actionError?: string;
|
|
46
|
+
readonly pidPath: string;
|
|
47
|
+
readonly lastError: null;
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
export interface CliServicePosture {
|
|
40
51
|
readonly config: {
|
|
41
52
|
readonly enabled: boolean;
|
|
@@ -43,10 +54,7 @@ export interface CliServicePosture {
|
|
|
43
54
|
readonly restartOnFailure: boolean;
|
|
44
55
|
readonly daemonEnabled: boolean;
|
|
45
56
|
};
|
|
46
|
-
readonly managed:
|
|
47
|
-
readonly pidPath: string;
|
|
48
|
-
readonly lastError: string | null;
|
|
49
|
-
};
|
|
57
|
+
readonly managed: CliExternalDaemonLifecyclePosture;
|
|
50
58
|
readonly endpoints: readonly CliServiceEndpointPosture[];
|
|
51
59
|
readonly log: CliServiceLogPosture;
|
|
52
60
|
readonly issues: readonly string[];
|
|
@@ -58,46 +66,9 @@ const ENDPOINTS: readonly { readonly id: RuntimeEndpointId; readonly label: stri
|
|
|
58
66
|
{ id: 'web', label: 'web surface', enabledKey: 'web.enabled' },
|
|
59
67
|
];
|
|
60
68
|
|
|
61
|
-
const SOURCE_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
62
|
-
|
|
63
|
-
export interface DaemonExecutableResolutionOptions {
|
|
64
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
65
|
-
readonly argv?: readonly string[];
|
|
66
|
-
readonly execPath?: string;
|
|
67
|
-
readonly packageRoot?: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface DaemonExecutableResolution {
|
|
71
|
-
readonly command: string;
|
|
72
|
-
readonly source: 'env' | 'sibling' | 'package' | 'path' | 'fallback';
|
|
73
|
-
readonly absolute: boolean;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
interface ServiceDefinitionOverride {
|
|
77
|
-
readonly name: string;
|
|
78
|
-
readonly description: string;
|
|
79
|
-
readonly workingDirectory: string;
|
|
80
|
-
readonly command: string;
|
|
81
|
-
readonly args: readonly string[];
|
|
82
|
-
readonly env: Readonly<Record<string, string>>;
|
|
83
|
-
readonly restartOnFailure: boolean;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface CliServiceStatusCommandResult {
|
|
87
|
-
readonly status: number | null;
|
|
88
|
-
readonly stdout?: string;
|
|
89
|
-
readonly stderr?: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface CliServiceStatusManager {
|
|
93
|
-
status(): ManagedServiceStatus;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
69
|
interface CliServicePostureOptions {
|
|
97
70
|
readonly probe?: boolean;
|
|
98
71
|
readonly logTailBytes?: number;
|
|
99
|
-
readonly manager?: CliServiceStatusManager;
|
|
100
|
-
readonly runCommand?: (command: string, args: readonly string[]) => CliServiceStatusCommandResult;
|
|
101
72
|
}
|
|
102
73
|
|
|
103
74
|
function connectHostForBindHost(host: string): string {
|
|
@@ -120,183 +91,6 @@ async function probeTcp(host: string, port: number, timeoutMs = 750): Promise<bo
|
|
|
120
91
|
});
|
|
121
92
|
}
|
|
122
93
|
|
|
123
|
-
function isExecutable(path: string): boolean {
|
|
124
|
-
try {
|
|
125
|
-
accessSync(path, constants.X_OK);
|
|
126
|
-
return true;
|
|
127
|
-
} catch {
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function platformDaemonArtifactName(platform = process.platform, arch = process.arch): string {
|
|
133
|
-
if (platform === 'linux' && arch === 'x64') return 'goodvibes-daemon-linux-x64';
|
|
134
|
-
if (platform === 'linux' && arch === 'arm64') return 'goodvibes-daemon-linux-arm64';
|
|
135
|
-
if (platform === 'darwin' && arch === 'x64') return 'goodvibes-daemon-macos-x64';
|
|
136
|
-
if (platform === 'darwin' && arch === 'arm64') return 'goodvibes-daemon-macos-arm64';
|
|
137
|
-
if (platform === 'win32') return 'goodvibes-daemon-windows.exe';
|
|
138
|
-
return 'goodvibes-daemon';
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function executablePathCandidates(path: string): string[] {
|
|
142
|
-
if (!path.trim() || !isAbsolute(path)) return [];
|
|
143
|
-
const dir = dirname(path);
|
|
144
|
-
const name = basename(path);
|
|
145
|
-
const artifact = platformDaemonArtifactName();
|
|
146
|
-
const candidates = [
|
|
147
|
-
join(dir, 'goodvibes-daemon'),
|
|
148
|
-
join(dir, artifact),
|
|
149
|
-
];
|
|
150
|
-
if (name.startsWith('goodvibes-') && !name.startsWith('goodvibes-daemon-')) {
|
|
151
|
-
candidates.push(join(dir, name.replace(/^goodvibes-/, 'goodvibes-daemon-')));
|
|
152
|
-
}
|
|
153
|
-
if (name === 'goodvibes') candidates.push(join(dir, 'goodvibes-daemon'));
|
|
154
|
-
return [...new Set(candidates)];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function resolveCommandFromPath(command: string, pathValue: string | undefined): string | null {
|
|
158
|
-
const pathEntries = (pathValue ?? '').split(delimiter).filter(Boolean);
|
|
159
|
-
const extensions = process.platform === 'win32'
|
|
160
|
-
? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)
|
|
161
|
-
: [''];
|
|
162
|
-
for (const entry of pathEntries) {
|
|
163
|
-
for (const extension of extensions) {
|
|
164
|
-
const candidate = join(entry, `${command}${extension}`);
|
|
165
|
-
if (isExecutable(candidate)) return candidate;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function firstExecutable(paths: readonly string[]): string | null {
|
|
172
|
-
for (const path of paths) {
|
|
173
|
-
if (isExecutable(path)) return path;
|
|
174
|
-
}
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function packageArtifactForBinWrapper(path: string): string | null {
|
|
179
|
-
let realPath = path;
|
|
180
|
-
try {
|
|
181
|
-
realPath = realpathSync(path);
|
|
182
|
-
} catch {
|
|
183
|
-
// Keep the original path if resolving a symlink fails.
|
|
184
|
-
}
|
|
185
|
-
if (basename(realPath) !== 'goodvibes-daemon') return null;
|
|
186
|
-
const binDir = dirname(realPath);
|
|
187
|
-
if (basename(binDir) !== 'bin') return null;
|
|
188
|
-
const packageRoot = dirname(binDir);
|
|
189
|
-
return firstExecutable([
|
|
190
|
-
join(packageRoot, 'vendor', platformDaemonArtifactName()),
|
|
191
|
-
join(packageRoot, 'dist', platformDaemonArtifactName()),
|
|
192
|
-
join(packageRoot, 'dist', 'goodvibes-daemon'),
|
|
193
|
-
]);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export function resolveGoodVibesDaemonExecutable(
|
|
197
|
-
options: DaemonExecutableResolutionOptions = {},
|
|
198
|
-
): DaemonExecutableResolution {
|
|
199
|
-
const env = options.env ?? process.env;
|
|
200
|
-
const override = env.GOODVIBES_DAEMON_COMMAND?.trim();
|
|
201
|
-
if (override) {
|
|
202
|
-
return { command: override, source: 'env', absolute: isAbsolute(override) };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const packageRoot = options.packageRoot ?? SOURCE_PACKAGE_ROOT;
|
|
206
|
-
const packaged = firstExecutable([
|
|
207
|
-
join(packageRoot, 'dist', platformDaemonArtifactName()),
|
|
208
|
-
join(packageRoot, 'dist', 'goodvibes-daemon'),
|
|
209
|
-
join(packageRoot, 'vendor', platformDaemonArtifactName()),
|
|
210
|
-
join(packageRoot, 'bin', 'goodvibes-daemon'),
|
|
211
|
-
]);
|
|
212
|
-
if (packaged) return { command: packaged, source: 'package', absolute: true };
|
|
213
|
-
|
|
214
|
-
const argv = options.argv ?? process.argv;
|
|
215
|
-
const execPath = options.execPath ?? process.execPath;
|
|
216
|
-
const sibling = firstExecutable([
|
|
217
|
-
...executablePathCandidates(execPath),
|
|
218
|
-
...executablePathCandidates(argv[1] ?? ''),
|
|
219
|
-
]);
|
|
220
|
-
if (sibling) {
|
|
221
|
-
return {
|
|
222
|
-
command: packageArtifactForBinWrapper(sibling) ?? sibling,
|
|
223
|
-
source: 'sibling',
|
|
224
|
-
absolute: true,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const pathResolved = resolveCommandFromPath('goodvibes-daemon', env.PATH);
|
|
229
|
-
if (pathResolved) {
|
|
230
|
-
return {
|
|
231
|
-
command: packageArtifactForBinWrapper(pathResolved) ?? pathResolved,
|
|
232
|
-
source: 'path',
|
|
233
|
-
absolute: true,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return { command: 'goodvibes-daemon', source: 'fallback', absolute: false };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export function getServiceStateRoot(runtime: CliServiceRuntime): string {
|
|
241
|
-
return join(runtime.homeDirectory, '.goodvibes', 'daemon');
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function pidFilePath(runtime: CliServiceRuntime, platform: ManagedServiceStatus['platform']): string {
|
|
245
|
-
return join(getServiceStateRoot(runtime), 'service', `${platform}.pid`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function runStatusCommand(
|
|
249
|
-
command: string,
|
|
250
|
-
args: readonly string[],
|
|
251
|
-
options: CliServicePostureOptions,
|
|
252
|
-
): CliServiceStatusCommandResult {
|
|
253
|
-
if (options.runCommand) return options.runCommand(command, args);
|
|
254
|
-
return spawnSync(command, [...args], {
|
|
255
|
-
stdio: 'pipe',
|
|
256
|
-
encoding: 'utf-8',
|
|
257
|
-
timeout: 1500,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function parseSystemdShowValue(lines: readonly string[], key: string): string | null {
|
|
262
|
-
const prefix = `${key}=`;
|
|
263
|
-
const match = lines.find((line) => line.startsWith(prefix));
|
|
264
|
-
return match ? match.slice(prefix.length).trim() : null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function reconcileSystemdServiceStatus(
|
|
268
|
-
runtime: CliServiceRuntime,
|
|
269
|
-
status: ManagedServiceStatus,
|
|
270
|
-
options: CliServicePostureOptions,
|
|
271
|
-
): ManagedServiceStatus {
|
|
272
|
-
if (status.platform !== 'systemd') return status;
|
|
273
|
-
|
|
274
|
-
const name = String(runtime.configManager.get('service.serviceName') ?? 'goodvibes').trim() || 'goodvibes';
|
|
275
|
-
const result = runStatusCommand('systemctl', [
|
|
276
|
-
'--user',
|
|
277
|
-
'show',
|
|
278
|
-
`${name}.service`,
|
|
279
|
-
'--property=LoadState,ActiveState,UnitFileState,MainPID',
|
|
280
|
-
'--no-page',
|
|
281
|
-
], options);
|
|
282
|
-
if ((result.status ?? 1) !== 0) return status;
|
|
283
|
-
|
|
284
|
-
const lines = (result.stdout ?? '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
285
|
-
const loadState = parseSystemdShowValue(lines, 'LoadState');
|
|
286
|
-
const activeState = parseSystemdShowValue(lines, 'ActiveState');
|
|
287
|
-
const unitFileState = parseSystemdShowValue(lines, 'UnitFileState');
|
|
288
|
-
const rawPid = Number.parseInt(parseSystemdShowValue(lines, 'MainPID') ?? '', 10);
|
|
289
|
-
const pid = Number.isFinite(rawPid) && rawPid > 0 ? rawPid : undefined;
|
|
290
|
-
|
|
291
|
-
return {
|
|
292
|
-
...status,
|
|
293
|
-
installed: loadState === 'loaded' || status.installed,
|
|
294
|
-
autostart: unitFileState === 'enabled' || unitFileState === 'linked' || status.autostart,
|
|
295
|
-
running: activeState === 'active',
|
|
296
|
-
...(pid === undefined ? {} : { pid }),
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
94
|
function readLogPosture(path: string | undefined, tailBytes: number): CliServiceLogPosture {
|
|
301
95
|
if (!path) return { path: null, exists: false, size: 0, modifiedAt: null };
|
|
302
96
|
if (!existsSync(path)) return { path, exists: false, size: 0, modifiedAt: null };
|
|
@@ -338,41 +132,34 @@ function endpointsConflict(a: CliServiceEndpointPosture, b: CliServiceEndpointPo
|
|
|
338
132
|
return hostA === hostB || hostA === '0.0.0.0' || hostB === '0.0.0.0' || hostA === '::' || hostB === '::';
|
|
339
133
|
}
|
|
340
134
|
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
135
|
+
function resolveConfiguredLogPath(runtime: CliServiceRuntime): string | undefined {
|
|
136
|
+
const value = runtime.configManager.get('service.logPath');
|
|
137
|
+
if (typeof value !== 'string') return undefined;
|
|
138
|
+
const trimmed = value.trim();
|
|
139
|
+
if (!trimmed) return undefined;
|
|
140
|
+
return isAbsolute(trimmed) ? trimmed : join(runtime.homeDirectory, trimmed);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function createExternalDaemonLifecycle(logPath: string | undefined): CliExternalDaemonLifecyclePosture {
|
|
144
|
+
return {
|
|
145
|
+
platform: 'manual',
|
|
146
|
+
path: 'external daemon host',
|
|
147
|
+
installed: false,
|
|
148
|
+
autostart: false,
|
|
149
|
+
running: false,
|
|
150
|
+
...(logPath ? { logPath } : {}),
|
|
151
|
+
commandPreview: 'managed outside goodvibes-agent',
|
|
152
|
+
suggestedCommands: [],
|
|
153
|
+
lastAction: 'status',
|
|
154
|
+
pidPath: 'external daemon host',
|
|
155
|
+
lastError: null,
|
|
359
156
|
};
|
|
360
|
-
return new PlatformServiceManager(runtime.configManager, {
|
|
361
|
-
workingDirectory: runtime.homeDirectory,
|
|
362
|
-
homeDirectory: runtime.homeDirectory,
|
|
363
|
-
surfaceRoot: 'daemon',
|
|
364
|
-
definitionOverride: definition,
|
|
365
|
-
defaultServiceName: 'goodvibes',
|
|
366
|
-
defaultServiceDescription: 'GoodVibes daemon, control-plane, listener, and web host',
|
|
367
|
-
});
|
|
368
157
|
}
|
|
369
158
|
|
|
370
159
|
export async function buildCliServicePosture(
|
|
371
160
|
runtime: CliServiceRuntime,
|
|
372
161
|
options: CliServicePostureOptions = {},
|
|
373
162
|
): Promise<CliServicePosture> {
|
|
374
|
-
const manager = options.manager ?? createPlatformServiceManager(runtime);
|
|
375
|
-
const status = reconcileSystemdServiceStatus(runtime, manager.status(), options);
|
|
376
163
|
const endpoints = await Promise.all(ENDPOINTS.map(async (endpoint): Promise<CliServiceEndpointPosture> => {
|
|
377
164
|
const enabled = runtime.configManager.get(endpoint.enabledKey as never) === true;
|
|
378
165
|
const binding = resolveRuntimeEndpointBinding(runtime.configManager, endpoint.id);
|
|
@@ -405,15 +192,6 @@ export async function buildCliServicePosture(
|
|
|
405
192
|
if (config.enabled && !config.restartOnFailure) {
|
|
406
193
|
issues.push('External daemon service config has restart-on-failure off.');
|
|
407
194
|
}
|
|
408
|
-
if (config.enabled && !status.installed) {
|
|
409
|
-
issues.push('External daemon service config is enabled, but no platform service definition is installed.');
|
|
410
|
-
}
|
|
411
|
-
if (config.enabled && !status.running) {
|
|
412
|
-
issues.push('External daemon service config is enabled, but the managed service is not running.');
|
|
413
|
-
}
|
|
414
|
-
if (status.actionError) {
|
|
415
|
-
issues.push(`Service manager reported an error: ${status.actionError}`);
|
|
416
|
-
}
|
|
417
195
|
for (const endpoint of endpoints) {
|
|
418
196
|
if (endpoint.enabled && options.probe && endpoint.reachable === false) {
|
|
419
197
|
issues.push(`${endpoint.label} is enabled but not reachable on ${endpoint.binding.host}:${endpoint.binding.port}.`);
|
|
@@ -429,18 +207,15 @@ export async function buildCliServicePosture(
|
|
|
429
207
|
}
|
|
430
208
|
}
|
|
431
209
|
}
|
|
432
|
-
const
|
|
210
|
+
const configuredLogPath = resolveConfiguredLogPath(runtime);
|
|
211
|
+
const log = readLogPosture(configuredLogPath, options.logTailBytes ?? 4096);
|
|
433
212
|
if (log.readError) {
|
|
434
213
|
issues.push(`Service log exists but could not be read: ${log.readError}`);
|
|
435
214
|
}
|
|
436
215
|
|
|
437
216
|
return {
|
|
438
217
|
config,
|
|
439
|
-
managed:
|
|
440
|
-
...status,
|
|
441
|
-
pidPath: pidFilePath(runtime, status.platform),
|
|
442
|
-
lastError: status.actionError ?? null,
|
|
443
|
-
},
|
|
218
|
+
managed: createExternalDaemonLifecycle(configuredLogPath),
|
|
444
219
|
endpoints,
|
|
445
220
|
log,
|
|
446
221
|
issues,
|
|
@@ -454,22 +229,14 @@ function yesNo(value: boolean): string {
|
|
|
454
229
|
export function formatCliServicePosture(posture: CliServicePosture, json = false): string {
|
|
455
230
|
if (json) return JSON.stringify(posture, null, 2);
|
|
456
231
|
return [
|
|
457
|
-
'GoodVibes external daemon
|
|
458
|
-
|
|
459
|
-
`
|
|
460
|
-
`
|
|
232
|
+
'GoodVibes external daemon diagnostics',
|
|
233
|
+
' lifecycle: managed outside goodvibes-agent',
|
|
234
|
+
` service config enabled: ${yesNo(posture.config.enabled)}`,
|
|
235
|
+
` autostart config: ${yesNo(posture.config.autostart)}`,
|
|
236
|
+
` restartOnFailure config: ${yesNo(posture.config.restartOnFailure)}`,
|
|
461
237
|
` daemon flag: ${yesNo(posture.config.daemonEnabled)}`,
|
|
462
|
-
'',
|
|
463
|
-
'Managed service:',
|
|
464
|
-
` platform: ${posture.managed.platform}`,
|
|
465
|
-
` installed: ${yesNo(posture.managed.installed)}`,
|
|
466
|
-
` running: ${yesNo(posture.managed.running)}`,
|
|
467
|
-
` pid: ${posture.managed.pid ?? 'n/a'}`,
|
|
468
|
-
` definition: ${posture.managed.path}`,
|
|
469
|
-
` pid file: ${posture.managed.pidPath}`,
|
|
470
238
|
` log: ${posture.log.path ?? 'n/a'} (${posture.log.exists ? 'present' : 'missing'})`,
|
|
471
239
|
...(posture.log.readError ? [` log read error: ${posture.log.readError}`] : []),
|
|
472
|
-
` command: ${posture.managed.commandPreview}`,
|
|
473
240
|
'',
|
|
474
241
|
'Endpoints:',
|
|
475
242
|
...posture.endpoints.map((endpoint) =>
|
package/src/cli/status.ts
CHANGED
|
@@ -334,7 +334,6 @@ export function renderCliStatus(options: CliStatusOptions): string {
|
|
|
334
334
|
` platform: ${options.service.managed.platform}`,
|
|
335
335
|
` installed: ${yesNo(options.service.managed.installed)}`,
|
|
336
336
|
` running: ${yesNo(options.service.managed.running)}`,
|
|
337
|
-
` pid: ${options.service.managed.pid ?? 'n/a'}`,
|
|
338
337
|
` definition: ${options.service.managed.path}`,
|
|
339
338
|
` log: ${options.service.log.path ?? 'n/a'} (${options.service.log.exists ? 'present' : 'missing'})`,
|
|
340
339
|
] : []),
|
package/src/cli/tui-startup.ts
CHANGED
|
@@ -3,6 +3,29 @@ import type { InputHandler } from '../input/handler.ts';
|
|
|
3
3
|
import { readOnboardingCheckMarker } from '../runtime/onboarding/index.ts';
|
|
4
4
|
import type { GoodVibesCliParseResult } from './types.ts';
|
|
5
5
|
|
|
6
|
+
export type InteractiveTerminalCheckInput = {
|
|
7
|
+
readonly binary: string;
|
|
8
|
+
readonly stdinIsTTY: boolean | undefined;
|
|
9
|
+
readonly stdoutIsTTY: boolean | undefined;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function getInteractiveTerminalLaunchError(input: InteractiveTerminalCheckInput): string | null {
|
|
13
|
+
const stdinReady = input.stdinIsTTY === true;
|
|
14
|
+
const stdoutReady = input.stdoutIsTTY === true;
|
|
15
|
+
if (stdinReady && stdoutReady) return null;
|
|
16
|
+
|
|
17
|
+
const missing = [
|
|
18
|
+
stdinReady ? null : 'stdin',
|
|
19
|
+
stdoutReady ? null : 'stdout',
|
|
20
|
+
].filter((entry): entry is string => entry !== null);
|
|
21
|
+
const missingLabel = missing.length === 1 ? `${missing[0]} is` : `${missing.join(' and ')} are`;
|
|
22
|
+
|
|
23
|
+
return [
|
|
24
|
+
`${input.binary} requires an interactive terminal for the TUI (${missingLabel} not a TTY).`,
|
|
25
|
+
`Run it from a terminal, or use non-interactive commands such as '${input.binary} --help', '${input.binary} status --json', or '${input.binary} run --print "<prompt>"'.`,
|
|
26
|
+
].join('\n');
|
|
27
|
+
}
|
|
28
|
+
|
|
6
29
|
export function applyInitialTuiCliState(options: {
|
|
7
30
|
readonly cli: GoodVibesCliParseResult;
|
|
8
31
|
readonly input: InputHandler;
|
|
@@ -9,8 +9,6 @@ export const SECRET_CONFIG_KEYS = new Set<ConfigKey>([
|
|
|
9
9
|
'surfaces.discord.botToken',
|
|
10
10
|
'surfaces.ntfy.token',
|
|
11
11
|
'surfaces.webhook.secret',
|
|
12
|
-
'surfaces.homeassistant.accessToken',
|
|
13
|
-
'surfaces.homeassistant.webhookSecret',
|
|
14
12
|
'surfaces.telegram.botToken',
|
|
15
13
|
'surfaces.telegram.webhookSecret',
|
|
16
14
|
'surfaces.googleChat.verificationToken',
|