@phnx-labs/agents-cli 1.22.23 → 1.22.25

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 +359 -0
  2. package/README.md +19 -8
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/attach.d.ts +2 -0
  5. package/dist/commands/attach.js +32 -7
  6. package/dist/commands/cloud.js +9 -5
  7. package/dist/commands/defaults.js +2 -0
  8. package/dist/commands/doctor.d.ts +24 -0
  9. package/dist/commands/doctor.js +105 -9
  10. package/dist/commands/exec.js +99 -44
  11. package/dist/commands/feed.d.ts +23 -0
  12. package/dist/commands/feed.js +65 -2
  13. package/dist/commands/focus.d.ts +34 -3
  14. package/dist/commands/focus.js +301 -57
  15. package/dist/commands/go.d.ts +14 -1
  16. package/dist/commands/go.js +49 -5
  17. package/dist/commands/insights.js +6 -2
  18. package/dist/commands/inspect.js +39 -5
  19. package/dist/commands/menubar.js +14 -1
  20. package/dist/commands/models.js +1 -0
  21. package/dist/commands/modes.d.ts +12 -0
  22. package/dist/commands/modes.js +147 -0
  23. package/dist/commands/routines.d.ts +3 -0
  24. package/dist/commands/routines.js +70 -47
  25. package/dist/commands/run-cloud.js +1 -1
  26. package/dist/commands/secrets-sync.js +11 -13
  27. package/dist/commands/secrets.d.ts +2 -0
  28. package/dist/commands/secrets.js +54 -17
  29. package/dist/commands/sessions-browser.d.ts +36 -1
  30. package/dist/commands/sessions-browser.js +165 -19
  31. package/dist/commands/sessions-resume.d.ts +2 -0
  32. package/dist/commands/sessions-resume.js +28 -12
  33. package/dist/commands/sessions.d.ts +23 -1
  34. package/dist/commands/sessions.js +58 -61
  35. package/dist/commands/setup-secrets.js +1 -1
  36. package/dist/commands/setup-watchdog.js +5 -10
  37. package/dist/commands/setup.js +1 -1
  38. package/dist/commands/sync.js +246 -42
  39. package/dist/commands/teams.js +3 -2
  40. package/dist/commands/view.js +2 -0
  41. package/dist/commands/watchdog.d.ts +3 -4
  42. package/dist/commands/watchdog.js +26 -66
  43. package/dist/index.js +38 -2
  44. package/dist/lib/agent-modes.d.ts +49 -0
  45. package/dist/lib/agent-modes.js +70 -0
  46. package/dist/lib/agents.js +126 -21
  47. package/dist/lib/cloud/cursor.d.ts +79 -0
  48. package/dist/lib/cloud/cursor.js +228 -0
  49. package/dist/lib/cloud/registry.js +2 -0
  50. package/dist/lib/cloud/types.d.ts +7 -2
  51. package/dist/lib/cloud/types.js +14 -0
  52. package/dist/lib/crabbox/cli.d.ts +2 -2
  53. package/dist/lib/crabbox/config.d.ts +7 -8
  54. package/dist/lib/crabbox/config.js +14 -14
  55. package/dist/lib/crabbox/lease.d.ts +11 -4
  56. package/dist/lib/crabbox/lease.js +40 -8
  57. package/dist/lib/crabbox/setup-copy.d.ts +5 -0
  58. package/dist/lib/crabbox/setup-copy.js +17 -1
  59. package/dist/lib/daemon.js +27 -1
  60. package/dist/lib/device-config.js +7 -0
  61. package/dist/lib/devices/doctor-findings.d.ts +7 -1
  62. package/dist/lib/devices/doctor-findings.js +40 -1
  63. package/dist/lib/doctor-diff.d.ts +3 -0
  64. package/dist/lib/doctor-diff.js +15 -13
  65. package/dist/lib/event-stream.d.ts +3 -1
  66. package/dist/lib/event-stream.js +14 -1
  67. package/dist/lib/events.d.ts +9 -0
  68. package/dist/lib/events.js +58 -0
  69. package/dist/lib/exec.d.ts +15 -3
  70. package/dist/lib/exec.js +112 -19
  71. package/dist/lib/feed-outcome.d.ts +3 -0
  72. package/dist/lib/feed-outcome.js +18 -10
  73. package/dist/lib/feed.d.ts +4 -0
  74. package/dist/lib/hooks/cache.js +36 -3
  75. package/dist/lib/hooks.d.ts +19 -7
  76. package/dist/lib/hooks.js +88 -38
  77. package/dist/lib/hosts/passthrough.d.ts +21 -0
  78. package/dist/lib/hosts/passthrough.js +39 -12
  79. package/dist/lib/hosts/session-index.d.ts +4 -0
  80. package/dist/lib/hosts/session-index.js +7 -0
  81. package/dist/lib/manifest.d.ts +12 -2
  82. package/dist/lib/manifest.js +60 -5
  83. package/dist/lib/mcp.js +5 -1
  84. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  85. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +5 -1
  86. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  87. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  88. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
  89. package/dist/lib/menubar/install-menubar.d.ts +52 -2
  90. package/dist/lib/menubar/install-menubar.js +128 -6
  91. package/dist/lib/menubar/snapshot.d.ts +15 -0
  92. package/dist/lib/menubar/snapshot.js +40 -0
  93. package/dist/lib/plugins.js +13 -1
  94. package/dist/lib/refresh.d.ts +5 -0
  95. package/dist/lib/refresh.js +37 -33
  96. package/dist/lib/resource-inventory.d.ts +79 -0
  97. package/dist/lib/resource-inventory.js +122 -0
  98. package/dist/lib/resources/mcp.js +3 -0
  99. package/dist/lib/resources.js +8 -5
  100. package/dist/lib/routine-process-cleanup.d.ts +9 -0
  101. package/dist/lib/routine-process-cleanup.js +73 -0
  102. package/dist/lib/run-defaults.d.ts +2 -0
  103. package/dist/lib/run-defaults.js +23 -2
  104. package/dist/lib/runner.js +27 -22
  105. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  106. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
  107. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  108. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  109. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
  110. package/dist/lib/secrets/bundles.js +158 -85
  111. package/dist/lib/secrets/filestore.d.ts +7 -3
  112. package/dist/lib/secrets/filestore.js +25 -8
  113. package/dist/lib/secrets/index.js +34 -8
  114. package/dist/lib/secrets/rc-hygiene.d.ts +14 -0
  115. package/dist/lib/secrets/rc-hygiene.js +14 -1
  116. package/dist/lib/secrets/reaper.d.ts +10 -0
  117. package/dist/lib/secrets/reaper.js +50 -15
  118. package/dist/lib/secrets/sync-passphrase.d.ts +27 -0
  119. package/dist/lib/secrets/sync-passphrase.js +78 -0
  120. package/dist/lib/session/active.d.ts +4 -0
  121. package/dist/lib/session/db.js +8 -2
  122. package/dist/lib/session/recovery.d.ts +37 -0
  123. package/dist/lib/session/recovery.js +95 -0
  124. package/dist/lib/session/remote-list.d.ts +2 -0
  125. package/dist/lib/session/remote-list.js +1 -0
  126. package/dist/lib/session/session-cache.d.ts +4 -4
  127. package/dist/lib/session/session-cache.js +4 -4
  128. package/dist/lib/shims.d.ts +2 -2
  129. package/dist/lib/shims.js +45 -6
  130. package/dist/lib/signin-badge.js +2 -0
  131. package/dist/lib/startup/command-registry.d.ts +16 -0
  132. package/dist/lib/startup/command-registry.js +44 -0
  133. package/dist/lib/sync-umbrella.d.ts +5 -0
  134. package/dist/lib/sync-umbrella.js +5 -4
  135. package/dist/lib/teams/agents.js +1 -1
  136. package/dist/lib/teams/parsers.d.ts +1 -1
  137. package/dist/lib/tmux/session.d.ts +4 -2
  138. package/dist/lib/tmux/session.js +5 -5
  139. package/dist/lib/types.d.ts +3 -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
