@phnx-labs/agents-cli 1.22.47 → 1.22.50
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 +503 -17
- package/README.md +76 -32
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +201 -39
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/browser.js +501 -103
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +0 -20
- package/dist/commands/daemon-test-harness.d.ts +42 -0
- package/dist/commands/daemon-test-harness.js +90 -0
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/device-config-test-harness.d.ts +11 -0
- package/dist/commands/device-config-test-harness.js +59 -0
- package/dist/commands/exec.js +76 -16
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +4 -3
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +3 -2
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-trace.js +1 -1
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +158 -53
- package/dist/commands/ssh.js +54 -7
- package/dist/commands/teams.d.ts +12 -0
- package/dist/commands/teams.js +44 -67
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +188 -0
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.js +17 -34
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/account-provider-registry.d.ts +9 -0
- package/dist/lib/account-provider-registry.js +20 -0
- package/dist/lib/accounting/account-pool-collect.d.ts +34 -0
- package/dist/lib/accounting/account-pool-collect.js +71 -0
- package/dist/lib/accounting/account-pool.d.ts +46 -0
- package/dist/lib/accounting/account-pool.js +33 -0
- package/dist/lib/accounting/capacity.d.ts +24 -0
- package/dist/lib/accounting/capacity.js +30 -0
- package/dist/lib/accounting/rotate.d.ts +11 -18
- package/dist/lib/accounting/rotate.js +6 -24
- package/dist/lib/accounting/usage.d.ts +14 -3
- package/dist/lib/accounting/usage.js +64 -18
- package/dist/lib/agent-spec/agents.js +11 -9
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.js +8 -3
- package/dist/lib/browser/profiles.d.ts +35 -101
- package/dist/lib/browser/profiles.js +130 -301
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +8 -6
- package/dist/lib/browser/remote-control.js +8 -6
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +40 -16
- package/dist/lib/browser/runtime-state.js +92 -25
- package/dist/lib/browser/service.d.ts +28 -9
- package/dist/lib/browser/service.js +139 -62
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +2 -8
- package/dist/lib/config-keys.js +2 -13
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +20 -18
- package/dist/lib/device-config.d.ts +0 -2
- package/dist/lib/device-config.js +1 -23
- package/dist/lib/devices/registry.d.ts +1 -1
- package/dist/lib/devices/registry.js +1 -1
- package/dist/lib/devices/stats-cache.d.ts +20 -0
- package/dist/lib/devices/stats-cache.js +49 -5
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +67 -13
- package/dist/lib/exec.js +85 -29
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +11 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +19 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/store.js +6 -3
- package/dist/lib/installations/versions.js +73 -48
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +31 -7
- package/dist/lib/menubar/install-menubar.js +79 -21
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.js +17 -1
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +138 -6
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +33 -5
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.js +11 -2
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +88 -0
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-html.d.ts +5 -1
- package/dist/lib/session/trajectory-html.js +42 -22
- package/dist/lib/session/trajectory-text.js +9 -2
- package/dist/lib/session/trajectory.js +53 -4
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +44 -15
- package/dist/lib/share/publish.js +58 -26
- package/dist/lib/share/worker-template.js +402 -25
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.js +1 -1
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +7 -3
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/teams/agents.d.ts +14 -12
- package/dist/lib/teams/agents.js +188 -73
- package/dist/lib/teams/api.d.ts +6 -1
- package/dist/lib/teams/api.js +2 -0
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/teams/scheduler.js +26 -8
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.js +31 -0
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +459 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +18 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-refresh.d.ts +1 -1
- package/dist/lib/usage-refresh.js +11 -8
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +4 -6
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -26
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
package/dist/commands/config.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* - run defaults (model, mode, effort)
|
|
6
6
|
* - tier overrides (folded into the run namespace)
|
|
7
7
|
* - interactive host
|
|
8
|
-
* - usage primary host
|
|
9
8
|
* - browser profile
|
|
10
9
|
* - local projects root
|
|
11
10
|
* - per-device config keys
|
|
@@ -40,8 +39,6 @@ function parseValue(key, parsed, raw) {
|
|
|
40
39
|
return raw.trim();
|
|
41
40
|
case 'interactive':
|
|
42
41
|
return raw.trim();
|
|
43
|
-
case 'usage':
|
|
44
|
-
return raw.trim();
|
|
45
42
|
case 'auto':
|
|
46
43
|
return raw.trim();
|
|
47
44
|
case 'browser':
|
|
@@ -109,10 +106,6 @@ function setConfig(parsed, value) {
|
|
|
109
106
|
setConfigValue('interactive.host', value);
|
|
110
107
|
return;
|
|
111
108
|
}
|
|
112
|
-
case 'usage': {
|
|
113
|
-
setConfigValue('usage.primary-host', value);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
109
|
case 'auto': {
|
|
117
110
|
setConfigValue('auto.pool', value);
|
|
118
111
|
return;
|
|
@@ -156,11 +149,6 @@ function unsetConfig(parsed) {
|
|
|
156
149
|
unsetConfigValue('interactive.host');
|
|
157
150
|
return had;
|
|
158
151
|
}
|
|
159
|
-
case 'usage': {
|
|
160
|
-
const had = getConfigValue('usage.primary-host').value !== undefined;
|
|
161
|
-
unsetConfigValue('usage.primary-host');
|
|
162
|
-
return had;
|
|
163
|
-
}
|
|
164
152
|
case 'auto': {
|
|
165
153
|
const had = getConfigValue('auto.pool').value !== undefined;
|
|
166
154
|
unsetConfigValue('auto.pool');
|
|
@@ -204,8 +192,6 @@ function getConfig(parsed) {
|
|
|
204
192
|
}
|
|
205
193
|
case 'interactive':
|
|
206
194
|
return getConfigValue('interactive.host').value;
|
|
207
|
-
case 'usage':
|
|
208
|
-
return getConfigValue('usage.primary-host').value;
|
|
209
195
|
case 'auto':
|
|
210
196
|
return getConfigValue('auto.pool').value;
|
|
211
197
|
case 'browser': {
|
|
@@ -263,9 +249,6 @@ function* listCentralConfigEntries() {
|
|
|
263
249
|
if (meta.config?.interactiveHost !== undefined) {
|
|
264
250
|
yield { key: 'interactive.host', value: meta.config.interactiveHost, hint: 'config.interactiveHost' };
|
|
265
251
|
}
|
|
266
|
-
if (meta.config?.usagePrimaryHost !== undefined) {
|
|
267
|
-
yield { key: 'usage.primary-host', value: meta.config.usagePrimaryHost, hint: 'config.usagePrimaryHost' };
|
|
268
|
-
}
|
|
269
252
|
if (meta.config?.autoPool !== undefined) {
|
|
270
253
|
yield { key: 'auto.pool', value: meta.config.autoPool, hint: 'config.autoPool' };
|
|
271
254
|
}
|
|
@@ -353,14 +336,11 @@ export function registerConfigCommand(program) {
|
|
|
353
336
|
agents config set run.claude@2.1.45.model claude-opus-4-8
|
|
354
337
|
agents config set run.claude@*.mode auto
|
|
355
338
|
agents config set interactive.host zion
|
|
356
|
-
agents config set usage.primary-host zion
|
|
357
339
|
agents config set browser.profile work
|
|
358
340
|
agents config set project.root ~/src/github.com/<you>
|
|
359
341
|
agents config set devices.mac-mini.max-agents 4
|
|
360
342
|
agents config get run.claude@*.model
|
|
361
|
-
agents config get usage.primary-host
|
|
362
343
|
agents config unset run.claude@*.tier.best
|
|
363
|
-
agents config unset usage.primary-host
|
|
364
344
|
agents config list
|
|
365
345
|
agents config budget
|
|
366
346
|
agents config budget set per_day 50
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared harness for the `agents daemon` CLI subprocess suites (RUSH-2354).
|
|
3
|
+
*
|
|
4
|
+
* Every test spawns the real CLI against an isolated mkdtemp HOME with no
|
|
5
|
+
* daemon running — no mocks. Modeled on routines.test.ts.
|
|
6
|
+
*
|
|
7
|
+
* EXTRACTED so the suite can live in several files. `daemon.test.ts` was 35
|
|
8
|
+
* tests in ONE file at 159s — the slowest file in the repo and therefore the
|
|
9
|
+
* SUITE'S FLOOR, because vitest parallelises across files and runs the tests
|
|
10
|
+
* inside one file sequentially in a single worker. Splitting the tests across
|
|
11
|
+
* files lets them run concurrently; sharing the harness is what makes that
|
|
12
|
+
* possible without duplicating the spawn plumbing.
|
|
13
|
+
*/
|
|
14
|
+
import { spawnSync, type ChildProcess } from 'child_process';
|
|
15
|
+
export declare const REPO_ROOT: string;
|
|
16
|
+
export declare const TSX_IMPORT: string;
|
|
17
|
+
export declare const CLI_ENTRYPOINT: string;
|
|
18
|
+
/**
|
|
19
|
+
* win32: subprocess CLI + process-group signals / path spawn assumptions
|
|
20
|
+
* (RUSH-2215). Exported as a PREDICATE, not as a pre-bound `describe.skip` —
|
|
21
|
+
* vitest's suite type is not nameable across a module boundary (TS4023), so
|
|
22
|
+
* each suite builds its own `describe` from this.
|
|
23
|
+
*/
|
|
24
|
+
export declare const DAEMON_TESTS_SUPPORTED: boolean;
|
|
25
|
+
/** Provision an isolated HOME with just enough scaffolding for the CLI to boot. */
|
|
26
|
+
export declare function makeHome(): string;
|
|
27
|
+
/** Run `agents daemon <args>` against an isolated HOME — no daemon process ever started. */
|
|
28
|
+
export declare function run(home: string, args: string[]): ReturnType<typeof spawnSync>;
|
|
29
|
+
/**
|
|
30
|
+
* Spawn a real, long-lived process whose command line ends in `__daemon-run`
|
|
31
|
+
* (so `isDaemonRunProcess`'s `ps` check accepts it — see
|
|
32
|
+
* `lib/daemon.test.ts`'s "reaps a live __daemon-run registrant" test, same
|
|
33
|
+
* technique) and register it in `home`'s OWN instance registry, exactly the
|
|
34
|
+
* marker `registerDaemonInstance` would write. A real live process, not a
|
|
35
|
+
* mock — `agents daemon status` reads it through the actual registry +
|
|
36
|
+
* `ps`-liveness path, the same one the reaper and `stopDaemon`'s postcondition
|
|
37
|
+
* use.
|
|
38
|
+
*/
|
|
39
|
+
export declare function spawnFakeRegisteredDaemon(home: string): Promise<ChildProcess>;
|
|
40
|
+
/** Register a pid in `home`'s instance registry — the scope stale/duplicate reporting uses. */
|
|
41
|
+
export declare function registerInstance(home: string, pid: number): void;
|
|
42
|
+
export declare function killFakeDaemon(child: ChildProcess): void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared harness for the `agents daemon` CLI subprocess suites (RUSH-2354).
|
|
3
|
+
*
|
|
4
|
+
* Every test spawns the real CLI against an isolated mkdtemp HOME with no
|
|
5
|
+
* daemon running — no mocks. Modeled on routines.test.ts.
|
|
6
|
+
*
|
|
7
|
+
* EXTRACTED so the suite can live in several files. `daemon.test.ts` was 35
|
|
8
|
+
* tests in ONE file at 159s — the slowest file in the repo and therefore the
|
|
9
|
+
* SUITE'S FLOOR, because vitest parallelises across files and runs the tests
|
|
10
|
+
* inside one file sequentially in a single worker. Splitting the tests across
|
|
11
|
+
* files lets them run concurrently; sharing the harness is what makes that
|
|
12
|
+
* possible without duplicating the spawn plumbing.
|
|
13
|
+
*/
|
|
14
|
+
import { spawnSync, spawn } from 'child_process';
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as os from 'os';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
19
|
+
import { createRequire } from 'module';
|
|
20
|
+
export const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
export const TSX_IMPORT = pathToFileURL(require.resolve('tsx')).href;
|
|
23
|
+
export const CLI_ENTRYPOINT = path.join(REPO_ROOT, 'src', 'index.ts');
|
|
24
|
+
/**
|
|
25
|
+
* win32: subprocess CLI + process-group signals / path spawn assumptions
|
|
26
|
+
* (RUSH-2215). Exported as a PREDICATE, not as a pre-bound `describe.skip` —
|
|
27
|
+
* vitest's suite type is not nameable across a module boundary (TS4023), so
|
|
28
|
+
* each suite builds its own `describe` from this.
|
|
29
|
+
*/
|
|
30
|
+
export const DAEMON_TESTS_SUPPORTED = process.platform !== 'win32';
|
|
31
|
+
/** Provision an isolated HOME with just enough scaffolding for the CLI to boot. */
|
|
32
|
+
export function makeHome() {
|
|
33
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'agents-daemon-test-'));
|
|
34
|
+
fs.mkdirSync(path.join(home, '.agents', '.system', '.git'), { recursive: true });
|
|
35
|
+
fs.writeFileSync(path.join(home, '.agents', 'agents.yaml'), 'agents: {}\n');
|
|
36
|
+
return home;
|
|
37
|
+
}
|
|
38
|
+
/** Run `agents daemon <args>` against an isolated HOME — no daemon process ever started. */
|
|
39
|
+
export function run(home, args) {
|
|
40
|
+
return spawnSync('node', ['--import', TSX_IMPORT, CLI_ENTRYPOINT, 'daemon', ...args], {
|
|
41
|
+
cwd: REPO_ROOT,
|
|
42
|
+
env: {
|
|
43
|
+
...process.env,
|
|
44
|
+
HOME: home,
|
|
45
|
+
USERPROFILE: home,
|
|
46
|
+
AGENTS_SKIP_MIGRATION: '1',
|
|
47
|
+
AGENTS_NO_AUTOPULL: '1',
|
|
48
|
+
AGENTS_CLI_DISABLE_AUTO_UPDATE: '1',
|
|
49
|
+
AGENTS_DAEMON_DIR: path.join(home, '.agents', '.cache', 'helpers', 'daemon'),
|
|
50
|
+
},
|
|
51
|
+
encoding: 'utf-8',
|
|
52
|
+
timeout: 30_000,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Spawn a real, long-lived process whose command line ends in `__daemon-run`
|
|
57
|
+
* (so `isDaemonRunProcess`'s `ps` check accepts it — see
|
|
58
|
+
* `lib/daemon.test.ts`'s "reaps a live __daemon-run registrant" test, same
|
|
59
|
+
* technique) and register it in `home`'s OWN instance registry, exactly the
|
|
60
|
+
* marker `registerDaemonInstance` would write. A real live process, not a
|
|
61
|
+
* mock — `agents daemon status` reads it through the actual registry +
|
|
62
|
+
* `ps`-liveness path, the same one the reaper and `stopDaemon`'s postcondition
|
|
63
|
+
* use.
|
|
64
|
+
*/
|
|
65
|
+
export async function spawnFakeRegisteredDaemon(home) {
|
|
66
|
+
const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1e9)', '__daemon-run'], {
|
|
67
|
+
stdio: 'ignore',
|
|
68
|
+
});
|
|
69
|
+
// Give the exec a moment to land before `ps` (read by the status command's
|
|
70
|
+
// isDaemonRunProcess check) is asked to see its real argv — mirrors
|
|
71
|
+
// lib/daemon.test.ts's identical fake-daemon technique.
|
|
72
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
73
|
+
const instancesDir = path.join(home, '.agents', '.cache', 'helpers', 'daemon', 'instances');
|
|
74
|
+
fs.mkdirSync(instancesDir, { recursive: true });
|
|
75
|
+
fs.writeFileSync(path.join(instancesDir, String(child.pid)), '__daemon-run', 'utf-8');
|
|
76
|
+
return child;
|
|
77
|
+
}
|
|
78
|
+
/** Register a pid in `home`'s instance registry — the scope stale/duplicate reporting uses. */
|
|
79
|
+
export function registerInstance(home, pid) {
|
|
80
|
+
const dir = path.join(home, '.agents', '.cache', 'helpers', 'daemon', 'instances');
|
|
81
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
82
|
+
fs.writeFileSync(path.join(dir, String(pid)), '__daemon-run', 'utf-8');
|
|
83
|
+
}
|
|
84
|
+
export function killFakeDaemon(child) {
|
|
85
|
+
try {
|
|
86
|
+
if (child.pid)
|
|
87
|
+
process.kill(child.pid, 'SIGKILL');
|
|
88
|
+
}
|
|
89
|
+
catch { /* already gone */ }
|
|
90
|
+
}
|
|
@@ -15,4 +15,25 @@
|
|
|
15
15
|
* duplicating it.
|
|
16
16
|
*/
|
|
17
17
|
import type { Command } from 'commander';
|
|
18
|
+
import { type DaemonServiceId } from '../lib/daemon-services.js';
|
|
19
|
+
/** One row of the roster every registered daemon service — supervisor-managed or legacy — renders in `agents daemon services`. */
|
|
20
|
+
export interface DaemonServiceRow {
|
|
21
|
+
id: DaemonServiceId;
|
|
22
|
+
title: string;
|
|
23
|
+
description: string;
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* `ServiceSupervisor`'s real lifecycle state when `supervised` is true
|
|
27
|
+
* (`idle`/`running`/`parked`/`stopped`, written cross-process via
|
|
28
|
+
* `recordSubsystemState`). A legacy `setInterval`-driven service has no such
|
|
29
|
+
* record, so its state is inferred from `enabled` + whether the daemon
|
|
30
|
+
* process is up — labelled distinctly so a reader can't mistake it for a
|
|
31
|
+
* measured value.
|
|
32
|
+
*/
|
|
33
|
+
state: string;
|
|
34
|
+
supervised: boolean;
|
|
35
|
+
lastRunMs: number | null;
|
|
36
|
+
lastError: string | null;
|
|
37
|
+
consecutiveFailures: number;
|
|
38
|
+
}
|
|
18
39
|
export declare function registerDaemonCommand(program: Command): void;
|
package/dist/commands/daemon.js
CHANGED
|
@@ -21,8 +21,8 @@ import * as path from 'path';
|
|
|
21
21
|
import { setHelpSections } from '../lib/help.js';
|
|
22
22
|
import { getDaemonStatus, isDaemonRunning, isDaemonWedged, readDaemonLog, startDaemon, stopDaemon, signalDaemonReload, findSurvivingStateDirDaemons, getDaemonLogPath, isDaemonAutostartCircuitOpen, } from '../lib/daemon/daemon.js';
|
|
23
23
|
import { getConfigValue, setConfigValue, isDaemonEnabled } from '../lib/device-config.js';
|
|
24
|
-
import { readSubsystemHealth, SUBSYSTEM_SECRETS_BROKER, SUBSYSTEM_BROWSER_IPC, SUBSYSTEM_DAEMON_START, } from '../lib/daemon-health.js';
|
|
25
|
-
import { DAEMON_SERVICE_IDS, listDaemonServiceStates, setDaemonServiceEnabled, getDaemonServicesConfigPath, } from '../lib/daemon-services.js';
|
|
24
|
+
import { readSubsystemHealth, readAllSubsystemHealth, SUBSYSTEM_SECRETS_BROKER, SUBSYSTEM_BROWSER_IPC, SUBSYSTEM_DAEMON_START, } from '../lib/daemon-health.js';
|
|
25
|
+
import { DAEMON_SERVICE_IDS, listDaemonServiceStates, setDaemonServiceEnabled, getDaemonServicesConfigPath, queueDaemonServiceRestart, } from '../lib/daemon-services.js';
|
|
26
26
|
import { listJobs, getLatestRun } from '../lib/scheduling/routines.js';
|
|
27
27
|
import { JobScheduler } from '../lib/scheduler.js';
|
|
28
28
|
import { followFile } from '../lib/log-follow.js';
|
|
@@ -474,19 +474,76 @@ async function runStatus(opts) {
|
|
|
474
474
|
console.log(chalk.red('\nThe daemon is wedged (heartbeat stale). Restart: agents daemon restart'));
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Combine the persisted enable/disable toggles with whatever health the
|
|
479
|
+
* `ServiceSupervisor` (or a legacy subsystem) has reported. Pure — reads two
|
|
480
|
+
* on-disk files, makes no probes — so it's cheap to call from both the
|
|
481
|
+
* non-interactive renderer and the interactive picker's refresh tick.
|
|
482
|
+
*/
|
|
483
|
+
function buildServiceRows(daemonRunning) {
|
|
484
|
+
const states = listDaemonServiceStates();
|
|
485
|
+
const healthById = new Map(readAllSubsystemHealth().map((h) => [h.subsystem, h]));
|
|
486
|
+
return states.map((s) => {
|
|
487
|
+
const h = healthById.get(s.id);
|
|
488
|
+
const supervised = h?.state !== undefined;
|
|
489
|
+
// A persisted supervisor state (health.json) is only trustworthy while the
|
|
490
|
+
// daemon is actually up. If it is not running (crash / kill -9 / never
|
|
491
|
+
// started), every service is stopped no matter what the last-written record
|
|
492
|
+
// says — trusting a stale 'running'/'parked'/'idle' here would print a
|
|
493
|
+
// report that contradicts the live-probed socket rows below (RUSH-2368).
|
|
494
|
+
const state = daemonRunning
|
|
495
|
+
? (h?.state ?? (s.enabled ? 'running (unsupervised)' : 'stopped'))
|
|
496
|
+
: 'stopped';
|
|
497
|
+
return {
|
|
498
|
+
id: s.id,
|
|
499
|
+
title: s.title,
|
|
500
|
+
description: s.description,
|
|
501
|
+
enabled: s.enabled,
|
|
502
|
+
state,
|
|
503
|
+
supervised,
|
|
504
|
+
lastRunMs: h?.lastOkAt ? Date.parse(h.lastOkAt) : null,
|
|
505
|
+
lastError: h?.lastError ?? null,
|
|
506
|
+
consecutiveFailures: h?.consecutiveFailures ?? 0,
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function serviceStateLabel(state) {
|
|
511
|
+
if (state === 'running')
|
|
512
|
+
return chalk.green('running');
|
|
513
|
+
if (state === 'parked')
|
|
514
|
+
return chalk.red('parked');
|
|
515
|
+
if (state === 'stopped')
|
|
516
|
+
return chalk.gray('stopped');
|
|
517
|
+
if (state === 'idle')
|
|
518
|
+
return chalk.gray('idle');
|
|
519
|
+
return chalk.yellow(state); // 'running (unsupervised)' or any future label
|
|
520
|
+
}
|
|
477
521
|
async function runServices(opts) {
|
|
478
522
|
const [secrets, browserIpc] = await Promise.all([probeSecretsBroker(), probeBrowserIPC()]);
|
|
523
|
+
const rows = buildServiceRows(isDaemonRunning());
|
|
479
524
|
if (opts.json) {
|
|
480
525
|
console.log(JSON.stringify({
|
|
526
|
+
// Existing fields — unchanged shape, agents/CI consume these directly.
|
|
481
527
|
secretsBroker: { reachable: secrets.reachable, socketPath: secrets.socketPath, heldBundles: secrets.heldBundles, health: secrets.record },
|
|
482
528
|
browserIpc: { bound: browserIpc.bound, socketPath: browserIpc.socketPath, sessionCount: browserIpc.sessionCount, health: browserIpc.record },
|
|
529
|
+
// Additive: every registered service, supervised or legacy.
|
|
530
|
+
services: rows,
|
|
483
531
|
}, null, 2));
|
|
484
532
|
return;
|
|
485
533
|
}
|
|
486
|
-
console.log(chalk.bold('
|
|
534
|
+
console.log(chalk.bold('Daemon services\n'));
|
|
535
|
+
for (const row of rows) {
|
|
536
|
+
const lastRun = row.lastRunMs ? new Date(row.lastRunMs).toLocaleString() : '-';
|
|
537
|
+
console.log(` ${row.id.padEnd(16)} ${serviceStateLabel(row.state).padEnd(20)} `
|
|
538
|
+
+ `enabled=${row.enabled ? 'yes' : 'no '} fails=${row.consecutiveFailures} last-run=${lastRun}`);
|
|
539
|
+
if (row.lastError)
|
|
540
|
+
console.log(chalk.gray(` last-error: ${row.lastError}`));
|
|
541
|
+
}
|
|
542
|
+
console.log(chalk.bold('\nHosted sockets\n'));
|
|
487
543
|
console.log(healthLine(`secrets broker ${secrets.reachable ? `(${secrets.socketPath}, ${secrets.heldBundles} bundle(s) held)` : '(unreachable)'}`, secrets.reachable, secrets.record));
|
|
488
544
|
console.log(healthLine(`browser IPC ${browserIpc.bound ? `(${browserIpc.socketPath}, ${browserIpc.sessionCount} session(s))` : '(unbound)'}`, browserIpc.bound, browserIpc.record));
|
|
489
545
|
console.log(chalk.gray('\nScheduled routines run through `agents routines` — see: agents routines stats'));
|
|
546
|
+
console.log(chalk.gray('agents daemon services enable|disable|restart <id> apply live for supervised services.'));
|
|
490
547
|
}
|
|
491
548
|
function parseLogLines(raw) {
|
|
492
549
|
const out = [];
|
|
@@ -795,9 +852,17 @@ export function registerDaemonCommand(program) {
|
|
|
795
852
|
# Reload config (SIGHUP) without restarting — picks up routine/scheduler-gate changes
|
|
796
853
|
agents daemon reload
|
|
797
854
|
|
|
798
|
-
# Every
|
|
855
|
+
# Every registered service — state, enabled, failures, last error
|
|
799
856
|
agents daemon services
|
|
800
857
|
|
|
858
|
+
# Toggle or restart a service live — applies without a daemon restart
|
|
859
|
+
# for supervisor-managed services (secrets-broker, browser-ipc,
|
|
860
|
+
# account-state, session-index, monitors' off-transition, watchdog,
|
|
861
|
+
# device-probe, self-heal, keychain-reap, state-dir-check)
|
|
862
|
+
agents daemon services disable secrets-broker
|
|
863
|
+
agents daemon services enable secrets-broker
|
|
864
|
+
agents daemon services restart secrets-broker
|
|
865
|
+
|
|
801
866
|
# Host a signed webhook receiver here, supervised and restarted on crash
|
|
802
867
|
agents daemon webhooks add --secrets-bundle linear-webhook
|
|
803
868
|
agents daemon webhooks list
|
|
@@ -821,6 +886,16 @@ export function registerDaemonCommand(program) {
|
|
|
821
886
|
the daemon (daemon.enabled: false in ~/.agents/devices/<host>/agents.yaml).
|
|
822
887
|
'agents daemon start' still starts it explicitly, same as
|
|
823
888
|
'systemctl start' on a disabled unit.
|
|
889
|
+
|
|
890
|
+
'agents daemon services enable|disable|restart <id>' applies live (no
|
|
891
|
+
daemon restart) for the 10 supervisor-managed services: secrets-broker,
|
|
892
|
+
browser-ipc, account-state, session-index, monitors (off only — turning
|
|
893
|
+
monitors back on still needs a restart), watchdog, device-probe,
|
|
894
|
+
self-heal, keychain-reap, and state-dir-check. The other 2 services
|
|
895
|
+
(scheduler, webhook-receiver) are still bare interval/socket setups with
|
|
896
|
+
no live start/stop hook, so a toggle there — and 'enable' on any service
|
|
897
|
+
that was disabled at daemon boot — still needs 'agents daemon restart'.
|
|
898
|
+
'agents daemon services' names which case you're in per row.
|
|
824
899
|
`,
|
|
825
900
|
});
|
|
826
901
|
cmd.command('status')
|
|
@@ -917,11 +992,38 @@ export function registerDaemonCommand(program) {
|
|
|
917
992
|
});
|
|
918
993
|
const servicesCmd = cmd
|
|
919
994
|
.command('services')
|
|
920
|
-
.description('
|
|
995
|
+
.description('Every registered daemon service: live health, enabled state, and live enable/disable/restart.')
|
|
921
996
|
.option('--json', 'Emit as JSON')
|
|
922
997
|
.action(async (opts, command) => {
|
|
923
998
|
await runServices({ json: command.optsWithGlobals().json === true });
|
|
924
999
|
});
|
|
1000
|
+
setHelpSections(servicesCmd, {
|
|
1001
|
+
examples: `
|
|
1002
|
+
# State, enabled, consecutive failures, last error for every service
|
|
1003
|
+
agents daemon services
|
|
1004
|
+
|
|
1005
|
+
# Machine-readable — additive: also carries the pre-existing
|
|
1006
|
+
# secretsBroker/browserIpc hosted-socket fields
|
|
1007
|
+
agents daemon services --json
|
|
1008
|
+
|
|
1009
|
+
# Just the enable/disable metadata, no health probe
|
|
1010
|
+
agents daemon services list
|
|
1011
|
+
|
|
1012
|
+
# Toggle or restart live — no daemon restart for a supervisor-managed
|
|
1013
|
+
# service (secrets-broker, browser-ipc, account-state, session-index)
|
|
1014
|
+
agents daemon services disable secrets-broker
|
|
1015
|
+
agents daemon services enable secrets-broker
|
|
1016
|
+
agents daemon services restart secrets-broker
|
|
1017
|
+
`,
|
|
1018
|
+
notes: `
|
|
1019
|
+
A service disabled at daemon boot was never registered on the
|
|
1020
|
+
supervisor, so 'enable' on it — and any toggle/restart on the 2
|
|
1021
|
+
services the supervisor doesn't manage yet (scheduler,
|
|
1022
|
+
webhook-receiver) — falls back to 'restart the daemon to apply'. Each
|
|
1023
|
+
row in the plain-text view names which case it is; 'supervised:
|
|
1024
|
+
true/false' does the same in --json.
|
|
1025
|
+
`,
|
|
1026
|
+
});
|
|
925
1027
|
servicesCmd
|
|
926
1028
|
.command('list')
|
|
927
1029
|
.description('List every daemon service and whether it is enabled.')
|
|
@@ -947,9 +1049,28 @@ export function registerDaemonCommand(program) {
|
|
|
947
1049
|
console.log(chalk.gray(`\nConfig: ${getDaemonServicesConfigPath()}`));
|
|
948
1050
|
console.log(chalk.gray('Changes take effect on the next daemon reload or restart.'));
|
|
949
1051
|
});
|
|
1052
|
+
/**
|
|
1053
|
+
* Apply an enable/disable toggle live (RUSH-3193 P4): persist it, then signal
|
|
1054
|
+
* the running daemon to reload — its handler diffs the toggle and drives
|
|
1055
|
+
* `supervisor.start/stop(id)` for a supervised service, so no restart is
|
|
1056
|
+
* needed for the 10 supervisor-managed services (secrets-broker,
|
|
1057
|
+
* browser-ipc, account-state, session-index, monitors' off-transition,
|
|
1058
|
+
* watchdog, device-probe, self-heal, keychain-reap, state-dir-check). The 2
|
|
1059
|
+
* remaining `setInterval`/socket-driven services (scheduler,
|
|
1060
|
+
* webhook-receiver) and monitors' on-transition still need a restart — the
|
|
1061
|
+
* daemon's own reload log says so.
|
|
1062
|
+
*/
|
|
1063
|
+
function applyServiceToggleLive(service) {
|
|
1064
|
+
if (!isDaemonRunning())
|
|
1065
|
+
return;
|
|
1066
|
+
const ok = signalDaemonReload();
|
|
1067
|
+
console.log(ok
|
|
1068
|
+
? chalk.gray('Signalled the daemon to apply live. Confirm: agents daemon services')
|
|
1069
|
+
: chalk.yellow('Reload signal not delivered — restart the daemon to apply: agents daemon restart'));
|
|
1070
|
+
}
|
|
950
1071
|
servicesCmd
|
|
951
1072
|
.command('enable <service>')
|
|
952
|
-
.description('Enable a daemon service.')
|
|
1073
|
+
.description('Enable a daemon service. Applies live for supervised services; a legacy service needs a restart.')
|
|
953
1074
|
.action((service) => {
|
|
954
1075
|
if (!DAEMON_SERVICE_IDS.includes(service)) {
|
|
955
1076
|
console.error(chalk.red(`Unknown service '${service}'. Run 'agents daemon services list' for valid services.`));
|
|
@@ -957,13 +1078,11 @@ export function registerDaemonCommand(program) {
|
|
|
957
1078
|
}
|
|
958
1079
|
setDaemonServiceEnabled(service, true);
|
|
959
1080
|
console.log(chalk.green(`Enabled '${service}'.`));
|
|
960
|
-
|
|
961
|
-
console.log(chalk.gray('Run `agents daemon reload` (or restart) to apply.'));
|
|
962
|
-
}
|
|
1081
|
+
applyServiceToggleLive(service);
|
|
963
1082
|
});
|
|
964
1083
|
servicesCmd
|
|
965
1084
|
.command('disable <service>')
|
|
966
|
-
.description('Disable a daemon service.')
|
|
1085
|
+
.description('Disable a daemon service. Applies live for supervised services; a legacy service needs a restart.')
|
|
967
1086
|
.action((service) => {
|
|
968
1087
|
if (!DAEMON_SERVICE_IDS.includes(service)) {
|
|
969
1088
|
console.error(chalk.red(`Unknown service '${service}'. Run 'agents daemon services list' for valid services.`));
|
|
@@ -971,9 +1090,25 @@ export function registerDaemonCommand(program) {
|
|
|
971
1090
|
}
|
|
972
1091
|
setDaemonServiceEnabled(service, false);
|
|
973
1092
|
console.log(chalk.green(`Disabled '${service}'.`));
|
|
974
|
-
|
|
975
|
-
|
|
1093
|
+
applyServiceToggleLive(service);
|
|
1094
|
+
});
|
|
1095
|
+
servicesCmd
|
|
1096
|
+
.command('restart <service>')
|
|
1097
|
+
.description('Restart a supervised daemon service live, right now, outside its normal backoff schedule.')
|
|
1098
|
+
.action((service) => {
|
|
1099
|
+
if (!DAEMON_SERVICE_IDS.includes(service)) {
|
|
1100
|
+
console.error(chalk.red(`Unknown service '${service}'. Run 'agents daemon services list' for valid services.`));
|
|
1101
|
+
process.exit(1);
|
|
976
1102
|
}
|
|
1103
|
+
if (!isDaemonRunning()) {
|
|
1104
|
+
console.log(chalk.yellow('Daemon is not running — nothing to restart. Start it: agents daemon start'));
|
|
1105
|
+
process.exit(1);
|
|
1106
|
+
}
|
|
1107
|
+
queueDaemonServiceRestart(service);
|
|
1108
|
+
const ok = signalDaemonReload();
|
|
1109
|
+
console.log(ok
|
|
1110
|
+
? chalk.green(`Restart requested for '${service}'.`) + chalk.gray(' Confirm: agents daemon services')
|
|
1111
|
+
: chalk.yellow('Reload signal not delivered (unsupported on this platform, or the daemon just exited).'));
|
|
977
1112
|
});
|
|
978
1113
|
registerWebhooksSubcommand(cmd);
|
|
979
1114
|
registerFunnelCommand(cmd);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const REPO_ROOT: string;
|
|
2
|
+
export declare const INDEX: string;
|
|
3
|
+
export declare function guardedHome(): void;
|
|
4
|
+
export declare function run(args: string[], extraEnv?: Record<string, string>): {
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
status: number | null;
|
|
8
|
+
};
|
|
9
|
+
export declare function centralDoc(): string;
|
|
10
|
+
export declare function deviceDoc(name: string): string;
|
|
11
|
+
export declare function addDevice(name: string, target?: string): void;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { afterEach, expect } from 'vitest';
|
|
2
|
+
import { spawnSync } from 'child_process';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
// End-to-end tests for the unified `agents devices config` surface and the
|
|
8
|
+
// retired-subcommand tombstones (configure / note / set / set-interactive /
|
|
9
|
+
// enable / disable / prefer / unprefer). Spawns the REAL CLI against a
|
|
10
|
+
// throwaway HOME (same pattern as ssh.test.ts) — no mocking; the assertions
|
|
11
|
+
// read the actual files the commands wrote: per-device docs under
|
|
12
|
+
// devices/<name>/agents.yaml (device layer) and central agents.yaml
|
|
13
|
+
// fleet.defaults.config (fleet layer).
|
|
14
|
+
export const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
15
|
+
export const INDEX = path.join(REPO_ROOT, 'src', 'index.ts');
|
|
16
|
+
let testHome = '';
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
if (testHome)
|
|
19
|
+
fs.rmSync(testHome, { recursive: true, force: true });
|
|
20
|
+
testHome = '';
|
|
21
|
+
});
|
|
22
|
+
export function guardedHome() {
|
|
23
|
+
testHome = fs.mkdtempSync(path.join(os.tmpdir(), 'agents-devices-config-home-'));
|
|
24
|
+
const systemDir = path.join(testHome, '.agents', '.system');
|
|
25
|
+
fs.mkdirSync(path.join(systemDir, '.git'), { recursive: true });
|
|
26
|
+
fs.writeFileSync(path.join(systemDir, '.update-check'), JSON.stringify({ lastCheck: 4102444800000, latestVersion: '0.0.0' }));
|
|
27
|
+
}
|
|
28
|
+
export function run(args, extraEnv = {}) {
|
|
29
|
+
const r = spawnSync('bun', [INDEX, ...args], {
|
|
30
|
+
encoding: 'utf-8',
|
|
31
|
+
env: {
|
|
32
|
+
...process.env,
|
|
33
|
+
HOME: testHome,
|
|
34
|
+
// os.homedir() reads USERPROFILE on Windows, so HOME alone leaves the
|
|
35
|
+
// spawned CLI resolving the real profile ('agents-cli is not set up').
|
|
36
|
+
USERPROFILE: testHome,
|
|
37
|
+
AGENTS_NO_UPDATE_CHECK: '1',
|
|
38
|
+
AGENTS_NO_USAGE_TRACK: '1',
|
|
39
|
+
// Default identity is mac-mini so machine-local keys (scheduler, tmux,
|
|
40
|
+
// browser consent) can be set in these tests. Override per-call to act
|
|
41
|
+
// as a different box.
|
|
42
|
+
AGENTS_SYNC_MACHINE_ID: 'mac-mini',
|
|
43
|
+
...extraEnv,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
return { stdout: r.stdout ?? '', stderr: r.stderr ?? '', status: r.status };
|
|
47
|
+
}
|
|
48
|
+
export function centralDoc() {
|
|
49
|
+
const p = path.join(testHome, '.agents', 'agents.yaml');
|
|
50
|
+
return fs.existsSync(p) ? fs.readFileSync(p, 'utf-8') : '';
|
|
51
|
+
}
|
|
52
|
+
export function deviceDoc(name) {
|
|
53
|
+
const p = path.join(testHome, '.agents', 'devices', name, 'agents.yaml');
|
|
54
|
+
return fs.existsSync(p) ? fs.readFileSync(p, 'utf-8') : '';
|
|
55
|
+
}
|
|
56
|
+
export function addDevice(name, target = 'muqsit@192.0.2.1') {
|
|
57
|
+
const r = run(['devices', 'add', name, target]);
|
|
58
|
+
expect(r.status, r.stderr).toBe(0);
|
|
59
|
+
}
|