@phnx-labs/agents-cli 1.14.1 → 1.14.3

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 (102) hide show
  1. package/README.md +31 -3
  2. package/dist/commands/browser.d.ts +2 -0
  3. package/dist/commands/browser.js +388 -0
  4. package/dist/commands/daemon.js +1 -1
  5. package/dist/commands/doctor.d.ts +16 -9
  6. package/dist/commands/doctor.js +248 -12
  7. package/dist/commands/exec.js +17 -17
  8. package/dist/commands/prune.js +9 -3
  9. package/dist/commands/refresh-rules.d.ts +15 -0
  10. package/dist/commands/{refresh-memory.js → refresh-rules.js} +14 -14
  11. package/dist/commands/routines.js +1 -1
  12. package/dist/commands/rules.js +100 -4
  13. package/dist/commands/secrets.js +206 -12
  14. package/dist/commands/sync.js +19 -0
  15. package/dist/commands/teams.js +162 -22
  16. package/dist/commands/trash.d.ts +10 -0
  17. package/dist/commands/trash.js +187 -0
  18. package/dist/commands/view.js +46 -13
  19. package/dist/index.js +62 -4
  20. package/dist/lib/agents.js +2 -2
  21. package/dist/lib/browser/cdp.d.ts +24 -0
  22. package/dist/lib/browser/cdp.js +94 -0
  23. package/dist/lib/browser/chrome.d.ts +16 -0
  24. package/dist/lib/browser/chrome.js +157 -0
  25. package/dist/lib/browser/drivers/local.d.ts +8 -0
  26. package/dist/lib/browser/drivers/local.js +22 -0
  27. package/dist/lib/browser/drivers/ssh.d.ts +9 -0
  28. package/dist/lib/browser/drivers/ssh.js +129 -0
  29. package/dist/lib/browser/index.d.ts +5 -0
  30. package/dist/lib/browser/index.js +5 -0
  31. package/dist/lib/browser/input.d.ts +6 -0
  32. package/dist/lib/browser/input.js +52 -0
  33. package/dist/lib/browser/ipc.d.ts +12 -0
  34. package/dist/lib/browser/ipc.js +223 -0
  35. package/dist/lib/browser/profiles.d.ts +11 -0
  36. package/dist/lib/browser/profiles.js +61 -0
  37. package/dist/lib/browser/refs.d.ts +21 -0
  38. package/dist/lib/browser/refs.js +88 -0
  39. package/dist/lib/browser/service.d.ts +45 -0
  40. package/dist/lib/browser/service.js +404 -0
  41. package/dist/lib/browser/types.d.ts +73 -0
  42. package/dist/lib/browser/types.js +7 -0
  43. package/dist/lib/cloud/codex.js +1 -1
  44. package/dist/lib/cloud/registry.js +2 -2
  45. package/dist/lib/cloud/rush.js +2 -2
  46. package/dist/lib/cloud/store.js +2 -2
  47. package/dist/lib/daemon.d.ts +1 -1
  48. package/dist/lib/daemon.js +47 -11
  49. package/dist/lib/diff-text.d.ts +25 -0
  50. package/dist/lib/diff-text.js +47 -0
  51. package/dist/lib/doctor-diff.d.ts +64 -0
  52. package/dist/lib/doctor-diff.js +497 -0
  53. package/dist/lib/git.js +3 -3
  54. package/dist/lib/hooks.d.ts +6 -0
  55. package/dist/lib/hooks.js +6 -1
  56. package/dist/lib/migrate.js +77 -0
  57. package/dist/lib/pty-client.js +3 -3
  58. package/dist/lib/pty-server.js +36 -7
  59. package/dist/lib/resources.js +1 -1
  60. package/dist/lib/rotate.d.ts +43 -26
  61. package/dist/lib/rotate.js +99 -44
  62. package/dist/lib/rules/compile.d.ts +104 -0
  63. package/dist/lib/{memory-compile.js → rules/compile.js} +160 -21
  64. package/dist/lib/rules/compose.d.ts +78 -0
  65. package/dist/lib/rules/compose.js +170 -0
  66. package/dist/lib/{memory.d.ts → rules/rules.d.ts} +5 -5
  67. package/dist/lib/{memory.js → rules/rules.js} +10 -10
  68. package/dist/lib/secrets/AgentsKeychain.app/Contents/CodeResources +0 -0
  69. package/dist/lib/secrets/AgentsKeychain.app/Contents/MacOS/AgentsKeychain +0 -0
  70. package/dist/lib/secrets/bundles.d.ts +61 -4
  71. package/dist/lib/secrets/bundles.js +222 -54
  72. package/dist/lib/secrets/index.d.ts +24 -5
  73. package/dist/lib/secrets/index.js +70 -41
  74. package/dist/lib/session/active.js +5 -5
  75. package/dist/lib/session/db.js +4 -4
  76. package/dist/lib/session/discover.js +2 -2
  77. package/dist/lib/session/render.js +21 -7
  78. package/dist/lib/shims.d.ts +28 -4
  79. package/dist/lib/shims.js +72 -14
  80. package/dist/lib/state.d.ts +22 -28
  81. package/dist/lib/state.js +83 -76
  82. package/dist/lib/sync-manifest.d.ts +2 -2
  83. package/dist/lib/sync-manifest.js +5 -5
  84. package/dist/lib/teams/agents.d.ts +4 -2
  85. package/dist/lib/teams/agents.js +11 -4
  86. package/dist/lib/teams/api.d.ts +1 -1
  87. package/dist/lib/teams/api.js +2 -2
  88. package/dist/lib/teams/index.d.ts +1 -0
  89. package/dist/lib/teams/index.js +1 -0
  90. package/dist/lib/teams/persistence.js +3 -3
  91. package/dist/lib/teams/registry.d.ts +8 -1
  92. package/dist/lib/teams/registry.js +8 -2
  93. package/dist/lib/teams/worktree.d.ts +30 -0
  94. package/dist/lib/teams/worktree.js +96 -0
  95. package/dist/lib/types.d.ts +13 -7
  96. package/dist/lib/types.js +3 -3
  97. package/dist/lib/versions.d.ts +30 -2
  98. package/dist/lib/versions.js +127 -105
  99. package/package.json +1 -1
  100. package/scripts/postinstall.js +29 -0
  101. package/dist/commands/refresh-memory.d.ts +0 -15
  102. package/dist/lib/memory-compile.d.ts +0 -66
