@phnx-labs/agents-cli 1.20.51 → 1.20.53

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 (142) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +12 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/browser.js +215 -7
  5. package/dist/commands/cloud.d.ts +3 -0
  6. package/dist/commands/cloud.js +8 -1
  7. package/dist/commands/events.d.ts +1 -1
  8. package/dist/commands/events.js +2 -3
  9. package/dist/commands/exec.js +82 -3
  10. package/dist/commands/factory.js +8 -0
  11. package/dist/commands/feed.d.ts +38 -0
  12. package/dist/commands/feed.js +274 -0
  13. package/dist/commands/logs.d.ts +5 -1
  14. package/dist/commands/logs.js +248 -3
  15. package/dist/commands/mcp.js +7 -0
  16. package/dist/commands/memory.d.ts +9 -0
  17. package/dist/commands/memory.js +164 -0
  18. package/dist/commands/message.d.ts +11 -6
  19. package/dist/commands/message.js +140 -5
  20. package/dist/commands/routines.js +12 -0
  21. package/dist/commands/secrets-migrate.d.ts +2 -1
  22. package/dist/commands/secrets-migrate.js +88 -13
  23. package/dist/commands/secrets.d.ts +22 -0
  24. package/dist/commands/secrets.js +176 -42
  25. package/dist/commands/sessions.js +10 -1
  26. package/dist/commands/teams.js +4 -0
  27. package/dist/commands/worktree.js +4 -2
  28. package/dist/index.js +21 -22
  29. package/dist/lib/agents.js +249 -17
  30. package/dist/lib/answer-router.d.ts +75 -0
  31. package/dist/lib/answer-router.js +149 -0
  32. package/dist/lib/ask-classifier.d.ts +71 -0
  33. package/dist/lib/ask-classifier.js +197 -0
  34. package/dist/lib/browser/login-detection.d.ts +94 -0
  35. package/dist/lib/browser/login-detection.js +274 -0
  36. package/dist/lib/browser/profiles.d.ts +17 -8
  37. package/dist/lib/browser/profiles.js +27 -8
  38. package/dist/lib/browser/secret-ref.d.ts +10 -0
  39. package/dist/lib/browser/secret-ref.js +14 -0
  40. package/dist/lib/browser/service.js +14 -12
  41. package/dist/lib/cloud/antigravity.d.ts +0 -2
  42. package/dist/lib/cloud/antigravity.js +2 -17
  43. package/dist/lib/cloud/codex.js +3 -18
  44. package/dist/lib/cloud/rush.d.ts +15 -0
  45. package/dist/lib/cloud/rush.js +10 -16
  46. package/dist/lib/cloud/stream.js +2 -0
  47. package/dist/lib/cloud/types.d.ts +21 -0
  48. package/dist/lib/cloud/types.js +81 -0
  49. package/dist/lib/crabbox/cli.d.ts +1 -1
  50. package/dist/lib/crabbox/cli.js +12 -2
  51. package/dist/lib/crabbox/lease.d.ts +19 -0
  52. package/dist/lib/crabbox/lease.js +22 -11
  53. package/dist/lib/crabbox/progress.d.ts +62 -0
  54. package/dist/lib/crabbox/progress.js +129 -0
  55. package/dist/lib/crabbox/runtimes.d.ts +38 -1
  56. package/dist/lib/crabbox/runtimes.js +98 -5
  57. package/dist/lib/daemon.d.ts +12 -9
  58. package/dist/lib/daemon.js +32 -17
  59. package/dist/lib/events.d.ts +31 -5
  60. package/dist/lib/events.js +291 -101
  61. package/dist/lib/exec.js +20 -1
  62. package/dist/lib/feed-outcome.d.ts +101 -0
  63. package/dist/lib/feed-outcome.js +244 -0
  64. package/dist/lib/feed-policy.d.ts +30 -0
  65. package/dist/lib/feed-policy.js +133 -0
  66. package/dist/lib/feed.d.ts +180 -0
  67. package/dist/lib/feed.js +627 -0
  68. package/dist/lib/git.d.ts +17 -1
  69. package/dist/lib/git.js +20 -1
  70. package/dist/lib/hooks.js +529 -14
  71. package/dist/lib/hosts/passthrough.d.ts +3 -3
  72. package/dist/lib/hosts/passthrough.js +3 -3
  73. package/dist/lib/mailbox-gc.d.ts +22 -0
  74. package/dist/lib/mailbox-gc.js +161 -0
  75. package/dist/lib/mailbox.d.ts +26 -2
  76. package/dist/lib/mailbox.js +80 -5
  77. package/dist/lib/mcp.js +82 -0
  78. package/dist/lib/memory.d.ts +55 -0
  79. package/dist/lib/memory.js +274 -0
  80. package/dist/lib/notify.d.ts +16 -0
  81. package/dist/lib/notify.js +61 -0
  82. package/dist/lib/operator.d.ts +26 -0
  83. package/dist/lib/operator.js +107 -0
  84. package/dist/lib/plugins.d.ts +35 -0
  85. package/dist/lib/plugins.js +217 -0
  86. package/dist/lib/remote-agents-json.d.ts +14 -0
  87. package/dist/lib/remote-agents-json.js +94 -0
  88. package/dist/lib/resources/mcp.js +44 -0
  89. package/dist/lib/resources/memory.d.ts +15 -0
  90. package/dist/lib/resources/memory.js +46 -0
  91. package/dist/lib/resources/types.d.ts +2 -2
  92. package/dist/lib/rotate.js +2 -0
  93. package/dist/lib/runner.d.ts +43 -0
  94. package/dist/lib/runner.js +323 -74
  95. package/dist/lib/sandbox.js +6 -0
  96. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  97. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  98. package/dist/lib/secrets/agent.d.ts +21 -0
  99. package/dist/lib/secrets/agent.js +63 -1
  100. package/dist/lib/secrets/bundles.d.ts +33 -1
  101. package/dist/lib/secrets/bundles.js +76 -21
  102. package/dist/lib/secrets/icloud-import.d.ts +79 -0
  103. package/dist/lib/secrets/icloud-import.js +203 -0
  104. package/dist/lib/secrets/index.d.ts +142 -2
  105. package/dist/lib/secrets/index.js +695 -30
  106. package/dist/lib/secrets/remote.js +1 -1
  107. package/dist/lib/secrets/sync.js +1 -1
  108. package/dist/lib/session/active.d.ts +18 -0
  109. package/dist/lib/session/active.js +47 -17
  110. package/dist/lib/session/db.d.ts +9 -1
  111. package/dist/lib/session/db.js +18 -3
  112. package/dist/lib/session/discover.d.ts +13 -0
  113. package/dist/lib/session/discover.js +32 -2
  114. package/dist/lib/session/parse.d.ts +8 -0
  115. package/dist/lib/session/parse.js +42 -21
  116. package/dist/lib/session/remote-active.js +8 -89
  117. package/dist/lib/session/state.d.ts +11 -0
  118. package/dist/lib/session/state.js +50 -1
  119. package/dist/lib/session/tail.d.ts +23 -4
  120. package/dist/lib/session/tail.js +34 -16
  121. package/dist/lib/session/throughput.d.ts +30 -0
  122. package/dist/lib/session/throughput.js +86 -0
  123. package/dist/lib/shim-heal.d.ts +12 -3
  124. package/dist/lib/shim-heal.js +12 -6
  125. package/dist/lib/staleness/detectors/subagents.js +57 -3
  126. package/dist/lib/staleness/writers/hooks.js +7 -3
  127. package/dist/lib/staleness/writers/subagents.js +37 -6
  128. package/dist/lib/startup/command-registry.d.ts +2 -0
  129. package/dist/lib/startup/command-registry.js +4 -0
  130. package/dist/lib/state.d.ts +2 -0
  131. package/dist/lib/state.js +25 -8
  132. package/dist/lib/subagents.d.ts +52 -0
  133. package/dist/lib/subagents.js +315 -12
  134. package/dist/lib/teams/agents.js +6 -3
  135. package/dist/lib/teams/worktree.d.ts +8 -0
  136. package/dist/lib/teams/worktree.js +8 -0
  137. package/dist/lib/types.d.ts +20 -2
  138. package/dist/lib/versions.js +38 -48
  139. package/dist/lib/whats-new.d.ts +5 -3
  140. package/dist/lib/whats-new.js +25 -5
  141. package/package.json +4 -3
  142. package/scripts/postinstall.js +61 -1
