@phnx-labs/agents-cli 1.20.78 → 1.20.83

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 (183) hide show
  1. package/CHANGELOG.md +595 -0
  2. package/README.md +26 -26
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +9 -0
  5. package/dist/commands/activity.js +153 -24
  6. package/dist/commands/apply.js +11 -4
  7. package/dist/commands/cli.js +1 -1
  8. package/dist/commands/commands.d.ts +1 -1
  9. package/dist/commands/commands.js +7 -6
  10. package/dist/commands/defaults.js +3 -11
  11. package/dist/commands/doctor.d.ts +66 -0
  12. package/dist/commands/doctor.js +596 -33
  13. package/dist/commands/events.js +1 -1
  14. package/dist/commands/exec.js +24 -5
  15. package/dist/commands/feed.d.ts +4 -0
  16. package/dist/commands/feed.js +87 -11
  17. package/dist/commands/harness.js +2 -2
  18. package/dist/commands/hooks.js +1 -1
  19. package/dist/commands/import.js +8 -4
  20. package/dist/commands/logs.js +6 -0
  21. package/dist/commands/menubar.js +12 -0
  22. package/dist/commands/models.js +1 -1
  23. package/dist/commands/packages.js +6 -6
  24. package/dist/commands/permissions.js +1 -1
  25. package/dist/commands/profiles.js +1 -1
  26. package/dist/commands/pull.d.ts +1 -1
  27. package/dist/commands/pull.js +4 -4
  28. package/dist/commands/repo.d.ts +78 -0
  29. package/dist/commands/repo.js +187 -11
  30. package/dist/commands/routines.d.ts +8 -1
  31. package/dist/commands/routines.js +241 -77
  32. package/dist/commands/rules.js +13 -12
  33. package/dist/commands/secrets.d.ts +3 -1
  34. package/dist/commands/secrets.js +100 -32
  35. package/dist/commands/sessions-inject.d.ts +7 -4
  36. package/dist/commands/sessions-inject.js +18 -18
  37. package/dist/commands/sessions-picker.js +32 -3
  38. package/dist/commands/sessions-resume.d.ts +16 -0
  39. package/dist/commands/sessions-resume.js +14 -13
  40. package/dist/commands/sessions.d.ts +115 -0
  41. package/dist/commands/sessions.js +189 -35
  42. package/dist/commands/set.d.ts +15 -0
  43. package/dist/commands/set.js +79 -0
  44. package/dist/commands/setup-fleet.d.ts +21 -0
  45. package/dist/commands/setup-fleet.js +201 -0
  46. package/dist/commands/setup-secrets.d.ts +20 -0
  47. package/dist/commands/setup-secrets.js +223 -0
  48. package/dist/commands/setup.js +17 -3
  49. package/dist/commands/skills.js +1 -1
  50. package/dist/commands/sync.js +7 -2
  51. package/dist/commands/teams-picker.js +0 -1
  52. package/dist/commands/teams.d.ts +33 -1
  53. package/dist/commands/teams.js +208 -44
  54. package/dist/commands/versions.js +10 -5
  55. package/dist/commands/view.d.ts +1 -0
  56. package/dist/commands/view.js +11 -0
  57. package/dist/index.js +52 -8
  58. package/dist/lib/acp/harnesses.js +0 -7
  59. package/dist/lib/activity.d.ts +156 -0
  60. package/dist/lib/activity.js +282 -0
  61. package/dist/lib/agents.d.ts +9 -2
  62. package/dist/lib/agents.js +72 -63
  63. package/dist/lib/browser/service.js +3 -0
  64. package/dist/lib/browser/types.d.ts +7 -0
  65. package/dist/lib/capabilities.js +6 -2
  66. package/dist/lib/crabbox/lease.js +2 -2
  67. package/dist/lib/crabbox/setup-copy.d.ts +4 -4
  68. package/dist/lib/crabbox/setup-copy.js +4 -4
  69. package/dist/lib/daemon.d.ts +18 -0
  70. package/dist/lib/daemon.js +47 -6
  71. package/dist/lib/devices/fleet.d.ts +1 -1
  72. package/dist/lib/devices/fleet.js +1 -1
  73. package/dist/lib/doctor-diff.d.ts +20 -0
  74. package/dist/lib/doctor-diff.js +6 -1
  75. package/dist/lib/drift.d.ts +31 -11
  76. package/dist/lib/drift.js +58 -7
  77. package/dist/lib/events.d.ts +8 -1
  78. package/dist/lib/events.js +10 -1
  79. package/dist/lib/exec.d.ts +7 -0
  80. package/dist/lib/exec.js +69 -11
  81. package/dist/lib/feed-post.d.ts +28 -1
  82. package/dist/lib/feed-post.js +110 -2
  83. package/dist/lib/fleet/apply.js +8 -1
  84. package/dist/lib/fleet/auth-sync.d.ts +18 -1
  85. package/dist/lib/fleet/auth-sync.js +25 -11
  86. package/dist/lib/fleet/remote-login.js +5 -0
  87. package/dist/lib/git.d.ts +14 -14
  88. package/dist/lib/git.js +41 -44
  89. package/dist/lib/hooks.d.ts +71 -0
  90. package/dist/lib/hooks.js +245 -95
  91. package/dist/lib/hosts/dispatch.d.ts +28 -1
  92. package/dist/lib/hosts/dispatch.js +33 -5
  93. package/dist/lib/hosts/option.js +2 -2
  94. package/dist/lib/hosts/passthrough.d.ts +22 -1
  95. package/dist/lib/hosts/passthrough.js +238 -13
  96. package/dist/lib/hosts/run-target.d.ts +2 -0
  97. package/dist/lib/hosts/run-target.js +1 -0
  98. package/dist/lib/hq/floor.d.ts +1 -1
  99. package/dist/lib/hq/floor.js +6 -1
  100. package/dist/lib/mcp.js +3 -71
  101. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  102. package/dist/lib/menubar/install-menubar.d.ts +24 -0
  103. package/dist/lib/menubar/install-menubar.js +52 -2
  104. package/dist/lib/menubar/notify-desktop.d.ts +15 -0
  105. package/dist/lib/menubar/notify-desktop.js +41 -5
  106. package/dist/lib/merged-resources.d.ts +11 -0
  107. package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
  108. package/dist/lib/permissions.d.ts +0 -32
  109. package/dist/lib/permissions.js +4 -157
  110. package/dist/lib/picker.js +1 -1
  111. package/dist/lib/platform/process.d.ts +16 -5
  112. package/dist/lib/platform/process.js +54 -0
  113. package/dist/lib/plugins.d.ts +0 -8
  114. package/dist/lib/plugins.js +4 -110
  115. package/dist/lib/project-resources.js +4 -1
  116. package/dist/lib/refresh.d.ts +5 -3
  117. package/dist/lib/refresh.js +7 -5
  118. package/dist/lib/resources/commands.js +4 -1
  119. package/dist/lib/resources/mcp.js +0 -4
  120. package/dist/lib/resources/permissions.d.ts +1 -1
  121. package/dist/lib/resources/permissions.js +1 -5
  122. package/dist/lib/resources/rules.js +4 -1
  123. package/dist/lib/resources/skills.js +4 -1
  124. package/dist/lib/resources/subagents.js +4 -0
  125. package/dist/lib/resources/types.d.ts +1 -1
  126. package/dist/lib/routines.d.ts +33 -0
  127. package/dist/lib/routines.js +122 -0
  128. package/dist/lib/run-defaults.d.ts +1 -0
  129. package/dist/lib/run-defaults.js +9 -0
  130. package/dist/lib/runner.d.ts +0 -12
  131. package/dist/lib/runner.js +49 -10
  132. package/dist/lib/scheduler.js +16 -4
  133. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  134. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  135. package/dist/lib/secrets/agent.d.ts +16 -0
  136. package/dist/lib/secrets/agent.js +105 -3
  137. package/dist/lib/secrets/audit.d.ts +46 -0
  138. package/dist/lib/secrets/audit.js +56 -0
  139. package/dist/lib/secrets/bundles.d.ts +11 -8
  140. package/dist/lib/secrets/bundles.js +31 -23
  141. package/dist/lib/secrets/index.d.ts +2 -1
  142. package/dist/lib/secrets/index.js +8 -2
  143. package/dist/lib/secrets/remote.d.ts +75 -2
  144. package/dist/lib/secrets/remote.js +139 -5
  145. package/dist/lib/secrets/sync.js +3 -3
  146. package/dist/lib/session/active.d.ts +128 -26
  147. package/dist/lib/session/active.js +192 -53
  148. package/dist/lib/session/actor-sidecar.d.ts +23 -0
  149. package/dist/lib/session/actor-sidecar.js +101 -0
  150. package/dist/lib/session/db.d.ts +4 -1
  151. package/dist/lib/session/db.js +65 -8
  152. package/dist/lib/session/discover.d.ts +4 -0
  153. package/dist/lib/session/discover.js +13 -0
  154. package/dist/lib/session/hook-sessions.d.ts +9 -0
  155. package/dist/lib/session/hook-sessions.js +57 -8
  156. package/dist/lib/session/pid-registry.d.ts +13 -0
  157. package/dist/lib/session/render.d.ts +2 -0
  158. package/dist/lib/session/render.js +1 -1
  159. package/dist/lib/session/types.d.ts +16 -0
  160. package/dist/lib/staleness/detectors/commands.js +2 -2
  161. package/dist/lib/staleness/detectors/permissions.js +1 -45
  162. package/dist/lib/staleness/writers/commands.js +4 -5
  163. package/dist/lib/staleness/writers/hooks.js +1 -1
  164. package/dist/lib/startup/command-registry.d.ts +1 -2
  165. package/dist/lib/startup/command-registry.js +2 -4
  166. package/dist/lib/state.js +57 -1
  167. package/dist/lib/subagents-registry.js +5 -12
  168. package/dist/lib/subagents.d.ts +0 -10
  169. package/dist/lib/subagents.js +0 -12
  170. package/dist/lib/teams/agents.d.ts +0 -2
  171. package/dist/lib/teams/agents.js +10 -27
  172. package/dist/lib/teams/index.d.ts +1 -1
  173. package/dist/lib/teams/index.js +1 -1
  174. package/dist/lib/types.d.ts +17 -9
  175. package/dist/lib/usage.d.ts +28 -5
  176. package/dist/lib/usage.js +271 -8
  177. package/dist/lib/versions.js +7 -1
  178. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  179. package/dist/lib/watchdog/watchdog.js +25 -13
  180. package/package.json +1 -2
  181. package/dist/commands/check.d.ts +0 -15
  182. package/dist/commands/check.js +0 -180
  183. package/dist/commands/resources.d.ts +0 -5