package/README.md CHANGED
@@ -250,16 +250,44 @@ agents secrets create prod-stripe
250
250
  agents secrets add prod-stripe STRIPE_SECRET_KEY # Prompts, stores in Keychain
251
251
  agents secrets add prod-stripe TEST_CARD --value "4242..."
252
252
 
253
- # Injected at run time. The YAML on disk has only refs.
253
+ # Injected at run time. Bundle definitions live in the Keychain, not on disk.
254
254
  agents run claude "charge a test card" --secrets prod-stripe
255
255
  ```
256
256
 
257
257
  <p align="center">
258
- <img src="assets/secrets.svg" alt="How agents-cli secrets work: stripe.yml holds a pointer, the macOS Keychain holds the value, agents-cli resolves at runtime and injects the env into the child process" width="100%" />
258
+ <img src="assets/secrets.svg" alt="How agents-cli secrets work: bundle definitions live in the macOS Keychain alongside their values, agents-cli resolves at runtime and injects the env into the child process" width="100%" />
259
259
  </p>
260
260
 
261
261
  Merge order: profile env < `--secrets` < `--env K=V`. A missing keychain item aborts before the child starts.
262
262
 
263
+ ### Cross-machine sync via iCloud Keychain
264
+
265
+ Pass `--icloud-sync` when creating a bundle and both the bundle definition and its values are written to the iCloud-synced keychain. Sign into the same iCloud account on another Mac (with iCloud Keychain enabled) and the bundle appears there within seconds — no copy-paste, no `.env` files emailed to yourself, no shared secret stores.
266
+
267
+ ```bash
268
+ # On laptop:
269
+ agents secrets create npm-tokens --icloud-sync
270
+ agents secrets add npm-tokens NPM_TOKEN # value lives in iCloud Keychain
271
+
272
+ # On another Mac (same iCloud account):
273
+ agents secrets list # npm-tokens is already there;
274
+ agents run claude "..." --secrets npm-tokens # injects NPM_TOKEN automatically
275
+ ```
276
+
277
+ Under the hood, `--icloud-sync` routes writes through a notarized helper app (`AgentsKeychain.app`) that holds the entitlement macOS requires for `kSecAttrSynchronizable`. Bundles without `--icloud-sync` stay device-local.
278
+
279
+ Bundle definitions sync via iCloud Keychain too — no `agents repo push` needed for secrets, no recreate step on each Mac. Nothing about secrets ever lives in plaintext on disk.
280
+
281
+ ### Per-secret metadata and rotation
282
+
283
+ Tag each secret with `--type`, `--expires`, and `--note` so the bundle is self-documenting. `--expires` is always future-dated (`YYYY-MM-DD`); past or same-day values are rejected. Use `agents secrets rotate <bundle> <key>` to refresh a credential — `add` only creates new keys, `rotate` replaces the value and preserves metadata unless overridden.
284
+
285
+ ```bash
286
+ agents secrets add prod STRIPE_API_KEY --type api-key --expires 2027-01-15 --note "Live key, owner: payments-team"
287
+ agents secrets rotate prod STRIPE_API_KEY --note "rotated after suspected leak"
288
+ agents secrets list # EXPIRING column flags secrets due in the next 30 days
289
+ ```
290
+
263
291
  ---
264
292
 
265
293
  ## Routines
@@ -403,7 +431,7 @@ No. API keys come from your shell environment or each agent CLI's existing auth.
403
431
 
404
432
  macOS and Linux. Windows via WSL works but isn't first-class yet.
405
433
 
406
- **macOS-only features:** Keychain-based secrets (`agents secrets`, `agents profiles login`) require macOS. On Linux, use environment variables or `.env` files for API keys. Native Linux credential store support is planned.
434
+ **macOS-only features:** Keychain-based secrets (`agents secrets`, `agents profiles login`) require macOS. The `--icloud-sync` flag on bundles requires macOS + iCloud Keychain enabled. On Linux, use environment variables or `.env` files for API keys. Native Linux credential store support is planned.
407
435
 
408
436
  ### Do I need Node.js?
409
437
 
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerBrowserCommand(program: Command): void;
@@ -0,0 +1,388 @@
1
+ import { listProfiles, getProfile, createProfile, deleteProfile, } from '../lib/browser/profiles.js';
2
+ import { sendIPCRequest } from '../lib/browser/ipc.js';
3
+ import { isValidTaskId } from '../lib/browser/types.js';
4
+ export function registerBrowserCommand(program) {
5
+ const browser = program
6
+ .command('browser')
7
+ .description('Browser automation via CDP');
8
+ registerProfilesCommands(browser);
9
+ registerTaskCommands(browser);
10
+ }
11
+ function registerProfilesCommands(browser) {
12
+ const profiles = browser
13
+ .command('profiles')
14
+ .description('Manage browser profiles');
15
+ profiles
16
+ .command('list')
17
+ .alias('ls')
18
+ .description('List all browser profiles')
19
+ .action(async () => {
20
+ const allProfiles = await listProfiles();
21
+ if (allProfiles.length === 0) {
22
+ console.log('No browser profiles configured.');
23
+ console.log('Create one with: agents browser profiles create <name> --endpoint <url>');
24
+ return;
25
+ }
26
+ console.log('NAME'.padEnd(20) + 'BROWSER'.padEnd(12) + 'ENDPOINTS');
27
+ console.log('-'.repeat(72));
28
+ for (const p of allProfiles) {
29
+ const endpoints = p.endpoints.join(', ');
30
+ console.log(p.name.padEnd(20) + (p.browser || '-').padEnd(12) + endpoints);
31
+ }
32
+ });
33
+ const VALID_BROWSERS = ['chrome', 'comet', 'chromium', 'brave', 'edge'];
34
+ profiles
35
+ .command('create <name>')
36
+ .description('Create a new browser profile')
37
+ .requiredOption('-b, --browser <type>', `Browser type: ${VALID_BROWSERS.join(', ')}`)
38
+ .requiredOption('-e, --endpoint <url>', 'CDP endpoint URL (repeatable)', collect, [])
39
+ .option('-s, --secrets <bundle>', 'Secrets bundle to inject')
40
+ .option('-d, --description <text>', 'Profile description')
41
+ .option('--headless', 'Run in headless mode')
42
+ .action(async (name, opts) => {
43
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
44
+ console.error('Profile name must be lowercase alphanumeric with hyphens');
45
+ process.exit(1);
46
+ }
47
+ if (!VALID_BROWSERS.includes(opts.browser)) {
48
+ console.error(`Invalid browser type. Must be one of: ${VALID_BROWSERS.join(', ')}`);
49
+ process.exit(1);
50
+ }
51
+ const profile = {
52
+ name,
53
+ description: opts.description,
54
+ browser: opts.browser,
55
+ endpoints: opts.endpoint,
56
+ secrets: opts.secrets,
57
+ chrome: opts.headless ? { headless: true } : undefined,
58
+ };
59
+ await createProfile(profile);
60
+ console.log(`Created profile: ${name}`);
61
+ });
62
+ profiles
63
+ .command('show <name>')
64
+ .description('Show profile details')
65
+ .action(async (name) => {
66
+ const profile = await getProfile(name);
67
+ if (!profile) {
68
+ console.error(`Profile "${name}" not found`);
69
+ process.exit(1);
70
+ }
71
+ console.log(`Name: ${profile.name}`);
72
+ console.log(`Browser: ${profile.browser}`);
73
+ if (profile.description)
74
+ console.log(`Description: ${profile.description}`);
75
+ console.log(`Endpoints:`);
76
+ for (const e of profile.endpoints) {
77
+ console.log(` - ${e}`);
78
+ }
79
+ if (profile.secrets)
80
+ console.log(`Secrets: ${profile.secrets}`);
81
+ if (profile.chrome?.headless)
82
+ console.log(`Headless: true`);
83
+ });
84
+ profiles
85
+ .command('delete <name>')
86
+ .description('Delete a browser profile')
87
+ .action(async (name) => {
88
+ await deleteProfile(name);
89
+ console.log(`Deleted profile: ${name}`);
90
+ });
91
+ }
92
+ function registerTaskCommands(browser) {
93
+ browser
94
+ .command('start [task]')
95
+ .description('Start a browser task')
96
+ .requiredOption('-p, --profile <name>', 'Browser profile to use')
97
+ .action(async (task, opts) => {
98
+ if (task && !isValidTaskId(task)) {
99
+ console.error('Task ID must be lowercase alphanumeric with hyphens');
100
+ process.exit(1);
101
+ }
102
+ const response = await sendIPCRequest({
103
+ action: 'start',
104
+ profile: opts.profile,
105
+ task,
106
+ });
107
+ if (!response.ok) {
108
+ console.error(response.error);
109
+ process.exit(1);
110
+ }
111
+ console.log(response.task);
112
+ });
113
+ browser
114
+ .command('stop <task>')
115
+ .description('Stop a browser task and close its tabs')
116
+ .action(async (task) => {
117
+ const response = await sendIPCRequest({
118
+ action: 'stop',
119
+ task,
120
+ });
121
+ if (!response.ok) {
122
+ console.error(response.error);
123
+ process.exit(1);
124
+ }
125
+ console.log(`Stopped task: ${task}`);
126
+ });
127
+ browser
128
+ .command('navigate <task> <url>')
129
+ .description('Open a URL in the task window')
130
+ .option('-p, --profile <name>', 'Browser profile (optional if task is unique)')
131
+ .action(async (task, url, opts) => {
132
+ const response = await sendIPCRequest({
133
+ action: 'navigate',
134
+ task,
135
+ url,
136
+ profile: opts.profile,
137
+ });
138
+ if (!response.ok) {
139
+ console.error(response.error);
140
+ process.exit(1);
141
+ }
142
+ console.log(`Opened tab ${response.tabId}: ${url}`);
143
+ });
144
+ browser
145
+ .command('tabs [task]')
146
+ .description('List open tabs')
147
+ .option('-p, --profile <name>', 'Filter by profile')
148
+ .action(async (task, opts) => {
149
+ const response = await sendIPCRequest({
150
+ action: 'tabs',
151
+ task,
152
+ profile: opts.profile,
153
+ });
154
+ if (!response.ok) {
155
+ console.error(response.error);
156
+ process.exit(1);
157
+ }
158
+ if (!response.tabs || response.tabs.length === 0) {
159
+ console.log('No tabs open');
160
+ return;
161
+ }
162
+ console.log('TASK'.padEnd(15) + 'TAB'.padEnd(12) + 'URL');
163
+ console.log('-'.repeat(80));
164
+ for (const tab of response.tabs) {
165
+ const shortId = tab.id.slice(0, 8);
166
+ console.log(tab.task.padEnd(15) +
167
+ shortId.padEnd(12) +
168
+ tab.url.slice(0, 55));
169
+ }
170
+ });
171
+ browser
172
+ .command('close <task> [tabId]')
173
+ .description('Close tabs for a task')
174
+ .action(async (task, tabId) => {
175
+ const response = await sendIPCRequest({
176
+ action: 'close',
177
+ task,
178
+ tabId,
179
+ });
180
+ if (!response.ok) {
181
+ console.error(response.error);
182
+ process.exit(1);
183
+ }
184
+ console.log(tabId ? `Closed tab ${tabId}` : `Closed all tabs for task ${task}`);
185
+ });
186
+ browser
187
+ .command('screenshot <task> [tabId]')
188
+ .description('Take a screenshot')
189
+ .option('-o, --output <path>', 'Output path')
190
+ .action(async (task, tabId, opts) => {
191
+ const response = await sendIPCRequest({
192
+ action: 'screenshot',
193
+ task,
194
+ tabId,
195
+ path: opts.output,
196
+ });
197
+ if (!response.ok) {
198
+ console.error(response.error);
199
+ process.exit(1);
200
+ }
201
+ console.log(response.path);
202
+ });
203
+ browser
204
+ .command('evaluate <task> <tabId> <expression>')
205
+ .description('Evaluate JavaScript in a tab')
206
+ .action(async (task, tabId, expression) => {
207
+ const response = await sendIPCRequest({
208
+ action: 'evaluate',
209
+ task,
210
+ tabId,
211
+ expr: expression,
212
+ });
213
+ if (!response.ok) {
214
+ console.error(response.error);
215
+ process.exit(1);
216
+ }
217
+ console.log(JSON.stringify(response.result, null, 2));
218
+ });
219
+ browser
220
+ .command('status')
221
+ .description('Show running browser tasks')
222
+ .option('-p, --profile <name>', 'Filter by profile')
223
+ .action(async (opts) => {
224
+ const response = await sendIPCRequest({
225
+ action: 'status',
226
+ profile: opts.profile,
227
+ });
228
+ if (!response.ok) {
229
+ console.error(response.error);
230
+ process.exit(1);
231
+ }
232
+ if (!response.profiles || response.profiles.length === 0) {
233
+ console.log('No browser profiles running');
234
+ return;
235
+ }
236
+ for (const profile of response.profiles) {
237
+ console.log(`\n${profile.name} (port ${profile.port}, pid ${profile.pid})`);
238
+ if (profile.tasks.length === 0) {
239
+ console.log(' No active tasks');
240
+ }
241
+ else {
242
+ console.log(' TASK'.padEnd(17) + 'TABS'.padEnd(8) + 'CREATED');
243
+ for (const task of profile.tasks) {
244
+ const age = formatAge(task.createdAt);
245
+ console.log(' ' +
246
+ task.id.padEnd(15) +
247
+ String(task.tabCount).padEnd(8) +
248
+ age);
249
+ }
250
+ }
251
+ }
252
+ });
253
+ browser
254
+ .command('tasks')
255
+ .description('List all browser tasks')
256
+ .option('-p, --profile <name>', 'Filter by profile')
257
+ .action(async (opts) => {
258
+ const response = await sendIPCRequest({
259
+ action: 'status',
260
+ profile: opts.profile,
261
+ });
262
+ if (!response.ok) {
263
+ console.error(response.error);
264
+ process.exit(1);
265
+ }
266
+ const allTasks = [];
267
+ for (const profile of response.profiles || []) {
268
+ for (const task of profile.tasks) {
269
+ allTasks.push({
270
+ profile: profile.name,
271
+ id: task.id,
272
+ tabs: task.tabCount,
273
+ created: task.createdAt,
274
+ });
275
+ }
276
+ }
277
+ if (allTasks.length === 0) {
278
+ console.log('No active tasks');
279
+ return;
280
+ }
281
+ console.log('PROFILE'.padEnd(18) + 'TASK'.padEnd(15) + 'TABS'.padEnd(8) + 'CREATED');
282
+ console.log('-'.repeat(55));
283
+ for (const t of allTasks) {
284
+ console.log(t.profile.padEnd(18) +
285
+ t.id.padEnd(15) +
286
+ String(t.tabs).padEnd(8) +
287
+ formatAge(t.created));
288
+ }
289
+ });
290
+ browser
291
+ .command('refs <task> [tabId]')
292
+ .description('Get DOM refs for interactive elements')
293
+ .option('--all', 'Include non-interactive elements')
294
+ .option('-l, --limit <n>', 'Max elements (default 500)', '500')
295
+ .action(async (task, tabId, opts) => {
296
+ const response = await sendIPCRequest({
297
+ action: 'refs',
298
+ task,
299
+ tabId,
300
+ interactive: !opts.all,
301
+ limit: parseInt(opts.limit, 10),
302
+ });
303
+ if (!response.ok) {
304
+ console.error(response.error);
305
+ process.exit(1);
306
+ }
307
+ console.log(response.refs);
308
+ });
309
+ browser
310
+ .command('click <task> <tabId> <ref>')
311
+ .description('Click an element by ref')
312
+ .action(async (task, tabId, ref) => {
313
+ const response = await sendIPCRequest({
314
+ action: 'click',
315
+ task,
316
+ tabId,
317
+ ref: parseInt(ref, 10),
318
+ });
319
+ if (!response.ok) {
320
+ console.error(response.error);
321
+ process.exit(1);
322
+ }
323
+ console.log('Clicked');
324
+ });
325
+ browser
326
+ .command('type <task> <tabId> <ref> <text>')
327
+ .description('Type text into an element by ref')
328
+ .action(async (task, tabId, ref, text) => {
329
+ const response = await sendIPCRequest({
330
+ action: 'type',
331
+ task,
332
+ tabId,
333
+ ref: parseInt(ref, 10),
334
+ text,
335
+ });
336
+ if (!response.ok) {
337
+ console.error(response.error);
338
+ process.exit(1);
339
+ }
340
+ console.log('Typed');
341
+ });
342
+ browser
343
+ .command('press <task> <tabId> <key>')
344
+ .description('Press a key (Enter, Tab, Escape, etc)')
345
+ .action(async (task, tabId, key) => {
346
+ const response = await sendIPCRequest({
347
+ action: 'press',
348
+ task,
349
+ tabId,
350
+ key,
351
+ });
352
+ if (!response.ok) {
353
+ console.error(response.error);
354
+ process.exit(1);
355
+ }
356
+ console.log('Pressed');
357
+ });
358
+ browser
359
+ .command('hover <task> <tabId> <ref>')
360
+ .description('Hover over an element by ref')
361
+ .action(async (task, tabId, ref) => {
362
+ const response = await sendIPCRequest({
363
+ action: 'hover',
364
+ task,
365
+ tabId,
366
+ ref: parseInt(ref, 10),
367
+ });
368
+ if (!response.ok) {
369
+ console.error(response.error);
370
+ process.exit(1);
371
+ }
372
+ console.log('Hovered');
373
+ });
374
+ }
375
+ function collect(val, memo) {
376
+ memo.push(val);
377
+ return memo;
378
+ }
379
+ function formatAge(timestamp) {
380
+ const seconds = Math.floor((Date.now() - timestamp) / 1000);
381
+ if (seconds < 60)
382
+ return `${seconds}s ago`;
383
+ const minutes = Math.floor(seconds / 60);
384
+ if (minutes < 60)
385
+ return `${minutes}m ago`;
386
+ const hours = Math.floor(minutes / 60);
387
+ return `${hours}h ago`;
388
+ }
@@ -92,7 +92,7 @@ you never need to start it manually.
92
92
  warnDeprecated('logs', 'agents routines scheduler-logs');
93
93
  if (options.follow) {
94
94
  const { getAgentsDir } = await import('../lib/state.js');
95
- const logPath = path.join(getAgentsDir(), 'daemon.log');
95
+ const logPath = path.join(getAgentsDir(), 'helpers/daemon/logs.jsonl');
96
96
  const child = spawn('tail', ['-f', logPath], { stdio: ['ignore', 'pipe', 'pipe'] });
97
97
  child.stdout.pipe(process.stdout);
98
98
  child.stderr.pipe(process.stderr);
@@ -1,15 +1,22 @@
1
1
  /**
2
2
  * `agents doctor` — diagnostic readout across the install.
3
3
  *
4
- * Three sections:
5
- * 1. CLI availability — which agent binaries can be invoked.
6
- * 2. Sync status — per (agent, default-version), is the version-home sync
7
- * manifest fresh? (sync runs at launch; this surfaces what would happen.)
8
- * 3. Orphans per resource type per default version, count of files that
9
- * would be removed by `agents prune`.
10
- *
11
- * Read-only: doctor never mutates state. Run `agents prune` to act on the
12
- * orphan readout, or just launch the agent to apply pending sync.
4
+ * Two modes:
5
+ *
6
+ * 1. Overview (no target): three sections
7
+ * - CLI availability (which agent binaries can be invoked).
8
+ * - Sync status per default version (fresh / stale / never-synced).
9
+ * - Orphans per default version per resource type.
10
+ *
11
+ * 2. Target mode: `agents doctor <agent>[@version]` full per-resource
12
+ * diff for a single (agent, version) against the current cwd's resolved
13
+ * sources. Reports ok / DIFF / MISS / EXTRA per resource with the source
14
+ * layer (project, user, system, extra repo). With `--diff`, renders a
15
+ * unified diff body for each divergent file. Mirrors the resolution that
16
+ * the shim drives at runtime: project > user > system > extras.
17
+ *
18
+ * Read-only: doctor never mutates state. Run `agents prune` to act on orphan
19
+ * readouts, or just launch the agent to apply pending sync.
13
20
  */
14
21
  import type { Command } from 'commander';
15
22
  export declare function registerDoctorCommand(program: Command): void;