@@ -1,10 +1,14 @@
1
1
  /**
2
- * `agents logs` — unified, discoverable run-log viewer.
2
+ * `agents logs` — unified, discoverable run-log viewer + audit trail.
3
3
  *
4
4
  * Resolves a run across two substrates and shows (or `-f` follows) its log:
5
5
  * - host-dispatch tasks (`agents run --host`) → combined-stdout log, offset-tailed
6
6
  * - sessions (the local index) → transcript, tailed via the sessions tailer
7
7
  *
8
+ * Subcommands:
9
+ * - `agents logs audit` — read the structured audit/event log
10
+ * - `agents logs stats` — show aggregate audit statistics
11
+ *
8
12
  * Concise by default: a bare `agents logs <id>` prints the same summary digest as
9
13
  * `agents sessions <id>` — cheap for an agent to glance at. The token-heavy full
10
14
  * transcript / raw stdout is opt-in behind `--full` (alias `-m/--markdown`).
@@ -17,12 +21,14 @@
17
21
  * the same underlying helpers (showHostTaskLog / streamSessionTail).
18
22
  */
19
23
  import chalk from 'chalk';
24
+ import * as fs from 'fs';
20
25
  import { discoverSessions, resolveSessionById } from '../lib/session/discover.js';
21
26
  import { parseAgentFilter, renderSessionLog } from './sessions.js';
