@phnx-labs/agents-cli 1.20.71 → 1.20.73

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 (238) hide show
  1. package/CHANGELOG.md +325 -1
  2. package/README.md +40 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +12 -0
  5. package/dist/commands/activity.js +68 -0
  6. package/dist/commands/apply.d.ts +13 -0
  7. package/dist/commands/apply.js +28 -3
  8. package/dist/commands/browser.js +1 -1
  9. package/dist/commands/cloud.js +21 -11
  10. package/dist/commands/commands.js +2 -0
  11. package/dist/commands/doctor.js +8 -4
  12. package/dist/commands/events.d.ts +11 -10
  13. package/dist/commands/events.js +30 -21
  14. package/dist/commands/exec.d.ts +34 -0
  15. package/dist/commands/exec.js +337 -39
  16. package/dist/commands/feed.d.ts +13 -0
  17. package/dist/commands/feed.js +130 -29
  18. package/dist/commands/hq.d.ts +2 -0
  19. package/dist/commands/hq.js +58 -0
  20. package/dist/commands/lease.d.ts +19 -0
  21. package/dist/commands/lease.js +138 -11
  22. package/dist/commands/login.d.ts +2 -0
  23. package/dist/commands/login.js +123 -0
  24. package/dist/commands/logs.js +39 -10
  25. package/dist/commands/mcp.js +2 -0
  26. package/dist/commands/mine.d.ts +27 -0
  27. package/dist/commands/mine.js +207 -0
  28. package/dist/commands/monitors.js +79 -49
  29. package/dist/commands/plugins.js +1 -0
  30. package/dist/commands/profiles.js +158 -0
  31. package/dist/commands/repo.js +2 -60
  32. package/dist/commands/resource-view.d.ts +2 -0
  33. package/dist/commands/resource-view.js +8 -0
  34. package/dist/commands/resources.d.ts +5 -0
  35. package/dist/commands/resources.js +95 -0
  36. package/dist/commands/routines.d.ts +2 -0
  37. package/dist/commands/routines.js +139 -37
  38. package/dist/commands/secrets.js +234 -47
  39. package/dist/commands/send.d.ts +15 -0
  40. package/dist/commands/send.js +62 -0
  41. package/dist/commands/sessions-tail.d.ts +1 -0
  42. package/dist/commands/sessions-tail.js +20 -9
  43. package/dist/commands/sessions.d.ts +6 -0
  44. package/dist/commands/sessions.js +26 -3
  45. package/dist/commands/setup-mine.d.ts +18 -0
  46. package/dist/commands/setup-mine.js +106 -0
  47. package/dist/commands/setup-share.js +2 -2
  48. package/dist/commands/setup.js +3 -1
  49. package/dist/commands/share.d.ts +8 -1
  50. package/dist/commands/share.js +122 -37
  51. package/dist/commands/skills.js +2 -0
  52. package/dist/commands/ssh.d.ts +9 -0
  53. package/dist/commands/ssh.js +196 -26
  54. package/dist/commands/status.js +8 -2
  55. package/dist/commands/subagents.js +3 -1
  56. package/dist/commands/sync.js +1 -1
  57. package/dist/commands/teams.js +11 -2
  58. package/dist/commands/uninstall.d.ts +11 -0
  59. package/dist/commands/uninstall.js +158 -0
  60. package/dist/commands/utils.d.ts +29 -0
  61. package/dist/commands/utils.js +24 -0
  62. package/dist/commands/versions.js +120 -11
  63. package/dist/index.js +83 -12
  64. package/dist/lib/activity.d.ts +124 -0
  65. package/dist/lib/activity.js +542 -0
  66. package/dist/lib/agents.js +16 -5
  67. package/dist/lib/artifact-actions.d.ts +1 -1
  68. package/dist/lib/artifact-actions.js +1 -1
  69. package/dist/lib/ask-classifier.d.ts +2 -1
  70. package/dist/lib/ask-classifier.js +3 -3
  71. package/dist/lib/auth-health.d.ts +32 -4
  72. package/dist/lib/auth-health.js +40 -1
  73. package/dist/lib/auto-pull.js +8 -1
  74. package/dist/lib/brand.d.ts +40 -0
  75. package/dist/lib/brand.js +122 -0
  76. package/dist/lib/browser/drivers/ssh.js +4 -7
  77. package/dist/lib/browser/service.js +6 -8
  78. package/dist/lib/channels/providers/index.d.ts +2 -0
  79. package/dist/lib/channels/providers/index.js +20 -0
  80. package/dist/lib/channels/providers/mailbox.d.ts +2 -0
  81. package/dist/lib/channels/providers/mailbox.js +26 -0
  82. package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
  83. package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
  84. package/dist/lib/channels/providers/rush.d.ts +6 -0
  85. package/dist/lib/channels/providers/rush.js +55 -0
  86. package/dist/lib/channels/registry.d.ts +42 -0
  87. package/dist/lib/channels/registry.js +20 -0
  88. package/dist/lib/channels/resolve.d.ts +11 -0
  89. package/dist/lib/channels/resolve.js +12 -0
  90. package/dist/lib/cli-resources.d.ts +18 -0
  91. package/dist/lib/cli-resources.js +53 -4
  92. package/dist/lib/cloud/codex.js +6 -3
  93. package/dist/lib/cloud/factory.d.ts +1 -0
  94. package/dist/lib/cloud/factory.js +10 -4
  95. package/dist/lib/cloud/rush.d.ts +3 -1
  96. package/dist/lib/cloud/rush.js +6 -1
  97. package/dist/lib/cloud/types.d.ts +2 -0
  98. package/dist/lib/codex-home.d.ts +35 -0
  99. package/dist/lib/codex-home.js +136 -0
  100. package/dist/lib/crabbox/cli.d.ts +44 -0
  101. package/dist/lib/crabbox/cli.js +120 -20
  102. package/dist/lib/crabbox/lease.d.ts +52 -1
  103. package/dist/lib/crabbox/lease.js +117 -16
  104. package/dist/lib/crabbox/progress.d.ts +31 -0
  105. package/dist/lib/crabbox/progress.js +76 -0
  106. package/dist/lib/crabbox/runtimes.d.ts +4 -0
  107. package/dist/lib/crabbox/runtimes.js +33 -8
  108. package/dist/lib/crabbox/setup-copy.d.ts +87 -0
  109. package/dist/lib/crabbox/setup-copy.js +127 -0
  110. package/dist/lib/daemon.d.ts +14 -5
  111. package/dist/lib/daemon.js +52 -9
  112. package/dist/lib/drive-sync.js +6 -3
  113. package/dist/lib/event-stream.d.ts +36 -0
  114. package/dist/lib/event-stream.js +68 -0
  115. package/dist/lib/events.d.ts +1 -1
  116. package/dist/lib/exec.d.ts +11 -0
  117. package/dist/lib/exec.js +23 -2
  118. package/dist/lib/feed-policy.d.ts +1 -1
  119. package/dist/lib/feed-policy.js +14 -9
  120. package/dist/lib/feed-ranking.d.ts +32 -0
  121. package/dist/lib/feed-ranking.js +224 -0
  122. package/dist/lib/feed.d.ts +35 -2
  123. package/dist/lib/feed.js +66 -3
  124. package/dist/lib/fleet/auth-sync.d.ts +53 -0
  125. package/dist/lib/fleet/auth-sync.js +92 -0
  126. package/dist/lib/fleet/remote-login.d.ts +187 -0
  127. package/dist/lib/fleet/remote-login.js +556 -0
  128. package/dist/lib/format.d.ts +30 -3
  129. package/dist/lib/format.js +34 -5
  130. package/dist/lib/fs-atomic.d.ts +7 -2
  131. package/dist/lib/fs-atomic.js +8 -12
  132. package/dist/lib/git.d.ts +16 -0
  133. package/dist/lib/git.js +79 -2
  134. package/dist/lib/heal.js +11 -3
  135. package/dist/lib/hosts/logs.d.ts +12 -0
  136. package/dist/lib/hosts/logs.js +18 -0
  137. package/dist/lib/hosts/progress.d.ts +28 -10
  138. package/dist/lib/hosts/progress.js +79 -22
  139. package/dist/lib/hosts/remote-cmd.js +4 -0
  140. package/dist/lib/hq/floor.d.ts +87 -0
  141. package/dist/lib/hq/floor.js +226 -0
  142. package/dist/lib/menubar/install-menubar.js +14 -20
  143. package/dist/lib/notify.d.ts +1 -0
  144. package/dist/lib/notify.js +3 -3
  145. package/dist/lib/open-url.d.ts +2 -0
  146. package/dist/lib/open-url.js +19 -0
  147. package/dist/lib/openclaw-keychain.d.ts +56 -0
  148. package/dist/lib/openclaw-keychain.js +236 -0
  149. package/dist/lib/overdue.js +10 -0
  150. package/dist/lib/permissions.d.ts +44 -1
  151. package/dist/lib/permissions.js +284 -7
  152. package/dist/lib/project-launch.d.ts +6 -12
  153. package/dist/lib/project-launch.js +13 -228
  154. package/dist/lib/project-resources.d.ts +7 -0
  155. package/dist/lib/project-resources.js +291 -0
  156. package/dist/lib/pty-client.d.ts +27 -0
  157. package/dist/lib/pty-client.js +136 -10
  158. package/dist/lib/refresh.js +14 -10
  159. package/dist/lib/resource-profiles.d.ts +26 -0
  160. package/dist/lib/resource-profiles.js +157 -0
  161. package/dist/lib/resources/permissions.js +7 -1
  162. package/dist/lib/resources/types.d.ts +1 -1
  163. package/dist/lib/resources.d.ts +1 -1
  164. package/dist/lib/resources.js +32 -3
  165. package/dist/lib/routines.d.ts +11 -0
  166. package/dist/lib/routines.js +56 -15
  167. package/dist/lib/runner.d.ts +1 -0
  168. package/dist/lib/runner.js +66 -5
  169. package/dist/lib/secrets/bundles.d.ts +16 -3
  170. package/dist/lib/secrets/bundles.js +206 -37
  171. package/dist/lib/secrets/icloud-import.d.ts +2 -2
  172. package/dist/lib/secrets/icloud-import.js +9 -6
  173. package/dist/lib/secrets/mcp.js +1 -1
  174. package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
  175. package/dist/lib/secrets/vault-age-helper.js +34 -0
  176. package/dist/lib/secrets/vault.d.ts +49 -0
  177. package/dist/lib/secrets/vault.js +397 -0
  178. package/dist/lib/self-heal/checks/path.js +3 -1
  179. package/dist/lib/self-heal/checks/shadowing.js +10 -2
  180. package/dist/lib/self-heal/checks/shims.js +19 -11
  181. package/dist/lib/session/cloud.d.ts +2 -2
  182. package/dist/lib/session/cloud.js +2 -2
  183. package/dist/lib/session/db.d.ts +4 -0
  184. package/dist/lib/session/db.js +44 -7
  185. package/dist/lib/session/discover.d.ts +2 -0
  186. package/dist/lib/session/discover.js +114 -3
  187. package/dist/lib/session/stream-render.d.ts +3 -0
  188. package/dist/lib/session/stream-render.js +130 -0
  189. package/dist/lib/session/types.d.ts +6 -0
  190. package/dist/lib/share/analytics.d.ts +13 -0
  191. package/dist/lib/share/analytics.js +45 -0
  192. package/dist/lib/share/config.d.ts +19 -5
  193. package/dist/lib/share/config.js +44 -8
  194. package/dist/lib/share/provision.d.ts +58 -6
  195. package/dist/lib/share/provision.js +97 -22
  196. package/dist/lib/share/publish.d.ts +36 -13
  197. package/dist/lib/share/publish.js +55 -8
  198. package/dist/lib/share/worker-template.js +83 -17
  199. package/dist/lib/shims.d.ts +36 -0
  200. package/dist/lib/shims.js +120 -16
  201. package/dist/lib/ssh-exec.d.ts +14 -0
  202. package/dist/lib/ssh-exec.js +57 -0
  203. package/dist/lib/staleness/detectors/hooks.js +25 -1
  204. package/dist/lib/staleness/detectors/permissions.js +66 -0
  205. package/dist/lib/staleness/detectors/workflows.js +20 -0
  206. package/dist/lib/staleness/writers/hooks.js +58 -4
  207. package/dist/lib/staleness/writers/permissions.js +2 -2
  208. package/dist/lib/staleness/writers/skills.js +1 -1
  209. package/dist/lib/staleness/writers/sources.d.ts +10 -1
  210. package/dist/lib/staleness/writers/sources.js +68 -1
  211. package/dist/lib/star-nudge.d.ts +45 -0
  212. package/dist/lib/star-nudge.js +91 -0
  213. package/dist/lib/startup/command-registry.d.ts +7 -1
  214. package/dist/lib/startup/command-registry.js +19 -3
  215. package/dist/lib/state.d.ts +2 -0
  216. package/dist/lib/state.js +3 -0
  217. package/dist/lib/subagents-registry.d.ts +2 -0
  218. package/dist/lib/subagents-registry.js +3 -0
  219. package/dist/lib/teams/parsers.js +214 -6
  220. package/dist/lib/teams/supervisor.d.ts +7 -0
  221. package/dist/lib/teams/supervisor.js +2 -1
  222. package/dist/lib/template.d.ts +1 -1
  223. package/dist/lib/template.js +1 -1
  224. package/dist/lib/triggers/webhook.js +36 -3
  225. package/dist/lib/types.d.ts +73 -0
  226. package/dist/lib/uninstall.d.ts +84 -0
  227. package/dist/lib/uninstall.js +347 -0
  228. package/dist/lib/usage.d.ts +13 -1
  229. package/dist/lib/usage.js +32 -14
  230. package/dist/lib/version.d.ts +40 -0
  231. package/dist/lib/version.js +94 -16
  232. package/dist/lib/versions.d.ts +27 -0
  233. package/dist/lib/versions.js +248 -79
  234. package/dist/lib/workflows.d.ts +2 -0
  235. package/dist/lib/workflows.js +88 -0
  236. package/package.json +2 -1
  237. package/dist/commands/daemon.d.ts +0 -10
  238. package/dist/commands/daemon.js +0 -121
