@phnx-labs/agents-cli 1.22.24 → 1.22.26

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.
Files changed (143) hide show
  1. package/CHANGELOG.md +350 -1
  2. package/README.md +31 -6
  3. package/dist/bin/agents +0 -0
  4. package/dist/browser.js +14 -4
  5. package/dist/commands/apply.js +52 -8
  6. package/dist/commands/browser.js +35 -0
  7. package/dist/commands/cloud.js +9 -5
  8. package/dist/commands/doctor.d.ts +24 -0
  9. package/dist/commands/doctor.js +108 -9
  10. package/dist/commands/exec.js +19 -16
  11. package/dist/commands/feed.d.ts +5 -0
  12. package/dist/commands/feed.js +21 -1
  13. package/dist/commands/focus.js +2 -2
  14. package/dist/commands/insights.d.ts +25 -19
  15. package/dist/commands/insights.js +107 -33
  16. package/dist/commands/menubar.js +8 -0
  17. package/dist/commands/reconnect.d.ts +46 -0
  18. package/dist/commands/reconnect.js +109 -0
  19. package/dist/commands/routines.d.ts +3 -0
  20. package/dist/commands/routines.js +72 -49
  21. package/dist/commands/run-cloud.js +1 -1
  22. package/dist/commands/secrets.d.ts +2 -8
  23. package/dist/commands/secrets.js +29 -105
  24. package/dist/commands/sessions-browser.d.ts +1 -1
  25. package/dist/commands/sessions-browser.js +27 -7
  26. package/dist/commands/sessions-resume.js +3 -2
  27. package/dist/commands/sessions.d.ts +13 -1
  28. package/dist/commands/sessions.js +28 -2
  29. package/dist/commands/setup-secrets.d.ts +1 -0
  30. package/dist/commands/setup-secrets.js +1 -1
  31. package/dist/commands/setup-watchdog.js +5 -10
  32. package/dist/commands/setup.d.ts +26 -3
  33. package/dist/commands/setup.js +106 -47
  34. package/dist/commands/teams.d.ts +6 -0
  35. package/dist/commands/teams.js +46 -2
  36. package/dist/commands/trends.d.ts +8 -0
  37. package/dist/commands/trends.js +10 -156
  38. package/dist/commands/watchdog.d.ts +3 -4
  39. package/dist/commands/watchdog.js +26 -66
  40. package/dist/index.js +37 -2
  41. package/dist/lib/agents.d.ts +11 -0
  42. package/dist/lib/agents.js +155 -23
  43. package/dist/lib/analytics/dashboard.d.ts +10 -6
  44. package/dist/lib/analytics/dashboard.js +6 -4
  45. package/dist/lib/analytics/mix-commands.d.ts +53 -0
  46. package/dist/lib/analytics/mix-commands.js +229 -0
  47. package/dist/lib/analytics/recipes.d.ts +19 -14
  48. package/dist/lib/analytics/recipes.js +4 -2
  49. package/dist/lib/browser/ipc.d.ts +26 -0
  50. package/dist/lib/browser/ipc.js +139 -24
  51. package/dist/lib/browser/profiles.d.ts +11 -0
  52. package/dist/lib/browser/profiles.js +1 -1
  53. package/dist/lib/browser/stream.d.ts +14 -0
  54. package/dist/lib/browser/stream.js +71 -0
  55. package/dist/lib/channels/owner-sink.d.ts +27 -0
  56. package/dist/lib/channels/owner-sink.js +93 -0
  57. package/dist/lib/cloud/cursor.d.ts +79 -0
  58. package/dist/lib/cloud/cursor.js +228 -0
  59. package/dist/lib/cloud/registry.js +2 -0
  60. package/dist/lib/cloud/types.d.ts +7 -2
  61. package/dist/lib/cloud/types.js +14 -0
  62. package/dist/lib/crabbox/cli.d.ts +2 -2
  63. package/dist/lib/crabbox/config.d.ts +7 -8
  64. package/dist/lib/crabbox/config.js +14 -14
  65. package/dist/lib/crabbox/lease.d.ts +11 -4
  66. package/dist/lib/crabbox/lease.js +40 -8
  67. package/dist/lib/crabbox/setup-copy.d.ts +5 -0
  68. package/dist/lib/crabbox/setup-copy.js +17 -1
  69. package/dist/lib/daemon.js +27 -1
  70. package/dist/lib/device-config.js +7 -0
  71. package/dist/lib/devices/doctor-findings.d.ts +13 -1
  72. package/dist/lib/devices/doctor-findings.js +73 -2
  73. package/dist/lib/events.d.ts +9 -0
  74. package/dist/lib/events.js +58 -0
  75. package/dist/lib/exec.d.ts +3 -3
  76. package/dist/lib/exec.js +24 -10
  77. package/dist/lib/feed-outcome.d.ts +3 -0
  78. package/dist/lib/feed-outcome.js +18 -10
  79. package/dist/lib/feed.d.ts +4 -0
  80. package/dist/lib/fleet/apply.d.ts +59 -3
  81. package/dist/lib/fleet/apply.js +183 -6
  82. package/dist/lib/fleet/types.d.ts +21 -2
  83. package/dist/lib/hooks/cache.js +15 -0
  84. package/dist/lib/hosts/passthrough.d.ts +44 -0
  85. package/dist/lib/hosts/passthrough.js +84 -12
  86. package/dist/lib/hosts/ready.d.ts +2 -0
  87. package/dist/lib/hosts/ready.js +10 -1
  88. package/dist/lib/hosts/reconnect.d.ts +14 -12
  89. package/dist/lib/hosts/reconnect.js +41 -40
  90. package/dist/lib/mcp.js +5 -1
  91. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  92. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  93. package/dist/lib/menubar/snapshot.d.ts +15 -0
  94. package/dist/lib/menubar/snapshot.js +40 -0
  95. package/dist/lib/plugins.js +13 -1
  96. package/dist/lib/resources/mcp.js +3 -0
  97. package/dist/lib/routine-process-cleanup.d.ts +9 -0
  98. package/dist/lib/routine-process-cleanup.js +73 -0
  99. package/dist/lib/routines.js +14 -2
  100. package/dist/lib/runner.d.ts +0 -3
  101. package/dist/lib/runner.js +6 -19
  102. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  103. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  104. package/dist/lib/secrets/bundles.js +1 -20
  105. package/dist/lib/secrets/filestore.d.ts +2 -0
  106. package/dist/lib/secrets/filestore.js +13 -0
  107. package/dist/lib/secrets/push.d.ts +94 -0
  108. package/dist/lib/secrets/push.js +145 -0
  109. package/dist/lib/secrets/rc-hygiene.d.ts +14 -0
  110. package/dist/lib/secrets/rc-hygiene.js +14 -1
  111. package/dist/lib/secrets/reaper.d.ts +15 -1
  112. package/dist/lib/secrets/reaper.js +30 -3
  113. package/dist/lib/session/active.d.ts +4 -0
  114. package/dist/lib/session/db.d.ts +21 -3
  115. package/dist/lib/session/db.js +229 -15
  116. package/dist/lib/session/discover.d.ts +1 -0
  117. package/dist/lib/session/discover.js +115 -19
  118. package/dist/lib/session/insights.d.ts +18 -0
  119. package/dist/lib/session/insights.js +143 -1
  120. package/dist/lib/session/remote-list.d.ts +2 -0
  121. package/dist/lib/session/remote-list.js +1 -0
  122. package/dist/lib/session/session-cache.d.ts +4 -4
  123. package/dist/lib/session/session-cache.js +4 -4
  124. package/dist/lib/session/tool-index.js +133 -22
  125. package/dist/lib/session/tool-store.d.ts +26 -2
  126. package/dist/lib/session/tool-store.js +36 -17
  127. package/dist/lib/shims.js +21 -1
  128. package/dist/lib/signin-badge.js +2 -0
  129. package/dist/lib/ssh-exec.js +8 -2
  130. package/dist/lib/startup/command-registry.d.ts +16 -0
  131. package/dist/lib/startup/command-registry.js +46 -0
  132. package/dist/lib/teams/agents.d.ts +13 -0
  133. package/dist/lib/teams/agents.js +76 -8
  134. package/dist/lib/teams/parsers.d.ts +1 -1
  135. package/dist/lib/teams/placement-probe.d.ts +21 -0
  136. package/dist/lib/teams/placement-probe.js +135 -0
  137. package/dist/lib/teams/scheduler.d.ts +74 -1
  138. package/dist/lib/teams/scheduler.js +187 -10
  139. package/dist/lib/types.d.ts +1 -1
  140. package/dist/lib/versions.js +16 -1
  141. package/dist/lib/watchdog/service.d.ts +17 -0
  142. package/dist/lib/watchdog/service.js +39 -0
  143. package/package.json +1 -1