22
27
  import { streamSessionTail, isTailable } from './sessions-tail.js';
23
28
  import { showHostTaskLog } from '../lib/hosts/logs.js';
24
29
  import { listTasks } from '../lib/hosts/tasks.js';
25
30
  import { itemPicker } from '../lib/picker.js';
31
+ import { query, stats, getLogsPath, rotate, levelFor, } from '../lib/events.js';
26
32
  /** Compact one-line label used by both the picker and the non-TTY list. */
27
33
  function candidateLabel(c) {
28
34
  if (c.kind === 'task') {
@@ -130,11 +136,207 @@ async function runLogs(id, opts) {
130
136
  return;
131
137
  await showCandidate(picked.item, follow, full);
132
138
  }
139
+ function parseSince(s) {
140
+ const m = s.match(/^(\d+)([smhdw])$/);
141
+ if (m) {
142
+ const n = parseInt(m[1], 10);
143
+ const unitMs = {
144
+ s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000,
145
+ };
146
+ return new Date(Date.now() - n * unitMs[m[2]]);
147
+ }
148
+ const ms = Date.parse(s);
149
+ if (isNaN(ms))
150
+ throw new Error(`Invalid --since value: ${s} (use e.g. 2h, 7d, or an ISO date)`);
151
+ return new Date(ms);
152
+ }
153
+ function originLabel(r) {
154
+ if (r.transport === 'ssh') {
155
+ return chalk.yellow(`ssh${r.sshClientIp ? ' ' + r.sshClientIp : ''}`);
156
+ }
157
+ return chalk.gray('local');
158
+ }
159
+ function auditDetailFor(r) {
160
+ if (r.command)
161
+ return r.command;
162
+ const bits = [];
163
+ if (typeof r.team === 'string')
164
+ bits.push(`team=${r.team}`);
165
+ if (typeof r.bundle === 'string')
166
+ bits.push(`bundle=${r.bundle}`);
167
+ if (typeof r.skill === 'string')
168
+ bits.push(`skill=${r.skill}`);
169
+ if (typeof r.version === 'string')
170
+ bits.push(`v=${r.version}`);
171
+ if (typeof r.profile === 'string')
172
+ bits.push(`profile=${r.profile}`);
173
+ if (typeof r.server === 'string')
174
+ bits.push(`server=${r.server}`);
175
+ if (typeof r.error === 'string')
176
+ bits.push(chalk.red(r.error));
177
+ return bits.join(' ');
178
+ }
179
+ function levelColor(level) {
180
+ if (level === 'audit')
181
+ return chalk.magenta(level);
182
+ if (level === 'warn')
183
+ return chalk.yellow(level);
184
+ if (level === 'debug')
185
+ return chalk.gray(level);
186
+ return chalk.blue(level);
187
+ }
188
+ function renderAuditRow(r) {
189
+ const time = chalk.gray(r.ts.slice(0, 19).replace('T', ' '));
190
+ const user = `${r.osUser ?? '?'}@${r.hostname}`;
191
+ const ev = r.event.startsWith('error') ? chalk.red(r.event) : chalk.cyan(r.event);
192
+ const lvl = levelColor(r.level ?? levelFor(r.event));
193
+ const agent = r.agent ? chalk.gray(` ${r.agent}`) : '';
194
+ const caller = chalk.gray(`via ${r.caller ?? 'unknown'}${r.session ? ` ${r.session}` : ''}`);
195
+ return `${time} ${lvl.padEnd(14)} ${originLabel(r).padEnd(24)} ${user.padEnd(22)} ${caller.padEnd(28)} ${ev.padEnd(26)}${agent} ${auditDetailFor(r)}`;
196
+ }
197
+ function collect(value, previous) {
198
+ return previous.concat([value]);
199
+ }
200
+ async function runAudit(opts) {
201
+ if (opts.follow) {
202
+ await followAuditLog();
203
+ return;
204
+ }
205
+ const limit = Math.max(1, parseInt(opts.limit ?? '50', 10) || 50);
206
+ let startDate;
207
+ try {
208
+ startDate = opts.since ? parseSince(opts.since) : undefined;
209
+ }
210
+ catch (err) {
211
+ console.error(chalk.red(err.message));
212
+ process.exit(2);
213
+ }
214
+ const records = query({
215
+ startDate,
216
+ eventTypes: opts.event?.length ? opts.event : undefined,
217
+ level: opts.level,
218
+ agent: opts.agent,
219
+ caller: opts.caller,
220
+ command: opts.command,
221
+ module: opts.module,
222
+ limit,
223
+ });
224
+ if (opts.json) {
225
+ console.log(JSON.stringify(records, null, 2));
226
+ return;
227
+ }
228
+ if (records.length === 0) {
229
+ console.log(chalk.gray('No matching events.'));
230
+ return;
231
+ }
232
+ for (const r of records.slice().reverse())
233
+ console.log(renderAuditRow(r));
234
+ console.log(chalk.gray(`\n${records.length} event(s). Log: ${getLogsPath()}`));
235
+ }
236
+ async function followAuditLog() {
237
+ const file = getLogsPath();
238
+ let offset = 0;
239
+ try {
240
+ offset = fs.statSync(file).size;
241
+ }
242
+ catch {
243
+ // File may not exist yet — start at 0.
244
+ }
245
+ console.log(chalk.gray(`Tailing ${file} — Ctrl-C to stop`));
246
+ const drain = () => {
247
+ let size = 0;
248
+ try {
249
+ size = fs.statSync(file).size;
250
+ }
251
+ catch {
252
+ return;
253
+ }
254
+ if (size <= offset) {
255
+ if (size < offset)
256
+ offset = 0;
257
+ return;
258
+ }
259
+ const fd = fs.openSync(file, 'r');
260
+ try {
261
+ const buf = Buffer.alloc(size - offset);
262
+ fs.readSync(fd, buf, 0, buf.length, offset);
263
+ offset = size;
264
+ for (const line of buf.toString('utf-8').split('\n').filter(Boolean)) {
265
+ try {
266
+ console.log(renderAuditRow(JSON.parse(line)));
267
+ }
268
+ catch {
269
+ // Skip malformed lines.
270
+ }
271
+ }
272
+ }
273
+ finally {
274
+ fs.closeSync(fd);
275
+ }
276
+ };
277
+ await new Promise(() => {
278
+ setInterval(drain, 500);
279
+ });
280
+ }
281
+ function humanBytes(bytes) {
282
+ if (bytes < 1024)
283
+ return `${bytes} B`;
284
+ if (bytes < 1024 * 1024)
285
+ return `${(bytes / 1024).toFixed(1)} KB`;
286
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
287
+ }
288
+ async function runStats(opts) {
289
+ let days = 7;
290
+ if (opts.since) {
291
+ try {
292
+ const d = parseSince(opts.since);
293
+ days = Math.max(1, Math.ceil((Date.now() - d.getTime()) / 86_400_000));
294
+ }
295
+ catch (err) {
296
+ console.error(chalk.red(err.message));
297
+ process.exit(2);
298
+ }
299
+ }
300
+ const s = stats({ days });
301
+ if (opts.json) {
302
+ console.log(JSON.stringify(s, null, 2));
303
+ return;
304
+ }
305
+ console.log(chalk.bold(`Audit statistics (last ${days} day${days === 1 ? '' : 's'})\n`));
306
+ console.log(` Total events: ${s.totalEvents}`);
307
+ console.log(` Log files: ${s.fileCount} (${humanBytes(s.totalBytes)})`);
308
+ console.log(` Log path: ${chalk.gray(getLogsPath())}`);
309
+ if (Object.keys(s.byLevel).length) {
310
+ console.log(chalk.bold('\n By level:'));
311
+ for (const [k, v] of Object.entries(s.byLevel).sort((a, b) => b[1] - a[1])) {
312
+ console.log(` ${levelColor(k).padEnd(20)} ${v}`);
313
+ }
314
+ }
315
+ if (Object.keys(s.byEvent).length) {
316
+ console.log(chalk.bold('\n By event (top 15):'));
317
+ for (const [k, v] of Object.entries(s.byEvent).sort((a, b) => b[1] - a[1]).slice(0, 15)) {
318
+ console.log(` ${chalk.cyan(k).padEnd(30)} ${v}`);
319
+ }
320
+ }
321
+ if (Object.keys(s.byModule).length) {
322
+ console.log(chalk.bold('\n By module:'));
323
+ for (const [k, v] of Object.entries(s.byModule).sort((a, b) => b[1] - a[1])) {
324
+ console.log(` ${k.padEnd(20)} ${v}`);
325
+ }
326
+ }
327
+ if (Object.keys(s.byUser).length) {
328
+ console.log(chalk.bold('\n By user:'));
329
+ for (const [k, v] of Object.entries(s.byUser).sort((a, b) => b[1] - a[1])) {
330
+ console.log(` ${k.padEnd(30)} ${v}`);
331
+ }
332
+ }
333
+ console.log();
334
+ }
133
335
  /** Register the top-level `agents logs` command. */
134
336
  export function registerLogsCommand(program) {
135
- program
337
+ const logsCmd = program
136
338
  .command('logs [id]')
137
- .description('Show a run’s concise summary a host-dispatch task or a session. --full for the raw transcript, -f to follow a live one.')
339
+ .description('Show a run log, audit trail, or stats. Subcommands: audit, stats, rotate.')
138
340
  .option('--host <name>', 'Scope to runs dispatched to a host')
139
341
  .option('-a, --agent <agent>', 'Filter by agent (e.g. claude, codex@0.116.0)')
140
342
  .option('--version <version>', 'Filter by agent version')
@@ -142,4 +344,47 @@ export function registerLogsCommand(program) {
142
344
  .option('-f, --follow', 'Follow live output')
143
345
  .option('-m, --full', 'Show the full raw transcript / stdout instead of the concise summary')
144
346
  .action((id, opts) => runLogs(id, opts));
347
+ logsCmd
348
+ .command('audit')
349
+ .description('Read the structured audit/event log (who ran what, from where)')
350
+ .option('--module <name>', 'Only events from this command group (e.g. teams, secrets)')
351
+ .option('--command <path>', 'Only this command path — prefix match (e.g. "teams create")')
352
+ .option('--event <type>', 'Only this typed event (repeatable)', collect, [])
353
+ .option('--agent <name>', 'Only events tagged with this agent')
354
+ .option('--caller <kind>', 'Only this caller kind (claude-code, codex, gemini, cursor, terminal, script)')
355
+ .option('--level <level>', 'Only this level: audit, warn, info, debug')
356
+ .option('--since <time>', 'Only events newer than this (e.g. 2h, 7d, or ISO date)')
357
+ .option('--limit <n>', 'Max records to show (default 50)', '50')
358
+ .option('--json', 'Output raw records as JSON')
359
+ .option('-f, --follow', "Tail today's log live")
360
+ .addHelpText('after', `
361
+ Examples:
362
+ agents logs audit Recent activity across everything
363
+ agents logs audit --module teams Team lifecycle (create / add / disband)
364
+ agents logs audit --module secrets Every secret accessed or revealed
365
+ agents logs audit --level audit Only audit-level events
366
+ agents logs audit --command "teams create" Just team creations
367
+ agents logs audit --event secrets.get --since 7d --json
368
+ agents logs audit -f Live tail`)
369
+ .action(async (options) => runAudit(options));
370
+ logsCmd
371
+ .command('stats')
372
+ .description('Show aggregate audit statistics')
373
+ .option('--since <time>', 'Window size (e.g. 7d, 30d; default 7d)')
374
+ .option('--json', 'Output stats as JSON')
375
+ .action(async (opts) => runStats(opts));
376
+ logsCmd
377
+ .command('rotate')
378
+ .description('Force log rotation — remove files older than the retention period')
379
+ .option('--days <n>', 'Retention period in days (default 7)', '7')
380
+ .action((opts) => {
381
+ const days = Math.max(1, parseInt(opts.days ?? '7', 10) || 7);
382
+ const removed = rotate(days);
383
+ if (removed > 0) {
384
+ console.log(`Removed ${removed} log file${removed === 1 ? '' : 's'} older than ${days} day${days === 1 ? '' : 's'}.`);
385
+ }
386
+ else {
387
+ console.log(chalk.gray('No log files to remove.'));
388
+ }
389
+ });
145
390
  }
@@ -3,6 +3,7 @@ import { truncate } from '../lib/format.js';
3
3
  import ora from 'ora';
4
4
  import { checkbox } from '@inquirer/prompts';
5
5
  import { capableAgents, isCapable } from '../lib/capabilities.js';
6
+ import { emit } from '../lib/events.js';
6
7
  import { AGENTS, getAllCliStates, resolveAgentName, formatAgentError, registerMcpToTargets, unregisterMcpFromTargets, listInstalledMcpsWithScope, parseMcpConfig, getMcpConfigPathForHome, agentLabel, } from '../lib/agents.js';
7
8
  import { readManifest, writeManifest, createDefaultManifest } from '../lib/manifest.js';
8
9
  import { listMcpServerConfigs, discoverMcpConfigsFromRepo, installMcpConfigCentrally, } from '../lib/mcp.js';
@@ -293,6 +294,7 @@ Examples:
293
294
  };
294
295
  }
295
296
  writeManifest(localPath, manifest);
297
+ emit('mcp.add', { module: 'mcp', server: name });
296
298
  console.log(chalk.green(`Added MCP server '${name}' to manifest`));
297
299
  console.log(chalk.gray('Run: agents mcp register to apply'));
298
300
  });
