@phnx-labs/agents-cli 1.20.72 → 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 (219) hide show
  1. package/CHANGELOG.md +299 -0
  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/doctor.js +8 -4
  11. package/dist/commands/events.d.ts +11 -10
  12. package/dist/commands/events.js +30 -21
  13. package/dist/commands/exec.d.ts +34 -0
  14. package/dist/commands/exec.js +337 -39
  15. package/dist/commands/feed.d.ts +13 -0
  16. package/dist/commands/feed.js +130 -29
  17. package/dist/commands/hq.d.ts +2 -0
  18. package/dist/commands/hq.js +58 -0
  19. package/dist/commands/lease.d.ts +19 -0
  20. package/dist/commands/lease.js +138 -11
  21. package/dist/commands/login.d.ts +2 -0
  22. package/dist/commands/login.js +123 -0
  23. package/dist/commands/logs.js +1 -1
  24. package/dist/commands/mine.d.ts +27 -0
  25. package/dist/commands/mine.js +207 -0
  26. package/dist/commands/monitors.js +79 -49
  27. package/dist/commands/plugins.js +1 -0
  28. package/dist/commands/profiles.js +158 -0
  29. package/dist/commands/repo.js +2 -60
  30. package/dist/commands/resources.d.ts +5 -0
  31. package/dist/commands/resources.js +95 -0
  32. package/dist/commands/routines.d.ts +2 -0
  33. package/dist/commands/routines.js +139 -37
  34. package/dist/commands/secrets.js +234 -47
  35. package/dist/commands/send.d.ts +15 -0
  36. package/dist/commands/send.js +62 -0
  37. package/dist/commands/sessions-tail.d.ts +1 -0
  38. package/dist/commands/sessions-tail.js +20 -9
  39. package/dist/commands/sessions.js +18 -3
  40. package/dist/commands/setup-mine.d.ts +18 -0
  41. package/dist/commands/setup-mine.js +106 -0
  42. package/dist/commands/setup-share.js +2 -2
  43. package/dist/commands/setup.js +3 -1
  44. package/dist/commands/share.d.ts +8 -1
  45. package/dist/commands/share.js +122 -37
  46. package/dist/commands/ssh.d.ts +9 -0
  47. package/dist/commands/ssh.js +196 -26
  48. package/dist/commands/sync.js +1 -1
  49. package/dist/commands/teams.js +11 -2
  50. package/dist/index.js +79 -10
  51. package/dist/lib/activity.d.ts +124 -0
  52. package/dist/lib/activity.js +542 -0
  53. package/dist/lib/agents.js +16 -5
  54. package/dist/lib/artifact-actions.d.ts +1 -1
  55. package/dist/lib/artifact-actions.js +1 -1
  56. package/dist/lib/ask-classifier.d.ts +2 -1
  57. package/dist/lib/ask-classifier.js +3 -3
  58. package/dist/lib/auth-health.d.ts +32 -4
  59. package/dist/lib/auth-health.js +40 -1
  60. package/dist/lib/auto-pull.js +8 -1
  61. package/dist/lib/brand.d.ts +40 -0
  62. package/dist/lib/brand.js +122 -0
  63. package/dist/lib/browser/drivers/ssh.js +4 -7
  64. package/dist/lib/browser/service.js +6 -8
  65. package/dist/lib/channels/providers/index.d.ts +2 -0
  66. package/dist/lib/channels/providers/index.js +20 -0
  67. package/dist/lib/channels/providers/mailbox.d.ts +2 -0
  68. package/dist/lib/channels/providers/mailbox.js +26 -0
  69. package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
  70. package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
  71. package/dist/lib/channels/providers/rush.d.ts +6 -0
  72. package/dist/lib/channels/providers/rush.js +55 -0
  73. package/dist/lib/channels/registry.d.ts +42 -0
  74. package/dist/lib/channels/registry.js +20 -0
  75. package/dist/lib/channels/resolve.d.ts +11 -0
  76. package/dist/lib/channels/resolve.js +12 -0
  77. package/dist/lib/cli-resources.d.ts +18 -0
  78. package/dist/lib/cli-resources.js +53 -4
  79. package/dist/lib/cloud/codex.js +6 -3
  80. package/dist/lib/cloud/factory.d.ts +1 -0
  81. package/dist/lib/cloud/factory.js +10 -4
  82. package/dist/lib/cloud/rush.d.ts +3 -1
  83. package/dist/lib/cloud/rush.js +6 -1
  84. package/dist/lib/cloud/types.d.ts +2 -0
  85. package/dist/lib/codex-home.d.ts +35 -0
  86. package/dist/lib/codex-home.js +136 -0
  87. package/dist/lib/crabbox/cli.d.ts +44 -0
  88. package/dist/lib/crabbox/cli.js +120 -20
  89. package/dist/lib/crabbox/lease.d.ts +52 -1
  90. package/dist/lib/crabbox/lease.js +117 -16
  91. package/dist/lib/crabbox/progress.d.ts +31 -0
  92. package/dist/lib/crabbox/progress.js +76 -0
  93. package/dist/lib/crabbox/runtimes.d.ts +4 -0
  94. package/dist/lib/crabbox/runtimes.js +33 -8
  95. package/dist/lib/crabbox/setup-copy.d.ts +87 -0
  96. package/dist/lib/crabbox/setup-copy.js +127 -0
  97. package/dist/lib/daemon.d.ts +5 -5
  98. package/dist/lib/daemon.js +9 -9
  99. package/dist/lib/drive-sync.js +6 -3
  100. package/dist/lib/event-stream.d.ts +36 -0
  101. package/dist/lib/event-stream.js +68 -0
  102. package/dist/lib/events.d.ts +1 -1
  103. package/dist/lib/exec.d.ts +11 -0
  104. package/dist/lib/exec.js +23 -2
  105. package/dist/lib/feed-policy.d.ts +1 -1
  106. package/dist/lib/feed-policy.js +14 -9
  107. package/dist/lib/feed-ranking.d.ts +32 -0
  108. package/dist/lib/feed-ranking.js +224 -0
  109. package/dist/lib/feed.d.ts +35 -2
  110. package/dist/lib/feed.js +66 -3
  111. package/dist/lib/fleet/auth-sync.d.ts +53 -0
  112. package/dist/lib/fleet/auth-sync.js +92 -0
  113. package/dist/lib/fleet/remote-login.d.ts +187 -0
  114. package/dist/lib/fleet/remote-login.js +556 -0
  115. package/dist/lib/format.d.ts +30 -3
  116. package/dist/lib/format.js +34 -5
  117. package/dist/lib/fs-atomic.d.ts +7 -2
  118. package/dist/lib/fs-atomic.js +8 -12
  119. package/dist/lib/git.d.ts +16 -0
  120. package/dist/lib/git.js +79 -2
  121. package/dist/lib/heal.js +11 -3
  122. package/dist/lib/hosts/progress.d.ts +28 -10
  123. package/dist/lib/hosts/progress.js +79 -22
  124. package/dist/lib/hosts/remote-cmd.js +4 -0
  125. package/dist/lib/hq/floor.d.ts +87 -0
  126. package/dist/lib/hq/floor.js +226 -0
  127. package/dist/lib/menubar/install-menubar.js +14 -20
  128. package/dist/lib/notify.d.ts +1 -0
  129. package/dist/lib/notify.js +3 -3
  130. package/dist/lib/open-url.d.ts +2 -0
  131. package/dist/lib/open-url.js +19 -0
  132. package/dist/lib/openclaw-keychain.d.ts +56 -0
  133. package/dist/lib/openclaw-keychain.js +236 -0
  134. package/dist/lib/overdue.js +10 -0
  135. package/dist/lib/permissions.d.ts +44 -1
  136. package/dist/lib/permissions.js +284 -7
  137. package/dist/lib/project-launch.d.ts +6 -12
  138. package/dist/lib/project-launch.js +13 -228
  139. package/dist/lib/project-resources.d.ts +7 -0
  140. package/dist/lib/project-resources.js +291 -0
  141. package/dist/lib/pty-client.d.ts +27 -0
  142. package/dist/lib/pty-client.js +136 -10
  143. package/dist/lib/refresh.js +14 -10
  144. package/dist/lib/resource-profiles.d.ts +26 -0
  145. package/dist/lib/resource-profiles.js +157 -0
  146. package/dist/lib/resources/permissions.js +7 -1
  147. package/dist/lib/resources/types.d.ts +1 -1
  148. package/dist/lib/resources.d.ts +1 -1
  149. package/dist/lib/resources.js +32 -3
  150. package/dist/lib/routines.d.ts +11 -0
  151. package/dist/lib/routines.js +56 -15
  152. package/dist/lib/runner.d.ts +1 -0
  153. package/dist/lib/runner.js +66 -5
  154. package/dist/lib/secrets/bundles.d.ts +16 -3
  155. package/dist/lib/secrets/bundles.js +206 -37
  156. package/dist/lib/secrets/icloud-import.d.ts +2 -2
  157. package/dist/lib/secrets/icloud-import.js +9 -6
  158. package/dist/lib/secrets/mcp.js +1 -1
  159. package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
  160. package/dist/lib/secrets/vault-age-helper.js +34 -0
  161. package/dist/lib/secrets/vault.d.ts +49 -0
  162. package/dist/lib/secrets/vault.js +397 -0
  163. package/dist/lib/self-heal/checks/path.js +3 -1
  164. package/dist/lib/self-heal/checks/shadowing.js +10 -2
  165. package/dist/lib/self-heal/checks/shims.js +19 -11
  166. package/dist/lib/session/cloud.d.ts +2 -2
  167. package/dist/lib/session/cloud.js +2 -2
  168. package/dist/lib/session/db.d.ts +4 -0
  169. package/dist/lib/session/db.js +44 -7
  170. package/dist/lib/session/discover.d.ts +2 -0
  171. package/dist/lib/session/discover.js +114 -3
  172. package/dist/lib/session/stream-render.d.ts +3 -0
  173. package/dist/lib/session/stream-render.js +130 -0
  174. package/dist/lib/session/types.d.ts +6 -0
  175. package/dist/lib/share/analytics.d.ts +13 -0
  176. package/dist/lib/share/analytics.js +45 -0
  177. package/dist/lib/share/config.d.ts +19 -5
  178. package/dist/lib/share/config.js +44 -8
  179. package/dist/lib/share/provision.d.ts +58 -6
  180. package/dist/lib/share/provision.js +97 -22
  181. package/dist/lib/share/publish.d.ts +36 -13
  182. package/dist/lib/share/publish.js +55 -8
  183. package/dist/lib/share/worker-template.js +83 -17
  184. package/dist/lib/shims.d.ts +9 -0
  185. package/dist/lib/shims.js +91 -13
  186. package/dist/lib/ssh-exec.d.ts +14 -0
  187. package/dist/lib/ssh-exec.js +57 -0
  188. package/dist/lib/staleness/detectors/hooks.js +25 -1
  189. package/dist/lib/staleness/detectors/permissions.js +66 -0
  190. package/dist/lib/staleness/detectors/workflows.js +20 -0
  191. package/dist/lib/staleness/writers/hooks.js +58 -4
  192. package/dist/lib/staleness/writers/permissions.js +2 -2
  193. package/dist/lib/staleness/writers/skills.js +1 -1
  194. package/dist/lib/staleness/writers/sources.d.ts +10 -1
  195. package/dist/lib/staleness/writers/sources.js +68 -1
  196. package/dist/lib/star-nudge.d.ts +45 -0
  197. package/dist/lib/star-nudge.js +91 -0
  198. package/dist/lib/startup/command-registry.d.ts +6 -1
  199. package/dist/lib/startup/command-registry.js +17 -3
  200. package/dist/lib/state.d.ts +2 -0
  201. package/dist/lib/state.js +3 -0
  202. package/dist/lib/subagents-registry.d.ts +2 -0
  203. package/dist/lib/subagents-registry.js +3 -0
  204. package/dist/lib/teams/parsers.js +214 -6
  205. package/dist/lib/teams/supervisor.d.ts +7 -0
  206. package/dist/lib/teams/supervisor.js +2 -1
  207. package/dist/lib/template.d.ts +1 -1
  208. package/dist/lib/template.js +1 -1
  209. package/dist/lib/triggers/webhook.js +36 -3
  210. package/dist/lib/types.d.ts +73 -0
  211. package/dist/lib/version.d.ts +40 -0
  212. package/dist/lib/version.js +94 -16
  213. package/dist/lib/versions.d.ts +11 -0
  214. package/dist/lib/versions.js +208 -76
  215. package/dist/lib/workflows.d.ts +2 -0
  216. package/dist/lib/workflows.js +88 -0
  217. package/package.json +2 -1
  218. package/dist/commands/daemon.d.ts +0 -10
  219. package/dist/commands/daemon.js +0 -121