@@ -36,11 +36,13 @@ import { refresh } from '../lib/refresh.js';
36
36
  import { capableAgents } from '../lib/capabilities.js';
37
37
  import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from '../lib/versions.js';
38
38
  import { syncAllMarketplaces } from '../lib/plugin-marketplace.js';
39
+ import { gatherRemoteAgentsJson } from '../lib/remote-agents-json.js';
40
+ import { machineId, normalizeHost } from '../lib/machine-id.js';
39
41
  /**
40
42
  * After a repo add/remove/enable/disable, reconcile each plugins-capable
41
43
  * agent's default version against the new marketplace set. Re-synthesizes
42
44
  * catalogs and known_marketplaces.json entries. Source-copy of plugins is
43
- * out of scope here — full sync still goes through `agents repo refresh`.
45
+ * out of scope here — full sync still goes through `agents sync`.
44
46
  */
45
47
  function syncMarketplacesForDefaults() {
46
48
  for (const agent of capableAgents('plugins')) {
@@ -432,13 +434,161 @@ function renderCards(rows, cols) {
432
434
  }
433
435
  }
434
436
  }
437
+ /**
438
+ * Recursion guard for the fleet fan-out, passed as an env var (not a CLI flag)
439
+ * so an OLDER remote `agents` that predates this feature ignores it harmlessly
440
+ * instead of erroring on an unknown option. A peer new enough to fan out reads
441
+ * it and answers only for itself. Mirrors `NO_FANOUT_ENV` in remote-active.ts.
442
+ */
443
+ export const NO_REPO_FANOUT_ENV = 'AGENTS_REPO_LOCAL';
444
+ /**
445
+ * Parse one peer's `repo status --json` stdout (a RepoRow[]) into a single
446
+ * device entry tagged with `machine`. Defensive against version skew: non-JSON
447
+ * or a non-array yields no entry, and non-object rows are dropped rather than
448
+ * throwing. Exported for unit testing without a live tailnet.
449
+ */
450
+ export function parseRemoteRepoRows(stdout, machine) {
451
+ let parsed;
452
+ try {
453
+ parsed = JSON.parse(stdout);
454
+ }
455
+ catch {
456
+ return [];
457
+ }
458
+ if (!Array.isArray(parsed))
459
+ return [];
460
+ const rows = parsed.filter((x) => !!x && typeof x === 'object' && !Array.isArray(x));
461
+ return [{ device: machine, reachable: true, rows }];
462
+ }
463
+ /**
464
+ * Query repo status on the fleet. With an explicit `hosts` list, fan out to
465
+ * exactly those; otherwise sweep every registered, online peer (excluding this
466
+ * machine). Each peer runs `repo status [alias] --json` locally — never its own
467
+ * fan-out — and unreachable peers are skipped with a gray note, never fatal.
468
+ */
469
+ async function gatherRemoteRepoStatus(alias, hosts) {
470
+ const args = ['repo', 'status', ...(alias ? [alias] : []), '--json'];
471
+ const { items } = await gatherRemoteAgentsJson({
472
+ args,
473
+ noFanoutEnv: NO_REPO_FANOUT_ENV,
474
+ hosts,
475
+ parse: parseRemoteRepoRows,
476
+ });
477
+ return items;
478
+ }
479
+ /**
480
+ * Render the fleet repo-status table: one row per (device, repo), with the
481
+ * device name shown once per group. SYNC and CHANGES reuse the same compact
482
+ * cells as the local wide table (`↓N (…) ↑N` / `clean` / `~N edits`), and an
483
+ * unreachable device collapses to a single `unreachable` marker row. Pure — no
484
+ * I/O — so the aggregation/formatting is unit-testable without live devices.
485
+ */
486
+ export function renderDeviceStatusRows(results) {
487
+ const cells = [];
488
+ for (const r of results) {
489
+ if (!r.reachable || !r.rows) {
490
+ cells.push({ device: r.device, repo: chalk.gray('unreachable'), sync: '', changes: '' });
491
+ continue;
492
+ }
493
+ if (r.rows.length === 0) {
494
+ cells.push({ device: r.device, repo: chalk.gray('no repos'), sync: '', changes: '' });
495
+ continue;
496
+ }
497
+ let firstOfDevice = true;
498
+ for (const row of r.rows) {
499
+ const device = firstOfDevice ? r.device : '';
500
+ firstOfDevice = false;
501
+ if (row.raw) {
502
+ // missing / no-git-remote / error — the free-form trailer says it all.
503
+ cells.push({ device, repo: row.alias, sync: row.raw, changes: '' });
504
+ }
505
+ else {
506
+ cells.push({ device, repo: row.alias, sync: syncCompact(row), changes: changesCompact(row) });
507
+ }
508
+ }
509
+ }
510
+ if (cells.length === 0)
511
+ return [];
512
+ const headers = ['DEVICE', 'REPO', 'SYNC', 'CHANGES'];
513
+ const devW = Math.max(headers[0].length, ...cells.map((c) => visibleWidth(c.device)));
514
+ const repoW = Math.max(headers[1].length, ...cells.map((c) => visibleWidth(c.repo)));
515
+ const syncW = Math.max(headers[2].length, ...cells.map((c) => visibleWidth(c.sync)));
516
+ const lines = [];
517
+ lines.push(` ${chalk.gray(headers[0].padEnd(devW))} ${chalk.gray(headers[1].padEnd(repoW))} ${chalk.gray(headers[2].padEnd(syncW))} ${chalk.gray(headers[3])}`);
518
+ for (const c of cells) {
519
+ lines.push(` ${chalk.cyan(padVisible(c.device, devW))} ${padVisible(c.repo, repoW)} ${padVisible(c.sync, syncW)} ${c.changes}`);
520
+ }
521
+ return lines;
522
+ }
523
+ /**
524
+ * Read the device-scope flags into an intent, or null for the default local-only
525
+ * run. `--devices-all`/`--hosts-all` (and `--devices all`/`--hosts all`) sweep
526
+ * every reachable peer; `--devices box1,box2` targets an explicit list. A peer
527
+ * carrying {@link NO_REPO_FANOUT_ENV} never fans out again (recursion guard).
528
+ */
529
+ export function resolveDeviceIntent(opts) {
530
+ if (process.env[NO_REPO_FANOUT_ENV] === '1')
531
+ return null;
532
+ if (opts.devicesAll || opts.hostsAll)
533
+ return { all: true };
534
+ const val = opts.devices ?? opts.hosts;
535
+ if (val === undefined)
536
+ return null;
537
+ const v = val.trim();
538
+ if (v === '' || v.toLowerCase() === 'all')
539
+ return { all: true };
540
+ const hosts = v.split(',').map((s) => s.trim()).filter(Boolean);
541
+ return hosts.length ? { hosts } : { all: true };
542
+ }
543
+ /**
544
+ * `repo status`/`list` across the fleet: read THIS machine's rows locally, fan
545
+ * out to peers, and render one aggregated table (device · repo · sync · changes).
546
+ * The local box is always shown first; unreachable peers were already noted by
547
+ * the fan-out and are simply absent from the table.
548
+ */
549
+ async function listReposAcrossDevices(alias, opts, intent) {
550
+ const localTargets = collectRepoTargets(alias);
551
+ if (!localTargets) {
552
+ process.exitCode = 1;
553
+ return;
554
+ }
555
+ const localRows = await Promise.all(localTargets.map(renderRepoRow));
556
+ const self = machineId();
557
+ const hosts = 'hosts' in intent ? intent.hosts : undefined;
558
+ const remote = (await gatherRemoteRepoStatus(alias, hosts))
559
+ // An explicit host list may name this box; the local rows already cover it.
560
+ .filter((r) => normalizeHost(r.device) !== self);
561
+ const combined = [
562
+ { device: self, reachable: true, rows: localRows },
563
+ ...remote,
564
+ ];
565
+ if (opts.json) {
566
+ const clean = combined.map((r) => ({
567
+ device: r.device,
568
+ reachable: r.reachable,
569
+ rows: (r.rows ?? []).map((row) => row.raw !== undefined ? { ...row, raw: stripAnsi(row.raw) } : row),
570
+ }));
571
+ console.log(JSON.stringify(clean, null, 2));
572
+ return;
573
+ }
574
+ console.log('');
575
+ for (const line of renderDeviceStatusRows(combined))
576
+ console.log(line);
577
+ console.log('');
578
+ }
435
579
  /**
436
580
  * Shared action body for `agents repo list` and the hidden `agents repo status`
437
581
  * alias. Responsive: a wide terminal gets an aligned table with compact SYNC /
438
582
  * CHANGES cells; a narrow one gets one card per repo with wrapping detail;
439
- * `--verbose` forces the full-detail table at any width.
583
+ * `--verbose` forces the full-detail table at any width. A device-scope flag
584
+ * (`--devices-all` / `--devices <list>`) instead fans out across the fleet.
440
585
  */