@@ -441,6 +443,8 @@ Examples:
441
443
  console.log(chalk.yellow('No MCP servers removed.'));
442
444
  }
443
445
  else {
446
+ for (const n of mcpsToRemove)
447
+ emit('mcp.remove', { module: 'mcp', server: n });
444
448
  console.log(chalk.green(`\nRemoved ${removed} MCP server(s).`));
445
449
  }
446
450
  });
@@ -575,6 +579,7 @@ Examples:
575
579
  targets = resolveConfiguredAgentTargets(config.agents, config.agentVersions, capableAgents('mcp'));
576
580
  }
577
581
  const results = await registerMcpToTargets(targets, mcpName, commandOrUrl, config.scope || 'user', transport, { headers: config.headers });
582
+ const applied = results.filter(r => r.success).length;
578
583
  for (const result of results) {
579
584
  if (result.success) {
580
585
  console.log(` ${chalk.green('+')} ${formatTargetLabel(result.agentId, result.version)}`);
@@ -586,6 +591,8 @@ Examples:
586
591
  console.log(` ${chalk.red('x')} ${formatTargetLabel(result.agentId, result.version)}: ${result.error}`);
587
592
  }
588
593
  }
594
+ if (applied > 0)
595
+ emit('mcp.register', { module: 'mcp', server: mcpName, applied });
589
596
  }