@@ -62,7 +62,7 @@ async function showSession(session, follow, full, json = false) {
62
62
  console.error(chalk.red(`Tailing is supported for claude and codex sessions only (got ${session.agent}).`));
63
63
  process.exit(2);
64
64
  }
65
- await streamSessionTail(session, {});
65
+ await streamSessionTail(session, { raw: full });
66
66
  return;
67
67
  }
68
68
  await renderSessionLog(session, full ? 'markdown' : 'summary');
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `agents mine` — white-label the CLI under your own name.
3
+ *
4
+ * `agents mine init <name>` mints a personally-named binary (e.g. `jack`) that
5
+ * IS agents-cli: a pure pass-through shim on PATH that runs every `agents` verb
6
+ * under the brand's name, with the brand's disabled commands and curated
7
+ * resource profile applied. `list` / `toggle` / `remove` manage brands.
8
+ *
9
+ * The discoverable entry point is the `agents setup mine` wizard
10
+ * (see setup-mine.ts), which delegates to `initBrand` here.
11
+ *
12
+ * Storage: brand config in `meta.brands` (agents.yaml); the curated resource set
13
+ * reuses the resource-profile engine — each brand owns a preset named
14
+ * `mine-<name>` in `meta.profiles.presets`. See lib/brand.ts.
15
+ */
16
+ import type { Command } from 'commander';
17
+ /**
18
+ * Create (or re-mint) a brand. Shared by `mine init` and the `setup mine`
19
+ * wizard. Writes the shim, the brand config, and an empty resource preset.
20
+ */
21
+ export declare function initBrand(name: string, opts?: {
22
+ disabledCommands?: string[];
23
+ force?: boolean;
24
+ }): {
25
+ pathWarning: boolean;
26
+ };
27
+ export declare function registerMineCommand(program: Command): void;
@@ -0,0 +1,207 @@
1
+ import chalk from 'chalk';
2
+ import { validateBrandName, listBrands, getBrandConfig, upsertBrand, removeBrand, brandPresetName, } from '../lib/brand.js';
3
+ import { createBrandShim, removeBrandShim, isShimsInPath } from '../lib/shims.js';
4
+ import { updateMeta } from '../lib/state.js';
5
+ import { COMMAND_LOADERS } from '../lib/startup/command-registry.js';
6
+ /** Built-in top-level command names, for validating `--disable <cmd>`. */
7
+ function knownCommandNames() {
8
+ const names = new Set(Object.keys(COMMAND_LOADERS));
9
+ // Inline aliases registered outside COMMAND_LOADERS (see src/index.ts).
10
+ for (const n of ['perms', 'exec', 'jobs', 'cron', 'upgrade'])
11
+ names.add(n);
12
+ return names;
13
+ }
14
+ /**
15
+ * Add/remove a `!name` exclusion on a preset's pattern list for one kind,
16
+ * keeping `*` so everything not explicitly disabled stays enabled. Deletes the
17
+ * key entirely once nothing but `*` remains, so a clean brand has an empty preset.
18
+ */
19
+ function applyResourceToggle(preset, kind, name, enable) {
20
+ // Cast to a plain string-bag: writing through the union-keyed `preset[kind]`
21
+ // collapses to `never` under strict TS, so index the bag instead.
22
+ const bag = preset;
23
+ const arr = [...(bag[kind] ?? [])];
24
+ if (arr.length === 0)
25
+ arr.push('*');
26
+ const excl = `!${name}`;
27
+ const idx = arr.indexOf(excl);
28
+ if (enable) {
29
+ if (idx !== -1)
30
+ arr.splice(idx, 1);
31
+ }
32
+ else if (idx === -1) {
33
+ arr.push(excl);
34
+ }
35
+ const onlyStar = arr.length === 1 && arr[0] === '*';
36
+ if (onlyStar)
37
+ delete bag[kind];
38
+ else
39
+ bag[kind] = arr;
40
+ }
41
+ /** Ensure the brand's preset exists in meta.profiles.presets and return a mutable copy path. */
42
+ function ensurePresetExists(name) {
43
+ const presetName = brandPresetName(name);
44
+ updateMeta((meta) => {
45
+ const profiles = meta.profiles ?? {};
46
+ const presets = { ...(profiles.presets ?? {}) };
47
+ if (!presets[presetName]) {
48
+ presets[presetName] = { description: `Resource set for the "${name}" brand` };
49
+ }
50
+ return { ...meta, profiles: { ...profiles, presets } };
51
+ });
52
+ }
53
+ /** Mutate the brand's resource preset under lock. */
54
+ function editPreset(name, fn) {
55
+ const presetName = brandPresetName(name);
56
+ updateMeta((meta) => {
57
+ const profiles = meta.profiles ?? {};
58
+ const presets = { ...(profiles.presets ?? {}) };
59
+ const preset = { ...(presets[presetName] ?? {}) };
60
+ fn(preset);
61
+ presets[presetName] = preset;
62
+ return { ...meta, profiles: { ...profiles, presets } };
63
+ });
64
+ }
65
+ /**
66
+ * Create (or re-mint) a brand. Shared by `mine init` and the `setup mine`
67
+ * wizard. Writes the shim, the brand config, and an empty resource preset.
68
+ */
69
+ export function initBrand(name, opts = {}) {
70
+ const err = validateBrandName(name);
71
+ if (err) {
72
+ console.error(chalk.red(err));
73
+ process.exit(1);
74
+ }
75
+ if (getBrandConfig(name) && !opts.force) {
76
+ console.error(chalk.red(`Brand "${name}" already exists. Use --force to re-mint, or 'agents mine toggle ${name}'.`));
77
+ process.exit(1);
78
+ }
79
+ ensurePresetExists(name);
80
+ const disabled = (opts.disabledCommands ?? []).filter((c) => c.length > 0);
81
+ const known = knownCommandNames();
82
+ for (const c of disabled) {
83
+ if (!known.has(c))
84
+ console.error(chalk.yellow(` note: "${c}" is not a known command — kept anyway.`));
85
+ }
86
+ const cfg = {
87
+ name,
88
+ enabled: true,
89
+ profile: brandPresetName(name),
90
+ ...(disabled.length > 0 ? { disabledCommands: disabled } : {}),
91
+ };
92
+ upsertBrand(cfg);
93
+ createBrandShim(name);
94
+ const pathWarning = !isShimsInPath();
95
+ return { pathWarning };
96
+ }
97
+ function printMinted(name, pathWarning) {
98
+ console.log(`${chalk.green('Minted')} ${chalk.bold(name)} — your own agents CLI.`);
99
+ console.log(chalk.dim(` try: ${name} --help ${name} run claude "hello"`));
100
+ if (pathWarning) {
101
+ console.log(chalk.yellow(` note: the shims dir isn't on your PATH yet — run 'agents setup' or open a new shell.`));
102
+ }
103
+ }
104
+ export function registerMineCommand(program) {
105
+ const cmd = program
106
+ .command('mine')
107
+ .description('White-label the CLI: your own personally-named binary (e.g. `jack`)');
108
+ cmd
109
+ .command('init <name>')
110
+ .description('Mint your own branded CLI that runs every agents verb under <name>')
111
+ .option('--disable <commands...>', 'Built-in commands to hide from this brand (e.g. teams cloud)')
112
+ .option('--force', 'Re-mint even if the brand already exists')
113
+ .action((name, options) => {
114
+ const { pathWarning } = initBrand(name, {
115
+ disabledCommands: options.disable,
116
+ force: options.force,
117
+ });
118
+ printMinted(name, pathWarning);
119
+ });
120
+ cmd
121
+ .command('list')
122
+ .alias('ls')
123
+ .description('Show your brands and what each has turned off')
124
+ .action(() => {
125
+ const brands = listBrands();
126
+ const entries = Object.entries(brands);
127
+ if (entries.length === 0) {
128
+ console.log(chalk.gray("No brands yet. Try: agents setup mine (or: agents mine init jack)"));
129
+ return;
130
+ }
131
+ const width = entries.reduce((m, [n]) => Math.max(m, n.length), 0);
132
+ for (const [name, cfg] of entries) {
133
+ const off = cfg.disabledCommands?.length ? `disabled: ${cfg.disabledCommands.join(', ')}` : 'all commands on';
134
+ const state = cfg.enabled === false ? chalk.yellow('(disabled) ') : '';
135
+ console.log(` ${chalk.bold(name.padEnd(width))} ${state}${chalk.dim(off)}`);
136
+ }
137
+ });
138
+ cmd
139
+ .command('toggle <name>')
140
+ .description('Enable/disable features for a brand')
141
+ .option('--disable <commands...>', 'Hide these built-in commands')
142
+ .option('--enable <commands...>', 'Un-hide these built-in commands')
143
+ .option('--disable-plugin <names...>', 'Disable these plugins for the brand')
144
+ .option('--enable-plugin <names...>', 'Re-enable these plugins')
145
+ .option('--disable-skill <names...>', 'Disable these skills for the brand')
146
+ .option('--enable-skill <names...>', 'Re-enable these skills')
147
+ .action((name, options) => {
148
+ const cfg = getBrandConfig(name);
149
+ if (!cfg) {
150
+ console.error(chalk.red(`No brand named "${name}". Create it with 'agents mine init ${name}'.`));
151
+ process.exit(1);
152
+ }
153
+ // Built-in command toggles → brand.disabledCommands.
154
+ const disabledSet = new Set(cfg.disabledCommands ?? []);
155
+ const known = knownCommandNames();
156
+ for (const c of options.disable ?? []) {
157
+ if (!known.has(c))
158
+ console.error(chalk.yellow(` note: "${c}" is not a known command — kept anyway.`));
159
+ disabledSet.add(c);
160
+ }
161
+ for (const c of options.enable ?? [])
162
+ disabledSet.delete(c);
163
+ const nextDisabled = [...disabledSet];
164
+ const nextCfg = { ...cfg };
165
+ if (nextDisabled.length > 0)
166
+ nextCfg.disabledCommands = nextDisabled;
167
+ else
168
+ delete nextCfg.disabledCommands;
169
+ upsertBrand(nextCfg);
170
+ // Resource toggles → the brand's profile preset.
171
+ const resourceOps = [];
172
+ for (const p of options.disablePlugin ?? [])
173
+ resourceOps.push(['plugins', p, false]);
174
+ for (const p of options.enablePlugin ?? [])
175
+ resourceOps.push(['plugins', p, true]);
176
+ for (const s of options.disableSkill ?? [])
177
+ resourceOps.push(['skills', s, false]);
178
+ for (const s of options.enableSkill ?? [])
179
+ resourceOps.push(['skills', s, true]);
180
+ if (resourceOps.length > 0) {
181
+ ensurePresetExists(name);
182
+ editPreset(name, (preset) => {
183
+ for (const [kind, n, enable] of resourceOps)
184
+ applyResourceToggle(preset, kind, n, enable);
185
+ });
186
+ }
187
+ console.log(`${chalk.green('Updated')} ${chalk.bold(name)}.`);
188
+ const finalCfg = getBrandConfig(name);
189
+ const off = finalCfg.disabledCommands?.length ? finalCfg.disabledCommands.join(', ') : '(none)';
190
+ console.log(chalk.dim(` disabled commands: ${off}`));
191
+ });
192
+ cmd
193
+ .command('remove <name>')
194
+ .alias('rm')
195
+ .description('Remove a brand (its shim + config)')
196
+ .option('--purge', "Also delete the brand's resource profile preset")
197
+ .action((name, options) => {
198
+ const existed = getBrandConfig(name) !== undefined;
199
+ const shimRemoved = removeBrandShim(name);
200
+ if (!existed && !shimRemoved) {
201
+ console.error(chalk.red(`No brand named "${name}".`));
202
+ process.exit(1);
203
+ }
204
+ removeBrand(name, options.purge === true);
205
+ console.log(`${chalk.yellow('Removed')} brand ${chalk.bold(name)}`);
206
+ });
207
+ }
@@ -23,6 +23,12 @@ import { machineId, normalizeHost } from '../lib/machine-id.js';
23
23
  import { loadDevices } from '../lib/devices/registry.js';
