@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
@@ -11,8 +11,9 @@ import * as fs from 'fs';
11
11
  import * as path from 'path';
12
12
  import * as yaml from 'yaml';
13
13
  import { isDaemonRunning, signalDaemonReload, startDaemon, stopDaemon, readDaemonLog, getDaemonStatus, } from '../lib/daemon.js';
14
+ import { resolveAgentName, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
14
15
  import { humanizeCron, humanizeNextRun, formatRepoLink, REPO_DISPLAY_MAX } from '../lib/routines-format.js';
15
- import { listJobs as listAllJobs, deleteJob, readJob, validateJob, writeJob, setJobEnabled, listRuns, getLatestRun, getRunDir, getJobPath, parseAtTime, jobRunsOnThisDevice, checkJobDeviceEligibility, normalizeTriggerEvent, parseHostStrategy, resolveHostStrategy, placementRequiresFiringPin, HOST_STRATEGIES, } from '../lib/routines.js';
16
+ import { listJobs as listAllJobs, deleteJob, readJob, validateJob, writeJob, setJobEnabled, listRuns, getLatestRun, getRunDir, getJobPath, parseAtTime, hasCompletedOneShotRun, isOneShotLikeSchedule, isOneShotRoutine, isPastOneShotRoutine, jobRunsOnThisDevice, checkJobDeviceEligibility, normalizeTriggerEvent, parseHostStrategy, resolveHostStrategy, placementRequiresFiringPin, HOST_STRATEGIES, } from '../lib/routines.js';
16
17
  import { discoverProjectRoutinesAt, enableProjectRoutines, disableProjectRoutines, syncProjectRoutines, syncAllProjectRoutines, listEnabledProjectRoots, resolveProjectRoot, displayProjectPath, listProjectRoutineFiles, } from '../lib/routines-project.js';
17
18
  import { fireWebhookJobs, matchJobsToWebhook } from '../lib/triggers/webhook.js';
18
19
  import { getRoutinesDir } from '../lib/state.js';
@@ -23,7 +24,7 @@ import { JobScheduler } from '../lib/scheduler.js';
23
24
  import { detectOverdueJobs } from '../lib/overdue.js';
24
25
  import { isInteractiveTerminal, requireInteractiveSelection } from './utils.js';
25
26
  import { setHelpSections } from '../lib/help.js';
26
- import { loadDevices } from '../lib/devices/registry.js';
27
+ import { loadDevices, loadDevicesSync } from '../lib/devices/registry.js';
27
28
  import { machineId, normalizeHost } from '../lib/machine-id.js';
28
29
  import { addHostOption } from '../lib/hosts/option.js';
29
30
  /**
@@ -74,6 +75,164 @@ function fireConditionLabel(job) {
74
75
  }
75
76
  return '-';
76
77
  }
78
+ function scheduleLabel(job) {
79
+ let label = fireConditionLabel(job);
80
+ if (isOneShotRoutine(job))
81
+ label = `${label} (one-shot)`;
82
+ if (job.endAt) {
83
+ const end = new Date(job.endAt);
84
+ const endLabel = Number.isFinite(end.getTime())
85
+ ? end.toLocaleDateString()
86
+ : job.endAt;
87
+ label = `${label} (until ${endLabel})`;
88
+ }
89
+ return label;
90
+ }
91
+ function nextRunForDisplay(job, scheduler) {
92
+ if (isPastOneShotRoutine(job))
93
+ return null;
94
+ return scheduler.getNextRun(job.name);
95
+ }
96
+ function nextRunLabel(job, scheduler, now) {
97
+ if (isPastOneShotRoutine(job))
98
+ return 'expired';
99
+ return humanizeNextRun(scheduler.getNextRun(job.name) ?? null, now, job.timezone);
100
+ }
101
+ function deviceStateLabel(name, registry) {
102
+ const profile = registry[normalizeHost(name)] ?? registry[name];
103
+ if (!profile)
104
+ return 'unknown';
105
+ if (profile.reachability?.reachable === false)
106
+ return 'offline';
107
+ if (profile.reachability?.reachable === true)
108
+ return 'online';
109
+ if (profile.tailscale?.online === false)
110
+ return 'offline';
111
+ if (profile.tailscale?.online === true)
112
+ return 'online';
113
+ return 'unknown';
114
+ }
115
+ function titleWithDeviceState(title, name, registry) {
116
+ const state = deviceStateLabel(name, registry);
117
+ return state && state !== 'online' ? `${title} (${state})` : title;
118
+ }
119
+ function placementTag(job) {
120
+ const strategy = resolveHostStrategy(job);
121
+ if (strategy === 'local')
122
+ return job.host ? `->${job.host}` : '';
123
+ if (strategy === 'host')
124
+ return `->${job.host ?? '?'}`;
125
+ if (strategy === 'fleet')
126
+ return '->fleet';
127
+ return '->cloud';
128
+ }
129
+ function deviceLabel(job, width) {
130
+ const full = [job.devices?.join(',') ?? '', placementTag(job)]
131
+ .filter(Boolean)
132
+ .join(' ');
133
+ const raw = full.length === 0 ? 'all' : full;
134
+ const display = width && raw.length > width
135
+ ? raw.slice(0, width - 1) + '…'
136
+ : raw;
137
+ return { raw, display, dim: full.length === 0 || !jobRunsOnThisDevice(job) };
138
+ }
139
+ export function groupRoutineJobsByDevice(jobs, registry, self = machineId()) {
140
+ const groups = new Map();
141
+ const add = (key, title, job) => {
142
+ const existing = groups.get(key);
143
+ if (existing) {
144
+ existing.jobs.push(job);
145
+ return;
146
+ }
147
+ groups.set(key, { key, title, jobs: [job] });
148
+ };
149
+ for (const job of jobs) {
150
+ const strategy = resolveHostStrategy(job);
151
+ if (strategy === 'cloud') {
152
+ add('cloud', 'Cloud', job);
153
+ continue;
154
+ }
155
+ if (strategy === 'fleet') {
156
+ add('fleet', 'Fleet-wide', job);
157
+ continue;
158
+ }
159
+ if (strategy === 'host') {
160
+ const host = job.host ?? 'unknown-host';
161
+ add(`host:${normalizeHost(host)}`, titleWithDeviceState(`Host: ${host}`, host, registry), job);
162
+ continue;
163
+ }
164
+ const devices = job.devices ?? [];
165
+ if (devices.length === 0) {
166
+ add('fleet', 'Fleet-wide', job);
167
+ continue;
168
+ }
169
+ for (const device of devices) {
170
+ const normalized = normalizeHost(device);
171
+ if (normalized === self) {
172
+ add('this-machine', `This machine (${self})`, job);
173
+ }
174
+ else {
175
+ add(`device:${normalized}`, titleWithDeviceState(`Device: ${normalized}`, normalized, registry), job);
176
+ }
177
+ }
178
+ }
179
+ const order = (group) => {
180
+ if (group.key === 'this-machine')
181
+ return 0;
182
+ if (group.key === 'fleet')
183
+ return 1;
184
+ if (group.key === 'cloud')
185
+ return 2;
186
+ if (group.key.startsWith('device:'))
187
+ return 3;
188
+ if (group.key.startsWith('host:'))
189
+ return 4;
190
+ return 5;
191
+ };
192
+ return [...groups.values()].sort((a, b) => order(a) - order(b) || a.title.localeCompare(b.title));
193
+ }
194
+ function renderRoutineRows({ jobs, scheduler, overdueSet, link, now }) {
195
+ const NAME_W = 24;
196
+ const AGENT_W = 10;
197
+ const REPO_W = REPO_DISPLAY_MAX;
198
+ const DEVICE_W = 22;
199
+ const SCHED_W = 34;
200
+ const ENABLED_W = 10;
201
+ const NEXT_W = 22;
202
+ const header = ` ${'Name'.padEnd(NAME_W)} ${'Agent'.padEnd(AGENT_W)} ${'Repo'.padEnd(REPO_W)} ${'Devices'.padEnd(DEVICE_W)} ${'Schedule'.padEnd(SCHED_W)} ${'Enabled'.padEnd(ENABLED_W)} ${'Next Run'.padEnd(NEXT_W)} Last Status`;
203
+ console.log(chalk.gray(header));
204
+ console.log(chalk.gray(' ' + '-'.repeat(NAME_W + AGENT_W + REPO_W + DEVICE_W + SCHED_W + ENABLED_W + NEXT_W + 20)));
205
+ for (const job of jobs) {
206
+ const nextStr = nextRunLabel(job, scheduler, now);
207
+ const schedStr = scheduleLabel(job);
208
+ const latestRun = getLatestRun(job.name);
209
+ const lastStatus = latestRun?.status || '-';
210
+ const sourceRepo = job.source?.repo ?? job.repo;
211
+ const sourceLabel = sourceRepo
212
+ ? (job.source?.branch ? `${sourceRepo}@${job.source.branch}` : sourceRepo)
213
+ : null;
214
+ const repoInfo = formatRepoLink(sourceLabel ?? job.repo);
215
+ const repoCell = link(repoInfo.display, repoInfo.href);
216
+ const repoPadding = Math.max(0, REPO_W - repoInfo.display.length);
217
+ const enabledStr = job.enabled ? chalk.green('yes') : chalk.gray('no');
218
+ const enabledWord = job.enabled ? 'yes' : 'no';
219
+ const enabledPad = Math.max(0, ENABLED_W - enabledWord.length);
220
+ const device = deviceLabel(job, DEVICE_W);
221
+ const deviceCell = device.dim ? chalk.gray(device.display) : device.display;
222
+ const devicePad = Math.max(0, DEVICE_W - device.display.length);
223
+ const statusColor = lastStatus === 'completed' ? chalk.green
224
+ : lastStatus === 'failed' ? chalk.red
225
+ : lastStatus === 'timeout' ? chalk.yellow
226
+ : chalk.gray;
227
+ const overdueTag = overdueSet.has(job.name) ? chalk.yellow(' (overdue)') : '';
228
+ const agentLabelPadded = job.command
229
+ ? chalk.magenta('command'.padEnd(10))
230
+ : job.workflow
231
+ ? chalk.magenta(`wf:${job.workflow}`.padEnd(10))
232
+ : (job.agent || '').padEnd(10);
233
+ console.log(` ${chalk.cyan(job.name.padEnd(NAME_W))} ${agentLabelPadded} ${repoCell}${' '.repeat(repoPadding)} ${deviceCell}${' '.repeat(devicePad)} ${schedStr.padEnd(SCHED_W)} ${enabledStr}${' '.repeat(enabledPad)} ${chalk.gray(nextStr.padEnd(NEXT_W))} ${statusColor(lastStatus)}${overdueTag}`);
234
+ }
235
+ }
77
236
  function parseRoutineTrigger(options) {
78
237
  const raw = typeof options.on === 'string' ? options.on : undefined;
79
238
  if (!raw)
@@ -280,7 +439,7 @@ export function registerRoutinesCommands(program) {
280
439
  `,
281
440
  notes: `
282
441
  A routine is a YAML file that schedules an agent invocation. It specifies:
283
- - which agent to run (claude, codex, gemini, ...)
442
+ - which agent to run (claude, codex, antigravity, ...)
284
443
  - when to run (cron schedule or one-shot time)
285
444
  - what task to give the agent (the prompt)
286
445
  - execution constraints (mode, effort, timeout)
@@ -307,7 +466,13 @@ export function registerRoutinesCommands(program) {
307
466
  .command('list')
308
467
  .description('See all scheduled jobs, when they run next, and their last execution status')
309
468
  .option('--json', 'Emit machine-readable JSON instead of the table (used by the menu bar helper)')
469
+ .option('--group-by <field>', 'Group table output by device (default for terminal output)')
470
+ .option('--flat', 'Print the legacy flat table instead of grouped sections')
310
471
  .action((options) => {
472
+ if (options.groupBy && options.groupBy !== 'device') {
473
+ console.error(chalk.red(`Unsupported --group-by '${options.groupBy}'. Use: device`));
474
+ process.exit(1);
475
+ }
311
476
  try {
312
477
  monitorRunningJobs();
313
478
  }
@@ -338,7 +503,6 @@ export function registerRoutinesCommands(program) {
338
503
  if (options.json) {
339
504
  const nowJson = new Date();
340
505
  const payload = jobs.map((job) => {
341
- const nextRun = scheduler.getNextRun(job.name);
342
506
  const latestRun = getLatestRun(job.name);
343
507
  return {
344
508
  name: job.name,
@@ -356,11 +520,14 @@ export function registerRoutinesCommands(program) {
356
520
  source: job.source ?? null,
357
521
  sourceRepo: job.source?.repo ?? job.repo ?? null,
358
522
  sourceBranch: job.source?.branch ?? null,
523
+ runOnce: Boolean(job.runOnce),
524
+ oneShot: isOneShotRoutine(job),
525
+ expired: isPastOneShotRoutine(job, nowJson),
359
526
  runsHere: jobRunsOnThisDevice(job),
360
527
  enabled: job.enabled,
361
528
  overdue: overdueSet.has(job.name),
362
- nextRun: nextRun ? nextRun.toISOString() : null,
363
- nextRunHuman: humanizeNextRun(nextRun ?? null, nowJson, job.timezone),
529
+ nextRun: nextRunForDisplay(job, scheduler)?.toISOString() ?? null,
530
+ nextRunHuman: nextRunLabel(job, scheduler, nowJson),
364
531
  lastStatus: latestRun?.status ?? null,
365
532
  exitCode: latestRun?.exitCode ?? null,
366
533
  failureReason: latestRun?.errorMessage ?? null,
@@ -378,76 +545,21 @@ export function registerRoutinesCommands(program) {
378
545
  // contains raw ESC ] 8 ;; ... BEL escape sequences.
379
546
  const link = (label, url) => url && process.stdout.isTTY ? `\x1b]8;;${url}\x07${label}\x1b]8;;\x07` : label;
380
547
  const now = new Date();
381
- const NAME_W = 24;
382
- const AGENT_W = 10;
383
- const REPO_W = REPO_DISPLAY_MAX;
384
- const DEVICE_W = 22;
385
- const SCHED_W = 22;
386
- const ENABLED_W = 10;
387
- const NEXT_W = 22;
388
- const header = ` ${'Name'.padEnd(NAME_W)} ${'Agent'.padEnd(AGENT_W)} ${'Repo'.padEnd(REPO_W)} ${'Devices'.padEnd(DEVICE_W)} ${'Schedule'.padEnd(SCHED_W)} ${'Enabled'.padEnd(ENABLED_W)} ${'Next Run'.padEnd(NEXT_W)} Last Status`;
389
- console.log(chalk.gray(header));
390
- console.log(chalk.gray(' ' + '-'.repeat(NAME_W + AGENT_W + REPO_W + DEVICE_W + SCHED_W + ENABLED_W + NEXT_W + 20)));
391
- for (const job of jobs) {
392
- const nextRun = scheduler.getNextRun(job.name);
393
- const nextStr = humanizeNextRun(nextRun ?? null, now, job.timezone);
394
- let schedStr = fireConditionLabel(job);
395
- if (job.endAt) {
396
- const end = new Date(job.endAt);
397
- const endLabel = Number.isFinite(end.getTime())
398
- ? end.toLocaleDateString()
399
- : job.endAt;
400
- schedStr = `${schedStr} (until ${endLabel})`;
401
- }
402
- const latestRun = getLatestRun(job.name);
403
- const lastStatus = latestRun?.status || '-';
404
- // Prefer project-source repo (with optional @branch) over bare job.repo.
405
- const sourceRepo = job.source?.repo ?? job.repo;
406
- const sourceLabel = sourceRepo
407
- ? (job.source?.branch ? `${sourceRepo}@${job.source.branch}` : sourceRepo)
408
- : null;
409
- const repoInfo = formatRepoLink(sourceLabel ?? job.repo);
410
- const repoCell = link(repoInfo.display, repoInfo.href);
411
- // Pad based on the display string, not the raw cell (which may include escape codes).
412
- const repoPadding = Math.max(0, REPO_W - repoInfo.display.length);
413
- const enabledStr = job.enabled ? chalk.green('yes') : chalk.gray('no');
414
- // chalk adds escape codes; pad the raw word and let chalk wrap it.
415
- const enabledWord = job.enabled ? 'yes' : 'no';
416
- const enabledPad = Math.max(0, ENABLED_W - enabledWord.length);
417
- // Placement rides in the Devices cell: eligibility →execution strategy.
418
- const strategy = resolveHostStrategy(job);
419
- const placementTag = strategy === 'local'
420
- ? (job.host ? `→${job.host}` : '')
421
- : strategy === 'host'
422
- ? `→${job.host ?? '?'}`
423
- : strategy === 'fleet'
424
- ? '→fleet'
425
- : '→cloud';
426
- const deviceFull = [job.devices?.join(',') ?? '', placementTag]
427
- .filter(Boolean)
428
- .join(' ');
429
- const deviceWord = deviceFull.length === 0
430
- ? 'all'
431
- : deviceFull.length > DEVICE_W
432
- ? deviceFull.slice(0, DEVICE_W - 1) + '…'
433
- : deviceFull;
434
- const deviceCell = deviceFull.length === 0
435
- ? chalk.gray('all')
436
- : jobRunsOnThisDevice(job)
437
- ? deviceWord
438
- : chalk.gray(deviceWord);
439
- const devicePad = Math.max(0, DEVICE_W - deviceWord.length);
440
- const statusColor = lastStatus === 'completed' ? chalk.green
441
- : lastStatus === 'failed' ? chalk.red
442
- : lastStatus === 'timeout' ? chalk.yellow
443
- : chalk.gray;
444
- const overdueTag = overdueSet.has(job.name) ? chalk.yellow(' (overdue)') : '';
445
- const agentLabelPadded = job.command
446
- ? chalk.magenta('command'.padEnd(10))
447
- : job.workflow
448
- ? chalk.magenta(`wf:${job.workflow}`.padEnd(10))
449
- : (job.agent || '').padEnd(10);
450
- console.log(` ${chalk.cyan(job.name.padEnd(NAME_W))} ${agentLabelPadded} ${repoCell}${' '.repeat(repoPadding)} ${deviceCell}${' '.repeat(devicePad)} ${schedStr.padEnd(SCHED_W)} ${enabledStr}${' '.repeat(enabledPad)} ${chalk.gray(nextStr.padEnd(NEXT_W))} ${statusColor(lastStatus)}${overdueTag}`);
548
+ if (options.flat) {
549
+ renderRoutineRows({ jobs, scheduler, overdueSet, link, now });
550
+ }
551
+ else {
552
+ let registry = {};
553
+ try {
554
+ registry = loadDevicesSync();
555
+ }
556
+ catch (err) {
557
+ console.error(chalk.yellow(`Could not read device registry: ${err.message}`));
558
+ }
559
+ for (const group of groupRoutineJobsByDevice(jobs, registry)) {
560
+ console.log(chalk.bold(`\n${group.title}`));
561
+ renderRoutineRows({ jobs: group.jobs, scheduler, overdueSet, link, now });
562
+ }
451
563
  }
452
564
  if (overdueSet.size > 0) {
453
565
  console.log();
@@ -460,7 +572,7 @@ export function registerRoutinesCommands(program) {
460
572
  .command('add [nameOrPath]')
461
573
  .description('Create a new routine from a YAML file or inline flags. Starts the scheduler automatically if it is not already running.')
462
574
  .option('-s, --schedule <cron>', 'Cron schedule in standard format (5 fields: minute hour day month weekday)')
463
- .option('-a, --agent <agent>', 'Which agent runs this routine: claude, codex, gemini, cursor, or opencode')
575
+ .option('-a, --agent <agent>', 'Which agent runs this routine: claude, codex, antigravity, cursor, or opencode')
464
576
  .option('--workflow <name>', 'Run an installed workflow (~/.agents/workflows/<name>) via `agents run`. Mutually exclusive with --agent.')
465
577
  .option('--command <sh>', 'Run a plain shell command directly (no agent, no auth, no sandbox) — for deterministic housekeeping routines. Mutually exclusive with --agent and --workflow; --prompt is not used.')
466
578
  .option('-p, --prompt <prompt>', 'Task instruction for the agent')
@@ -519,6 +631,10 @@ export function registerRoutinesCommands(program) {
519
631
  schedule = parsed.schedule;
520
632
  runOnce = parsed.runOnce;
521
633
  }
634
+ if (!options.at && isOneShotLikeSchedule(schedule)) {
635
+ runOnce = true;
636
+ console.error(chalk.yellow(`Schedule "${schedule}" pins minute, hour, day, and month; treating it as one-shot. Prefer --at for one-time routines.`));
637
+ }
522
638
  if (!schedule && !trigger) {
523
639
  console.error(chalk.red('Schedule or trigger is required (use --schedule, --at, or --on)'));
524
640
  process.exit(1);
@@ -527,6 +643,15 @@ export function registerRoutinesCommands(program) {
527
643
  console.error(chalk.red('An agent, workflow, or command is required (use --agent, --workflow, or --command)'));
528
644
  process.exit(1);
529
645
  }
646
+ // Hard-deprecated harnesses cannot be scheduled — refuse at create time so
647
+ // no new recurring job silently fails against a retired backend.
648
+ if (options.agent) {
649
+ const routineAgentId = resolveAgentName(options.agent);
650
+ if (routineAgentId && isAgentHardDeprecated(routineAgentId)) {
651
+ console.error(chalk.red(hardDeprecationError(routineAgentId)));
652
+ process.exit(1);
653
+ }
654
+ }
530
655
  // Command routines run a plain shell and take no prompt; agent/workflow routines require one.
531
656
  if (!options.command && !options.prompt) {
532
657
  console.error(chalk.red('Prompt is required (use --prompt)'));
@@ -651,6 +776,10 @@ export function registerRoutinesCommands(program) {
651
776
  enabled: true,
652
777
  ...parsed,
653
778
  };
779
+ if (isOneShotLikeSchedule(config.schedule)) {
780
+ config.runOnce = true;
781
+ console.error(chalk.yellow(`Schedule "${config.schedule}" pins minute, hour, day, and month; treating it as one-shot. Prefer --at for one-time routines.`));
782
+ }
654
783
  // Same duplicate-fire guard as --placement/--run-on: off-box placement
655
784
  // with no eligibility pin would fire from every daemon in the fleet.
656
785
  const fileStrategy = resolveHostStrategy(config);
@@ -678,6 +807,41 @@ export function registerRoutinesCommands(program) {
678
807
  ensureSchedulerRunning();
679
808
  }
680
809
  });
810
+ routinesCmd
811
+ .command('cleanup')
812
+ .description('Remove expired one-shot routines that already fired and still have a user-layer YAML file.')
813
+ .option('--dry-run', 'Show routines that would be removed without deleting files')
814
+ .action((options) => {
815
+ const jobs = listAllJobs()
816
+ .filter((job) => getJobPath(job.name) !== null)
817
+ .filter((job) => hasCompletedOneShotRun(job));
818
+ if (jobs.length === 0) {
819
+ console.log(chalk.gray('No completed expired one-shot routines to clean up.'));
820
+ return;
821
+ }
822
+ if (options.dryRun) {
823
+ console.log(chalk.bold('Expired one-shot routines eligible for cleanup\n'));
824
+ for (const job of jobs) {
825
+ console.log(` ${chalk.cyan(job.name)} ${chalk.gray(scheduleLabel(job))}`);
826
+ }
827
+ console.log(chalk.gray(`\nDry run. Remove with: agents routines cleanup`));
828
+ return;
829
+ }
830
+ let removed = 0;
831
+ for (const job of jobs) {
832
+ if (deleteJob(job.name)) {
833
+ removed++;
834
+ console.log(chalk.green(`Removed ${job.name}`));
835
+ }
836
+ }
837
+ console.log(chalk.gray(`Cleaned up ${removed} expired one-shot routine(s).`));
838
+ try {
839
+ signalDaemonReload();
840
+ }
841
+ catch {
842
+ // The daemon may not be running; the next start will read the cleaned directory.
843
+ }
844
+ });
681
845
  routinesCmd
682
846
  .command('remove [name]')
683
847
  .description('Delete a routine. Stops scheduling future runs; past execution logs remain on disk.')
@@ -4,7 +4,8 @@ import * as fs from 'fs';
4
4
  import * as os from 'os';
5
5
  import * as path from 'path';
6
6
  import { select, checkbox } from '@inquirer/prompts';
7
- import { AGENTS, agentConfigDirName, ALL_AGENT_IDS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
7
+ import { AGENTS, agentConfigDirName, MANAGED_AGENT_IDS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
8
+ import { capableAgents } from '../lib/capabilities.js';
8
9
  import { cloneRepo } from '../lib/git.js';
9
10
  import { discoverInstructionsFromRepo, discoverRuleFilesFromRepo, installInstructionsCentrally, uninstallInstructions, listInstalledInstructionsWithScope, instructionsExists, getInstructionsContent, listCentralRules, } from '../lib/rules/rules.js';
10
11
  import { listInstalledVersions, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, } from '../lib/versions.js';
@@ -28,7 +29,7 @@ Examples:
28
29
  agents rules list claude@2.1.112
29
30
 
30
31
  # Install rules from your .agents repo to multiple agents
31
- agents rules add --agents claude,codex,gemini
32
+ agents rules add --agents claude,codex,cursor
32
33
 
33
34
  # Install a shared rule file from GitHub
34
35
  agents rules add gh:anthropics/agent-rules --agents codex@0.116.0
@@ -44,13 +45,13 @@ When to use:
44
45
 
45
46
  Project rules & @-imports:
46
47
  Project rules live in <repo>/.agents/rules/. On every agent launch, the shim compiles
47
- them into <repo>/AGENTS.md (with CLAUDE.md, GEMINI.md, .cursorrules symlinked to it).
48
+ them into <repo>/AGENTS.md (with CLAUDE.md and .cursorrules symlinked to it).
48
49
  A hand-authored AGENTS.md is preserved — the compile pipeline only overwrites files
49
50
  it owns (those starting with the auto-compile header). Delete the file to migrate.
50
51
 
51
52
  @path imports inside AGENTS.md/CLAUDE.md are resolved at session start by the agent
52
53
  itself, not by agents-cli. Support is per-agent:
53
- Inlined natively: claude, gemini
54
+ Inlined natively: claude
54
55
  Literal text: codex, cursor, opencode, copilot, amp, kiro, goose
55
56
 
56
57
  For rules that need to work across all agents, inline the content rather than using
@@ -161,7 +162,7 @@ Project rules & @-imports:
161
162
  return;
162
163
  }
163
164
  console.log(chalk.bold('Installed Rules\n'));
164
- for (const aid of ALL_AGENT_IDS) {
165
+ for (const aid of MANAGED_AGENT_IDS) {
165
166
  const agent = AGENTS[aid];
166
167
  const installedVersions = listInstalledVersions(aid);
167
168
  const defaultVer = getGlobalDefault(aid);
@@ -200,7 +201,7 @@ Project rules & @-imports:
200
201
  rulesCmd
201
202
  .command('add [source]')
202
203
  .description('Install rule files from a source (GitHub, local path) or pick from central storage')
203
- .option('-a, --agents <list>', 'Targets: claude, codex@0.116.0, or gemini@default')
204
+ .option('-a, --agents <list>', 'Targets: claude, codex@0.116.0, or cursor@default')
204
205
  .option('--names <list>', 'Rule file names from ~/.agents/rules/ (comma-separated)')
205
206
  .option('-y, --yes', 'Skip all prompts and use defaults')
206
207
  .addHelpText('after', `
@@ -212,7 +213,7 @@ Examples:
212
213
  agents rules add --names AGENTS.md --agents codex@0.116.0
213
214
 
214
215
  # Install all rules from a GitHub repo to multiple agents
215
- agents rules add gh:team/agent-rules --agents claude,gemini
216
+ agents rules add gh:team/agent-rules --agents claude,codex
216
217
 
217
218
  # Sync a local rules directory to your active agents
218
219
  agents rules add ~/projects/my-rules --agents claude@default
@@ -317,7 +318,7 @@ Examples:
317
318
  let selectedAgents;
318
319
  let versionSelections;
319
320
  if (options.agents) {
320
- const result = await resolveAgentTargetsAutoInstalling(options.agents, ALL_AGENT_IDS, { yes: options.yes });
321
+ const result = await resolveAgentTargetsAutoInstalling(options.agents, capableAgents('rules'), { yes: options.yes });
321
322
  if (!result) {
322
323
  console.log(chalk.gray('Cancelled.'));
323
324
  return;
@@ -326,7 +327,7 @@ Examples:
326
327
  versionSelections = result.versionSelections;
327
328
  }
328
329
  else {
329
- const result = await promptAgentVersionSelection(ALL_AGENT_IDS, {
330
+ const result = await promptAgentVersionSelection(capableAgents('rules'), {
330
331
  skipPrompts: options.yes,
331
332
  });
332
333
  selectedAgents = result.selectedAgents;
@@ -376,7 +377,7 @@ Examples:
376
377
  agents rules view codex --scope project
377
378
 
378
379
  # View rules for a specific installed version
379
- agents rules view gemini@1.5.0
380
+ agents rules view codex@0.116.0
380
381
  `)
381
382
  .action(async (agentArg, options) => {
382
383
  const cwd = process.cwd();
@@ -393,7 +394,7 @@ Examples:
393
394
  requestedVersion = resolveVersionAlias(agentId, parts[1]) ?? null;
394
395
  }
395
396
  else {
396
- const choices = ALL_AGENT_IDS.filter((id) => instructionsExists(id, 'user', cwd) || instructionsExists(id, 'project', cwd));
397
+ const choices = MANAGED_AGENT_IDS.filter((id) => instructionsExists(id, 'user', cwd) || instructionsExists(id, 'project', cwd));
397
398
  if (choices.length === 0) {
398
399
  console.log(chalk.yellow('No rule files found.'));
399
400
  return;
@@ -460,7 +461,7 @@ Examples:
460
461
  if (!agentArg) {
461
462
  // Only list agents that actually have a rules file installed — avoids
462
463
  // suggesting agents the user hasn't touched.
463
- const candidates = ALL_AGENT_IDS.filter((id) => instructionsExists(id));
464
+ const candidates = MANAGED_AGENT_IDS.filter((id) => instructionsExists(id));
464
465
  requireDestructiveArg({
465
466
  argName: 'agent',
466
467
  command: 'agents rules remove',
@@ -8,7 +8,7 @@
8
8
  import { type Command } from 'commander';
9
9
  import { SSH_TARGET_RE, assertValidSshTarget } from '../lib/ssh-exec.js';
10
10
  import { quoteWin32ExecArg } from '../lib/platform/index.js';
11
- import { type SecretsBundle, type SecretsPolicy } from '../lib/secrets/bundles.js';
11
+ import { type SecretsBackend, type SecretsBundle, type SecretsPolicy } from '../lib/secrets/bundles.js';
12
12
  /** Read all available data from stdin synchronously, trimmed. */
13
13
  /**
14
14
  * Read the raw `.env` text for `import --from <path|->`. A `-` reads the .env
@@ -109,4 +109,6 @@ export declare function assertNeverPolicyAcknowledged(policy: SecretsPolicy | un
109
109
  iUnderstand?: boolean;
110
110
  interactive: boolean;
111
111
  }): 'ok' | 'prompt';
112
+ /** Validate a --backend value, exiting with a clear message on a bad one. */
113
+ export declare function secretsDefaultBackend(): SecretsBackend;
112
114
  export { quoteWin32ExecArg };