441
586
  async function listRepos(alias, opts = {}) {
587
+ const intent = resolveDeviceIntent(opts);
588
+ if (intent) {
589
+ await listReposAcrossDevices(alias, opts, intent);
590
+ return;
591
+ }
442
592
  const targets = collectRepoTargets(alias);
443
593
  if (!targets) {
444
594
  process.exitCode = 1;
@@ -487,6 +637,20 @@ function formatRepoTarget(alias, dir, branch) {
487
637
  const ref = branch ? `origin/${branch}` : 'origin';
488
638
  return `${alias} (${displayHomePath(dir)} → ${ref})`;
489
639
  }
640
+ /**
641
+ * Register the fleet device-scope flags shared by `repo list` / `repo status`.
642
+ * `--devices-all` (alias `--hosts-all`) sweeps every reachable device;
643
+ * `--devices <who>` (alias `--hosts`) takes `all` or a comma-separated list.
644
+ * A single `--host`/`--device` is handled upstream by maybeRunOnHost, which
645
+ * streams that one box's `agents repo status` — these are the aggregating forms.
646
+ */
647
+ function addDeviceStatusOptions(cmd) {
648
+ return cmd
649
+ .option('--devices-all', 'Report repo sync state across ALL reachable fleet devices (alias: --hosts-all).')
650
+ .option('--hosts-all', 'Alias of --devices-all.')
651
+ .option('--devices <who>', 'Fleet devices to report on: "all", or a comma-separated device list (alias: --hosts).')
652
+ .option('--hosts <who>', 'Alias of --devices.');
653
+ }
490
654
  /** Register the `agents repos` command tree (`repo` is a convenience alias). */
491
655
  export function registerRepoCommands(program) {
492
656
  // addHostOption on the group so --help documents --host/--device; remote
@@ -512,6 +676,9 @@ export function registerRepoCommands(program) {
512
676
  # See what's registered
513
677
  agents repos list
514
678
 
679
+ # Report repo sync state across the whole fleet (one box: --device <name>)
680
+ agents repos status --devices-all
681
+
515
682
  # View one repo's contents (git state + resource counts); omit the name for a picker
516
683
  agents repos view system
517
684
 
@@ -714,13 +881,13 @@ export function registerRepoCommands(program) {
714
881
  console.log(chalk.gray(`\nRegistered as "${alias}". Skills and commands from this repo will be`));
715
882
  console.log(chalk.gray(`picked up automatically the next time you launch any agent.`));
716
883
  });
717
- repoCmd
884
+ const listCmd = repoCmd
718
885
  .command('list [alias]')
719
886
  .alias('ls')
720
887
  .description('Show all repos with resource-level sync (skills/commands/plugins to pull or push) and local changes.')
721
888
  .option('-v, --verbose', 'Full per-resource detail in a table, regardless of terminal width')
722
- .option('--json', 'machine-readable JSON output')
723
- .action(async (alias, options) => {
889
+ .option('--json', 'machine-readable JSON output');
890
+ addDeviceStatusOptions(listCmd).action(async (alias, options) => {
724
891
  await listRepos(alias, options);
725
892
  });
726
893
  repoCmd
@@ -922,12 +1089,21 @@ export function registerRepoCommands(program) {
922
1089
  }
923
1090
  }
924
1091
  });
1092
+ // Deprecated: superseded by `agents sync`. `agents sync --local` runs the same
1093
+ // reconcile stage (sync-umbrella.ts calls this exact `refresh()`), and `agents
1094
+ // sync <agent>` targets one agent — with the added reach `refresh` lacks
1095
+ // (all installed versions, no silent skip when there is no global default).
1096
+ // Hidden from help; kept as a warned, functional alias so old muscle-memory and
1097
+ // scripts don't break. Migrate callers to `agents sync`.
925
1098
  repoCmd
926
- .command('refresh [agent]')
927
- .description('Re-materialize resources into installed agent version homes. No git, no network.')
1099
+ .command('refresh [agent]', { hidden: true })
1100
+ .description('Deprecated use `agents sync` instead.')
928
1101
  .option('-y, --yes', 'Auto-sync everything without prompting')
929
1102
  .option('--skip-clis', 'Skip CLI version install/upgrade from agents.yaml')
930
1103
  .action(async (arg, options) => {
1104
+ console.warn(chalk.yellow('`agents repo refresh` is deprecated — use `agents sync` instead:'));
1105
+ console.warn(chalk.gray(' all agents: agents sync --local'));
1106
+ console.warn(chalk.gray(' one agent: agents sync <agent>'));
931
1107
  let agentFilter;
932
1108
  if (arg) {
933
1109
  if (!isAgentName(arg)) {
@@ -955,12 +1131,12 @@ export function registerRepoCommands(program) {
955
1131
  process.exit(1);
956
1132
  }
957
1133
  });
958
- repoCmd
1134
+ const statusCmd = repoCmd
959
1135
  .command('status [alias]', { hidden: true })
960
- .description('Alias of `list` (kept for muscle memory).')
1136
+ .description('Alias of `list` (kept for muscle memory). Add --devices-all to report across the fleet.')
961
1137
  .option('-v, --verbose', 'Full per-resource detail in a table, regardless of terminal width')
962
- .option('--json', 'machine-readable JSON output')
963
- .action(async (alias, options) => {
1138
+ .option('--json', 'machine-readable JSON output');
1139
+ addDeviceStatusOptions(statusCmd).action(async (alias, options) => {
964
1140
  await listRepos(alias, options);
965
1141
  });
966
1142
  }
@@ -6,13 +6,20 @@
6
6
  * Also exposes scheduler lifecycle controls (start/stop/status/logs).
7
7
  */
8
8
  import type { Command } from 'commander';
9
- import type { RunMeta } from '../lib/routines.js';
9
+ import type { JobConfig, RunMeta } from '../lib/routines.js';
10
+ import type { DeviceRegistry } from '../lib/devices/registry.js';
10
11
  /**
11
12
  * Human-friendly wall-clock a run took (e.g. " · 3 min", " · 45 sec"), or ""
12
13
  * when it hasn't completed or timestamps are unparseable. Leading separator lets
13
14
  * callers drop it straight into a status line.
14
15
  */
15
16
  export declare function formatRunDuration(startedAt: string, completedAt: string | null): string;
17
+ export interface RoutineListGroup {
18
+ key: string;
19
+ title: string;
20
+ jobs: JobConfig[];
21
+ }
22
+ export declare function groupRoutineJobsByDevice(jobs: JobConfig[], registry: DeviceRegistry, self?: string): RoutineListGroup[];
16
23
  export declare function buildRunsJson(runs: RunMeta[]): Record<string, unknown>[];
17
24
  /** Register the `agents routines` command tree. */
18
25
  export declare function registerRoutinesCommands(program: Command): void;