@phnx-labs/agents-cli 1.22.43 → 1.22.45
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 +85 -0
- package/README.md +14 -12
- package/dist/bootstrap.js +3 -6
- package/dist/cli/command-registry.d.ts +0 -7
- package/dist/cli/command-registry.js +1 -21
- package/dist/commands/accounts.d.ts +1 -9
- package/dist/commands/accounts.js +12 -90
- package/dist/commands/apply.d.ts +7 -10
- package/dist/commands/apply.js +15 -35
- package/dist/commands/artifacts.js +4 -5
- package/dist/commands/audit.d.ts +6 -6
- package/dist/commands/audit.js +12 -12
- package/dist/commands/beta.d.ts +7 -1
- package/dist/commands/beta.js +16 -9
- package/dist/commands/commands.js +1 -1
- package/dist/commands/doctor.js +1 -4
- package/dist/commands/events.d.ts +2 -2
- package/dist/commands/events.js +6 -5
- package/dist/commands/exec.d.ts +1 -1
- package/dist/commands/exec.js +8 -4
- package/dist/commands/factory.d.ts +1 -1
- package/dist/commands/fleet-capture.d.ts +1 -1
- package/dist/commands/fleet-capture.js +4 -5
- package/dist/commands/harness.js +1 -49
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +85 -156
- package/dist/commands/menubar.js +58 -1
- package/dist/commands/output.js +2 -2
- package/dist/commands/prune.js +5 -4
- package/dist/commands/routines.test-fixture.d.ts +86 -0
- package/dist/commands/routines.test-fixture.js +345 -0
- package/dist/commands/sessions.test-fixture.d.ts +24 -0
- package/dist/commands/sessions.test-fixture.js +224 -0
- package/dist/commands/setup.js +5 -1
- package/dist/commands/share.d.ts +9 -10
- package/dist/commands/share.js +68 -50
- package/dist/commands/skills.js +1 -1
- package/dist/commands/ssh.js +15 -62
- package/dist/commands/subagents.js +1 -1
- package/dist/commands/view.js +5 -2
- package/dist/lib/analytics/mix-commands.d.ts +6 -28
- package/dist/lib/analytics/mix-commands.js +14 -40
- package/dist/lib/audit/log.d.ts +2 -2
- package/dist/lib/audit/log.js +2 -2
- package/dist/lib/beta.js +1 -1
- package/dist/lib/browser/domain-skills.d.ts +26 -6
- package/dist/lib/browser/domain-skills.js +81 -43
- package/dist/lib/daemon/daemon.js +11 -0
- package/dist/lib/daemon/daemon.test-fixture.d.ts +24 -0
- package/dist/lib/daemon/daemon.test-fixture.js +71 -0
- package/dist/lib/daemon-ticks.d.ts +38 -1
- package/dist/lib/daemon-ticks.js +52 -5
- package/dist/lib/device-config.js +1 -2
- package/dist/lib/devices/discovery-policy.d.ts +3 -3
- package/dist/lib/devices/discovery-policy.js +3 -3
- package/dist/lib/devices/fleet.d.ts +4 -8
- package/dist/lib/devices/fleet.js +3 -15
- package/dist/lib/devices/health-report.js +3 -3
- package/dist/lib/devices/pool.d.ts +0 -6
- package/dist/lib/devices/pool.js +0 -6
- package/dist/lib/devices/registry.d.ts +0 -15
- package/dist/lib/devices/registry.js +0 -9
- package/dist/lib/devices/stats-cache.d.ts +12 -0
- package/dist/lib/devices/stats-cache.js +24 -5
- package/dist/lib/fleet/capture.d.ts +1 -1
- package/dist/lib/fleet/manifest.js +1 -1
- package/dist/lib/fleet/remote-login.d.ts +2 -2
- package/dist/lib/fleet/remote-login.js +3 -4
- package/dist/lib/hosts/dispatch.d.ts +2 -0
- package/dist/lib/hosts/dispatch.js +8 -1
- package/dist/lib/hosts/passthrough.js +0 -1
- package/dist/lib/hosts/providers/devices.js +1 -13
- package/dist/lib/hosts/ready.d.ts +12 -3
- package/dist/lib/hosts/ready.js +27 -12
- package/dist/lib/hosts/registry.d.ts +4 -5
- package/dist/lib/hosts/registry.js +3 -7
- package/dist/lib/installations/versions.js +21 -2
- package/dist/lib/menubar/install-menubar.d.ts +109 -0
- package/dist/lib/menubar/install-menubar.js +199 -4
- package/dist/lib/remote-agents-json.js +1 -7
- package/dist/lib/routines-placement.d.ts +1 -1
- package/dist/lib/routines-placement.js +1 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
- package/dist/lib/session/active.d.ts +4 -16
- package/dist/lib/session/active.js +2 -6
- package/dist/lib/session/db.d.ts +13 -98
- package/dist/lib/session/db.js +4 -11
- package/dist/lib/session/discover.d.ts +5 -81
- package/dist/lib/session/discover.js +5 -5
- package/dist/lib/session/remote/remote-bundle.d.ts +12 -0
- package/dist/lib/session/remote/remote-bundle.js +61 -0
- package/dist/lib/session/remote/remote-list.d.ts +148 -0
- package/dist/lib/session/remote/remote-list.js +607 -0
- package/dist/lib/session/remote/remote.d.ts +132 -0
- package/dist/lib/session/remote/remote.js +314 -0
- package/dist/lib/session/remote/watch.d.ts +101 -0
- package/dist/lib/session/remote/watch.js +241 -0
- package/dist/lib/session/remote-bundle.d.ts +1 -12
- package/dist/lib/session/remote-bundle.js +3 -61
- package/dist/lib/session/remote-list.d.ts +1 -148
- package/dist/lib/session/remote-list.js +3 -607
- package/dist/lib/session/remote.d.ts +1 -132
- package/dist/lib/session/remote.js +3 -314
- package/dist/lib/session/watch.d.ts +1 -101
- package/dist/lib/session/watch.js +3 -242
- package/dist/lib/share/config.d.ts +12 -2
- package/dist/lib/share/config.js +47 -11
- package/dist/lib/share/delete.js +1 -1
- package/dist/lib/share/publish.js +1 -1
- package/dist/lib/share/worker-template.js +2 -2
- package/dist/lib/smart-launch.d.ts +4 -4
- package/dist/lib/smart-launch.js +8 -18
- package/dist/lib/staleness/index.d.ts +3 -1
- package/dist/lib/staleness/index.js +21 -1
- package/dist/lib/staleness/types.d.ts +11 -0
- package/dist/lib/staleness/writers/commands.js +13 -4
- package/dist/lib/staleness/writers/hooks.js +3 -1
- package/dist/lib/staleness/writers/mcp.d.ts +0 -7
- package/dist/lib/staleness/writers/mcp.js +16 -1
- package/dist/lib/staleness/writers/rules.js +1 -1
- package/dist/lib/staleness/writers/skills.js +3 -1
- package/dist/lib/staleness/writers/subagents.d.ts +0 -9
- package/dist/lib/staleness/writers/subagents.js +18 -1
- package/dist/lib/staleness/writers/types.d.ts +10 -0
- package/dist/lib/startup/command-registry.d.ts +8 -1
- package/dist/lib/startup/command-registry.js +21 -6
- package/dist/lib/startup/root-command.d.ts +18 -1
- package/dist/lib/startup/root-command.js +18 -1
- package/dist/lib/state.js +7 -0
- package/dist/lib/teams/placement-probe.js +27 -12
- package/dist/lib/teams/scheduler.d.ts +3 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/view-types.d.ts +3 -0
- package/package.json +2 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/auth.d.ts +0 -9
- package/dist/commands/auth.js +0 -108
- package/dist/commands/org.d.ts +0 -6
- package/dist/commands/org.js +0 -175
- package/dist/commands/serve.d.ts +0 -10
- package/dist/commands/serve.js +0 -68
- package/dist/commands/trends.d.ts +0 -10
- package/dist/commands/trends.js +0 -12
- package/dist/lib/entitlement.d.ts +0 -31
- package/dist/lib/entitlement.js +0 -137
- package/dist/lib/prix-account.d.ts +0 -158
- package/dist/lib/prix-account.js +0 -214
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/serve/control.d.ts +0 -95
- package/dist/lib/serve/control.js +0 -260
- package/dist/lib/serve/data.d.ts +0 -81
- package/dist/lib/serve/data.js +0 -91
- package/dist/lib/serve/page.d.ts +0 -7
- package/dist/lib/serve/page.js +0 -140
- package/dist/lib/serve/server.d.ts +0 -80
- package/dist/lib/serve/server.js +0 -145
- package/dist/lib/serve/stream.d.ts +0 -43
- package/dist/lib/serve/stream.js +0 -116
- package/dist/lib/serve/token.d.ts +0 -35
- package/dist/lib/serve/token.js +0 -85
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { describe, afterAll } from 'vitest';
|
|
2
|
+
import { spawnSync, spawn, execFileSync } from 'child_process';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as yaml from 'yaml';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
/**
|
|
10
|
+
* Shared fixture for the routines.*.test.ts suite slices (RUSH-2819).
|
|
11
|
+
*
|
|
12
|
+
* routines.test.ts was one 2,249-line file measured at ~194s of test time —
|
|
13
|
+
* one of the slowest files in CI, serializing an entire fork while every
|
|
14
|
+
* other selected file finished. The suite is split into topical slices so
|
|
15
|
+
* vitest's per-file fork parallelism can spread the subprocess-heavy tests
|
|
16
|
+
* across workers; the helpers each slice shares live here.
|
|
17
|
+
*
|
|
18
|
+
* Every test spawns the real CLI (`node --import tsx src/index.ts routines ...`)
|
|
19
|
+
* against an isolated mkdtemp HOME — no live ~/.agents state, no mocks, no
|
|
20
|
+
* imported writeJob/readJob. Modeled on `routines-webhook.test.ts`.
|
|
21
|
+
*/
|
|
22
|
+
export const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
// `--import` takes a module specifier, not a path: a bare Windows path like
|
|
25
|
+
// `D:\a\...\tsx\dist\loader.mjs` is parsed as a URL with protocol 'd:' and the
|
|
26
|
+
// child dies with ERR_UNSUPPORTED_ESM_URL_SCHEME before running the CLI. Same
|
|
27
|
+
// pattern as sessions.test.ts:21.
|
|
28
|
+
export const TSX_IMPORT = pathToFileURL(require.resolve('tsx')).href;
|
|
29
|
+
export const CLI_ENTRYPOINT = path.join(REPO_ROOT, 'src', 'index.ts');
|
|
30
|
+
// win32: subprocess CLI + process-group signals / path spawn assumptions (RUSH-2215).
|
|
31
|
+
export const describeRoutines = process.platform === 'win32' ? describe.skip : describe;
|
|
32
|
+
/** Provision an isolated HOME with agents.yaml, .system/.git, and optional routines + device registry. */
|
|
33
|
+
export function makeHome(opts = {}) {
|
|
34
|
+
const home = fs.mkdtempSync(opts.tmpPrefix ?? path.join(os.tmpdir(), 'agents-routines-test-'));
|
|
35
|
+
const agentsDir = path.join(home, '.agents');
|
|
36
|
+
const routinesDir = path.join(agentsDir, 'routines');
|
|
37
|
+
const projectDir = path.join(home, 'project');
|
|
38
|
+
const projectRoutinesDir = path.join(projectDir, '.agents', 'routines');
|
|
39
|
+
fs.mkdirSync(routinesDir, { recursive: true });
|
|
40
|
+
fs.mkdirSync(projectRoutinesDir, { recursive: true });
|
|
41
|
+
fs.mkdirSync(path.join(projectDir, '.git'), { recursive: true });
|
|
42
|
+
fs.writeFileSync(path.join(agentsDir, 'agents.yaml'), 'agents: {}\n');
|
|
43
|
+
fs.mkdirSync(path.join(agentsDir, '.system', '.git'), { recursive: true });
|
|
44
|
+
for (const job of opts.jobs ?? []) {
|
|
45
|
+
fs.writeFileSync(path.join(routinesDir, `${job.name}.yml`), yaml.stringify(job));
|
|
46
|
+
}
|
|
47
|
+
for (const job of opts.projectJobs ?? []) {
|
|
48
|
+
fs.writeFileSync(path.join(projectRoutinesDir, `${job.name}.yml`), yaml.stringify(job));
|
|
49
|
+
}
|
|
50
|
+
if (opts.registry) {
|
|
51
|
+
const devicesDir = path.join(agentsDir, '.history', 'devices');
|
|
52
|
+
fs.mkdirSync(devicesDir, { recursive: true });
|
|
53
|
+
fs.writeFileSync(path.join(devicesDir, 'registry.json'), JSON.stringify(opts.registry));
|
|
54
|
+
}
|
|
55
|
+
for (const [device, routines] of Object.entries(opts.deviceRoutines ?? {})) {
|
|
56
|
+
writeDeviceRoutines(home, device, routines);
|
|
57
|
+
}
|
|
58
|
+
return home;
|
|
59
|
+
}
|
|
60
|
+
/** Run `agents routines <args>` against an isolated HOME. */
|
|
61
|
+
export function run(home, args, extraEnv = {}, cwd = REPO_ROOT) {
|
|
62
|
+
return spawnSync('node', ['--import', TSX_IMPORT, CLI_ENTRYPOINT, 'routines', ...args], {
|
|
63
|
+
cwd,
|
|
64
|
+
env: {
|
|
65
|
+
...process.env,
|
|
66
|
+
HOME: home,
|
|
67
|
+
USERPROFILE: home,
|
|
68
|
+
// Point the device registry at this test's home (RUSH-2042): getDevicesDir()
|
|
69
|
+
// reads AGENTS_DEVICES_DIR, and the parent vitest fork exports its own via
|
|
70
|
+
// setup.ts — override it so the child reads the registry makeHome() wrote.
|
|
71
|
+
AGENTS_DEVICES_DIR: path.join(home, '.agents', '.history', 'devices'),
|
|
72
|
+
AGENTS_SKIP_MIGRATION: '1',
|
|
73
|
+
...extraEnv,
|
|
74
|
+
},
|
|
75
|
+
encoding: 'utf-8',
|
|
76
|
+
timeout: 30_000,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export function readRoutineYaml(home, name) {
|
|
80
|
+
const p = path.join(home, '.agents', 'routines', `${name}.yml`);
|
|
81
|
+
if (!fs.existsSync(p))
|
|
82
|
+
return null;
|
|
83
|
+
return yaml.parse(fs.readFileSync(p, 'utf-8'));
|
|
84
|
+
}
|
|
85
|
+
export function writeRunMeta(home, jobName, runId, meta) {
|
|
86
|
+
const runDir = path.join(home, '.agents', '.history', 'runs', jobName, runId);
|
|
87
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
88
|
+
fs.writeFileSync(path.join(runDir, 'meta.json'), JSON.stringify(meta));
|
|
89
|
+
}
|
|
90
|
+
function daemonPidPath(home) {
|
|
91
|
+
return path.join(home, '.agents', '.cache', 'helpers', 'daemon', 'daemon.pid');
|
|
92
|
+
}
|
|
93
|
+
function readDaemonPid(home) {
|
|
94
|
+
const pidPath = daemonPidPath(home);
|
|
95
|
+
if (!fs.existsSync(pidPath))
|
|
96
|
+
return null;
|
|
97
|
+
const raw = fs.readFileSync(pidPath, 'utf-8').trim();
|
|
98
|
+
const pid = parseInt(raw, 10);
|
|
99
|
+
return isNaN(pid) ? null : pid;
|
|
100
|
+
}
|
|
101
|
+
export function isProcessAlive(pid) {
|
|
102
|
+
try {
|
|
103
|
+
process.kill(pid, 0);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Per-file isolated-daemon harness (RUSH-2367, RUSH-2819). Each slice that
|
|
112
|
+
* spawns a real `__daemon-run` process against an isolated HOME calls this
|
|
113
|
+
* once at module scope and registers its own leak detector — the tracked pid
|
|
114
|
+
* set MUST be private to the file that spawned it, since vitest's per-file
|
|
115
|
+
* fork isolation means the leak sweep only ever needs to answer for what THIS
|
|
116
|
+
* file started.
|
|
117
|
+
*
|
|
118
|
+
* `fileSlug` MUST be unique per test file: it names the mkdtemp prefix
|
|
119
|
+
* `makeDaemonHome` uses, and the CI leak sweep matches ONLY that prefix.
|
|
120
|
+
* The suite slices run in parallel forks, so a sweep over the shared
|
|
121
|
+
* `agents-routines-test-` prefix would catch a sibling file's still-running
|
|
122
|
+
* daemon and kill it mid-test — exactly what failed the first CI run of the
|
|
123
|
+
* RUSH-2819 split (run 32552954164: add's sweep killed a live daemon whose
|
|
124
|
+
* HOME belonged to a concurrently running slice).
|
|
125
|
+
*/
|
|
126
|
+
export function createDaemonHarness(fileSlug) {
|
|
127
|
+
const homePrefix = path.join(os.tmpdir(), `agents-routines-${fileSlug}-`);
|
|
128
|
+
/**
|
|
129
|
+
* Every daemon pid spawned via `startIsolatedDaemon` in this file, live for as
|
|
130
|
+
* long as `stopIsolatedDaemon` has not yet reaped it. Backstops the per-test
|
|
131
|
+
* try/finally: this file's `afterAll` (registered via `registerLeakDetector`)
|
|
132
|
+
* asserts the set is empty and force-kills + fails the suite on anything left
|
|
133
|
+
* in it — a leaked real daemon process is exactly the RUSH-2367 bug (three
|
|
134
|
+
* left running for up to 3.5 days on a fleet box, invisible to `agents daemon`
|
|
135
|
+
* because each served its own fixture HOME/registry).
|
|
136
|
+
*/
|
|
137
|
+
const trackedDaemonPids = new Set();
|
|
138
|
+
/** Start the real scheduler foreground process against an isolated HOME. */
|
|
139
|
+
function startIsolatedDaemon(home) {
|
|
140
|
+
const child = spawn('node', ['--import', 'tsx', 'src/index.ts', '__daemon-run'], {
|
|
141
|
+
cwd: REPO_ROOT,
|
|
142
|
+
env: {
|
|
143
|
+
...process.env,
|
|
144
|
+
HOME: home,
|
|
145
|
+
USERPROFILE: home,
|
|
146
|
+
// Without this override the daemon inherits the parent vitest process's real
|
|
147
|
+
// AGENTS_HISTORY_DIR (pointing at ~/.agents/.history). The daemon's SIGTERM
|
|
148
|
+
// sweep then reads live session records from the production history directory
|
|
149
|
+
// and kills real tmux-wrapped processes every five-minute tick (RUSH-2545).
|
|
150
|
+
AGENTS_HISTORY_DIR: path.join(home, '.agents', '.history'),
|
|
151
|
+
AGENTS_SKIP_MIGRATION: '1',
|
|
152
|
+
},
|
|
153
|
+
detached: true,
|
|
154
|
+
stdio: 'ignore',
|
|
155
|
+
});
|
|
156
|
+
if (child.pid)
|
|
157
|
+
trackedDaemonPids.add(child.pid);
|
|
158
|
+
const pidPromise = new Promise((resolve) => {
|
|
159
|
+
const deadline = Date.now() + 15_000;
|
|
160
|
+
const interval = setInterval(() => {
|
|
161
|
+
const pid = readDaemonPid(home);
|
|
162
|
+
if (pid) {
|
|
163
|
+
clearInterval(interval);
|
|
164
|
+
resolve(pid);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (Date.now() >= deadline) {
|
|
168
|
+
clearInterval(interval);
|
|
169
|
+
resolve(null);
|
|
170
|
+
}
|
|
171
|
+
}, 50);
|
|
172
|
+
});
|
|
173
|
+
return { child, pidPromise };
|
|
174
|
+
}
|
|
175
|
+
/** Terminate a daemon process started by startIsolatedDaemon and wait for it to exit. */
|
|
176
|
+
async function stopIsolatedDaemon(child) {
|
|
177
|
+
const pid = child.pid;
|
|
178
|
+
try {
|
|
179
|
+
if (!pid)
|
|
180
|
+
return;
|
|
181
|
+
const closePromise = new Promise((resolve) => {
|
|
182
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
183
|
+
resolve();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
child.on('close', () => resolve());
|
|
187
|
+
});
|
|
188
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
189
|
+
return;
|
|
190
|
+
const signalProcessGroup = process.platform !== 'win32';
|
|
191
|
+
const signal = (sig) => {
|
|
192
|
+
try {
|
|
193
|
+
if (signalProcessGroup) {
|
|
194
|
+
process.kill(-pid, sig);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
child.kill(sig);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// already gone
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
signal('SIGTERM');
|
|
205
|
+
const timer = setTimeout(() => signal('SIGKILL'), 3_000);
|
|
206
|
+
await closePromise;
|
|
207
|
+
clearTimeout(timer);
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
if (pid)
|
|
211
|
+
trackedDaemonPids.delete(pid);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Suite-level leak detector (RUSH-2367). Every per-test try/finally above
|
|
216
|
+
* already reaps its own daemon on success, failure, or a thrown assertion —
|
|
217
|
+
* but nothing in JS runs if the whole vitest worker is killed externally
|
|
218
|
+
* before reaching `finally`, which is what actually produced three real
|
|
219
|
+
* orphaned daemons found alive on a fleet box for up to 3.5 days: their
|
|
220
|
+
* fixture HOME dirs still existed (the `finally`'s `fs.rmSync` never ran
|
|
221
|
+
* either), so no amount of in-test cleanup logic would have caught it. This
|
|
222
|
+
* is the second line of defense, not a substitute for the self-terminate
|
|
223
|
+
* guard in the daemon itself (`runDaemon`'s state-dir check).
|
|
224
|
+
*
|
|
225
|
+
* Two checks, after every test in this file has run:
|
|
226
|
+
* 1. Always: nothing THIS run spawned via `startIsolatedDaemon` may still be
|
|
227
|
+
* alive — `trackedDaemonPids` is only ever non-empty here if a bug (not
|
|
228
|
+
* an external kill) let one slip past its own test's `finally`.
|
|
229
|
+
* 2. CI only: sweep for any OTHER live `__daemon-run` process whose HOME
|
|
230
|
+
* sits under THIS FILE's unique `agents-routines-<fileSlug>-` prefix —
|
|
231
|
+
* a leak from a previous interrupted run of this same file. Scoped to
|
|
232
|
+
* the per-file prefix because sibling slices run in parallel forks and
|
|
233
|
+
* legitimately have live daemons under their own prefixes. POSIX-only
|
|
234
|
+
* (`/proc`); best-effort and skipped where `/proc` is unavailable
|
|
235
|
+
* (macOS CI legs).
|
|
236
|
+
*
|
|
237
|
+
* Either check force-kills what it finds and fails the suite — a silent
|
|
238
|
+
* "still running, we'll get it next time" is exactly how the original three
|
|
239
|
+
* accumulated.
|
|
240
|
+
*/
|
|
241
|
+
function registerLeakDetector() {
|
|
242
|
+
afterAll(() => {
|
|
243
|
+
const leaks = [];
|
|
244
|
+
const killDaemon = (pid) => {
|
|
245
|
+
try {
|
|
246
|
+
process.kill(-pid, 'SIGKILL');
|
|
247
|
+
}
|
|
248
|
+
catch { /* already gone, or never its own group leader */ }
|
|
249
|
+
try {
|
|
250
|
+
process.kill(pid, 'SIGKILL');
|
|
251
|
+
}
|
|
252
|
+
catch { /* already gone */ }
|
|
253
|
+
};
|
|
254
|
+
for (const pid of trackedDaemonPids) {
|
|
255
|
+
if (isProcessAlive(pid)) {
|
|
256
|
+
leaks.push(`pid ${pid} (spawned by this test run, never reaped by its own test)`);
|
|
257
|
+
killDaemon(pid);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
trackedDaemonPids.clear();
|
|
261
|
+
if (process.env.CI && process.platform !== 'win32') {
|
|
262
|
+
const prefix = homePrefix;
|
|
263
|
+
let psOut = '';
|
|
264
|
+
try {
|
|
265
|
+
psOut = execFileSync('ps', ['-eo', 'pid=,args='], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
266
|
+
}
|
|
267
|
+
catch { /* ps unavailable — nothing to sweep */ }
|
|
268
|
+
for (const line of psOut.split('\n')) {
|
|
269
|
+
const m = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
270
|
+
if (!m)
|
|
271
|
+
continue;
|
|
272
|
+
const tokens = m[2].trim().split(/\s+/);
|
|
273
|
+
if (tokens[tokens.length - 1] !== '__daemon-run')
|
|
274
|
+
continue;
|
|
275
|
+
const pid = parseInt(m[1], 10);
|
|
276
|
+
if (isNaN(pid))
|
|
277
|
+
continue;
|
|
278
|
+
let home = null;
|
|
279
|
+
try {
|
|
280
|
+
const environ = fs.readFileSync(`/proc/${pid}/environ`, 'utf-8');
|
|
281
|
+
const homeVar = environ.split('\0').find((v) => v.startsWith('HOME='));
|
|
282
|
+
home = homeVar ? homeVar.slice(5) : null;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
continue;
|
|
286
|
+
} // /proc unreadable (macOS, permissions) — best-effort only
|
|
287
|
+
if (!home || !home.startsWith(prefix))
|
|
288
|
+
continue;
|
|
289
|
+
leaks.push(`pid ${pid} HOME=${home} (leaked from a previous interrupted run of this file)`);
|
|
290
|
+
killDaemon(pid);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (leaks.length > 0) {
|
|
294
|
+
throw new Error(`RUSH-2367 leak detector: ${leaks.length} __daemon-run process(es) survived this test file, ` +
|
|
295
|
+
`now force-killed: ${leaks.join('; ')}`);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
/** makeHome under this file's unique prefix — REQUIRED for any home a daemon runs against. */
|
|
300
|
+
function makeDaemonHome(opts = {}) {
|
|
301
|
+
return makeHome({ ...opts, tmpPrefix: homePrefix });
|
|
302
|
+
}
|
|
303
|
+
return { startIsolatedDaemon, stopIsolatedDaemon, registerLeakDetector, makeDaemonHome };
|
|
304
|
+
}
|
|
305
|
+
export const baseJob = {
|
|
306
|
+
name: 'test-job',
|
|
307
|
+
schedule: '0 3 * * *',
|
|
308
|
+
agent: 'claude',
|
|
309
|
+
prompt: 'noop',
|
|
310
|
+
// Agent routines now need an execution anchor to activate (RUSH-2290); home
|
|
311
|
+
// is a valid one and keeps these device/eligibility fixtures ready.
|
|
312
|
+
cwd: '~',
|
|
313
|
+
// Legacy fixture state: tests that specifically cover the new manifest model
|
|
314
|
+
// materialize a device document below.
|
|
315
|
+
enabled: true,
|
|
316
|
+
};
|
|
317
|
+
export const registry = {
|
|
318
|
+
'yosemite-s0': { name: 'yosemite-s0', platform: 'linux' },
|
|
319
|
+
'mac-mini': { name: 'mac-mini', platform: 'macos' },
|
|
320
|
+
'zion': { name: 'zion', platform: 'macos' },
|
|
321
|
+
};
|
|
322
|
+
export function readDeviceRoutines(home, device) {
|
|
323
|
+
const file = path.join(home, '.agents', 'devices', device, 'agents.yaml');
|
|
324
|
+
if (!fs.existsSync(file))
|
|
325
|
+
return [];
|
|
326
|
+
return yaml.parse(fs.readFileSync(file, 'utf-8')).routines ?? [];
|
|
327
|
+
}
|
|
328
|
+
export function writeDeviceRoutines(home, device, routines) {
|
|
329
|
+
const dir = path.join(home, '.agents', 'devices', device);
|
|
330
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
331
|
+
fs.writeFileSync(path.join(dir, 'agents.yaml'), yaml.stringify({ routines }));
|
|
332
|
+
}
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
// Project-tagging tests (--project / --all-projects / list --json projectGroup)
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
/** Write a minimal project YAML so listProjectDefs() sees it. */
|
|
337
|
+
export function writeProject(home, name) {
|
|
338
|
+
const dir = path.join(home, '.agents', 'projects');
|
|
339
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
340
|
+
fs.writeFileSync(path.join(dir, `${name}.yaml`), yaml.stringify({ name }));
|
|
341
|
+
}
|
|
342
|
+
/** Env that points the projects dir at the isolated home. */
|
|
343
|
+
export function projectsEnv(home) {
|
|
344
|
+
return { AGENTS_PROJECTS_DIR: path.join(home, '.agents', 'projects') };
|
|
345
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe } from 'vitest';
|
|
2
|
+
/**
|
|
3
|
+
* Shared fixture for the sessions.*.test.ts suite slices (RUSH-2819).
|
|
4
|
+
*
|
|
5
|
+
* sessions.test.ts was one 2,600-line file measured at 172s — the single
|
|
6
|
+
* slowest file in CI, serializing an entire fork while every other selected
|
|
7
|
+
* file finished. The suite is split into topical slices so vitest's per-file
|
|
8
|
+
* fork parallelism can spread the subprocess-heavy tests across workers; the
|
|
9
|
+
* helpers each slice shares live here.
|
|
10
|
+
*/
|
|
11
|
+
export declare const describeLive: typeof describe.skip;
|
|
12
|
+
export declare const repoRoot: string;
|
|
13
|
+
export declare const cliEntry: string;
|
|
14
|
+
export declare const tsxLoaderUrl: string;
|
|
15
|
+
export declare function writeUpdateCache(tempHome: string): void;
|
|
16
|
+
export declare function writeClaudeSession(tempHome: string, projectKey: string, sessionId: string, cwd: string, content: string, timestamp: string): void;
|
|
17
|
+
export declare function writeCodexSession(tempHome: string, sessionId: string, cwd: string, prompt: string, timestamp: string): void;
|
|
18
|
+
export declare function writeGeminiSession(tempHome: string, sessionId: string, cwd: string, prompt: string, timestamp: string, version?: string): void;
|
|
19
|
+
export declare function writeOpenClawSetup(tempHome: string, version?: string): string;
|
|
20
|
+
export declare function runAgents(args: string[], cwd: string, home: string, envOverrides?: Record<string, string>): import("node:child_process").SpawnSyncReturns<string>;
|
|
21
|
+
export declare function outputOf(result: {
|
|
22
|
+
stdout: string;
|
|
23
|
+
stderr: string;
|
|
24
|
+
}): string;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { describe } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as crypto from 'crypto';
|
|
5
|
+
import { spawnSync } from 'child_process';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { pathToFileURL } from 'node:url';
|
|
8
|
+
/**
|
|
9
|
+
* Shared fixture for the sessions.*.test.ts suite slices (RUSH-2819).
|
|
10
|
+
*
|
|
11
|
+
* sessions.test.ts was one 2,600-line file measured at 172s — the single
|
|
12
|
+
* slowest file in CI, serializing an entire fork while every other selected
|
|
13
|
+
* file finished. The suite is split into topical slices so vitest's per-file
|
|
14
|
+
* fork parallelism can spread the subprocess-heavy tests across workers; the
|
|
15
|
+
* helpers each slice shares live here.
|
|
16
|
+
*/
|
|
17
|
+
// win32: Claude projects path joins absolute cwd with colons (RUSH-2215).
|
|
18
|
+
export const describeLive = process.platform === 'win32' ? describe.skip : describe;
|
|
19
|
+
export const repoRoot = process.cwd();
|
|
20
|
+
export const cliEntry = path.join(repoRoot, 'src', 'index.ts');
|
|
21
|
+
// Run the CLI as `node --import <tsx loader> src/index.ts`: spawning `node`
|
|
22
|
+
// (always on PATH, no .cmd shell launcher) with the tsx ESM loader resolved to
|
|
23
|
+
// an absolute file URL keeps tsx loadable regardless of the spawn cwd (which we
|
|
24
|
+
// point at the project dir). Avoids both the Windows `tsx.cmd`-needs-a-shell
|
|
25
|
+
// problem and shell:true arg-concatenation (which would split multi-word query
|
|
26
|
+
// args like "prompt text").
|
|
27
|
+
export const tsxLoaderUrl = pathToFileURL(createRequire(import.meta.url).resolve('tsx')).href;
|
|
28
|
+
export function writeUpdateCache(tempHome) {
|
|
29
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf-8'));
|
|
30
|
+
fs.mkdirSync(path.join(tempHome, '.agents', '.cache'), { recursive: true });
|
|
31
|
+
fs.writeFileSync(path.join(tempHome, '.agents', '.cache', '.update-check'), JSON.stringify({ lastCheck: Date.now(), latestVersion: packageJson.version }), 'utf-8');
|
|
32
|
+
// ensureInitialized() checks for ~/.agents/.system/.git to confirm setup.
|
|
33
|
+
fs.mkdirSync(path.join(tempHome, '.agents', '.system', '.git'), { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
export function writeClaudeSession(tempHome, projectKey, sessionId, cwd, content, timestamp) {
|
|
36
|
+
fs.mkdirSync(cwd, { recursive: true });
|
|
37
|
+
const sessionsDir = path.join(tempHome, '.claude', 'projects', projectKey);
|
|
38
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
39
|
+
fs.writeFileSync(path.join(sessionsDir, `${sessionId}.jsonl`), JSON.stringify({
|
|
40
|
+
type: 'user',
|
|
41
|
+
timestamp,
|
|
42
|
+
cwd,
|
|
43
|
+
sessionId,
|
|
44
|
+
version: '2.1.110',
|
|
45
|
+
gitBranch: 'main',
|
|
46
|
+
message: { role: 'user', content },
|
|
47
|
+
}) + '\n', 'utf-8');
|
|
48
|
+
}
|
|
49
|
+
export function writeCodexSession(tempHome, sessionId, cwd, prompt, timestamp) {
|
|
50
|
+
fs.mkdirSync(cwd, { recursive: true });
|
|
51
|
+
const sessionsDir = path.join(tempHome, '.codex', 'sessions', '2026', '04', '17');
|
|
52
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
53
|
+
const filePath = path.join(sessionsDir, `rollout-${timestamp.replace(/[:.]/g, '-')}-${sessionId}.jsonl`);
|
|
54
|
+
const lines = [
|
|
55
|
+
JSON.stringify({
|
|
56
|
+
timestamp,
|
|
57
|
+
type: 'session_meta',
|
|
58
|
+
payload: {
|
|
59
|
+
id: sessionId,
|
|
60
|
+
timestamp,
|
|
61
|
+
cwd,
|
|
62
|
+
originator: 'codex_cli_rs',
|
|
63
|
+
cli_version: '0.113.0',
|
|
64
|
+
source: 'cli',
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
JSON.stringify({
|
|
68
|
+
timestamp,
|
|
69
|
+
type: 'response_item',
|
|
70
|
+
payload: {
|
|
71
|
+
type: 'message',
|
|
72
|
+
role: 'developer',
|
|
73
|
+
content: [{ type: 'input_text', text: '<permissions instructions>\nFilesystem sandboxing.\n</permissions instructions>' }],
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
JSON.stringify({
|
|
77
|
+
timestamp,
|
|
78
|
+
type: 'response_item',
|
|
79
|
+
payload: {
|
|
80
|
+
type: 'message',
|
|
81
|
+
role: 'user',
|
|
82
|
+
content: [{ type: 'input_text', text: '<environment_context>\n <cwd>/tmp/project</cwd>\n <shell>zsh</shell>\n</environment_context>' }],
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
JSON.stringify({
|
|
86
|
+
timestamp,
|
|
87
|
+
type: 'response_item',
|
|
88
|
+
payload: {
|
|
89
|
+
type: 'message',
|
|
90
|
+
role: 'user',
|
|
91
|
+
content: [{ type: 'input_text', text: '<collaboration_mode># Collaboration Mode: Default\n</collaboration_mode>' }],
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
JSON.stringify({
|
|
95
|
+
timestamp,
|
|
96
|
+
type: 'response_item',
|
|
97
|
+
payload: {
|
|
98
|
+
type: 'message',
|
|
99
|
+
role: 'user',
|
|
100
|
+
content: [{ type: 'input_text', text: '# AGENTS.md instructions for /tmp/project\n\n<INSTRUCTIONS>\nDo work.\n</INSTRUCTIONS>' }],
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
JSON.stringify({
|
|
104
|
+
timestamp,
|
|
105
|
+
type: 'response_item',
|
|
106
|
+
payload: {
|
|
107
|
+
type: 'message',
|
|
108
|
+
role: 'user',
|
|
109
|
+
content: [{ type: 'input_text', text: prompt }],
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
JSON.stringify({
|
|
113
|
+
timestamp,
|
|
114
|
+
type: 'response_item',
|
|
115
|
+
payload: {
|
|
116
|
+
type: 'message',
|
|
117
|
+
role: 'assistant',
|
|
118
|
+
content: [{ type: 'output_text', text: 'Looking into it now.' }],
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
];
|
|
122
|
+
fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf-8');
|
|
123
|
+
}
|
|
124
|
+
export function writeGeminiSession(tempHome, sessionId, cwd, prompt, timestamp, version = '0.29.5') {
|
|
125
|
+
const versionHome = path.join(tempHome, '.agents', '.history', 'versions', 'gemini', version, 'home');
|
|
126
|
+
const geminiHome = path.join(versionHome, '.gemini');
|
|
127
|
+
const projectHash = crypto.createHash('sha256').update(cwd).digest('hex');
|
|
128
|
+
const chatsDir = path.join(geminiHome, 'tmp', projectHash, 'chats');
|
|
129
|
+
fs.mkdirSync(cwd, { recursive: true });
|
|
130
|
+
fs.mkdirSync(chatsDir, { recursive: true });
|
|
131
|
+
fs.mkdirSync(path.dirname(path.join(tempHome, '.gemini')), { recursive: true });
|
|
132
|
+
const activeGeminiHome = path.join(tempHome, '.gemini');
|
|
133
|
+
if (!fs.existsSync(activeGeminiHome)) {
|
|
134
|
+
fs.symlinkSync(geminiHome, activeGeminiHome);
|
|
135
|
+
}
|
|
136
|
+
fs.writeFileSync(path.join(geminiHome, 'projects.json'), JSON.stringify({ projects: [cwd] }), 'utf-8');
|
|
137
|
+
fs.writeFileSync(path.join(chatsDir, `session-${timestamp.replace(/[:.]/g, '-')}-${sessionId.slice(0, 8)}.json`), JSON.stringify({
|
|
138
|
+
sessionId,
|
|
139
|
+
projectHash,
|
|
140
|
+
startTime: timestamp,
|
|
141
|
+
lastUpdated: timestamp,
|
|
142
|
+
messages: [
|
|
143
|
+
{
|
|
144
|
+
id: `${sessionId}-user`,
|
|
145
|
+
timestamp,
|
|
146
|
+
type: 'user',
|
|
147
|
+
content: [{ text: prompt }],
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: `${sessionId}-assistant`,
|
|
151
|
+
timestamp,
|
|
152
|
+
type: 'gemini',
|
|
153
|
+
content: 'Investigating now.',
|
|
154
|
+
model: 'gemini-3-flash-preview',
|
|
155
|
+
tokens: { total: 1234 },
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
}, null, 2), 'utf-8');
|
|
159
|
+
}
|
|
160
|
+
export function writeOpenClawSetup(tempHome, version = '2026.3.8') {
|
|
161
|
+
const managedHome = path.join(tempHome, '.agents', '.history', 'versions', 'openclaw', version, 'home', '.openclaw');
|
|
162
|
+
fs.mkdirSync(managedHome, { recursive: true });
|
|
163
|
+
const activeHome = path.join(tempHome, '.openclaw');
|
|
164
|
+
fs.mkdirSync(path.dirname(activeHome), { recursive: true });
|
|
165
|
+
if (!fs.existsSync(activeHome)) {
|
|
166
|
+
fs.symlinkSync(managedHome, activeHome);
|
|
167
|
+
}
|
|
168
|
+
const managedWorkspace = path.join(managedHome, 'sergey');
|
|
169
|
+
fs.mkdirSync(managedWorkspace, { recursive: true });
|
|
170
|
+
fs.writeFileSync(path.join(managedHome, 'openclaw.json'), JSON.stringify({
|
|
171
|
+
agents: {
|
|
172
|
+
list: [
|
|
173
|
+
{ id: 'sergey', workspace: path.join(activeHome, 'sergey') },
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
}, null, 2), 'utf-8');
|
|
177
|
+
const binDir = path.join(tempHome, 'bin');
|
|
178
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
179
|
+
const openclawBin = path.join(binDir, 'openclaw');
|
|
180
|
+
fs.writeFileSync(openclawBin, `#!/bin/sh
|
|
181
|
+
if [ "$1" = "--version" ]; then
|
|
182
|
+
echo "openclaw/${version}"
|
|
183
|
+
exit 0
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
if [ "$1" = "channels" ] && [ "$2" = "status" ]; then
|
|
187
|
+
echo "- Telegram sergey (Sergey): enabled, configured, running, out:2h ago, mode:polling, token:config"
|
|
188
|
+
exit 0
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
if [ "$1" = "cron" ] && [ "$2" = "list" ]; then
|
|
192
|
+
echo "ID NAME SCHEDULE NEXT LAST STATUS TARGET AGENT MODEL"
|
|
193
|
+
echo "12345678-1234-1234-1234-123456789abc sergey-hourly cron */30 * * * * in 7h 48m ago ok isolated sergey -"
|
|
194
|
+
exit 0
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
exit 1
|
|
198
|
+
`, 'utf-8');
|
|
199
|
+
fs.chmodSync(openclawBin, 0o755);
|
|
200
|
+
return path.join(activeHome, 'sergey');
|
|
201
|
+
}
|
|
202
|
+
export function runAgents(args, cwd, home, envOverrides = {}) {
|
|
203
|
+
return spawnSync('node', ['--import', tsxLoaderUrl, cliEntry, ...args], {
|
|
204
|
+
cwd,
|
|
205
|
+
env: {
|
|
206
|
+
...process.env,
|
|
207
|
+
HOME: home,
|
|
208
|
+
// os.homedir() (used via homeDir() in discovery) reads USERPROFILE on
|
|
209
|
+
// Windows and ignores HOME, so set both to redirect the home to tempHome.
|
|
210
|
+
USERPROFILE: home,
|
|
211
|
+
PATH: `${path.join(home, 'bin')}${path.delimiter}${process.env.PATH || ''}`,
|
|
212
|
+
// Some fixtures place files at $HOME/.agents/versions/<agent>/<ver>/ as
|
|
213
|
+
// legacy / synthetic state. The bootstrap-time migration would otherwise
|
|
214
|
+
// move those into ~/.agents-system/, breaking workspace-scoped lookups.
|
|
215
|
+
AGENTS_SKIP_MIGRATION: '1',
|
|
216
|
+
NODE_NO_WARNINGS: '1',
|
|
217
|
+
...envOverrides,
|
|
218
|
+
},
|
|
219
|
+
encoding: 'utf-8',
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
export function outputOf(result) {
|
|
223
|
+
return `${result.stdout}${result.stderr}`;
|
|
224
|
+
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -26,6 +26,7 @@ import { registerSetupSecretsCommand } from './setup-secrets.js';
|
|
|
26
26
|
import { registerSetupFleetCommand } from './setup-fleet.js';
|
|
27
27
|
import { registerSetupWatchdogCommand, runWatchdogSetupWizard } from './setup-watchdog.js';
|
|
28
28
|
import { registerAliasCommand } from './alias.js';
|
|
29
|
+
import { registerBetaCommands } from './beta.js';
|
|
29
30
|
import { runPreferencesStep } from './setup-preferences.js';
|
|
30
31
|
import { getConfiguredDefaultProfileName, getProfile, getAutoDetectedProfile, isProfileLaunchableHere } from '../lib/browser/profiles.js';
|
|
31
32
|
import { listInstalledBrowsers } from '../lib/browser/chrome.js';
|
|
@@ -363,7 +364,7 @@ export function registerSetupCommand(program) {
|
|
|
363
364
|
.description('Set up agents-cli, or re-open the capability onboarding hub.')
|
|
364
365
|
.option('-f, --force', 'Re-run setup even if ~/.agents/.system/ already exists (use with caution)')
|
|
365
366
|
.option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents/.system/ yourself)');
|
|
366
|
-
// Capability subcommands: `agents setup browser|computer|mine|secrets|fleet|alias`.
|
|
367
|
+
// Capability subcommands: `agents setup browser|computer|mine|secrets|fleet|alias|beta`.
|
|
367
368
|
// Share/artifact publishing is set up by `agents artifacts setup` (RUSH-2580);
|
|
368
369
|
// the hub below still offers it as a phase via runShareWizard.
|
|
369
370
|
registerSetupBrowserCommand(setupCmd);
|
|
@@ -373,6 +374,7 @@ export function registerSetupCommand(program) {
|
|
|
373
374
|
registerSetupFleetCommand(setupCmd);
|
|
374
375
|
registerSetupWatchdogCommand(setupCmd);
|
|
375
376
|
registerAliasCommand(setupCmd);
|
|
377
|
+
registerBetaCommands(setupCmd);
|
|
376
378
|
setupCmd.command('status')
|
|
377
379
|
.description('Show setup readiness for core, browser, computer, secrets, fleet, share, watchdog, and preferences.')
|
|
378
380
|
.option('--json', 'print machine-readable JSON')
|
|
@@ -400,6 +402,7 @@ export function registerSetupCommand(program) {
|
|
|
400
402
|
agents setup fleet
|
|
401
403
|
agents setup watchdog
|
|
402
404
|
agents setup alias add teams
|
|
405
|
+
agents setup beta enable factory
|
|
403
406
|
`,
|
|
404
407
|
notes: `
|
|
405
408
|
What it does:
|
|
@@ -416,6 +419,7 @@ export function registerSetupCommand(program) {
|
|
|
416
419
|
agents setup fleet # discover Tailscale devices + configure SSH access
|
|
417
420
|
agents setup watchdog # choose which devices run the daemon watchdog pass
|
|
418
421
|
agents setup alias add teams # PATH shorthand: teams runs agents teams
|
|
422
|
+
agents setup beta enable factory # opt into preview features
|
|
419
423
|
|
|
420
424
|
To install CLIs from agents.yaml and sync resources into version homes:
|
|
421
425
|
agents sync --local -y
|