@@ -3,17 +3,12 @@ import { spawnSync } from 'node:child_process';
3
3
  import { getCliLaunch } from '../lib/cli-entry.js';
4
4
  import { loadDevices } from '../lib/devices/registry.js';
5
5
  import { machineId, normalizeHost } from '../lib/machine-id.js';
6
- import { devicesWithRoutineEnabled } from '../lib/routine-activation.js';
7
- import { readJob, setJobEnabled } from '../lib/routines.js';
6
+ import { getConfigValue, setConfigValue } from '../lib/device-config.js';
8
7
  import { isPromptCancelled, requireInteractiveSelection } from './utils.js';
9
- const ROUTINE = 'watchdog';
10
8
  export async function runWatchdogSetupWizard() {
11
- if (!readJob(ROUTINE)) {
12
- throw new Error("Built-in routine 'watchdog' is missing. Run: agents repo pull system");
13
- }
14
9
  const registry = await loadDevices();
15
10
  const devices = [...new Set([...Object.keys(registry), machineId()].map(normalizeHost))].sort();
16
- const enabled = new Set(devicesWithRoutineEnabled(ROUTINE).map(normalizeHost));
11
+ const enabled = new Set(devices.filter((device) => getConfigValue('watchdog.enabled', { device }).value === true));
17
12
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
18
13
  requireInteractiveSelection('watchdog devices', ['agents watchdog on', 'agents watchdog on --host <device>']);
19
14
  }