590
597
  });
591
598
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `agents memory` — first-class knowledge/facts resource (not rules).
3
+ *
4
+ * Mirrors the skills surface at list / add / remove / view / sync scale.
5
+ * Canonical storage: ~/.agents/memory/ (project > user > system layering).
6
+ */
7
+ import type { Command } from 'commander';
8
+ /** Register the `agents memory` command tree. */
9
+ export declare function registerMemoryCommands(program: Command): void;
@@ -0,0 +1,164 @@
1
+ /**
2
+ * `agents memory` — first-class knowledge/facts resource (not rules).
3
+ *
4
+ * Mirrors the skills surface at list / add / remove / view / sync scale.
5
+ * Canonical storage: ~/.agents/memory/ (project > user > system layering).
6
+ */
7
+ import chalk from 'chalk';
8
+ import * as fs from 'fs';
9
+ import { listMemoryFacts, addMemoryFact, removeMemoryFact, readMemoryFact, ensureUserMemoryDir, getUserMemoryDir, syncMemoryToVersionHome, } from '../lib/memory.js';
10
+ import { capableAgents } from '../lib/capabilities.js';
11
+ import { resolveAgentName, formatAgentError, agentLabel, ALL_AGENT_IDS } from '../lib/agents.js';
12
+ import { listInstalledVersions, getVersionHomePath, } from '../lib/versions.js';
13
+ import { supports } from '../lib/capabilities.js';
14
+ import { setHelpSections } from '../lib/help.js';
15
+ /** Register the `agents memory` command tree. */
16
+ export function registerMemoryCommands(program) {
17
+ const memoryCmd = program
18
+ .command('memory')
19
+ .description('Manage portable agent memory (facts, preferences, project knowledge)');
20
+ setHelpSections(memoryCmd, {
21
+ examples: `
22
+ # List all memory facts (project > user > system)
23
+ agents memory list
24
+
25
+ # Add a fact
26
+ agents memory add preferred-editor --body "User prefers vim keybindings"
27
+
28
+ # View a fact
29
+ agents memory view preferred-editor
30
+
31
+ # Remove a fact from the user layer
32
+ agents memory remove preferred-editor
33
+
34
+ # Fan out to all capable installed agent versions
35
+ agents memory sync
36
+ `,
37
+ notes: `
38
+ Memory is the knowledge store (learned facts), distinct from rules
39
+ (instructions / AGENTS.md persona). Stored under ~/.agents/memory/ and
40
+ synced into capable agents' version homes (claude/codex/openclaw/grok).
41
+
42
+ Format: MEMORY.md index + one <slug>.md file per fact.
43
+ `,
44
+ });
45
+ memoryCmd
46
+ .command('list')
47
+ .description('List memory facts from project, user, and system layers')
48
+ .option('--json', 'Emit machine-readable JSON')
49
+ .action((options) => {
50
+ const facts = listMemoryFacts(process.cwd());
51
+ if (options.json) {
52
+ process.stdout.write(JSON.stringify(facts, null, 2) + '\n');
53
+ return;
54
+ }
55
+ if (facts.length === 0) {
56
+ console.log(chalk.gray('No memory facts yet.'));
57
+ console.log(chalk.gray(` Add one: agents memory add <name> --body "..."`));
58
+ console.log(chalk.gray(` Dir: ${getUserMemoryDir()}`));
59
+ return;
60
+ }
61
+ console.log(chalk.bold(`Memory (${facts.length})`));
62
+ for (const f of facts) {
63
+ const layer = chalk.dim(`[${f.layer}]`);
64
+ console.log(` ${chalk.cyan(f.name)} ${layer} ${f.summary}`);
65
+ }
66
+ const capable = capableAgents('memory');
67
+ if (capable.length > 0) {
68
+ console.log(chalk.gray(`\nCapable agents: ${capable.join(', ')}`));
69
+ }
70
+ });
71
+ memoryCmd
72
+ .command('add <name>')
73
+ .description('Add or overwrite a user-layer memory fact')
74
+ .requiredOption('--body <text>', 'Fact body (markdown)')
75
+ .option('--sync', 'Immediately sync to all capable installed versions')
76
+ .action((name, options) => {
77
+ const filePath = addMemoryFact(name, options.body);
78
+ console.log(chalk.green(`Added memory fact: ${name}`));
79
+ console.log(chalk.gray(` ${filePath}`));
80
+ if (options.sync) {
81
+ for (const r of syncAllMemory(process.cwd())) {
82
+ console.log(chalk.gray(` synced ${r.agent}@${r.version} (${r.facts.length} facts)`));
83
+ }
84
+ }
85
+ else {
86
+ console.log(chalk.gray(' Run: agents memory sync (or agents sync) to fan out'));
87
+ }
88
+ });
89
+ memoryCmd
90
+ .command('remove <name>')
91
+ .alias('rm')
92
+ .description('Remove a user-layer memory fact')
93
+ .action((name) => {
94
+ if (removeMemoryFact(name)) {
95
+ console.log(chalk.green(`Removed memory fact: ${name}`));
96
+ }
97
+ else {
98
+ console.error(chalk.red(`No user-layer fact named "${name}"`));
99
+ process.exit(1);
100
+ }
101
+ });
102
+ memoryCmd
103
+ .command('view <name>')
104
+ .description('Print a memory fact (winning layer)')
105
+ .action((name) => {
106
+ const fact = readMemoryFact(name, process.cwd());
107
+ if (!fact) {
108
+ console.error(chalk.red(`No memory fact named "${name}"`));
109
+ process.exit(1);
110
+ }
111
+ console.log(chalk.dim(`# ${fact.name} [${fact.layer}] ${fact.path}`));
112
+ console.log(fs.readFileSync(fact.path, 'utf-8'));
113
+ });
114
+ memoryCmd
115
+ .command('sync [agent]')
116
+ .description('Copy canonical memory into capable agent version homes')
117
+ .option('-a, --agent <agent>', 'Limit to one agent (or agent@version)')
118
+ .action(async (agentArg, options) => {
119
+ ensureUserMemoryDir();
120
+ const input = agentArg || options.agent;
121
+ if (input) {
122
+ const parts = input.split('@');
123
+ const resolved = resolveAgentName(parts[0]);
124
+ if (!resolved) {
125
+ console.error(chalk.red(formatAgentError(parts[0], capableAgents('memory'))));
126
+ process.exit(1);
127
+ }
128
+ const agent = resolved;
129
+ const versions = parts[1]
130
+ ? [parts[1]]
131
+ : listInstalledVersions(agent);
132
+ if (versions.length === 0) {
133
+ console.log(chalk.yellow(`No installed versions of ${agentLabel(agent)}`));
134
+ return;
135
+ }
136
+ for (const version of versions) {
137
+ const home = getVersionHomePath(agent, version);
138
+ const facts = syncMemoryToVersionHome(agent, home, process.cwd());
139
+ console.log(chalk.green(`Synced ${facts.length} fact(s) → ${agent}@${version}`));
140
+ }
141
+ return;
142
+ }
143
+ const results = syncAllMemory(process.cwd());
144
+ if (results.length === 0) {
145
+ console.log(chalk.gray('No memory-capable agent versions installed.'));
146
+ return;
147
+ }
148
+ for (const r of results) {
149
+ console.log(chalk.green(`Synced ${r.facts.length} fact(s) → ${r.agent}@${r.version}`));
150
+ }
151
+ });
152
+ }
153
+ function syncAllMemory(cwd) {
154
+ const out = [];
155
+ for (const agent of ALL_AGENT_IDS) {
156
+ if (!supports(agent, 'memory').ok)
157
+ continue;
158
+ for (const version of listInstalledVersions(agent)) {
159
+ const home = getVersionHomePath(agent, version);
160
+ out.push({ agent, version, facts: syncMemoryToVersionHome(agent, home, cwd) });
161
+ }
162
+ }
163
+ return out;
164
+ }
@@ -1,15 +1,20 @@
1
1
  /**
2
- * `agents message <target> <text>` — send a message to a running agent.
2
+ * `agents message <target> <text>` — send a message to a running or parked agent.
3
3
  *
4
- * Unifies two delivery paths behind one verb:
5
- * - a live LOCAL/teams/loop agentenqueue into its file-spool mailbox; the
6
- * PreToolUse hook injects it at the agent's next tool call.
7
- * - a CLOUD task idthe existing provider follow-up path (was
8
- * `agents cloud message`).
4
+ * Delivery is routed by agent state (RUSH-1474):
5
+ * - running, between tool callsmailbox spool (PreToolUse inject)
6
+ * - parked on AskUserQuestion with a tmux/iterm/pty rail keystroke inject
7
+ * - parked headless (no rail)`agents run --resume <id> -- <answer>`
8
+ * - cloud task → provider.message()
9
9
  *
10
10
  * Cross-host is handled one layer up: `--host <h>` routes the whole command over
11
11
  * ssh via `REMOTE_PASSTHROUGH` (see src/lib/hosts/passthrough.ts), so the box is
12
12
  * written on the host that actually owns the agent.
13
+ *
14
+ * For local agents, the message is tied to the agent's current open feed block
15
+ * (if any). The first answer to a block wins: a second concurrent answer is
16
+ * rejected with the surface that already answered. Delivery receipts
17
+ * (queued → consumed → continued) are surfaced in the feed store.
13
18
  */
14
19
  import type { Command } from 'commander';
15
20
  export declare function registerMessageCommand(program: Command): void;