@@ -31,6 +31,7 @@
31
31
  */
32
32
  import * as path from 'path';
33
33
  import chalk from 'chalk';
34
+ import { resolveSyncPassphraseFromEnv } from '../lib/secrets/sync-passphrase.js';
34
35
  import { agentLabel, resolveAgentName, MANAGED_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
35
36
  import { isVersionInstalled, syncResourcesToVersion, parseAgentSpec, resolveVersion, resolveVersionAlias, listInstalledVersions, getAvailableResources, getActuallySyncedResources, getProjectOnlyResources, getNewResources, hasNewResources, promptResourceSelection, promptNewResourceSelection, buildRepoScopedSelection, mergeRepoScopedSelections, listRepoNames, getVersionHomePath, } from '../lib/versions.js';
36
37
  import { capableAgents } from '../lib/capabilities.js';
@@ -43,6 +44,10 @@ import { runUmbrellaSync } from '../lib/sync-umbrella.js';
43
44
  import { addHostOption } from '../lib/hosts/option.js';
44
45
  import { syncRepoGit } from '../lib/git.js';
45
46
  import { getSystemAgentsDir, getUserAgentsDir, getEnabledExtraRepos } from '../lib/state.js';
47
+ /** Emit one JSON object to stdout for `--json` callers / fleet fan-out. */
48
+ function emitJson(payload) {
49
+ console.log(JSON.stringify(payload));
50
+ }
46
51
  /** Register the `agents sync` command. */
47
52
  export function registerSyncCommand(program) {
48
53
  addHostOption(program.command('sync [agentSpec] [repo]'))
@@ -57,6 +62,7 @@ export function registerSyncCommand(program) {
57
62
  .option('-y, --yes', 'Skip the interactive preview and auto-sync all detected resources', false)
58
63
  .option('--force', 'Re-sync even if no changes are detected since the last sync', false)
59
64
  .option('--quiet', 'Suppress all output (exit code indicates success)', false)
65
+ .option('--json', 'Emit machine-readable JSON (also accepted so fleet fan-out via --host all can parse each peer)', false)
60
66
  // Umbrella verb (no agent given): make this machine current.
61
67
  .option('--repos', 'Umbrella: git-pull ~/.agents + enabled ~/.agents-* extras', false)
62
68
  .option('--secrets', 'Umbrella: pull encrypted secret bundles from the remote', false)
@@ -88,22 +94,47 @@ function resolveRepoGitTarget(repo) {
88
94
  * tree, else pull --rebase against origin, pushing local commits for user-owned
89
95
  * repos. Delegates the git work to `syncRepoGit`.
90
96
  */
91
- async function runRepoGitSync(repo, quiet, outLog, errLog) {
97
+ async function runRepoGitSync(repo, quiet, outLog, errLog, json = false) {
92
98
  const target = resolveRepoGitTarget(repo);
93
99
  if (!target) {
94
- errLog(chalk.red(`The '${repo}' repo isn't independently git-synced.`));
95
- errLog(chalk.gray('Syncable repos: system (pull-only), user, and enabled extra-repo aliases.'));
100
+ if (json) {
101
+ emitJson({
102
+ ok: false,
103
+ mode: 'repo-git',
104
+ repo,
105
+ error: `The '${repo}' repo isn't independently git-synced.`,
106
+ });
107
+ }
108
+ else {
109
+ errLog(chalk.red(`The '${repo}' repo isn't independently git-synced.`));
110
+ errLog(chalk.gray('Syncable repos: system (pull-only), user, and enabled extra-repo aliases.'));
111
+ }
96
112
  process.exitCode = 1;
97
113
  return;
98
114
  }
99
- if (!quiet)
115
+ if (!quiet && !json)
100
116
  outLog(chalk.bold(`Syncing ${repo} repo…`) + chalk.gray(` (${target.dir})`));
101
117
  const result = await syncRepoGit(target.dir, { push: target.push });
102
118
  if (!result.success) {
103
- errLog(chalk.red(`sync ${repo} failed: ${result.error}`));
119
+ if (json) {
120
+ emitJson({ ok: false, mode: 'repo-git', repo, error: result.error ?? 'sync failed' });
121
+ }
122
+ else {
123
+ errLog(chalk.red(`sync ${repo} failed: ${result.error}`));
124
+ }
104
125
  process.exitCode = 1;
105
126
  return;
106
127
  }
128
+ if (json) {
129
+ emitJson({
130
+ ok: true,
131
+ mode: 'repo-git',
132
+ repo,
133
+ commit: result.commit,
134
+ pushed: !!result.pushed,
135
+ });
136
+ return;
137
+ }
107
138
  if (!quiet) {
108
139
  const note = result.pushed ? ' · pushed' : ' · pull-only';
109
140
  outLog(chalk.green(`✓ ${repo} → ${result.commit}${note}`));
@@ -201,12 +232,14 @@ async function runInteractiveReconcile(opts, outLog, errLog) {
201
232
  * arrives with `agents login`) and runs the fetch+reconcile stages, then prints
202
233
  * a one-line summary. Stage failures are non-fatal and surfaced as warnings.
203
234
  */
204
- async function runUmbrella(opts, quiet, outLog, errLog) {
235
+ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
205
236
  // Interactive bare `agents sync` (a TTY, no --yes, no scope flag) drops into
206
237
  // the two-checklist picker: which repos to sync from, which agents to sync
207
- // into. Any explicit flag or --yes keeps the non-interactive umbrella below.
238
+ // into. Any explicit flag, --yes, or --json keeps the non-interactive path.
239
+ // --json is a machine consumer (and the fleet fan-out injects it), so never
240
+ // open a picker under it.
208
241
  const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.cloud || opts.local);
209
- if (!quiet && !opts.yes && !anyExplicitFlag && isInteractiveTerminal()) {
242
+ if (!quiet && !json && !opts.yes && !anyExplicitFlag && isInteractiveTerminal()) {
210
243
  await runInteractiveReconcile(opts, outLog, errLog);
211
244
  return;
212
245
  }
@@ -216,17 +249,36 @@ async function runUmbrella(opts, quiet, outLog, errLog) {
216
249
  cloud: opts.cloud,
217
250
  local: opts.local,
218
251
  };
219
- const passphrase = process.env.AGENTS_SECRETS_PASSPHRASE || undefined;
220
- if (!quiet)
252
+ // Same chokepoint as `agents secrets push/pull` — prefers AGENTS_SYNC_PASSPHRASE,
253
+ // falls back to the deprecated master-key name with a single warning.
254
+ const passphrase = resolveSyncPassphraseFromEnv().value ?? undefined;
255
+ // Fleet fan-out only injects --json (not --yes). Treat --json as non-interactive
256
+ // so refresh({ skipPrompts }) never tries to prompt over SSH.
257
+ const yes = !!opts.yes || json;
258
+ if (!quiet && !json)
221
259
  outLog(chalk.bold('Syncing this machine…'));
222
260
  try {
223
261
  const result = await runUmbrellaSync({
224
262
  flags,
225
- yes: !!opts.yes,
263
+ yes,
226
264
  passphrase,
227
- log: (msg) => { if (!quiet)
265
+ // quiet under --json so refresh() cannot pollute the JSON stdout the fleet parses.
266
+ quiet: quiet || json,
267
+ log: (msg) => { if (!quiet && !json)
228
268
  outLog(chalk.gray(` ${msg}`)); },
229
269
  });
270
+ if (json) {
271
+ emitJson({
272
+ ok: true,
273
+ mode: 'umbrella',
274
+ plan: result.plan,
275
+ repos: result.repos,
276
+ secrets: result.secrets,
277
+ devices: result.devices,
278
+ reconciled: result.reconciled,
279
+ });
280
+ return;
281
+ }
230
282
  if (!quiet) {
231
283
  const parts = [];
232
284
  if (result.repos) {
@@ -245,16 +297,30 @@ async function runUmbrella(opts, quiet, outLog, errLog) {
245
297
  }
246
298
  }
247
299
  catch (err) {
248
- errLog(chalk.red(`sync failed: ${err.message}`));
300
+ if (json) {
301
+ emitJson({ ok: false, mode: 'umbrella', error: err.message });
302
+ }
303
+ else {
304
+ errLog(chalk.red(`sync failed: ${err.message}`));
305
+ }
249
306
  process.exitCode = 1;
250
307
  }
251
308
  }
252
309
  async function runSync(agentSpec, repoArg, opts) {
253
- const quiet = !!opts.quiet;
310
+ const json = !!opts.json;
311
+ // --json is a machine consumer: suppress human stdout/stderr chatter so the
312
+ // single JSON object on stdout stays parseable for fleet fan-out.
313
+ const quiet = !!opts.quiet || json;
254
314
  const errLog = (msg) => { if (!quiet)
255
315
  console.error(msg); };
256
316
  const outLog = (msg) => { if (!quiet)
257
317
  console.log(msg); };
318
+ // Failures under --json still need a structured line on stdout so fleet
319
+ // fan-out's safeJsonParse gets a real object (not "unknown option").
320
+ const failJson = (payload) => {
321
+ if (json)
322
+ emitJson({ ok: false, ...payload });
323
+ };
258
324
  // ---------- 1. Resolve agent + version ----------
259
325
  let agentId;
260
326
  let version;
@@ -271,14 +337,21 @@ async function runSync(agentSpec, repoArg, opts) {
271
337
  // below, and it precedes agent-spec parsing because repo names like
272
338
  // "system"/"user" would otherwise fail parseAgentSpec.
273
339
  if (agentSpec && !opts.agent && !repoArg && listRepoNames().includes(agentSpec)) {
274
- await runRepoGitSync(agentSpec, quiet, outLog, errLog);
340
+ await runRepoGitSync(agentSpec, quiet, outLog, errLog, json);
275
341
  return;
276
342
  }
277
343
  if (agentSpec) {
278
344
  const parsed = parseAgentSpec(agentSpec);
279
345
  if (!parsed) {
280
- errLog(chalk.red(`Invalid agent spec '${agentSpec}'.`));
281
- errLog(chalk.gray('Examples: claude, claude@2.1.142, claude@latest, claude@oldest, claude@pinned, claude@all'));
346
+ failJson({
347
+ mode: 'agent',
348
+ error: `Invalid agent spec '${agentSpec}'.`,
349
+ hint: 'Examples: claude, claude@2.1.142, claude@latest, claude@oldest, claude@pinned, claude@all',
350
+ });
351
+ if (!json) {
352
+ errLog(chalk.red(`Invalid agent spec '${agentSpec}'.`));
353
+ errLog(chalk.gray('Examples: claude, claude@2.1.142, claude@latest, claude@oldest, claude@pinned, claude@all'));
354
+ }
282
355
  process.exitCode = 1;
283
356
  return;
284
357
  }
@@ -292,8 +365,11 @@ async function runSync(agentSpec, repoArg, opts) {
292
365
  if (repoScope !== undefined) {
293
366
  const known = listRepoNames();
294
367
  if (!known.includes(repoScope)) {
295
- errLog(chalk.red(`Unknown repo '${repoScope}'.`));
296
- errLog(chalk.gray(`Known repos: ${known.join(', ')}`));
368
+ failJson({ mode: 'agent', error: `Unknown repo '${repoScope}'.`, known });
369
+ if (!json) {
370
+ errLog(chalk.red(`Unknown repo '${repoScope}'.`));
371
+ errLog(chalk.gray(`Known repos: ${known.join(', ')}`));
372
+ }
297
373
  process.exitCode = 1;
298
374
  return;
299
375
  }
@@ -301,14 +377,18 @@ async function runSync(agentSpec, repoArg, opts) {
301
377
  if (opts.agent) {
302
378
  const resolved = resolveAgentName(opts.agent);
303
379
  if (!resolved) {
304
- errLog(chalk.red(`Unknown agent '${opts.agent}'.`));
380
+ failJson({ mode: 'agent', error: `Unknown agent '${opts.agent}'.` });
381
+ if (!json)
382
+ errLog(chalk.red(`Unknown agent '${opts.agent}'.`));
305
383
  process.exitCode = 1;
306
384
  return;
307
385
  }
308
386
  agentId = resolved;
309
387
  }
310
388
  if (agentId && isAgentHardDeprecated(agentId)) {
311
- errLog(chalk.red(hardDeprecationError(agentId)));
389
+ failJson({ mode: 'agent', error: hardDeprecationError(agentId) });
390
+ if (!json)
391
+ errLog(chalk.red(hardDeprecationError(agentId)));
312
392
  process.exitCode = 1;
313
393
  return;
314
394
  }
@@ -320,7 +400,8 @@ async function runSync(agentSpec, repoArg, opts) {
320
400
  if (!agentId) {
321
401
  // No agent specified → the umbrella verb: make this machine current
322
402
  // (fetch repos + secrets + sessions, then reconcile all installed agents).
323
- await runUmbrella(opts, quiet, outLog, errLog);
403
+ // This is the path fleet fan-out (`--host all`) hits with injected --json.
404
+ await runUmbrella(opts, quiet, outLog, errLog, json);
324
405
  return;
325
406
  }
326
407
  const projectDir = opts.projectDir;
@@ -332,8 +413,11 @@ async function runSync(agentSpec, repoArg, opts) {
332
413
  if (selector === 'all') {
333
414
  const installed = listInstalledVersions(agentId);
334
415
  if (installed.length === 0) {
335
- errLog(chalk.red(`No ${agentLabel(agentId)} versions installed.`));
336
- errLog(chalk.gray(`Install one: agents add ${agentId}@latest`));
416
+ failJson({ mode: 'agent-all', agent: agentId, error: `No ${agentLabel(agentId)} versions installed.` });
417
+ if (!json) {
418
+ errLog(chalk.red(`No ${agentLabel(agentId)} versions installed.`));
419
+ errLog(chalk.gray(`Install one: agents add ${agentId}@latest`));
420
+ }
337
421
  process.exitCode = 1;
338
422
  return;
339
423
  }
@@ -341,17 +425,43 @@ async function runSync(agentSpec, repoArg, opts) {
341
425
  if (repoScope) {
342
426
  selection = buildRepoScopedSelection(repoScope, cwd);
343
427
  if (Object.keys(selection).length === 0) {
344
- outLog(chalk.gray(`Nothing from repo '${repoScope}' to sync.`));
428
+ if (json)
429
+ emitJson({ ok: true, mode: 'agent-all', agent: agentId, repo: repoScope, versions: [], note: 'nothing to sync' });
430
+ else
431
+ outLog(chalk.gray(`Nothing from repo '${repoScope}' to sync.`));
345
432
  return;
346
433
  }
347
434
  }
348
435
  const scopeLabel = repoScope ? chalk.gray(` (repo: ${repoScope})`) : '';
349
- outLog(chalk.cyan(`Syncing ${installed.length} ${agentLabel(agentId)} version(s)${scopeLabel}.`));
436
+ if (!json)
437
+ outLog(chalk.cyan(`Syncing ${installed.length} ${agentLabel(agentId)} version(s)${scopeLabel}.`));
438
+ const versions = [];
350
439
  for (const v of installed) {
351
440
  const result = syncResourcesToVersion(agentId, v, selection, { projectDir, cwd, force });
352
- if (!quiet)
441
+ versions.push({ version: v, result });
442
+ if (!quiet && !json)
353
443
  printSyncDetail(result, agentId, v, cwd);
354
444
  }
445
+ if (json) {
446
+ emitJson({
447
+ ok: true,
448
+ mode: 'agent-all',
449
+ agent: agentId,
450
+ repo: repoScope,
451
+ versions: versions.map(({ version: v, result }) => ({
452
+ version: v,
453
+ commands: !!result.commands,
454
+ skills: !!result.skills,
455
+ hooks: !!result.hooks,
456
+ memory: result.memory,
457
+ mcp: result.mcp,
458
+ permissions: !!result.permissions,
459
+ subagents: result.subagents,
460
+ plugins: result.plugins,
461
+ workflows: result.workflows,
462
+ })),
463
+ });
464
+ }
355
465
  return;
356
466
  }
357
467
  // ---------- 2. Resolve version (project pin → global default → sole installed) ----------
@@ -370,15 +480,26 @@ async function runSync(agentSpec, repoArg, opts) {
370
480
  version = installed[0];
371
481
  }
372
482
  else if (installed.length === 0) {
373
- errLog(chalk.red(`No ${agentLabel(agentId)} versions installed.`));
374
- errLog(chalk.gray(`Install one: agents add ${agentId}@latest`));
483
+ failJson({ mode: 'agent', agent: agentId, error: `No ${agentLabel(agentId)} versions installed.` });
484
+ if (!json) {
485
+ errLog(chalk.red(`No ${agentLabel(agentId)} versions installed.`));
486
+ errLog(chalk.gray(`Install one: agents add ${agentId}@latest`));
487
+ }
375
488
  process.exitCode = 1;
376
489
  return;
377
490
  }
378
491
  else {
379
- errLog(chalk.red(`No default ${agentLabel(agentId)} version pinned. Specify one:`));
380
- for (const v of installed) {
381
- errLog(chalk.gray(` agents sync ${agentId}@${v}`));
492
+ failJson({
493
+ mode: 'agent',
494
+ agent: agentId,
495
+ error: `No default ${agentLabel(agentId)} version pinned.`,
496
+ installed,
497
+ });
498
+ if (!json) {
499
+ errLog(chalk.red(`No default ${agentLabel(agentId)} version pinned. Specify one:`));
500
+ for (const v of installed) {
501
+ errLog(chalk.gray(` agents sync ${agentId}@${v}`));
502
+ }
382
503
  }
383
504
  process.exitCode = 1;
384
505
  return;
@@ -386,18 +507,27 @@ async function runSync(agentSpec, repoArg, opts) {
386
507
  }
387
508
  }
388
509
  if (!isVersionInstalled(agentId, version)) {
389
- errLog(chalk.red(`${agentLabel(agentId)}@${version} is not installed.`));
390
510
  const installed = listInstalledVersions(agentId);
391
- if (installed.length > 0) {
392
- errLog(chalk.gray(`Installed: ${installed.join(', ')}`));
511
+ failJson({
512
+ mode: 'agent',
513
+ agent: agentId,
514
+ version,
515
+ error: `${agentLabel(agentId)}@${version} is not installed.`,
516
+ installed,
517
+ });
518
+ if (!json) {
519
+ errLog(chalk.red(`${agentLabel(agentId)}@${version} is not installed.`));
520
+ if (installed.length > 0) {
521
+ errLog(chalk.gray(`Installed: ${installed.join(', ')}`));
522
+ }
523
+ errLog(chalk.gray(`Install it: agents add ${agentId}@${version}`));
393
524
  }
394
- errLog(chalk.gray(`Install it: agents add ${agentId}@${version}`));
395
525
  process.exitCode = 1;
396
526
  return;
397
527
  }
398
528
  // ---------- 3. --launch mode bypasses everything below ----------
399
529
  if (opts.launch) {
400
- runLaunchMode(agentId, version, cwd, quiet);
530
+ runLaunchMode(agentId, version, cwd, quiet, json);
401
531
  return;
402
532
  }
403
533
  // ---------- 3b. Repo-scoped single-version sync ----------
@@ -406,16 +536,33 @@ async function runSync(agentSpec, repoArg, opts) {
406
536
  if (repoScope) {
407
537
  const scoped = buildRepoScopedSelection(repoScope, cwd);
408
538
  if (Object.keys(scoped).length === 0) {
409
- outLog(chalk.gray(`Nothing from repo '${repoScope}' to sync into ${agentLabel(agentId)}@${version}.`));
539
+ if (json) {
540
+ emitJson({
541
+ ok: true,
542
+ mode: 'agent',
543
+ agent: agentId,
544
+ version,
545
+ repo: repoScope,
546
+ note: 'nothing to sync',
547
+ });
548
+ }
549
+ else {
550
+ outLog(chalk.gray(`Nothing from repo '${repoScope}' to sync into ${agentLabel(agentId)}@${version}.`));
551
+ }
410
552
  return;
411
553
  }
412
554
  const result = syncResourcesToVersion(agentId, version, scoped, { projectDir, cwd, force });
413
- if (!quiet)
555
+ if (json) {
556
+ emitJson(agentSyncJson(agentId, version, result, repoScope));
557
+ }
558
+ else if (!quiet) {
414
559
  printSyncDetail(result, agentId, version, cwd);
560
+ }
415
561
  return;
416
562
  }
417
563
  // ---------- 4. Decide selection (interactive preview vs auto) ----------
418
- const yes = !!opts.yes;
564
+ // --json forces non-interactive (machine consumer / fleet fan-out).
565
+ const yes = !!opts.yes || json;
419
566
  const interactive = !quiet && !yes && isInteractiveTerminal();
420
567
  let selection;
421
568
  if (interactive) {
@@ -468,6 +615,20 @@ async function runSync(agentSpec, repoArg, opts) {
468
615
  const projectRoot = path.dirname(projectDir);
469
616
  projectCompile = compileRulesForProject(projectRoot);
470
617
  }
618
+ if (json) {
619
+ emitJson({
620
+ ...agentSyncJson(agentId, version, result),
621
+ projectCompile: projectCompile
622
+ ? {
623
+ compiled: !!projectCompile.compiled,
624
+ agentsPath: projectCompile.agentsPath,
625
+ symlinks: projectCompile.symlinks,
626
+ skippedClobber: projectCompile.skippedClobber,
627
+ }
628
+ : null,
629
+ });
630
+ return;
631
+ }
471
632
  if (quiet)
472
633
  return;
473
634
  // ---------- 6. Detailed output ----------
@@ -482,6 +643,26 @@ async function runSync(agentSpec, repoArg, opts) {
482
643
  console.log(chalk.yellow(`Skipped (user-authored, not overwritten): ${projectCompile.skippedClobber.join(', ')}`));
483
644
  }
484
645
  }
646
+ /** Stable `--json` payload for a single agent@version resource sync. */
647
+ function agentSyncJson(agent, version, result, repo) {
648
+ return {
649
+ ok: true,
650
+ mode: 'agent',
651
+ agent,
652
+ version,
653
+ ...(repo !== undefined ? { repo } : {}),
654
+ commands: !!result.commands,
655
+ skills: !!result.skills,
656
+ hooks: !!result.hooks,
657
+ memory: result.memory,
658
+ mcp: result.mcp,
659
+ permissions: !!result.permissions,
660
+ subagents: result.subagents,
661
+ plugins: result.plugins,
662
+ workflows: result.workflows,
663
+ projectSkipped: result.projectSkipped,
664
+ };
665
+ }
485
666
  function anyResources(r) {
486
667
  return r.commands.length + r.skills.length + r.hooks.length + r.memory.length +
487
668
  r.mcp.length + r.permissions.length + r.subagents.length +
@@ -533,17 +714,40 @@ function printSyncDetail(result, agent, version, cwd) {
533
714
  if (kept)
534
715
  console.log(chalk.gray(kept));
535
716
  }
536
- function runLaunchMode(agent, version, cwd, quiet) {
717
+ function runLaunchMode(agent, version, cwd, quiet, json = false) {
537
718
  let result;
538
719
  try {
539
720
  result = runLaunchSync({ agent, version, cwd });
540
721
  }
541
722
  catch (err) {
542
- if (!quiet) {
723
+ if (json) {
724
+ emitJson({
725
+ ok: false,
726
+ mode: 'launch',
727
+ agent,
728
+ version,
729
+ error: err.message,
730
+ });
731
+ process.exitCode = 1;
732
+ }
733
+ else if (!quiet) {
543
734
  console.error(chalk.yellow(`agents: launch sync skipped (${err.message})`));
544
735
  }
545
736
  return;
546
737
  }
738
+ if (json) {
739
+ emitJson({
740
+ ok: true,
741
+ mode: 'launch',
742
+ agent,
743
+ version,
744
+ rulesCompiled: !!result.rulesCompiled,
745
+ workspaceLinks: result.workspaceLinks,
746
+ marketplaces: result.marketplaces,
747
+ workspaceSkipped: result.workspaceSkipped,
748
+ });
749
+ return;
750
+ }
547
751
  if (quiet)
548
752
  return;
549
753
  const bits = [];
@@ -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'];
@@ -1279,7 +1280,7 @@ export function registerTeamsCommands(program) {
1279
1280
  .alias('a')
1280
1281
  .description("Add a teammate to work on a task. Runs in background; returns immediately. Use 'status' to check in.")
1281
1282
  .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')
1283
+ .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
1284
  .option('-e, --effort <effort>', `Reasoning intensity: ${VALID_EFFORTS.join('|')}`, 'medium')
1284
1285
  .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
1286
  .option('--env <key=value>', 'Set an environment variable for this teammate (repeatable for multiple vars)', (val, prev) => [...prev, val], [])
@@ -578,6 +578,8 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
578
578
  const runDefaultBits = [];
579
579
  if (runDefaults.mode)
580
580
  runDefaultBits.push(`mode:${runDefaults.mode}`);
581
+ if (runDefaults.effort)
582
+ runDefaultBits.push('effort:' + runDefaults.effort);
581
583
  if (!hasEmail && !hasUsage && !signedIn) {
582
584
  // No per-version credential. That is NOT the same as unusable: Claude
583
585
  // Code authenticates from `CLAUDE_CODE_OAUTH_TOKEN` when the
@@ -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. */