@@ -27,10 +22,10 @@ export async function runWatchdogSetupWizard() {
27
22
  for (const device of devices) {
28
23
  const on = selectedSet.has(device);
29
24
  if (device === normalizeHost(machineId())) {
30
- setJobEnabled(ROUTINE, on);
25
+ setConfigValue('watchdog.enabled', on);
31
26
  continue;
32
27
  }
33
- const launch = getCliLaunch(['routines', on ? 'resume' : 'pause', ROUTINE, '--host', device]);
28
+ const launch = getCliLaunch(['watchdog', on ? 'on' : 'off', '--host', device]);
34
29
  const result = spawnSync(launch.command, launch.args, { stdio: 'inherit', env: process.env });
35
30
  if ((result.status ?? 1) !== 0)
36
31
  throw new Error(`Could not configure watchdog on ${device}`);
@@ -49,6 +44,6 @@ export async function runWatchdogSetupWizard() {
49
44
  }
50
45
  export function registerSetupWatchdogCommand(setup) {
51
46
  setup.command('watchdog')
52
- .description('Choose the devices where the built-in watchdog routine runs.')
47
+ .description('Choose the devices where the daemon watchdog pass runs.')
53
48
  .action(runWatchdogSetupWizard);
54
49
  }
@@ -5,12 +5,14 @@
5
5
  * ~/.agents/.system/ and installs agent CLIs with resource syncing.
6
6
  */
7
7
  import type { Command } from 'commander';
8
- /** First-run setup. Clones ~/.agents/.system/ from the system repo if needed. */
9
- export declare function runSetup(program: Command, options?: {
8
+ interface RunSetupOptions {
10
9
  force?: boolean;
11
10
  suppressFooter?: boolean;
12
11
  systemRepo?: boolean;
13
- }): Promise<void>;
12
+ runHub?: () => Promise<void>;
13
+ }
14
+ /** First-run setup. Clones ~/.agents/.system/ from the system repo if needed. */
15
+ export declare function runSetup(program: Command, options?: RunSetupOptions): Promise<void>;
14
16
  /**
15
17
  * Ensure the system repo exists before running a command that needs it.
16
18
  * If ~/.agents/.system/ is not a git repo AND we're in an interactive TTY,
@@ -18,5 +20,26 @@ export declare function runSetup(program: Command, options?: {
18
20
  * error and exit.
19
21
  */
20
22
  export declare function ensureInitialized(program: Command): Promise<void>;
23
+ /**
24
+ * Interactive "what else do you want to set up?" menu shown after the bare
25
+ * `agents setup` finishes on a TTY. Each pick runs that capability's guided
26
+ * wizard. Never throws — a cancel or an optional wizard's error just skips the
27
+ * rest and lets core setup complete.
28
+ */
29
+ export type SetupPhase = 'browser' | 'computer' | 'share' | 'secrets' | 'fleet' | 'watchdog' | 'preferences';
30
+ export type SetupStatusState = 'ready' | 'missing' | 'n/a';
31
+ export interface SetupStatusRow {
32
+ phase: 'core' | SetupPhase;
33
+ state: SetupStatusState;
34
+ detail: string;
35
+ }
36
+ export declare function getSetupStatus(): Promise<SetupStatusRow[]>;
37
+ export declare function renderSetupStatus(rows: SetupStatusRow[]): void;
38
+ export declare function runSetupHub(deps?: {
39
+ interactive?: boolean;
40
+ selectPhase?: (rows: SetupStatusRow[]) => Promise<SetupPhase | 'exit'>;
41
+ runPhase?: (phase: SetupPhase) => Promise<void>;
42
+ }): Promise<void>;
21
43
  /** Register the `agents setup` command and its capability subcommands. */
22
44
  export declare function registerSetupCommand(program: Command): void;
45
+ export {};
@@ -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
- console.log(chalk.gray('~/.agents/.system/ is already set up.'));
74
- console.log(chalk.gray('\nTo sync updates: agents repo pull system'));
75
- console.log(chalk.gray('To re-run setup: agents setup --force'));
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
- * Interactive "what else do you want to set up?" menu shown after the bare
234
- * `agents setup` finishes on a TTY. Each pick runs that capability's guided
235
- * wizard. Never throws a cancel or an optional wizard's error just skips the
236
- * rest and lets core setup complete.
237
- */
238
- async function runSetupHub() {
239
- if (!isInteractiveTerminal())
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
- const { checkbox } = await import('@inquirer/prompts');
243
- const picks = await checkbox({
244
- message: 'Set up optional capabilities now? (space to select, enter to confirm)',
245
- choices: [
246
- { name: 'browser — drive a real Chrome/Brave/Edge for web automation', value: 'browser' },
247
- { name: 'computer — control native macOS apps (screenshot, click, type)', value: 'computer' },
248
- { name: 'share — publish shareable links (Cloudflare R2 + Worker)', value: 'share' },
249
- { name: 'secrets — choose storage defaults and import existing credentials', value: 'secrets' },
250
- { name: 'fleet — discover Tailscale devices and configure SSH access', value: 'fleet' },
251
- { name: 'watchdog — resume stalled agent sessions on selected devices', value: 'watchdog' },
252
- ],
253
- });
254
- for (const pick of picks) {
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
- if (pick === 'browser')
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('First-time setup. Clones a config repo and installs agent CLIs.')
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. On a TTY, offers to set up optional capabilities (browser/computer/share/secrets/fleet/watchdog)
312
- 4. On a TTY, asks preferences: which machine you sit at (interactive host)
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
@@ -319,7 +378,7 @@ export function registerSetupCommand(program) {
319
378
  agents setup share # provision or join a Cloudflare share endpoint
320
379
  agents setup secrets # choose secrets backend/policy defaults + import
321
380
  agents setup fleet # discover Tailscale devices + configure SSH access
322
- agents setup watchdog # choose which devices run the watchdog routine
381
+ agents setup watchdog # choose which devices run the daemon watchdog pass
323
382
 
324
383
  To install CLIs from agents.yaml and sync resources into version homes:
325
384
  agents sync --local -y
@@ -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';
@@ -47,8 +47,9 @@ const AGENT_NAMES = {
47
47
  antigravity: 'Antigravity',
48
48
  kimi: 'Kimi',
49
49
  droid: 'Droid',
50
+ warp: 'Warp',
50
51
  };
51
- const VALID_AGENTS = ['claude', 'codex', 'cursor', 'opencode', 'grok', 'antigravity', 'kimi', 'droid'];
52
+ const VALID_AGENTS = ['claude', 'codex', 'cursor', 'opencode', 'grok', 'antigravity', 'kimi', 'droid', 'warp'];
52
53
  // 'full' kept as historical alias for 'skip'; normalized to 'skip' downstream.
53
54
  const VALID_MODES = ['plan', 'edit', 'auto', 'skip', 'full'];
54
55
  const VALID_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'auto'];
@@ -152,6 +153,18 @@ function fullName(type, version) {
152
153
  const name = AGENT_NAMES[type];
153
154
  return version ? `${name} ${version}` : name;
154
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
+ }
155
168
  /**
156
169
  * Resolve a teammate spec to its execution target.
157
170
  *
@@ -364,6 +377,8 @@ async function runOneWave(mgr, team, json) {
364
377
  console.log(` ${chalk.blue(h)} ${chalk.gray('after')} ${a.after.join(', ')}`);
365
378
  }
366
379
  }
380
+ if (launched.length > 0)
381
+ printFeedHint(team);
367
382
  }
368
383
  /**
369
384
  * Path to the pr-watch dedupe-state file for a team. Persists the set of
@@ -1279,7 +1294,7 @@ export function registerTeamsCommands(program) {
1279
1294
  .alias('a')
1280
1295
  .description("Add a teammate to work on a task. Runs in background; returns immediately. Use 'status' to check in.")
1281
1296
  .option('-n, --name <name>', 'Friendly name for this teammate (e.g. alice). Required if using --after. Unique within team.')
1282
- .option('-m, --mode <mode>', `Permissions: plan (read-only) | edit (can write files) | auto (smart classifier auto-approves safe ops) | skip (bypass all permission prompts). 'full' accepted as alias for skip. Teammates run headless: plan works headless on claude/codex/droid/opencode; kimi/grok/cursor/antigravity have no headless plan mode and auto-downgrade a plan request to auto.`, 'edit')
1297
+ .option('-m, --mode <mode>', `Permissions: plan (read-only) | edit (can write files) | auto (smart classifier auto-approves safe ops) | skip (bypass all permission prompts). 'full' accepted as alias for skip. Teammates run headless: plan works headless on claude/codex/cursor/droid/opencode; kimi/grok/antigravity have no headless plan mode and auto-downgrade a plan request to auto.`, 'edit')
1283
1298
  .option('-e, --effort <effort>', `Reasoning intensity: ${VALID_EFFORTS.join('|')}`, 'medium')
1284
1299
  .option('--model <model>', 'Cost tier (cheap|default|best|ultra) or a concrete id (e.g. claude-opus-4-8); tiers resolve per harness+version to a supported model')
1285
1300
  .option('--env <key=value>', 'Set an environment variable for this teammate (repeatable for multiple vars)', (val, prev) => [...prev, val], [])
@@ -1765,11 +1780,40 @@ export function registerTeamsCommands(program) {
1765
1780
  await warnUnsignedTeammates(mgr, team);
1766
1781
  await warnThrottledTeammates(mgr, team);
1767
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
+ }
1768
1810
  emit('teams.start', { module: 'teams', team, watch: Boolean(opts.watch) });
1769
1811
  if (!opts.watch) {
1770
1812
  await runOneWave(mgr, team, Boolean(opts.json));
1771
1813
  return;
1772
1814
  }
1815
+ if (!isJsonMode(opts))
1816
+ printFeedHint(team);
1773
1817
  const intervalMs = Math.max(1000, Number.parseInt(opts.interval, 10) * 1000 || 8000);
1774
1818
  const maxWaves = Math.max(1, Number.parseInt(opts.maxWaves, 10) || 1000);
1775
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;
@@ -1,158 +1,12 @@
1
- import chalk from 'chalk';
2
- import { setHelpSections } from '../lib/help.js';
3
- import { buildTrendsDashboard, trendsWindow, runRecipe, RECIPE_IDS } from '../lib/analytics/dashboard.js';
4
- import { listRecipes } from '../lib/analytics/recipes.js';
5
- import { queryUsage, usageDbPath, USAGE_KINDS } from '../lib/analytics/usage-db.js';
6
- function parseDays(raw) {
7
- const n = parseInt(raw ?? '7', 10);
8
- return Number.isFinite(n) && n > 0 ? n : 7;
9
- }
10
- function parseLimit(raw, fallback) {
11
- const n = parseInt(raw ?? String(fallback), 10);
12
- return Number.isFinite(n) && n > 0 ? n : fallback;
13
- }
14
- function printSection(section) {
15
- console.log(chalk.bold(section.title));
16
- if (section.rows.length === 0) {
17
- console.log(chalk.gray(' (no data)'));
18
- console.log();
19
- return;
20
- }
21
- const keys = Object.keys(section.rows[0]);
22
- const widths = keys.map((k) => Math.max(k.length, ...section.rows.map((r) => String(r[k] ?? '').length), 4));
23
- const pad = (s, w) => (s.length >= w ? s.slice(0, w) : s + ' '.repeat(w - s.length));
24
- console.log(chalk.gray(keys.map((k, i) => pad(k.toUpperCase(), widths[i])).join(' ')));
25
- for (const row of section.rows) {
26
- console.log(keys.map((k, i) => pad(String(row[k] ?? ''), widths[i])).join(' '));
27
- }
28
- console.log();
29
- }
30
- function renderDashboard(days, asJson) {
31
- const dash = buildTrendsDashboard({ days });
32
- if (asJson) {
33
- console.log(JSON.stringify(dash, null, 2));
34
- return;
35
- }
36
- console.log(chalk.bold(`agents trends — last ${dash.window.days} days`));
37
- console.log(chalk.gray(`compute ${dash.durationMs}ms · usage ${usageDbPath()}`));
38
- console.log();
39
- if (dash.sections.length === 0) {
40
- console.log(chalk.gray('No session or usage data in this window yet.'));
41
- return;
42
- }
43
- for (const section of dash.sections)
44
- printSection(section);
45
- }
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
+ */
9
+ import { registerDeprecatedTrendsAlias } from '../lib/analytics/mix-commands.js';
46
10
  export function registerTrendsCommand(program) {
47
- const trends = program
48
- .command('trends')
49
- .description('Usage analytics — harness/model mix, token ratios, resource frequency')
50
- .option('--days <n>', 'Days of history to include', '7')
51
- .option('--json', 'Emit JSON instead of tables')
52
- .action(function summary() {
53
- const opts = this.opts();
54
- renderDashboard(parseDays(opts.days), Boolean(opts.json));
55
- });
56
- setHelpSections(trends, {
57
- examples: `
58
- # Auto recipe dashboard (7d)
59
- agents trends
60
-
61
- # Last 30 days
62
- agents trends --days 30
63
-
64
- # One recipe as JSON
65
- agents trends harness-mix --json
66
-
67
- # Raw usage events
68
- agents trends query --kind secret --days 7
69
-
70
- # List baked recipe ids
71
- agents trends recipes
72
- `,
73
- notes: `
74
- Session recipes read sessions.db; resource recipes read ~/.agents/.history/analytics/usage.db.
75
- Empty recipes are skipped on the default dashboard.
76
- Quota / rate-limits remain on \`agents usage\`; latency on \`agents perf\`.
77
- `,
78
- });
79
- trends.command('recipes')
80
- .description('List baked recipe ids')
81
- .option('--json', 'Emit JSON')
82
- .action((opts) => {
83
- const list = listRecipes();
84
- if (opts.json) {
85
- console.log(JSON.stringify(list, null, 2));
86
- return;
87
- }
88
- for (const r of list) {
89
- console.log(`${r.id.padEnd(22)} ${r.store.padEnd(10)} ${r.title}`);
90
- }
91
- });
92
- trends.command('query')
93
- .description('Raw usage-event query')
94
- .option('--kind <kind>', `One of: ${USAGE_KINDS.join(', ')}`)
95
- .option('--name <name>', 'Resource name filter')
96
- .option('--event <event>', 'Event name filter')
97
- .option('--days <n>', 'Days of history', '7')
98
- .option('--limit <n>', 'Max rows', '40')
99
- .option('--json', 'Emit JSON')
100
- .action((opts) => {
101
- const win = trendsWindow(parseDays(opts.days));
102
- const kind = opts.kind && USAGE_KINDS.includes(opts.kind)
103
- ? opts.kind
104
- : undefined;
105
- if (opts.kind && !kind) {
106
- console.error(`Unknown kind '${opts.kind}'. Expected: ${USAGE_KINDS.join(', ')}`);
107
- process.exitCode = 1;
108
- return;
109
- }
110
- const rows = queryUsage({
111
- kind,
112
- name: opts.name,
113
- event: opts.event,
114
- sinceIso: win.sinceIso,
115
- limit: parseLimit(opts.limit, 40),
116
- });
117
- if (opts.json) {
118
- console.log(JSON.stringify({ window: win, rows }, null, 2));
119
- return;
120
- }
121
- if (rows.length === 0) {
122
- console.log(chalk.gray('No usage events.'));
123
- return;
124
- }
125
- printSection({
126
- id: 'query',
127
- title: 'Usage events',
128
- rows: rows.map((r) => ({
129
- ts: r.ts,
130
- kind: r.kind,
131
- name: r.name,
132
- event: r.event,
133
- agent: r.agent,
134
- })),
135
- });
136
- });
137
- for (const id of RECIPE_IDS) {
138
- trends.command(id)
139
- .description(`Recipe: ${id}`)
140
- .option('--days <n>', 'Days of history', '7')
141
- .option('--json', 'Emit JSON')
142
- .action(function recipeAction() {
143
- const parent = this.parent?.opts?.();
144
- const opts = { ...parent, ...this.opts() };
145
- const win = trendsWindow(parseDays(opts.days));
146
- const section = runRecipe(id, win);
147
- if (opts.json) {
148
- console.log(JSON.stringify({ window: win, section }, null, 2));
149
- return;
150
- }
151
- if (section.empty) {
152
- console.log(chalk.gray(`No data for recipe '${id}' in the last ${win.days} days.`));
153
- return;
154
- }
155
- printSection(section);
156
- });
157
- }
11
+ registerDeprecatedTrendsAlias(program);
158
12
  }
@@ -10,12 +10,11 @@
10
10
  * agents watchdog --nudge one tick, actually injects (explicit opt-in)
11
11
  * agents watchdog --watch manual poll loop (dry unless --nudge)
12
12
  * agents watchdog --json machine-readable tick output (for the menu-bar)
13
- * agents watchdog on|off turn the always-on watchdog routine on/off
13
+ * agents watchdog on|off turn the device-local daemon pass on/off
14
14
  * agents watchdog policy <id> <p> per-session policy: off | keep | handsoff
15
15
  *
16
- * The always-on watchdog is a daemon-fired ROUTINE, not a private sentinel + a
17
- * hand-rolled loop. Its immutable definition comes from the system DotAgents
18
- * repo; on/off only changes this device's routine membership.
16
+ * The agents daemon is the sole automatic watchdog scheduler. The menu bar reads
17
+ * persisted state; it never runs a tick.
19
18
  */
20
19
  import type { Command } from 'commander';
21
20
  /** Register the `agents watchdog` command tree. */