@@ -14,6 +14,14 @@ import { isInteractiveTerminal, isPromptCancelled, printWithPager } from './util
14
14
  import { terminalWidth, truncateToWidth, padToWidth, stringWidth, stripAnsi } from '../lib/session/width.js';
15
15
  /** Display a resource list: interactive picker in TTY mode, plain table otherwise. */
16
16
  export async function showResourceList(opts) {
17
+ if (opts.json) {
18
+ // Strip the non-serializable buildDetail thunk; emit the row metadata plus
19
+ // each resource's per-agent-version sync targets. One shared JSON contract
20
+ // for every resource `list` built on this helper.
21
+ const rows = opts.rows.map(({ buildDetail, ...row }) => row);
22
+ console.log(JSON.stringify(rows, null, 2));
23
+ return;
24
+ }
17
25
  if (opts.rows.length === 0) {
18
26
  console.log(chalk.gray(opts.emptyMessage));
19
27
  return;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * `agents resources` — show the merged DotAgents resource surface.
3
+ */
4
+ import type { Command } from 'commander';
5
+ export declare function registerResourcesCommand(program: Command): void;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * `agents resources` — show the merged DotAgents resource surface.
3
+ */
4
+ import * as path from 'path';
5
+ import chalk from 'chalk';
6
+ import { listResources } from '../lib/resources.js';
7
+ import { terminalWidth, truncateToWidth, stringWidth } from '../lib/session/width.js';
8
+ const DRILLABLE_KINDS = [
9
+ 'skills',
10
+ 'commands',
11
+ 'mcp',
12
+ 'hooks',
13
+ 'rules',
14
+ 'plugins',
15
+ 'workflows',
16
+ 'subagents',
17
+ ];
18
+ const KIND_LABELS = {
19
+ skills: 'Skills',
20
+ commands: 'Commands',
21
+ mcp: 'MCP',
22
+ hooks: 'Hooks',
23
+ rules: 'Rules',
24
+ plugins: 'Plugins',
25
+ workflows: 'Workflows',
26
+ subagents: 'Subagents',
27
+ };
28
+ export function registerResourcesCommand(program) {
29
+ program
30
+ .command('resources')
31
+ .description('Show the merged DotAgents resources resolved across project, user, system, and extras')
32
+ .option('--merged', 'Show the merged first-wins resource surface (default)')
33
+ .addHelpText('after', `
34
+ Examples:
35
+ agents resources
36
+ agents resources --merged
37
+ `)
38
+ .action((options) => {
39
+ void options;
40
+ renderMergedResources();
41
+ });
42
+ }
43
+ function renderMergedResources() {
44
+ const groups = DRILLABLE_KINDS.map((kind) => ({
45
+ kind,
46
+ rows: listResources(kind),
47
+ }));
48
+ const total = groups.reduce((sum, group) => sum + group.rows.length, 0);
49
+ if (total === 0) {
50
+ console.log(chalk.gray('No merged resources found.'));
51
+ return;
52
+ }
53
+ console.log(chalk.bold(`Resources (${total} merged)`));
54
+ for (const group of groups) {
55
+ console.log();
56
+ console.log(chalk.bold(`${KIND_LABELS[group.kind]} (${group.rows.length})`));
57
+ if (group.rows.length === 0) {
58
+ console.log(chalk.gray(' none'));
59
+ continue;
60
+ }
61
+ for (const line of renderResourceRows(group.rows))
62
+ console.log(line);
63
+ }
64
+ }
65
+ function renderResourceRows(rows) {
66
+ const nameW = Math.min(28, Math.max('Name'.length, ...rows.map((row) => stringWidth(row.name))));
67
+ const layerW = Math.min(16, Math.max('Layer'.length, ...rows.map((row) => stringWidth(row.source))));
68
+ const prefixW = 2 + nameW + 2 + layerW + 2;
69
+ const pathW = Math.max(12, terminalWidth() - prefixW);
70
+ const lines = [
71
+ ` ${chalk.bold(pad(row('Name', nameW), nameW))} ${chalk.bold(pad(row('Layer', layerW), layerW))} ${chalk.bold('Path')}`,
72
+ ` ${chalk.gray(`${'-'.repeat(nameW)} ${'-'.repeat(layerW)} ${'-'.repeat(Math.min(pathW, 40))}`)}`,
73
+ ];
74
+ for (const resource of rows) {
75
+ lines.push(` ${chalk.cyan(pad(truncateToWidth(resource.name, nameW), nameW))} ${pad(resource.source, layerW)} ${chalk.gray(truncateToWidth(formatPath(resource.path), pathW))}`);
76
+ }
77
+ return lines;
78
+ }
79
+ function row(value, width) {
80
+ return truncateToWidth(value, width);
81
+ }
82
+ function pad(value, width) {
83
+ return value + ' '.repeat(Math.max(0, width - stringWidth(value)));
84
+ }
85
+ function formatPath(value) {
86
+ const home = process.env.HOME;
87
+ if (home) {
88
+ const rel = path.relative(home, value);
89
+ if (rel === '')
90
+ return '~';
91
+ if (!rel.startsWith('..') && !path.isAbsolute(rel))
92
+ return `~/${rel}`;
93
+ }
94
+ return value;
95
+ }
@@ -6,11 +6,13 @@
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
10
  /**
10
11
  * Human-friendly wall-clock a run took (e.g. " · 3 min", " · 45 sec"), or ""
11
12
  * when it hasn't completed or timestamps are unparseable. Leading separator lets
12
13
  * callers drop it straight into a status line.
13
14
  */
14
15
  export declare function formatRunDuration(startedAt: string, completedAt: string | null): string;
16
+ export declare function buildRunsJson(runs: RunMeta[]): Record<string, unknown>[];
15
17
  /** Register the `agents routines` command tree. */
16
18
  export declare function registerRoutinesCommands(program: Command): void;
@@ -58,7 +58,11 @@ function fireConditionLabel(job) {
58
58
  const scope = job.trigger.repo
59
59
  ? ` (${job.trigger.repo}${job.trigger.branch ? `@${job.trigger.branch}` : ''})`
60
60
  : '';
61
- return `on github:${job.trigger.event}${scope}`;
61
+ const filters = [
62
+ job.trigger.action ? `action=${job.trigger.action}` : null,
63
+ job.trigger.label ? `label=${job.trigger.label}` : null,
64
+ ].filter(Boolean).join(', ');
65
+ return `on github:${job.trigger.event}${scope}${filters ? ` (${filters})` : ''}`;
62
66
  }
63
67
  const filters = [
64
68
  job.trigger.action ? `action=${job.trigger.action}` : null,
@@ -83,6 +87,10 @@ function parseRoutineTrigger(options) {
83
87
  trigger.repo = options.repo;
84
88
  if (typeof options.branch === 'string')
85
89
  trigger.branch = options.branch;
90
+ if (typeof options.action === 'string')
91
+ trigger.action = options.action;
92
+ if (typeof options.label === 'string')
93
+ trigger.label = options.label;
86
94
  return trigger;
87
95
  }
88
96
  if (sourceMaybe === 'linear') {
@@ -97,22 +105,47 @@ function parseRoutineTrigger(options) {
97
105
  }
98
106
  throw new Error('--on source must be github or linear');
99
107
  }
100
- /** Start or reload the background scheduler so newly-added jobs fire on time. */
101
- function ensureSchedulerRunning() {
108
+ /**
109
+ * Start or reload the background scheduler so newly-added jobs fire on time.
110
+ * `quiet` suppresses human status lines for JSON callers.
111
+ */
112
+ function ensureSchedulerRunning(opts = {}) {
113
+ const log = opts.stderr ? console.error : console.log;
102
114
  if (isDaemonRunning()) {
103
115
  signalDaemonReload();
104
- console.log(chalk.gray('Scheduler reloaded'));
116
+ if (!opts.quiet)
117
+ log(chalk.gray('Scheduler reloaded'));
105
118
  return;
106
119
  }
107
120
  const result = startDaemon();
121
+ if (opts.quiet)
122
+ return;
108
123
  if (result.pid) {
109
- console.log(chalk.green(`Scheduler started (PID: ${result.pid}). It will run in the background and fire routines on schedule.`));
110
- console.log(chalk.gray(`Stop anytime with: agents routines stop`));
124
+ log(chalk.green(`Scheduler started (PID: ${result.pid}). It will run in the background and fire routines on schedule.`));
125
+ log(chalk.gray(`Stop anytime with: agents routines stop`));
111
126
  }
112
127
  else {
113
- console.log(chalk.yellow('Could not start the scheduler. Start it manually with: agents routines start'));
128
+ log(chalk.yellow('Could not start the scheduler. Start it manually with: agents routines start'));
114
129
  }
115
130
  }
131
+ function writeJson(payload) {
132
+ process.stdout.write(JSON.stringify(payload) + '\n');
133
+ }
134
+ function runMetaJson(run) {
135
+ return {
136
+ jobId: run.jobName,
137
+ jobName: run.jobName,
138
+ runId: run.runId,
139
+ status: run.status,
140
+ startedAt: run.startedAt,
141
+ completedAt: run.completedAt,
142
+ exitCode: run.exitCode,
143
+ errorMessage: run.errorMessage ?? null,
144
+ };
145
+ }
146
+ export function buildRunsJson(runs) {
147
+ return runs.map(runMetaJson);
148
+ }
116
149
  /** Detect Ctrl+C or premature stream close during an interactive prompt. */
117
150
  function isPromptCancelled(err) {
118
151
  return (err instanceof Error &&
@@ -296,6 +329,8 @@ export function registerRoutinesCommands(program) {
296
329
  nextRun: nextRun ? nextRun.toISOString() : null,
297
330
  nextRunHuman: humanizeNextRun(nextRun ?? null, nowJson, job.timezone),
298
331
  lastStatus: latestRun?.status ?? null,
332
+ exitCode: latestRun?.exitCode ?? null,
333
+ failureReason: latestRun?.errorMessage ?? null,
299
334
  lastRunStartedAt: latestRun?.startedAt ?? null,
300
335
  lastRunCompletedAt: latestRun?.completedAt ?? null,
301
336
  };
@@ -394,25 +429,26 @@ export function registerRoutinesCommands(program) {
394
429
  .option('--on <source:event>', 'Webhook trigger instead of/in addition to a schedule: github:pull_request or linear:Issue')
395
430
  .option('--repo <owner/name>', 'GitHub repo filter for --on github:<event>')
396
431
  .option('--branch <name>', 'GitHub branch filter for --on github:<event>')
397
- .option('--action <name>', 'Linear action filter for --on linear:<event> (e.g. update)')
432
+ .option('--action <name>', 'Webhook action filter for --on triggers (GitHub: labeled/opened; Linear: update)')
398
433
  .option('--team-key <key>', 'Linear team key filter for --on linear:<event> (e.g. RUSH)')
399
- .option('--label <name>', 'Linear issue label filter for --on linear:Issue')
434
+ .option('--label <name>', 'Label filter for --on triggers (GitHub label name or Linear issue label)')
400
435
  .option('--end-at <iso>', 'Stop firing on or after this ISO 8601 timestamp (e.g., "2026-12-31T23:59:00Z"); routine auto-disables.')
401
436
  .option('--disabled', 'Create the routine but keep it paused (enable later with resume)')
402
437
  .option('--resume <sessionId>', 'At fire time, resume this existing session id (via `agents run <agent> --resume`) instead of starting fresh — the actual session reopens with full context and the prompt becomes its next turn. Powers self-scheduled wake-ups (e.g. /hibernate). Requires --agent claude or codex; runs un-sandboxed (the session store lives in the real home, not the job overlay).')
438
+ .option('--json', 'Emit machine-readable JSON with the created routine id and status')
403
439
  .action(async (nameOrPath, options) => {
404
440
  // Check if inline mode (has flags) or file mode
405
441
  const hasInlineFlags = options.schedule || options.agent || options.workflow || options.command || options.prompt || options.at || options.on;
406
442
  if (hasInlineFlags) {
407
443
  // Inline mode: create job from flags
408
444
  if (!nameOrPath) {
409
- console.log(chalk.red('Job name is required'));
410
- console.log(chalk.gray('Usage: agents routines add <name> --schedule "..." --agent <agent> --prompt "..."'));
445
+ console.error(chalk.red('Job name is required'));
446
+ console.error(chalk.gray('Usage: agents routines add <name> --schedule "..." --agent <agent> --prompt "..."'));
411
447
  process.exit(1);
412
448
  }
413
449
  // Validate mutually exclusive --agent / --workflow / --command
414
450
  if ([options.agent, options.workflow, options.command].filter(Boolean).length > 1) {
415
- console.log(chalk.red('--agent, --workflow, and --command are mutually exclusive; specify exactly one'));
451
+ console.error(chalk.red('--agent, --workflow, and --command are mutually exclusive; specify exactly one'));
416
452
  process.exit(1);
417
453
  }
418
454
  let schedule = options.schedule;
@@ -422,31 +458,31 @@ export function registerRoutinesCommands(program) {
422
458
  trigger = parseRoutineTrigger(options);
423
459
  }
424
460
  catch (err) {
425
- console.log(chalk.red(err.message));
461
+ console.error(chalk.red(err.message));
426
462
  process.exit(1);
427
463
  }
428
464
  // Handle --at for one-shot jobs
429
465
  if (options.at) {
430
466
  const parsed = parseAtTime(options.at);
431
467
  if (!parsed) {
432
- console.log(chalk.red(`Invalid --at format: ${options.at}`));
433
- console.log(chalk.gray('Supported formats: "14:30" or "2026-02-24 09:00"'));
468
+ console.error(chalk.red(`Invalid --at format: ${options.at}`));
469
+ console.error(chalk.gray('Supported formats: "14:30" or "2026-02-24 09:00"'));
434
470
  process.exit(1);
435
471
  }
436
472
  schedule = parsed.schedule;
437
473
  runOnce = parsed.runOnce;
438
474
  }
439
475
  if (!schedule && !trigger) {
440
- console.log(chalk.red('Schedule or trigger is required (use --schedule, --at, or --on)'));
476
+ console.error(chalk.red('Schedule or trigger is required (use --schedule, --at, or --on)'));
441
477
  process.exit(1);
442
478
  }
443
479
  if (!options.agent && !options.workflow && !options.command) {
444
- console.log(chalk.red('An agent, workflow, or command is required (use --agent, --workflow, or --command)'));
480
+ console.error(chalk.red('An agent, workflow, or command is required (use --agent, --workflow, or --command)'));
445
481
  process.exit(1);
446
482
  }
447
483
  // Command routines run a plain shell and take no prompt; agent/workflow routines require one.
448
484
  if (!options.command && !options.prompt) {
449
- console.log(chalk.red('Prompt is required (use --prompt)'));
485
+ console.error(chalk.red('Prompt is required (use --prompt)'));
450
486
  process.exit(1);
451
487
  }
452
488
  // Parse and validate --devices against the fleet registry.
@@ -459,7 +495,7 @@ export function registerRoutinesCommands(program) {
459
495
  // this machine unless the user chose an explicit eligibility set.
460
496
  if (options.runOn && !devices) {
461
497
  devices = [machineId()];
462
- console.log(chalk.gray(`--run-on set with no --devices: pinned firing to this machine (${devices[0]}).`));
498
+ console.error(chalk.gray(`--run-on set with no --devices: pinned firing to this machine (${devices[0]}).`));
463
499
  }
464
500
  const config = {
465
501
  name: nameOrPath,
@@ -483,13 +519,28 @@ export function registerRoutinesCommands(program) {
483
519
  };
484
520
  const errors = validateJob(config);
485
521
  if (errors.length > 0) {
486
- console.log(chalk.red('Validation errors:'));
522
+ console.error(chalk.red('Validation errors:'));
487
523
  for (const err of errors) {
488
- console.log(chalk.red(` - ${err}`));
524
+ console.error(chalk.red(` - ${err}`));
489
525
  }
490
526
  process.exit(1);
491
527
  }
492
528
  writeJob(config);
529
+ if (options.json) {
530
+ writeJson({
531
+ ok: true,
532
+ added: nameOrPath,
533
+ job: config,
534
+ jobId: config.name,
535
+ name: config.name,
536
+ status: 'added',
537
+ enabled: config.enabled,
538
+ schedule: config.schedule ?? null,
539
+ trigger: config.trigger ?? null,
540
+ });
541
+ ensureSchedulerRunning({ quiet: true });
542
+ return;
543
+ }
493
544
  console.log(chalk.green(`Job '${nameOrPath}' added`));
494
545
  if (runOnce) {
495
546
  console.log(chalk.gray(`One-shot job scheduled for: ${options.at}`));
@@ -499,14 +550,14 @@ export function registerRoutinesCommands(program) {
499
550
  else {
500
551
  // File mode: load from YAML file
501
552
  if (!nameOrPath) {
502
- console.log(chalk.red('File path or job name with flags is required'));
503
- console.log(chalk.gray('Usage: agents routines add <path-to-job.yml>'));
504
- console.log(chalk.gray(' or: agents routines add <name> --schedule "..." --agent <agent> --prompt "..."'));
553
+ console.error(chalk.red('File path or job name with flags is required'));
554
+ console.error(chalk.gray('Usage: agents routines add <path-to-job.yml>'));
555
+ console.error(chalk.gray(' or: agents routines add <name> --schedule "..." --agent <agent> --prompt "..."'));
505
556
  process.exit(1);
506
557
  }
507
558
  const resolved = path.resolve(nameOrPath);
508
559
  if (!fs.existsSync(resolved)) {
509
- console.log(chalk.red(`File not found: ${resolved}`));
560
+ console.error(chalk.red(`File not found: ${resolved}`));
510
561
  process.exit(1);
511
562
  }
512
563
  const content = fs.readFileSync(resolved, 'utf-8');
@@ -515,16 +566,16 @@ export function registerRoutinesCommands(program) {
515
566
  parsed = yaml.parse(content);
516
567
  }
517
568
  catch (err) {
518
- console.log(chalk.red(`Invalid YAML: ${err.message}`));
569
+ console.error(chalk.red(`Invalid YAML: ${err.message}`));
519
570
  process.exit(1);
520
571
  }
521
572
  const name = parsed.name || path.basename(resolved).replace(/\.ya?ml$/, '');
522
573
  parsed.name = name;
523
574
  const errors = validateJob(parsed);
524
575
  if (errors.length > 0) {
525
- console.log(chalk.red('Validation errors:'));
576
+ console.error(chalk.red('Validation errors:'));
526
577
  for (const err of errors) {
527
- console.log(chalk.red(` - ${err}`));
578
+ console.error(chalk.red(` - ${err}`));
528
579
  }
529
580
  process.exit(1);
530
581
  }
@@ -539,9 +590,24 @@ export function registerRoutinesCommands(program) {
539
590
  // with no eligibility pin would fire from every daemon in the fleet.
540
591
  if (config.host && (!config.devices || config.devices.length === 0)) {
541
592
  config.devices = [machineId()];
542
- console.log(chalk.gray(`host: set with no devices pin: pinned firing to this machine (${config.devices[0]}).`));
593
+ console.error(chalk.gray(`host: set with no devices pin: pinned firing to this machine (${config.devices[0]}).`));
543
594
  }
544
595
  writeJob(config);
596
+ if (options.json) {
597
+ writeJson({
598
+ ok: true,
599
+ added: name,
600
+ job: config,
601
+ jobId: config.name,
602
+ name: config.name,
603
+ status: 'added',
604
+ enabled: config.enabled,
605
+ schedule: config.schedule ?? null,
606
+ trigger: config.trigger ?? null,
607
+ });
608
+ ensureSchedulerRunning({ quiet: true });
609
+ return;
610
+ }
545
611
  console.log(chalk.green(`Job '${name}' added`));
546
612
  ensureSchedulerRunning();
547
613
  }
@@ -644,13 +710,22 @@ export function registerRoutinesCommands(program) {
644
710
  routinesCmd
645
711
  .command('runs [name]')
646
712
  .description('See execution history: run IDs, completion status, and start times (up to last 10 runs)')
647
- .action(async (name) => {
713
+ .option('--json', 'Emit machine-readable JSON with run ids and statuses')
714
+ .action(async (name, options) => {
648
715
  if (!name) {
649
716
  name = await pickJob('Select job to view runs', undefined, ['agents routines runs <name>']) ?? undefined;
650
717
  if (!name)
651
718
  return;
652
719
  }
653
720
  const runs = listRuns(name);
721
+ if (options.json) {
722
+ writeJson({
723
+ jobId: name,
724
+ name,
725
+ runs: buildRunsJson(runs.slice(-10)),
726
+ });
727
+ return;
728
+ }
654
729
  if (runs.length === 0) {
655
730
  console.log(chalk.yellow(`No runs found for job '${name}'`));
656
731
  return;
@@ -668,7 +743,8 @@ export function registerRoutinesCommands(program) {
668
743
  routinesCmd
669
744
  .command('run [name]')
670
745
  .description('Execute a routine right now in the foreground. Ignores the schedule; useful for testing before enabling.')
671
- .action(async (name) => {
746
+ .option('--json', 'Emit machine-readable JSON with the run id and status')
747
+ .action(async (name, options) => {
672
748
  if (!name) {
673
749
  name = await pickJob('Select job to run', undefined, ['agents routines run <name>']) ?? undefined;
674
750
  if (!name)
@@ -682,20 +758,42 @@ export function registerRoutinesCommands(program) {
682
758
  // the trusted user layer.
683
759
  const job = readJob(name);
684
760
  if (!job) {
685
- console.log(chalk.red(`Job '${name}' not found`));
761
+ if (options.json) {
762
+ writeJson({ error: `Job '${name}' not found` });
763
+ process.exit(1);
764
+ }
765
+ console.error(chalk.red(`Job '${name}' not found`));
686
766
  process.exit(1);
687
767
  }
688
768
  const eligibility = checkJobDeviceEligibility(job);
689
769
  if (eligibility) {
690
- console.log(chalk.red(eligibility.message));
691
- console.log(chalk.gray(` ${eligibility.suggestion}`));
770
+ if (options.json) {
771
+ writeJson({ error: eligibility.message, hint: eligibility.suggestion });
772
+ process.exit(1);
773
+ }
774
+ console.error(chalk.red(eligibility.message));
775
+ console.error(chalk.gray(` ${eligibility.suggestion}`));
692
776
  process.exit(1);
693
777
  }
694
778
  const runLabel = job.command ? 'command' : job.workflow ? `workflow: ${job.workflow}` : `agent: ${job.agent}`;
695
- console.log(chalk.bold(`Running job '${name}' (${runLabel}, mode: ${job.mode})\n`));
696
- const spinner = ora('Executing...').start();
779
+ // A spinner writes to stderr but its human framing is noise for a JSON consumer.
780
+ const spinner = options.json ? null : ora('Executing...').start();
781
+ if (!options.json)
782
+ console.log(chalk.bold(`Running job '${name}' (${runLabel}, mode: ${job.mode})\n`));
697
783
  try {
698
784
  const result = await executeJob(job);
785
+ const logPath = `${getRunDir(name, result.meta.runId)}/stdout.log`;
786
+ if (options.json) {
787
+ writeJson({
788
+ ok: true,
789
+ job: name,
790
+ logDir: getRunDir(name, result.meta.runId),
791
+ ...runMetaJson(result.meta),
792
+ logPath,
793
+ reportPath: result.reportPath ?? null,
794
+ });
795
+ return;
796
+ }
699
797
  if (result.meta.status === 'completed') {
700
798
  spinner.succeed(`Job completed (exit code: ${result.meta.exitCode})`);
701
799
  }
@@ -706,13 +804,17 @@ export function registerRoutinesCommands(program) {
706
804
  spinner.fail(`Job failed (exit code: ${result.meta.exitCode})`);
707
805
  }
708
806
  console.log(chalk.gray(` Run: ${result.meta.runId}`));
709
- console.log(chalk.gray(` Log: ${getRunDir(name, result.meta.runId)}/stdout.log`));
807
+ console.log(chalk.gray(` Log: ${logPath}`));
710
808
  if (result.reportPath) {
711
809
  console.log(chalk.bold('\nReport:\n'));
712
810
  console.log(fs.readFileSync(result.reportPath, 'utf-8'));
713
811
  }
714
812
  }
715
813
  catch (err) {
814
+ if (options.json) {
815
+ writeJson({ error: err.message });
816
+ process.exit(1);
817
+ }
716
818
  spinner.fail('Execution failed');
717
819
  console.error(chalk.red(err.message));
718
820
  process.exit(1);