@phnx-labs/agents-cli 1.22.25 → 1.22.27
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 +427 -0
- package/README.md +35 -3
- package/dist/bin/agents +0 -0
- package/dist/browser.js +14 -4
- package/dist/commands/apply.js +52 -8
- package/dist/commands/bench.d.ts +2 -0
- package/dist/commands/bench.js +101 -0
- package/dist/commands/browser.js +35 -0
- package/dist/commands/doctor.js +32 -10
- package/dist/commands/exec.js +17 -16
- package/dist/commands/insights.d.ts +25 -19
- package/dist/commands/insights.js +146 -38
- package/dist/commands/output.js +100 -25
- package/dist/commands/reconnect.d.ts +46 -0
- package/dist/commands/reconnect.js +109 -0
- package/dist/commands/resume.d.ts +11 -0
- package/dist/commands/resume.js +51 -0
- package/dist/commands/routines.js +2 -2
- package/dist/commands/secrets.d.ts +2 -8
- package/dist/commands/secrets.js +29 -105
- package/dist/commands/sessions-picker.js +12 -7
- package/dist/commands/sessions.d.ts +47 -1
- package/dist/commands/sessions.js +349 -37
- package/dist/commands/setup-secrets.d.ts +1 -0
- package/dist/commands/setup-secrets.js +1 -1
- package/dist/commands/setup.d.ts +26 -3
- package/dist/commands/setup.js +105 -46
- package/dist/commands/ssh.js +184 -11
- package/dist/commands/teams.d.ts +6 -0
- package/dist/commands/teams.js +46 -3
- package/dist/commands/trends.d.ts +8 -0
- package/dist/commands/trends.js +10 -156
- package/dist/index.js +17 -21
- package/dist/lib/agents.d.ts +11 -0
- package/dist/lib/agents.js +29 -2
- package/dist/lib/analytics/dashboard.d.ts +10 -6
- package/dist/lib/analytics/dashboard.js +6 -4
- package/dist/lib/analytics/mix-commands.d.ts +53 -0
- package/dist/lib/analytics/mix-commands.js +229 -0
- package/dist/lib/analytics/recipes.d.ts +19 -14
- package/dist/lib/analytics/recipes.js +4 -2
- package/dist/lib/auth-health.d.ts +47 -3
- package/dist/lib/auth-health.js +74 -15
- package/dist/lib/bench/index.d.ts +4 -0
- package/dist/lib/bench/index.js +4 -0
- package/dist/lib/bench/runner.d.ts +16 -0
- package/dist/lib/bench/runner.js +111 -0
- package/dist/lib/bench/schema.d.ts +5 -0
- package/dist/lib/bench/schema.js +91 -0
- package/dist/lib/bench/storage.d.ts +5 -0
- package/dist/lib/bench/storage.js +32 -0
- package/dist/lib/bench/types.d.ts +40 -0
- package/dist/lib/bench/types.js +1 -0
- package/dist/lib/browser/ipc.d.ts +26 -0
- package/dist/lib/browser/ipc.js +139 -24
- package/dist/lib/browser/profiles.d.ts +11 -0
- package/dist/lib/browser/profiles.js +1 -1
- package/dist/lib/browser/stream.d.ts +14 -0
- package/dist/lib/browser/stream.js +71 -0
- package/dist/lib/channels/owner-sink.d.ts +27 -0
- package/dist/lib/channels/owner-sink.js +93 -0
- package/dist/lib/claude-account-token.d.ts +2 -0
- package/dist/lib/claude-account-token.js +27 -1
- package/dist/lib/crabbox/cli.d.ts +2 -0
- package/dist/lib/crabbox/cli.js +2 -0
- package/dist/lib/crabbox/lease.js +7 -1
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/devices/connect.d.ts +2 -0
- package/dist/lib/devices/connect.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +10 -1
- package/dist/lib/devices/doctor-findings.js +47 -1
- package/dist/lib/devices/harness-inventory.d.ts +97 -0
- package/dist/lib/devices/harness-inventory.js +0 -0
- package/dist/lib/devices/registry.d.ts +2 -0
- package/dist/lib/devices/ssh-config.js +3 -0
- package/dist/lib/devices/windows-ssh-enrollment.d.ts +20 -0
- package/dist/lib/devices/windows-ssh-enrollment.js +98 -0
- package/dist/lib/exec.bench.d.ts +1 -0
- package/dist/lib/exec.bench.js +186 -0
- package/dist/lib/exec.js +18 -8
- package/dist/lib/fleet/apply.d.ts +59 -3
- package/dist/lib/fleet/apply.js +191 -12
- package/dist/lib/fleet/remote-login.d.ts +4 -3
- package/dist/lib/fleet/remote-login.js +11 -9
- package/dist/lib/fleet/types.d.ts +21 -2
- package/dist/lib/gemini-settings.d.ts +0 -1
- package/dist/lib/gemini-settings.js +12 -7
- package/dist/lib/hooks/cache.js +15 -0
- package/dist/lib/hooks.d.ts +8 -5
- package/dist/lib/hooks.js +10 -6
- package/dist/lib/hosts/dispatch.js +7 -3
- package/dist/lib/hosts/passthrough.d.ts +45 -0
- package/dist/lib/hosts/passthrough.js +59 -8
- package/dist/lib/hosts/progress.d.ts +2 -1
- package/dist/lib/hosts/progress.js +4 -3
- package/dist/lib/hosts/providers/devices.js +1 -0
- package/dist/lib/hosts/ready.d.ts +6 -2
- package/dist/lib/hosts/ready.js +33 -8
- package/dist/lib/hosts/reconcile.d.ts +1 -1
- package/dist/lib/hosts/reconcile.js +16 -7
- package/dist/lib/hosts/reconnect.d.ts +56 -21
- package/dist/lib/hosts/reconnect.js +128 -71
- package/dist/lib/hosts/registry.d.ts +2 -2
- package/dist/lib/hosts/registry.js +3 -5
- package/dist/lib/hosts/remote-cmd.d.ts +17 -0
- package/dist/lib/hosts/remote-cmd.js +29 -0
- package/dist/lib/hosts/tasks.d.ts +2 -0
- package/dist/lib/hosts/types.d.ts +1 -0
- package/dist/lib/hosts/types.js +3 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +9 -6
- package/dist/lib/menubar/install-menubar.js +20 -9
- package/dist/lib/models.d.ts +30 -1
- package/dist/lib/models.js +42 -144
- package/dist/lib/pricing/cost.d.ts +9 -0
- package/dist/lib/pricing/cost.js +24 -0
- package/dist/lib/pricing/index.d.ts +1 -1
- package/dist/lib/pricing/index.js +1 -1
- package/dist/lib/redact.js +8 -3
- package/dist/lib/remote-agents-json.d.ts +32 -0
- package/dist/lib/remote-agents-json.js +47 -16
- package/dist/lib/resource-profiles.js +1 -2
- package/dist/lib/routine-notify-owner.d.ts +102 -0
- package/dist/lib/routine-notify-owner.js +232 -0
- package/dist/lib/routines.d.ts +10 -0
- package/dist/lib/routines.js +14 -2
- package/dist/lib/runner.d.ts +9 -4
- package/dist/lib/runner.js +182 -30
- package/dist/lib/sandbox.d.ts +0 -2
- package/dist/lib/sandbox.js +2 -19
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.js +3 -2
- package/dist/lib/secrets/lease.d.ts +25 -0
- package/dist/lib/secrets/lease.js +44 -0
- package/dist/lib/secrets/push.d.ts +94 -0
- package/dist/lib/secrets/push.js +145 -0
- package/dist/lib/secrets/reaper.d.ts +15 -1
- package/dist/lib/secrets/reaper.js +30 -3
- package/dist/lib/self-update.d.ts +20 -5
- package/dist/lib/self-update.js +93 -16
- package/dist/lib/session/db.d.ts +38 -3
- package/dist/lib/session/db.js +273 -16
- package/dist/lib/session/discover.d.ts +22 -1
- package/dist/lib/session/discover.js +239 -51
- package/dist/lib/session/insights.d.ts +37 -0
- package/dist/lib/session/insights.js +219 -9
- package/dist/lib/session/recovery.d.ts +17 -1
- package/dist/lib/session/recovery.js +111 -4
- package/dist/lib/session/remote-list.d.ts +10 -9
- package/dist/lib/session/remote-list.js +25 -23
- package/dist/lib/session/resume-owner.d.ts +55 -0
- package/dist/lib/session/resume-owner.js +69 -0
- package/dist/lib/session/team-filter.d.ts +65 -0
- package/dist/lib/session/team-filter.js +98 -3
- package/dist/lib/session/tool-index.js +133 -22
- package/dist/lib/session/tool-store.d.ts +26 -2
- package/dist/lib/session/tool-store.js +36 -17
- package/dist/lib/session/types.d.ts +33 -0
- package/dist/lib/smart-launch.d.ts +31 -5
- package/dist/lib/smart-launch.js +43 -7
- package/dist/lib/ssh-exec.d.ts +27 -0
- package/dist/lib/ssh-exec.js +42 -3
- package/dist/lib/ssh-tunnel.d.ts +3 -2
- package/dist/lib/ssh-tunnel.js +25 -16
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +6 -0
- package/dist/lib/teams/agents.d.ts +13 -0
- package/dist/lib/teams/agents.js +75 -7
- package/dist/lib/teams/placement-probe.d.ts +21 -0
- package/dist/lib/teams/placement-probe.js +135 -0
- package/dist/lib/teams/scheduler.d.ts +74 -1
- package/dist/lib/teams/scheduler.js +187 -10
- package/dist/lib/tmux/session.d.ts +8 -0
- package/dist/lib/tmux/session.js +22 -0
- package/dist/lib/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/commands/setup.js
CHANGED
|
@@ -26,6 +26,13 @@ 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 { runPreferencesStep } from './setup-preferences.js';
|
|
29
|
+
import { getConfiguredDefaultProfileName, getProfile, isProfileLaunchableHere, DEFAULT_BROWSER_PROFILE_NAME } from '../lib/browser/profiles.js';
|
|
30
|
+
import { listInstalledBrowsers } from '../lib/browser/chrome.js';
|
|
31
|
+
import { probeComputerTrust } from './computer.js';
|
|
32
|
+
import { readShareConfig } from '../lib/share/config.js';
|
|
33
|
+
import { loadDevices } from '../lib/devices/registry.js';
|
|
34
|
+
import { getConfigValue } from '../lib/device-config.js';
|
|
35
|
+
import { setupSecretsPrefsPath } from './setup-secrets.js';
|
|
29
36
|
const HOME = os.homedir();
|
|
30
37
|
/**
|
|
31
38
|
* Import an existing unmanaged agent installation into agents-cli.
|
|
@@ -70,9 +77,9 @@ export async function runSetup(program, options = {}) {
|
|
|
70
77
|
const agentsDir = getAgentsDir();
|
|
71
78
|
const alreadyConfigured = isGitRepo(agentsDir);
|
|
72
79
|
if (alreadyConfigured && !options.force) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
if (options.suppressFooter)
|
|
81
|
+
return;
|
|
82
|
+
await (options.runHub ?? runSetupHub)();
|
|
76
83
|
return;
|
|
77
84
|
}
|
|
78
85
|
// Detect existing installations BEFORE cloning (they won't exist after if we import)
|
|
@@ -193,11 +200,7 @@ export async function runSetup(program, options = {}) {
|
|
|
193
200
|
// Fresh-machine hub: offer to set up the optional capabilities that need their
|
|
194
201
|
// own guided flow. TTY-only and fully opt-in — a non-interactive `agents setup`
|
|
195
202
|
// stops at the system-repo bootstrap above, unchanged.
|
|
196
|
-
await runSetupHub();
|
|
197
|
-
// Preferences step: the two questions that keep agents off the wrong machine —
|
|
198
|
-
// which box you sit at (interactive host) and which browser agents drive here.
|
|
199
|
-
// TTY-only, skippable, and writes the same keys as `agents devices …`.
|
|
200
|
-
await runPreferencesStep();
|
|
203
|
+
await (options.runHub ?? runSetupHub)();
|
|
201
204
|
console.log(chalk.bold('\nSetup complete. Try:'));
|
|
202
205
|
console.log(chalk.cyan(' agents view ') + chalk.gray(' # see what\'s installed'));
|
|
203
206
|
console.log(chalk.cyan(' agents run <agent> "hello" ') + chalk.gray(' # run an agent'));
|
|
@@ -229,42 +232,88 @@ export async function ensureInitialized(program) {
|
|
|
229
232
|
}
|
|
230
233
|
await runSetup(program, { suppressFooter: true });
|
|
231
234
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
export async function getSetupStatus() {
|
|
236
|
+
const configuredBrowserProfile = getConfiguredDefaultProfileName();
|
|
237
|
+
const browserProfile = await getProfile(configuredBrowserProfile ?? DEFAULT_BROWSER_PROFILE_NAME);
|
|
238
|
+
const browserReady = browserProfile !== null && isProfileLaunchableHere(browserProfile);
|
|
239
|
+
const installedBrowsers = listInstalledBrowsers();
|
|
240
|
+
const computerState = process.platform === 'darwin' ? (await probeComputerTrust() ? 'ready' : 'missing') : 'n/a';
|
|
241
|
+
const devices = await loadDevices();
|
|
242
|
+
const coreReady = isGitRepo(getAgentsDir());
|
|
243
|
+
const secretsReady = fs.existsSync(setupSecretsPrefsPath());
|
|
244
|
+
const shareConfig = readShareConfig();
|
|
245
|
+
const watchdogEnabled = getConfigValue('watchdog.enabled').value === true;
|
|
246
|
+
const interactiveHost = getConfigValue('interactive.host').value;
|
|
247
|
+
const defaultBrowser = getConfigValue('browser.profile').value;
|
|
248
|
+
return [
|
|
249
|
+
{ phase: 'core', state: coreReady ? 'ready' : 'missing', detail: coreReady ? 'system repo ready' : 'system repo missing' },
|
|
250
|
+
{ phase: 'browser', state: browserReady ? 'ready' : 'missing', detail: browserReady ? `profile ${browserProfile.name}` : browserProfile ? `profile ${browserProfile.name} cannot launch here` : installedBrowsers.length ? 'no default profile' : 'no supported browser found' },
|
|
251
|
+
{ phase: 'computer', state: computerState, detail: computerState === 'ready' ? 'helper trusted' : computerState === 'n/a' ? 'macOS local setup only' : 'helper not running or not trusted' },
|
|
252
|
+
{ phase: 'secrets', state: secretsReady ? 'ready' : 'missing', detail: secretsReady ? 'defaults chosen' : 'defaults not chosen' },
|
|
253
|
+
{ phase: 'fleet', state: Object.keys(devices).length ? 'ready' : 'missing', detail: Object.keys(devices).length ? `${Object.keys(devices).length} device${Object.keys(devices).length === 1 ? '' : 's'} registered` : 'no devices registered' },
|
|
254
|
+
{ phase: 'share', state: shareConfig ? 'ready' : 'missing', detail: shareConfig?.baseUrl ?? 'endpoint not configured' },
|
|
255
|
+
{ phase: 'watchdog', state: watchdogEnabled ? 'ready' : 'missing', detail: watchdogEnabled ? 'enabled on this device' : 'disabled on this device' },
|
|
256
|
+
{ phase: 'preferences', state: interactiveHost || defaultBrowser ? 'ready' : 'missing', detail: [interactiveHost && `host ${interactiveHost}`, defaultBrowser && `browser ${defaultBrowser}`].filter(Boolean).join(' · ') || 'interactive host and browser unset' },
|
|
257
|
+
];
|
|
258
|
+
}
|
|
259
|
+
export function renderSetupStatus(rows) {
|
|
260
|
+
console.log(chalk.bold('\nagents setup — onboarding\n'));
|
|
261
|
+
for (const row of rows) {
|
|
262
|
+
const marker = row.state === 'ready' ? chalk.green('[x]') : row.state === 'n/a' ? chalk.gray('[-]') : chalk.yellow('[ ]');
|
|
263
|
+
const label = row.phase[0].toUpperCase() + row.phase.slice(1);
|
|
264
|
+
console.log(` ${marker} ${label.padEnd(12)} ${chalk.gray(row.detail)}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function runSetupPhase(phase) {
|
|
268
|
+
if (phase === 'browser')
|
|
269
|
+
await runBrowserWizard();
|
|
270
|
+
else if (phase === 'computer')
|
|
271
|
+
await runComputerWizard();
|
|
272
|
+
else if (phase === 'share')
|
|
273
|
+
await runShareWizard();
|
|
274
|
+
else if (phase === 'secrets')
|
|
275
|
+
await import('./setup-secrets.js').then((m) => m.runSecretsSetupWizard());
|
|
276
|
+
else if (phase === 'fleet')
|
|
277
|
+
await import('./setup-fleet.js').then((m) => m.runFleetSetupWizard());
|
|
278
|
+
else if (phase === 'watchdog')
|
|
279
|
+
await runWatchdogSetupWizard();
|
|
280
|
+
else
|
|
281
|
+
await runPreferencesStep();
|
|
282
|
+
}
|
|
283
|
+
export async function runSetupHub(deps = {}) {
|
|
284
|
+
const interactive = deps.interactive ?? isInteractiveTerminal();
|
|
285
|
+
if (!interactive) {
|
|
286
|
+
const rows = await getSetupStatus();
|
|
287
|
+
renderSetupStatus(rows);
|
|
288
|
+
if (rows.some((row) => row.state === 'missing'))
|
|
289
|
+
process.exitCode = 1;
|
|
240
290
|
return;
|
|
291
|
+
}
|
|
241
292
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
{
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
293
|
+
while (true) {
|
|
294
|
+
const rows = await getSetupStatus();
|
|
295
|
+
renderSetupStatus(rows);
|
|
296
|
+
let pick;
|
|
297
|
+
if (deps.selectPhase) {
|
|
298
|
+
pick = await deps.selectPhase(rows);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
const { select } = await import('@inquirer/prompts');
|
|
302
|
+
pick = await select({
|
|
303
|
+
message: 'Choose a phase to configure',
|
|
304
|
+
choices: [
|
|
305
|
+
...rows.filter((row) => row.phase !== 'core' && row.state !== 'n/a').map((row) => ({
|
|
306
|
+
name: `${row.state === 'ready' ? '[x]' : '[ ]'} ${row.phase.padEnd(12)} ${row.detail}`,
|
|
307
|
+
value: row.phase,
|
|
308
|
+
})),
|
|
309
|
+
{ name: 'Exit setup', value: 'exit' },
|
|
310
|
+
],
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
if (pick === 'exit')
|
|
314
|
+
return;
|
|
255
315
|
console.log();
|
|
256
|
-
|
|
257
|
-
await runBrowserWizard();
|
|
258
|
-
else if (pick === 'computer')
|
|
259
|
-
await runComputerWizard();
|
|
260
|
-
else if (pick === 'share')
|
|
261
|
-
await runShareWizard();
|
|
262
|
-
else if (pick === 'secrets')
|
|
263
|
-
await import('./setup-secrets.js').then((m) => m.runSecretsSetupWizard());
|
|
264
|
-
else if (pick === 'fleet')
|
|
265
|
-
await import('./setup-fleet.js').then((m) => m.runFleetSetupWizard());
|
|
266
|
-
else if (pick === 'watchdog')
|
|
267
|
-
await runWatchdogSetupWizard();
|
|
316
|
+
await (deps.runPhase ?? runSetupPhase)(pick);
|
|
268
317
|
}
|
|
269
318
|
}
|
|
270
319
|
catch (err) {
|
|
@@ -277,7 +326,7 @@ async function runSetupHub() {
|
|
|
277
326
|
export function registerSetupCommand(program) {
|
|
278
327
|
const setupCmd = program
|
|
279
328
|
.command('setup')
|
|
280
|
-
.description('
|
|
329
|
+
.description('Set up agents-cli, or re-open the capability onboarding hub.')
|
|
281
330
|
.option('-f, --force', 'Re-run setup even if ~/.agents/.system/ already exists (use with caution)')
|
|
282
331
|
.option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents/.system/ yourself)');
|
|
283
332
|
// Capability subcommands: `agents setup browser|computer|share|mine|secrets|fleet`.
|
|
@@ -288,6 +337,18 @@ export function registerSetupCommand(program) {
|
|
|
288
337
|
registerSetupSecretsCommand(setupCmd);
|
|
289
338
|
registerSetupFleetCommand(setupCmd);
|
|
290
339
|
registerSetupWatchdogCommand(setupCmd);
|
|
340
|
+
setupCmd.command('status')
|
|
341
|
+
.description('Show setup readiness for core, browser, computer, secrets, fleet, share, watchdog, and preferences.')
|
|
342
|
+
.option('--json', 'print machine-readable JSON')
|
|
343
|
+
.action(async (options) => {
|
|
344
|
+
const rows = await getSetupStatus();
|
|
345
|
+
if (options.json)
|
|
346
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
347
|
+
else
|
|
348
|
+
renderSetupStatus(rows);
|
|
349
|
+
if (rows.some((row) => row.state === 'missing'))
|
|
350
|
+
process.exitCode = 1;
|
|
351
|
+
});
|
|
291
352
|
setHelpSections(setupCmd, {
|
|
292
353
|
examples: `
|
|
293
354
|
# First-time setup (clones the system repo into ~/.agents/.system/)
|
|
@@ -308,10 +369,8 @@ export function registerSetupCommand(program) {
|
|
|
308
369
|
What it does:
|
|
309
370
|
1. Clones the system repo into ~/.agents/.system/
|
|
310
371
|
2. Imports any unmanaged agent installations it finds
|
|
311
|
-
3.
|
|
312
|
-
4.
|
|
313
|
-
and which browser agents drive here — both skippable, both the same
|
|
314
|
-
keys 'agents devices set-interactive' / 'browser profiles set-default' write
|
|
372
|
+
3. Opens a re-runnable capability menu with live ready/missing status
|
|
373
|
+
4. Delegates each selection to its existing setup wizard
|
|
315
374
|
|
|
316
375
|
Capability setup can also be run any time on its own:
|
|
317
376
|
agents setup browser # detect a browser + create the default profile
|
package/dist/commands/ssh.js
CHANGED
|
@@ -31,7 +31,7 @@ import { resolveDeviceTarget, splitUserHost } from '../lib/devices/resolve-targe
|
|
|
31
31
|
import { clearPendingSentinel } from '../lib/devices/pending.js';
|
|
32
32
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
33
33
|
import { hostNameFor, renderSshConfig } from '../lib/devices/ssh-config.js';
|
|
34
|
-
import { ASKPASS_BUNDLE_ENV, ASKPASS_KEY_ENV, buildSshInvocation, fleetDialTarget, writeAskpassShim, } from '../lib/devices/connect.js';
|
|
34
|
+
import { ASKPASS_BUNDLE_ENV, ASKPASS_KEY_ENV, buildSshInvocation, deviceIdentityArgs, fleetDialTarget, writeAskpassShim, } from '../lib/devices/connect.js';
|
|
35
35
|
import { ensureManagedKnownHostsDir, isHostPinned } from '../lib/devices/known-hosts.js';
|
|
36
36
|
import { shouldSyncTerminfo, syncTerminfoToDevice, terminfoHostKey } from '../lib/devices/terminfo.js';
|
|
37
37
|
import { fanOutDevices, fleetHealthSkip, planFleetTargets, remoteFleetTargets, runFleet, skipLabel, upgradeCommand, } from '../lib/devices/fleet.js';
|
|
@@ -44,6 +44,7 @@ import { checkAllClis } from '../lib/teams/agents.js';
|
|
|
44
44
|
import { buildRemoteAgentsInvocation } from '../lib/hosts/remote-cmd.js';
|
|
45
45
|
import { sshExecAsync } from '../lib/ssh-exec.js';
|
|
46
46
|
import { ALL_AGENT_IDS } from '../lib/agents.js';
|
|
47
|
+
import { collectLocalHarnessInventory, groupByAccount, renderAccountsMatrix, renderHarnessMatrix, } from '../lib/devices/harness-inventory.js';
|
|
47
48
|
import { crabboxList, crabboxFind, crabboxSshArgv } from '../lib/crabbox/cli.js';
|
|
48
49
|
import { boxAddress, boxStatus, fmtIdleShort, fmtExpiresShort } from './lease.js';
|
|
49
50
|
import { authCellColor, formatCheckedAge, isDeadVerdict, probeLocalFleetAuth, readAuthHealthCache, summarizeHostAuth, summarizeVerdicts, verdictColor, verdictLabel, writeFleetAuthRows, } from '../lib/auth-health.js';
|
|
@@ -351,7 +352,7 @@ async function probeRemoteFleetStatus(target) {
|
|
|
351
352
|
const isWin = /^win/i.test((target.platform ?? '').trim());
|
|
352
353
|
const env = isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' };
|
|
353
354
|
const cmd = buildRemoteAgentsInvocation(['devices', 'status', '--local', '--json'], undefined, isWin ? 'windows' : undefined, env);
|
|
354
|
-
const res = await sshExecAsync(target.dialTarget, cmd, { timeoutMs: 15000, multiplex: true });
|
|
355
|
+
const res = await sshExecAsync(target.dialTarget, cmd, { timeoutMs: 15000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
355
356
|
if (res.code !== 0) {
|
|
356
357
|
throw new Error(res.timedOut ? 'timed out' : (res.stderr.trim() || `exit ${res.code ?? 'unknown'}`));
|
|
357
358
|
}
|
|
@@ -361,10 +362,10 @@ async function probeRemoteHealth(target) {
|
|
|
361
362
|
const isWin = /^win/i.test((target.platform ?? '').trim());
|
|
362
363
|
const env = isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' };
|
|
363
364
|
const versionCmd = buildRemoteAgentsInvocation(['--version'], undefined, isWin ? 'windows' : undefined, env);
|
|
364
|
-
const versionRes = await sshExecAsync(target.dialTarget, versionCmd, { timeoutMs: 15000, multiplex: true });
|
|
365
|
+
const versionRes = await sshExecAsync(target.dialTarget, versionCmd, { timeoutMs: 15000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
365
366
|
const version = versionRes.code === 0 ? versionRes.stdout.trim().split(/\s+/)[0] || null : null;
|
|
366
367
|
const doctorCmd = buildRemoteAgentsInvocation(['doctor', '--json'], undefined, isWin ? 'windows' : undefined, env);
|
|
367
|
-
const doctorRes = await sshExecAsync(target.dialTarget, doctorCmd, { timeoutMs: 30000, multiplex: true });
|
|
368
|
+
const doctorRes = await sshExecAsync(target.dialTarget, doctorCmd, { timeoutMs: 30000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
368
369
|
if (doctorRes.code !== 0) {
|
|
369
370
|
throw new Error(doctorRes.timedOut ? 'timed out' : (doctorRes.stderr.trim() || `exit ${doctorRes.code ?? 'unknown'}`));
|
|
370
371
|
}
|
|
@@ -425,6 +426,7 @@ async function runFleetStatus(opts) {
|
|
|
425
426
|
// this is trusted on the default path, not just under `--refresh`.
|
|
426
427
|
skip: fleetHealthSkip(t.skip, statsMap.get(t.device.name)),
|
|
427
428
|
dialTarget: fleetDialTarget(t.device),
|
|
429
|
+
extraSshArgs: deviceIdentityArgs(t.device),
|
|
428
430
|
}));
|
|
429
431
|
const remote = await fanOutDevices(remoteTargets, probeRemoteHealth);
|
|
430
432
|
for (const result of remote) {
|
|
@@ -533,7 +535,7 @@ async function probeRemoteAuth(target) {
|
|
|
533
535
|
const isWin = /^win/i.test((target.platform ?? '').trim());
|
|
534
536
|
const env = isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' };
|
|
535
537
|
const cmd = buildRemoteAgentsInvocation(['devices', 'ping', '--local', '--json'], undefined, isWin ? 'windows' : undefined, env);
|
|
536
|
-
const res = await sshExecAsync(target.dialTarget, cmd, { timeoutMs: 15000, multiplex: true });
|
|
538
|
+
const res = await sshExecAsync(target.dialTarget, cmd, { timeoutMs: 15000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
537
539
|
if (res.code !== 0) {
|
|
538
540
|
throw new Error(res.timedOut ? 'timed out' : (res.stderr.trim() || `exit ${res.code ?? 'unknown'}`));
|
|
539
541
|
}
|
|
@@ -601,6 +603,7 @@ async function runFleetPing(opts) {
|
|
|
601
603
|
platform: t.device.platform,
|
|
602
604
|
skip: t.skip,
|
|
603
605
|
dialTarget: fleetDialTarget(t.device),
|
|
606
|
+
extraSshArgs: deviceIdentityArgs(t.device),
|
|
604
607
|
}));
|
|
605
608
|
const probeable = remoteTargets.filter((t) => !t.skip).length;
|
|
606
609
|
const spinner = isInteractiveTerminal() && !opts.json
|
|
@@ -645,6 +648,118 @@ async function runFleetPing(opts) {
|
|
|
645
648
|
if (opts.strict && anyBad)
|
|
646
649
|
process.exitCode = 1;
|
|
647
650
|
}
|
|
651
|
+
/** SSH into a host and read its raw harness rows (the `--local --json` worker). */
|
|
652
|
+
async function probeRemoteHarnesses(target, refresh) {
|
|
653
|
+
const isWin = /^win/i.test((target.platform ?? '').trim());
|
|
654
|
+
const env = isWin ? undefined : { PATH: '$HOME/.agents/.cache/shims:$HOME/.local/bin:$PATH' };
|
|
655
|
+
const args = ['devices', 'harnesses', '--local', '--json'];
|
|
656
|
+
if (refresh)
|
|
657
|
+
args.push('--refresh');
|
|
658
|
+
const cmd = buildRemoteAgentsInvocation(args, undefined, isWin ? 'windows' : undefined, env);
|
|
659
|
+
const res = await sshExecAsync(target.dialTarget, cmd, { timeoutMs: 15000, multiplex: true, extraSshArgs: target.extraSshArgs });
|
|
660
|
+
if (res.code !== 0) {
|
|
661
|
+
throw new Error(res.timedOut ? 'timed out' : (res.stderr.trim() || `exit ${res.code ?? 'unknown'}`));
|
|
662
|
+
}
|
|
663
|
+
const parsed = JSON.parse(res.stdout);
|
|
664
|
+
return parsed.rows ?? [];
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Gather harness rows across the fleet: THIS host in process, every reachable
|
|
668
|
+
* peer over SSH. Shared by both `harnesses` and `accounts` (they differ only in
|
|
669
|
+
* how the rows are rendered). Honors an optional `--device` allowlist on both
|
|
670
|
+
* the local and remote rows. Bounded by the same per-device + overall deadlines
|
|
671
|
+
* as `fleet ping`, so one unreachable box can never stall the glance.
|
|
672
|
+
*/
|
|
673
|
+
async function collectFleetHarnesses(opts) {
|
|
674
|
+
const self = machineId();
|
|
675
|
+
const want = opts.devices?.length ? new Set(opts.devices) : null;
|
|
676
|
+
const results = [];
|
|
677
|
+
if (!want || want.has(self)) {
|
|
678
|
+
const localRows = await collectLocalHarnessInventory({ agents: opts.agents, refresh: opts.refresh });
|
|
679
|
+
results.push({ host: self, rows: localRows });
|
|
680
|
+
}
|
|
681
|
+
const reg = await loadDevices();
|
|
682
|
+
const planned = planFleetTargets(reg);
|
|
683
|
+
let remoteTargets = remoteFleetTargets(planned, self).map((t) => ({
|
|
684
|
+
name: t.device.name,
|
|
685
|
+
platform: t.device.platform,
|
|
686
|
+
skip: t.skip,
|
|
687
|
+
dialTarget: fleetDialTarget(t.device),
|
|
688
|
+
extraSshArgs: deviceIdentityArgs(t.device),
|
|
689
|
+
}));
|
|
690
|
+
if (want)
|
|
691
|
+
remoteTargets = remoteTargets.filter((t) => want.has(t.name));
|
|
692
|
+
if (remoteTargets.length > 0) {
|
|
693
|
+
const probeable = remoteTargets.filter((t) => !t.skip).length;
|
|
694
|
+
const spinner = isInteractiveTerminal() && !opts.json
|
|
695
|
+
? ora(`Probing ${probeable} device${probeable === 1 ? '' : 's'}…`).start()
|
|
696
|
+
: undefined;
|
|
697
|
+
let remote;
|
|
698
|
+
try {
|
|
699
|
+
const fanOut = fanOutDevices(remoteTargets, (t) => probeRemoteHarnesses(t, !!opts.refresh), { perDeviceTimeoutMs: 15_000 });
|
|
700
|
+
remote = await raceFleetPingDeadline(fanOut, remoteTargets, 30_000);
|
|
701
|
+
}
|
|
702
|
+
finally {
|
|
703
|
+
spinner?.stop();
|
|
704
|
+
}
|
|
705
|
+
for (const r of remote) {
|
|
706
|
+
if (r.status === 'ok' && r.value) {
|
|
707
|
+
results.push({ host: r.name, rows: r.value });
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
results.push({
|
|
711
|
+
host: r.name,
|
|
712
|
+
rows: [],
|
|
713
|
+
error: r.error,
|
|
714
|
+
skipped: r.reason ? String(r.reason) : undefined,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return results;
|
|
720
|
+
}
|
|
721
|
+
async function runDevicesHarnesses(opts) {
|
|
722
|
+
if (opts.local) {
|
|
723
|
+
const rows = await collectLocalHarnessInventory({ agents: opts.agents, refresh: opts.refresh });
|
|
724
|
+
if (opts.json)
|
|
725
|
+
console.log(JSON.stringify({ host: machineId(), rows }));
|
|
726
|
+
else
|
|
727
|
+
for (const line of renderHarnessMatrix([{ host: machineId(), rows }]))
|
|
728
|
+
console.log(line);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const results = await collectFleetHarnesses(opts);
|
|
732
|
+
if (opts.json)
|
|
733
|
+
console.log(JSON.stringify(results, null, 2));
|
|
734
|
+
else
|
|
735
|
+
for (const line of renderHarnessMatrix(results))
|
|
736
|
+
console.log(line);
|
|
737
|
+
}
|
|
738
|
+
async function runDevicesAccounts(opts) {
|
|
739
|
+
if (opts.local) {
|
|
740
|
+
const rows = await collectLocalHarnessInventory({ agents: opts.agents, refresh: opts.refresh });
|
|
741
|
+
if (opts.json)
|
|
742
|
+
console.log(JSON.stringify({ host: machineId(), accounts: groupByAccount(rows) }));
|
|
743
|
+
else
|
|
744
|
+
for (const line of renderAccountsMatrix([{ host: machineId(), rows }]))
|
|
745
|
+
console.log(line);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const results = await collectFleetHarnesses(opts);
|
|
749
|
+
if (opts.json) {
|
|
750
|
+
const grouped = results.map((r) => ({
|
|
751
|
+
host: r.host,
|
|
752
|
+
error: r.error,
|
|
753
|
+
skipped: r.skipped,
|
|
754
|
+
accounts: groupByAccount(r.rows),
|
|
755
|
+
}));
|
|
756
|
+
console.log(JSON.stringify(grouped, null, 2));
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
for (const line of renderAccountsMatrix(results))
|
|
760
|
+
console.log(line);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
648
763
|
/** Resolve an {@link AuthCellColor} to a chalk painter. Single map for cells + labels. */
|
|
649
764
|
const CELL_PAINT = {
|
|
650
765
|
green: chalk.green,
|
|
@@ -777,6 +892,7 @@ Typical workflow:
|
|
|
777
892
|
agents devices configure mac-mini --max-agents 4 --scheduler off
|
|
778
893
|
agents devices note mac-mini "runs the releases — don't reboot"
|
|
779
894
|
agents devices set win-mini --auth password --bundle muqsit
|
|
895
|
+
agents devices set worker --auth key --identity-file ~/.ssh/worker_ed25519
|
|
780
896
|
agents devices render --write # write ~/.ssh/config.d/agents include
|
|
781
897
|
agents fleet update # roll out latest agents-cli to every online device
|
|
782
898
|
agents fleet run uname -a # run a command on every online device
|
|
@@ -1219,6 +1335,59 @@ Typical workflow:
|
|
|
1219
1335
|
const verbose = opts.verbose ?? Boolean(cmd.optsWithGlobals().verbose);
|
|
1220
1336
|
await runFleetPing({ ...opts, verbose });
|
|
1221
1337
|
});
|
|
1338
|
+
const csvList = (s) => s ? s.split(',').map((x) => x.trim()).filter(Boolean) : undefined;
|
|
1339
|
+
const harnessInvOpts = (opts) => ({
|
|
1340
|
+
agents: csvList(opts.agents),
|
|
1341
|
+
devices: csvList(opts.device ?? opts.devices),
|
|
1342
|
+
refresh: opts.refresh || opts.live,
|
|
1343
|
+
json: opts.json,
|
|
1344
|
+
local: opts.local,
|
|
1345
|
+
});
|
|
1346
|
+
const harnessesCmd = devicesCmd
|
|
1347
|
+
.command('harnesses')
|
|
1348
|
+
.description('Per device, one row per installed agent@version: account, signed-in, quota, and a single ready verdict. SSH-probes each online box.')
|
|
1349
|
+
.option('--json', 'output machine-readable JSON (per-host rows)')
|
|
1350
|
+
.option('--agents <csv>', 'only these agents (comma-separated)')
|
|
1351
|
+
.option('--device <csv>', 'only these devices (comma-separated); default: every online box')
|
|
1352
|
+
.option('--refresh', 'fetch live quota instead of the cached snapshot (slower)')
|
|
1353
|
+
.option('--live', 'alias of --refresh')
|
|
1354
|
+
.option('--local', "this host only: emit THIS box's rows (the per-host worker the fan-out reads over ssh)")
|
|
1355
|
+
.action(async (opts) => {
|
|
1356
|
+
await runDevicesHarnesses(harnessInvOpts(opts));
|
|
1357
|
+
});
|
|
1358
|
+
setHelpSections(harnessesCmd, {
|
|
1359
|
+
examples: `
|
|
1360
|
+
agents devices harnesses # every box: agent@version · account · signed · quota · ready
|
|
1361
|
+
agents devices harnesses --agents claude,codex # just these harnesses
|
|
1362
|
+
agents devices harnesses --device zion # one box
|
|
1363
|
+
agents devices harnesses --refresh # live quota (bypass the cached snapshot)
|
|
1364
|
+
agents devices harnesses --json # machine-readable, per-host rows`,
|
|
1365
|
+
notes: `
|
|
1366
|
+
"ready" = signed in AND not rate-limited — usable for a run right now.
|
|
1367
|
+
Quota is the cached usage snapshot (the daemon warms it); --refresh fetches live.
|
|
1368
|
+
Use \`agents devices accounts\` for the same data grouped by account.`,
|
|
1369
|
+
});
|
|
1370
|
+
const accountsCmd = devicesCmd
|
|
1371
|
+
.command('accounts')
|
|
1372
|
+
.description('Per device, one row per account: which harnesses share it, signed-in, quota, and ready. The identity lens on `agents devices harnesses`.')
|
|
1373
|
+
.option('--json', 'output machine-readable JSON (per-host account groups)')
|
|
1374
|
+
.option('--agents <csv>', 'only these agents (comma-separated)')
|
|
1375
|
+
.option('--device <csv>', 'only these devices (comma-separated); default: every online box')
|
|
1376
|
+
.option('--refresh', 'fetch live quota instead of the cached snapshot (slower)')
|
|
1377
|
+
.option('--live', 'alias of --refresh')
|
|
1378
|
+
.option('--local', "this host only: emit THIS box's account groups")
|
|
1379
|
+
.action(async (opts) => {
|
|
1380
|
+
await runDevicesAccounts(harnessInvOpts(opts));
|
|
1381
|
+
});
|
|
1382
|
+
setHelpSections(accountsCmd, {
|
|
1383
|
+
examples: `
|
|
1384
|
+
agents devices accounts # every box: account · agents · signed · quota · ready
|
|
1385
|
+
agents devices accounts --device mac-mini
|
|
1386
|
+
agents devices accounts --json # machine-readable, per-host account groups`,
|
|
1387
|
+
notes: `
|
|
1388
|
+
Collapses the installs that share one account (e.g. five claude versions on one
|
|
1389
|
+
email) into a single row. Use \`agents devices harnesses\` for the per-install view.`,
|
|
1390
|
+
});
|
|
1222
1391
|
devicesCmd
|
|
1223
1392
|
.command('login')
|
|
1224
1393
|
.description('Log agent CLIs into fleet boxes over SSH: drive each box\'s device-code OAuth, scrape the URL + code, and surface every pending login in one local browser page. Default drives all codes at once; --interactive walks one box at a time (codes requested just-in-time so they don\'t expire).')
|
|
@@ -1281,15 +1450,19 @@ Typical workflow:
|
|
|
1281
1450
|
.option('--auth <method>', 'key | password')
|
|
1282
1451
|
.option('--bundle <bundle>', 'secrets bundle holding the password (for --auth password)')
|
|
1283
1452
|
.option('--bundle-key <key>', "key within the bundle (default 'password')")
|
|
1453
|
+
.option('--identity-file <path>', 'private-key path for --auth key')
|
|
1454
|
+
.option('--clear-identity-file', 'return key auth to ssh-agent/default-key discovery')
|
|
1284
1455
|
.action(async (name, opts) => {
|
|
1285
1456
|
try {
|
|
1286
1457
|
const existing = await mustGetDevice(name);
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1458
|
+
const nextMethod = opts.auth ?? existing.auth.method;
|
|
1459
|
+
if (opts.identityFile && nextMethod !== 'key') {
|
|
1460
|
+
throw new Error('--identity-file requires key auth; pass --auth key in the same command.');
|
|
1461
|
+
}
|
|
1462
|
+
const auth = opts.auth || opts.bundle || opts.bundleKey || opts.identityFile || opts.clearIdentityFile
|
|
1463
|
+
? nextMethod === 'key'
|
|
1464
|
+
? { method: nextMethod, identityFile: opts.clearIdentityFile ? undefined : (opts.identityFile ?? existing.auth.identityFile) }
|
|
1465
|
+
: { method: nextMethod, bundle: opts.bundle ?? existing.auth.bundle, bundleKey: opts.bundleKey ?? existing.auth.bundleKey }
|
|
1293
1466
|
: undefined;
|
|
1294
1467
|
const d = await upsertDevice(name, {
|
|
1295
1468
|
platform: opts.platform ?? undefined,
|
package/dist/commands/teams.d.ts
CHANGED
|
@@ -34,6 +34,12 @@ export interface TeamListAgentSnapshot {
|
|
|
34
34
|
cloud_provider: string | null;
|
|
35
35
|
pr_url: string | null;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* One-line operator nudge after `teams start`: teammates are briefed to post
|
|
39
|
+
* IMPORTANT milestones to the feed (RUSH-2250), and this is how to watch them.
|
|
40
|
+
* Print-only — no engine behavior.
|
|
41
|
+
*/
|
|
42
|
+
export declare function printFeedHint(team: string): void;
|
|
37
43
|
/** Where `teams message`/`teams resume` routes a follow-up, by teammate status. */
|
|
38
44
|
export type TeamMessageRoute = {
|
|
39
45
|
kind: 'steer';
|
package/dist/commands/teams.js
CHANGED
|
@@ -17,7 +17,7 @@ import { createTeam, ensureTeam, getTeam, loadTeams, removeTeam, teamExists, } f
|
|
|
17
17
|
import { setHelpSections } from '../lib/help.js';
|
|
18
18
|
import { createWorktree, isGitRepo, hasUncommittedChanges, removeWorktree, } from '../lib/teams/worktree.js';
|
|
19
19
|
import { resolveHost } from '../lib/hosts/registry.js';
|
|
20
|
-
import { isDeviceAuto,
|
|
20
|
+
import { isDeviceAuto, resolveDeviceAuto } from '../lib/smart-launch.js';
|
|
21
21
|
import { sshTargetFor } from '../lib/hosts/types.js';
|
|
22
22
|
import { ensureHostReady } from '../lib/hosts/ready.js';
|
|
23
23
|
import { remoteShellFor } from '../lib/hosts/remote-cmd.js';
|
|
@@ -153,6 +153,18 @@ function fullName(type, version) {
|
|
|
153
153
|
const name = AGENT_NAMES[type];
|
|
154
154
|
return version ? `${name} ${version}` : name;
|
|
155
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* One-line operator nudge after `teams start`: teammates are briefed to post
|
|
158
|
+
* IMPORTANT milestones to the feed (RUSH-2250), and this is how to watch them.
|
|
159
|
+
* Print-only — no engine behavior.
|
|
160
|
+
*/
|
|
161
|
+
export function printFeedHint(team) {
|
|
162
|
+
console.log(chalk.gray('Tip: teammates post IMPORTANT milestones to the feed (') +
|
|
163
|
+
chalk.cyan('agents feed timeline') +
|
|
164
|
+
chalk.gray('); watch team progress with ') +
|
|
165
|
+
chalk.cyan(`agents teams status ${team}`) +
|
|
166
|
+
chalk.gray('.'));
|
|
167
|
+
}
|
|
156
168
|
/**
|
|
157
169
|
* Resolve a teammate spec to its execution target.
|
|
158
170
|
*
|
|
@@ -365,6 +377,8 @@ async function runOneWave(mgr, team, json) {
|
|
|
365
377
|
console.log(` ${chalk.blue(h)} ${chalk.gray('after')} ${a.after.join(', ')}`);
|
|
366
378
|
}
|
|
367
379
|
}
|
|
380
|
+
if (launched.length > 0)
|
|
381
|
+
printFeedHint(team);
|
|
368
382
|
}
|
|
369
383
|
/**
|
|
370
384
|
* Path to the pr-watch dedupe-state file for a team. Persists the set of
|
|
@@ -1340,12 +1354,12 @@ export function registerTeamsCommands(program) {
|
|
|
1340
1354
|
}
|
|
1341
1355
|
return h ?? d ?? null;
|
|
1342
1356
|
})();
|
|
1343
|
-
// `auto` is the same
|
|
1357
|
+
// `auto` is the same live fleet sentinel `agents run --device auto` resolves
|
|
1344
1358
|
// (RUSH-2185) — pick the concrete device name up front so the local-machine
|
|
1345
1359
|
// check right below (and every dieFriction message further down) sees the
|
|
1346
1360
|
// real target instead of the literal string "auto".
|
|
1347
1361
|
if (explicitDevice && isDeviceAuto(explicitDevice)) {
|
|
1348
|
-
const plan =
|
|
1362
|
+
const plan = await resolveDeviceAuto(parseTeammate(teammate).agent);
|
|
1349
1363
|
const picked = plan.host ?? machineId();
|
|
1350
1364
|
process.stderr.write(chalk.gray(`[teams] device=auto → ${picked === machineId() ? 'local' : picked}\n`));
|
|
1351
1365
|
explicitDevice = picked;
|
|
@@ -1766,11 +1780,40 @@ export function registerTeamsCommands(program) {
|
|
|
1766
1780
|
await warnUnsignedTeammates(mgr, team);
|
|
1767
1781
|
await warnThrottledTeammates(mgr, team);
|
|
1768
1782
|
}
|
|
1783
|
+
// Health-/harness-aware pre-flight (RUSH-2002): if the team declares a
|
|
1784
|
+
// device pool and no device in it can run a pending teammate's agent, fail
|
|
1785
|
+
// loud here rather than launching a wave that strands teammates pending.
|
|
1786
|
+
// --force downgrades it to a warning so the user can still try.
|
|
1787
|
+
const placementError = await mgr.preflightPlacement(team);
|
|
1788
|
+
if (placementError) {
|
|
1789
|
+
if (isJsonMode(opts)) {
|
|
1790
|
+
console.log(JSON.stringify({
|
|
1791
|
+
team,
|
|
1792
|
+
error: 'no-viable-device',
|
|
1793
|
+
message: placementError.message,
|
|
1794
|
+
excluded: placementError.excluded,
|
|
1795
|
+
}));
|
|
1796
|
+
if (!opts.force) {
|
|
1797
|
+
process.exitCode = 1;
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
else if (opts.force) {
|
|
1802
|
+
console.error(chalk.yellow(`⚠ ${placementError.message}\n Continuing anyway (--force).`));
|
|
1803
|
+
}
|
|
1804
|
+
else {
|
|
1805
|
+
console.error(chalk.red(placementError.message));
|
|
1806
|
+
process.exitCode = 1;
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1769
1810
|
emit('teams.start', { module: 'teams', team, watch: Boolean(opts.watch) });
|
|
1770
1811
|
if (!opts.watch) {
|
|
1771
1812
|
await runOneWave(mgr, team, Boolean(opts.json));
|
|
1772
1813
|
return;
|
|
1773
1814
|
}
|
|
1815
|
+
if (!isJsonMode(opts))
|
|
1816
|
+
printFeedHint(team);
|
|
1774
1817
|
const intervalMs = Math.max(1000, Number.parseInt(opts.interval, 10) * 1000 || 8000);
|
|
1775
1818
|
const maxWaves = Math.max(1, Number.parseInt(opts.maxWaves, 10) || 1000);
|
|
1776
1819
|
const json = isJsonMode(opts);
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deprecated top-level spelling of the counter-mix tree.
|
|
3
|
+
*
|
|
4
|
+
* Implementation lives under `agents insights mix` (see
|
|
5
|
+
* `lib/analytics/mix-commands.ts`). This file only registers the thin alias so
|
|
6
|
+
* old scripts and agent briefs keep working while printing one deprecation line
|
|
7
|
+
* — no second recipe implementation.
|
|
8
|
+
*/
|
|
1
9
|
import type { Command } from 'commander';
|
|
2
10
|
export declare function registerTrendsCommand(program: Command): void;
|