24
24
  import { setHelpSections } from '../lib/help.js';
25
25
  import { isInteractiveTerminal, requireInteractiveSelection } from './utils.js';
26
+ function stdoutJson(payload) {
27
+ process.stdout.write(JSON.stringify(payload) + '\n');
28
+ }
29
+ function stderrLine(message) {
30
+ process.stderr.write(message + '\n');
31
+ }
26
32
  /** A one-line human label for what a monitor watches. */
27
33
  function sourceLabel(source) {
28
34
  switch (source.type) {
@@ -70,7 +76,7 @@ function ownerLabel(monitor) {
70
76
  function ensureDaemonRunning() {
71
77
  if (isDaemonRunning()) {
72
78
  signalDaemonReload();
73
- console.log(chalk.gray('Daemon reloaded'));
79
+ stderrLine(chalk.gray('Daemon reloaded'));
74
80
  return;
75
81
  }
76
82
  const result = startDaemon();
@@ -79,22 +85,22 @@ function ensureDaemonRunning() {
79
85
  console.log(chalk.gray('Stop anytime with: agents routines stop'));
80
86
  }
81
87
  else {
82
- console.log(chalk.yellow('Could not start the daemon. Start it manually with: agents routines start'));
88
+ stderrLine(chalk.yellow('Could not start the daemon. Start it manually with: agents routines start'));
83
89
  }
84
90
  }
85
91
  /** Validate a single device name against the registered fleet; exit on miss. */
86
92
  async function validateDevice(name) {
87
93
  const normalized = normalizeHost(name.trim());
88
94
  if (!normalized) {
89
- console.log(chalk.red('device name must be non-empty'));
95
+ stderrLine(chalk.red('device name must be non-empty'));
90
96
  process.exit(1);
91
97
  }
92
98
  const registry = await loadDevices();
93
99
  const registered = new Set(Object.keys(registry).map((k) => normalizeHost(k)));
94
100
  if (!registered.has(normalized)) {
95
- console.log(chalk.red(`Unknown device: ${normalized}`));
96
- console.log(chalk.gray(`Registered: ${[...registered].sort().join(', ') || '(none)'}`));
97
- console.log(chalk.gray('Enroll devices with: agents devices sync'));
101
+ stderrLine(chalk.red(`Unknown device: ${normalized}`));
102
+ stderrLine(chalk.gray(`Registered: ${[...registered].sort().join(', ') || '(none)'}`));
103
+ stderrLine(chalk.gray('Enroll devices with: agents devices sync'));
98
104
  process.exit(1);
99
105
  }
100
106
  return normalized;
@@ -126,7 +132,7 @@ function buildSource(options) {
126
132
  const raw = String(options.on);
127
133
  const [src, event] = raw.includes(':') ? raw.split(':', 2) : ['github', raw];
128
134
  if (src !== 'github' && src !== 'linear') {
129
- console.log(chalk.red('--on source must be github or linear'));
135
+ stderrLine(chalk.red('--on source must be github or linear'));
130
136
  process.exit(1);
131
137
  }
132
138
  const webhook = { source: src, event };
@@ -143,11 +149,11 @@ function buildSource(options) {
143
149
  chosen.push({ type: 'webhook', source: { type: 'webhook', webhook } });
144
150
  }
145
151
  if (chosen.length === 0) {
146
- console.log(chalk.red('A source is required: --watch, --poll, --poll-http, --ws, --watch-file, --watch-device, or --on'));
152
+ stderrLine(chalk.red('A source is required: --watch, --poll, --poll-http, --ws, --watch-file, --watch-device, or --on'));
147
153
  process.exit(1);
148
154
  }
149
155
  if (chosen.length > 1) {
150
- console.log(chalk.red(`Exactly one source is allowed; got ${chosen.map((c) => c.type).join(', ')}`));
156
+ stderrLine(chalk.red(`Exactly one source is allowed; got ${chosen.map((c) => c.type).join(', ')}`));
151
157
  process.exit(1);
152
158
  }
153
159
  return chosen[0].source;
@@ -162,7 +168,7 @@ function buildCondition(options) {
162
168
  if (options.every)
163
169
  modes.push('every');
164
170
  if (modes.length > 1) {
165
- console.log(chalk.red('--on-change, --match, and --every are mutually exclusive'));
171
+ stderrLine(chalk.red('--on-change, --match, and --every are mutually exclusive'));
166
172
  process.exit(1);
167
173
  }
168
174
  const mode = modes[0] ?? (options.match ? 'match' : 'on-change');
@@ -196,11 +202,11 @@ function buildAction(options) {
196
202
  if (options.webhookOut)
197
203
  chosen.push({ type: 'webhook-out', url: options.webhookOut });
198
204
  if (chosen.length === 0) {
199
- console.log(chalk.red('An action is required: --run <agent> --prompt, --routine, --notify, or --webhook-out'));
205
+ stderrLine(chalk.red('An action is required: --run <agent> --prompt, --routine, --notify, or --webhook-out'));
200
206
  process.exit(1);
201
207
  }
202
208
  if (chosen.length > 1) {
203
- console.log(chalk.red(`Exactly one action is allowed; got ${chosen.map((c) => c.type).join(', ')}`));
209
+ stderrLine(chalk.red(`Exactly one action is allowed; got ${chosen.map((c) => c.type).join(', ')}`));
204
210
  process.exit(1);
205
211
  }
206
212
  return chosen[0];
@@ -209,7 +215,7 @@ function buildAction(options) {
209
215
  async function pickMonitor(message, alternatives = []) {
210
216
  const monitors = listMonitors();
211
217
  if (monitors.length === 0) {
212
- console.log(chalk.yellow('No monitors configured'));
218
+ stderrLine(chalk.yellow('No monitors configured'));
213
219
  return null;
214
220
  }
215
221
  if (!isInteractiveTerminal()) {
@@ -227,7 +233,7 @@ async function pickMonitor(message, alternatives = []) {
227
233
  }
228
234
  catch (err) {
229
235
  if (err instanceof Error && (err.name === 'ExitPromptError' || err.message.includes('User force closed'))) {
230
- console.log(chalk.gray('Cancelled'));
236
+ stderrLine(chalk.gray('Cancelled'));
231
237
  return null;
232
238
  }
233
239
  throw err;
@@ -322,16 +328,16 @@ export function registerMonitorsCommands(program) {
322
328
  parsed = yaml.parse(fs.readFileSync(resolved, 'utf-8'));
323
329
  }
324
330
  catch (err) {
325
- console.log(chalk.red(`Invalid YAML: ${err.message}`));
331
+ stderrLine(chalk.red(`Invalid YAML: ${err.message}`));
326
332
  process.exit(1);
327
333
  }
328
334
  const name = parsed?.name || path.basename(resolved).replace(/\.ya?ml$/, '');
329
335
  const config = { enabled: true, ...parsed, name };
330
336
  const errors = validateMonitor(config);
331
337
  if (errors.length > 0) {
332
- console.log(chalk.red('Validation errors:'));
338
+ stderrLine(chalk.red('Validation errors:'));
333
339
  for (const err of errors)
334
- console.log(chalk.red(` - ${err}`));
340
+ stderrLine(chalk.red(` - ${err}`));
335
341
  process.exit(1);
336
342
  }
337
343
  writeMonitor(config);
@@ -340,8 +346,8 @@ export function registerMonitorsCommands(program) {
340
346
  return;
341
347
  }
342
348
  if (!nameOrPath) {
343
- console.log(chalk.red('Monitor name is required'));
344
- console.log(chalk.gray('Usage: agents monitors add <name> --poll "<cmd>" 30s --match fail --run claude --prompt "..."'));
349
+ stderrLine(chalk.red('Monitor name is required'));
350
+ stderrLine(chalk.gray('Usage: agents monitors add <name> --poll "<cmd>" 30s --match fail --run claude --prompt "..."'));
345
351
  process.exit(1);
346
352
  }
347
353
  const source = buildSource(options);
@@ -357,7 +363,7 @@ export function registerMonitorsCommands(program) {
357
363
  let device;
358
364
  let devices;
359
365
  if (options.device && options.devices) {
360
- console.log(chalk.red('--device (single owner) and --devices (allowlist) are mutually exclusive'));
366
+ stderrLine(chalk.red('--device (single owner) and --devices (allowlist) are mutually exclusive'));
361
367
  process.exit(1);
362
368
  }
363
369
  if (options.device)
@@ -371,13 +377,13 @@ export function registerMonitorsCommands(program) {
371
377
  // --run-on with no owner pin would fire from every daemon → duplicate actions.
372
378
  if (options.runOn && !device && !devices) {
373
379
  device = machineId();
374
- console.log(chalk.gray(`--run-on set with no --device/--devices: pinned owner to this machine (${device}).`));
380
+ stderrLine(chalk.gray(`--run-on set with no --device/--devices: pinned owner to this machine (${device}).`));
375
381
  }
376
382
  let rateLimit;
377
383
  if (options.rateLimit) {
378
384
  const m = String(options.rateLimit).match(/^(\d+)\/(.+)$/);
379
385
  if (!m) {
380
- console.log(chalk.red('--rate-limit must be N/<interval>, e.g. 5/1m'));
386
+ stderrLine(chalk.red('--rate-limit must be N/<interval>, e.g. 5/1m'));
381
387
  process.exit(1);
382
388
  }
383
389
  rateLimit = { max: parseInt(m[1], 10), per: m[2] };
@@ -395,15 +401,15 @@ export function registerMonitorsCommands(program) {
395
401
  };
396
402
  const errors = validateMonitor(config);
397
403
  if (errors.length > 0) {
398
- console.log(chalk.red('Validation errors:'));
404
+ stderrLine(chalk.red('Validation errors:'));
399
405
  for (const err of errors)
400
- console.log(chalk.red(` - ${err}`));
406
+ stderrLine(chalk.red(` - ${err}`));
401
407
  process.exit(1);
402
408
  }
403
409
  // Coverage lint (Anthropic's "silence is not success"): warn when a --match
404
410
  // names only a success-shaped token with no failure branch.
405
411
  if (condition.mode === 'match' && condition.match && /^(issued|success|ok|pass(ed)?|done|ready)$/i.test(condition.match)) {
406
- console.log(chalk.yellow(` Note: --match '${condition.match}' only fires on success — it stays silent if the source breaks or never matches.`));
412
+ stderrLine(chalk.yellow(` Note: --match '${condition.match}' only fires on success — it stays silent if the source breaks or never matches.`));
407
413
  }
408
414
  writeMonitor(config);
409
415
  console.log(chalk.green(`Monitor '${nameOrPath}' added`));
@@ -432,7 +438,7 @@ export function registerMonitorsCommands(program) {
432
438
  lastFiredAt: state?.lastFiredAt ?? null,
433
439
  };
434
440
  });
435
- process.stdout.write(JSON.stringify(payload) + '\n');
441
+ stdoutJson(payload);
436
442
  return;
437
443
  }
438
444
  if (monitors.length === 0) {
@@ -456,7 +462,8 @@ export function registerMonitorsCommands(program) {
456
462
  monitorsCmd
457
463
  .command('view [name]')
458
464
  .description('Show a monitor’s full YAML config plus its current watched-state and recent fires.')
459
- .action(async (name) => {
465
+ .option('--json', 'Emit machine-readable JSON')
466
+ .action(async (name, options) => {
460
467
  if (!name) {
461
468
  name = (await pickMonitor('Select monitor to view', ['agents monitors view <name>'])) ?? undefined;
462
469
  if (!name)
@@ -464,12 +471,24 @@ export function registerMonitorsCommands(program) {
464
471
  }
465
472
  const monitor = readMonitor(name);
466
473
  if (!monitor) {
467
- console.log(chalk.red(`Monitor '${name}' not found`));
474
+ stderrLine(chalk.red(`Monitor '${name}' not found`));
468
475
  process.exit(1);
469
476
  }
477
+ const state = readState(name);
478
+ const recentFires = listFires(name).slice(-5);
479
+ if (options.json) {
480
+ stdoutJson({
481
+ name,
482
+ monitor,
483
+ owner: ownerLabel(monitor),
484
+ runsHere: monitorRunsOnThisDevice(monitor),
485
+ state,
486
+ recentFires,
487
+ });
488
+ return;
489
+ }
470
490
  console.log(chalk.bold(`Monitor: ${name}\n`));
471
491
  console.log(yaml.stringify(monitor));
472
- const state = readState(name);
473
492
  if (state) {
474
493
  console.log(chalk.bold('Watched state'));
475
494
  console.log(chalk.gray(` last seen: ${state.lastSeenAt}`));
@@ -477,10 +496,9 @@ export function registerMonitorsCommands(program) {
477
496
  console.log(chalk.gray(` last fired: ${state.lastFiredAt}`));
478
497
  console.log(chalk.gray(` last value: ${state.lastValue.replace(/\s+/g, ' ').slice(0, 120)}`));
479
498
  }
480
- const fires = listFires(name).slice(-5);
481
- if (fires.length > 0) {
499
+ if (recentFires.length > 0) {
482
500
  console.log(chalk.bold('\nRecent fires'));
483
- for (const f of fires) {
501
+ for (const f of recentFires) {
484
502
  console.log(` ${chalk.gray(f.firedAt)} ${f.action ?? '?'} ${f.ok === false ? chalk.red('failed') : chalk.green('ok')}`);
485
503
  }
486
504
  }
@@ -489,7 +507,8 @@ export function registerMonitorsCommands(program) {
489
507
  monitorsCmd
490
508
  .command('test [name]')
491
509
  .description('DRY-RUN: evaluate the source once and print the emitted event + whether it would fire. No action is taken.')
492
- .action(async (name) => {
510
+ .option('--json', 'Emit machine-readable JSON')
511
+ .action(async (name, options) => {
493
512
  if (!name) {
494
513
  name = (await pickMonitor('Select monitor to test', ['agents monitors test <name>'])) ?? undefined;
495
514
  if (!name)
@@ -497,12 +516,24 @@ export function registerMonitorsCommands(program) {
497
516
  }
498
517
  const monitor = readMonitor(name);
499
518
  if (!monitor) {
500
- console.log(chalk.red(`Monitor '${name}' not found`));
519
+ stderrLine(chalk.red(`Monitor '${name}' not found`));
501
520
  process.exit(1);
502
521
  }
522
+ const { observation, decision } = await evaluateMonitorOnce(monitor);
523
+ const wouldFire = Boolean(decision?.fire);
524
+ if (options.json) {
525
+ stdoutJson({
526
+ name,
527
+ dryRun: true,
528
+ monitor,
529
+ observation,
530
+ decision,
531
+ wouldFire,
532
+ });
533
+ return;
534
+ }
503
535
  console.log(chalk.bold(`Dry-run: ${name}\n`));
504
536
  console.log(chalk.gray(` ${sourceLabel(monitor.source)} · [${monitor.condition.mode}] · ${actionLabel(monitor.action)}\n`));
505
- const { observation, decision } = await evaluateMonitorOnce(monitor);
506
537
  if (!observation) {
507
538
  console.log(chalk.yellow('No observation — this source is push-only (ws/webhook) or produced nothing this tick.'));
508
539
  return;
@@ -511,7 +542,6 @@ export function registerMonitorsCommands(program) {
511
542
  console.log(observation.raw.split('\n').slice(0, 20).map((l) => ` ${l}`).join('\n'));
512
543
  if (observation.meta)
513
544
  console.log(chalk.gray(` meta: ${JSON.stringify(observation.meta)}`));
514
- const wouldFire = Boolean(decision?.fire);
515
545
  console.log('');
516
546
  console.log(`Would fire: ${wouldFire ? chalk.green('yes') : chalk.gray('no')}`);
517
547
  if (decision?.event) {
@@ -560,15 +590,15 @@ export function registerMonitorsCommands(program) {
560
590
  return;
561
591
  const errors = validateMonitor(monitor);
562
592
  if (errors.length > 0) {
563
- console.log(chalk.yellow('\nWarning: monitor has validation errors:'));
593
+ stderrLine(chalk.yellow('\nWarning: monitor has validation errors:'));
564
594
  for (const err of errors)
565
- console.log(chalk.yellow(` - ${err}`));
595
+ stderrLine(chalk.yellow(` - ${err}`));
566
596
  }
567
597
  else {
568
598
  console.log(chalk.green(`\nMonitor '${name}' saved`));
569
599
  if (isDaemonRunning()) {
570
600
  signalDaemonReload();
571
- console.log(chalk.gray('Daemon reloaded'));
601
+ stderrLine(chalk.gray('Daemon reloaded'));
572
602
  }
573
603
  }
574
604
  });
@@ -587,14 +617,14 @@ export function registerMonitorsCommands(program) {
587
617
  }
588
618
  const run = options.run ? listRuns(name).find((r) => r.runId === options.run) : getLatestRun(name);
589
619
  if (!run) {
590
- console.log(chalk.yellow(`No action runs found for monitor '${name}'`));
591
- console.log(chalk.gray(' (notify / webhook-out actions have no run log — see: agents monitors runs)'));
620
+ stderrLine(chalk.yellow(`No action runs found for monitor '${name}'`));
621
+ stderrLine(chalk.gray(' (notify / webhook-out actions have no run log — see: agents monitors runs)'));
592
622
  return;
593
623
  }
594
624
  const logPath = path.join(getRunDir(name, run.runId), 'stdout.log');
595
625
  if (options.full) {
596
626
  if (!fs.existsSync(logPath)) {
597
- console.log(chalk.yellow(`Log not found: ${logPath}`));
627
+ stderrLine(chalk.yellow(`Log not found: ${logPath}`));
598
628
  return;
599
629
  }
600
630
  console.log(chalk.gray(`Run: ${run.runId}\n`));
@@ -628,7 +658,7 @@ export function registerMonitorsCommands(program) {
628
658
  }
629
659
  const fires = listFires(name);
630
660
  if (fires.length === 0) {
631
- console.log(chalk.yellow(`No fires recorded for monitor '${name}'`));
661
+ stderrLine(chalk.yellow(`No fires recorded for monitor '${name}'`));
632
662
  return;
633
663
  }
634
664
  console.log(chalk.bold(`Fire history: ${name}\n`));
@@ -656,7 +686,7 @@ export function registerMonitorsCommands(program) {
656
686
  signalDaemonReload();
657
687
  }
658
688
  catch (err) {
659
- console.log(chalk.red(err.message));
689
+ stderrLine(chalk.red(err.message));
660
690
  process.exit(1);
661
691
  }
662
692
  });
@@ -676,7 +706,7 @@ export function registerMonitorsCommands(program) {
676
706
  signalDaemonReload();
677
707
  }
678
708
  catch (err) {
679
- console.log(chalk.red(err.message));
709
+ stderrLine(chalk.red(err.message));
680
710
  process.exit(1);
681
711
  }
682
712
  });
@@ -688,7 +718,7 @@ export function registerMonitorsCommands(program) {
688
718
  .option('--clear', 'Remove the owner pin so the monitor runs on every device')
689
719
  .action(async (name, options) => {
690
720
  if (options.set !== undefined && options.clear) {
691
- console.log(chalk.red('--set and --clear are mutually exclusive'));
721
+ stderrLine(chalk.red('--set and --clear are mutually exclusive'));
692
722
  process.exit(1);
693
723
  }
694
724
  if (!name) {
@@ -698,7 +728,7 @@ export function registerMonitorsCommands(program) {
698
728
  }
699
729
  const monitor = readMonitor(name);
700
730
  if (!monitor) {
701
- console.log(chalk.red(`Monitor '${name}' not found`));
731
+ stderrLine(chalk.red(`Monitor '${name}' not found`));
702
732
  process.exit(1);
703
733
  }
704
734
  if (options.clear) {
@@ -737,11 +767,11 @@ export function registerMonitorsCommands(program) {
737
767
  console.log(chalk.green(`Monitor '${name}' removed`));
738
768
  if (isDaemonRunning()) {
739
769
  signalDaemonReload();
740
- console.log(chalk.gray('Daemon reloaded'));
770
+ stderrLine(chalk.gray('Daemon reloaded'));
741
771
  }
742
772
  }
743
773
  else {
744
- console.log(chalk.red(`Monitor '${name}' not found`));
774
+ stderrLine(chalk.red(`Monitor '${name}' not found`));
745
775
  process.exit(1);
746
776
  }
747
777
  });
@@ -493,6 +493,7 @@ Examples:
493
493
  // agents plugins install <spec>
494
494
  pluginsCmd
495
495
  .command('install <spec>')
496
+ .alias('add')
496
497
  .description('Install a plugin from a git URL or local path (format: name@source or source)')
497
498
  .option('--allow-exec-surfaces', 'Allow installing plugins that ship executable surfaces')
498
499
  .addHelpText('after', `