@phnx-labs/agents-cli 1.20.50 → 1.20.52

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 (124) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/commands/browser-picker.js +1 -18
  3. package/dist/commands/browser.js +215 -7
  4. package/dist/commands/cloud.js +7 -25
  5. package/dist/commands/computer.d.ts +1 -0
  6. package/dist/commands/computer.js +129 -8
  7. package/dist/commands/events.d.ts +1 -1
  8. package/dist/commands/events.js +2 -3
  9. package/dist/commands/exec.js +66 -8
  10. package/dist/commands/factory.js +9 -4
  11. package/dist/commands/feed.d.ts +9 -0
  12. package/dist/commands/feed.js +69 -0
  13. package/dist/commands/inspect.js +1 -11
  14. package/dist/commands/logs.d.ts +5 -1
  15. package/dist/commands/logs.js +248 -3
  16. package/dist/commands/mcp.js +9 -6
  17. package/dist/commands/message.js +1 -4
  18. package/dist/commands/profiles.js +1 -18
  19. package/dist/commands/repo.js +33 -14
  20. package/dist/commands/resource-view.d.ts +1 -0
  21. package/dist/commands/resource-view.js +5 -17
  22. package/dist/commands/secrets.d.ts +23 -0
  23. package/dist/commands/secrets.js +174 -70
  24. package/dist/commands/sessions-picker.js +1 -18
  25. package/dist/commands/sessions.js +6 -8
  26. package/dist/commands/teams-picker.js +1 -32
  27. package/dist/commands/teams.js +5 -27
  28. package/dist/commands/tmux.js +1 -3
  29. package/dist/commands/view.js +1 -9
  30. package/dist/commands/worktree.js +1 -4
  31. package/dist/index.js +6 -2
  32. package/dist/lib/agents.d.ts +0 -4
  33. package/dist/lib/agents.js +20 -33
  34. package/dist/lib/auto-dispatch-linear.d.ts +18 -0
  35. package/dist/lib/auto-dispatch-linear.js +107 -0
  36. package/dist/lib/auto-dispatch-provider.d.ts +10 -0
  37. package/dist/lib/auto-dispatch-provider.js +25 -0
  38. package/dist/lib/auto-dispatch.d.ts +87 -0
  39. package/dist/lib/auto-dispatch.js +142 -0
  40. package/dist/lib/browser/cdp.js +11 -2
  41. package/dist/lib/browser/drivers/ssh.d.ts +28 -10
  42. package/dist/lib/browser/drivers/ssh.js +57 -18
  43. package/dist/lib/browser/login-detection.d.ts +94 -0
  44. package/dist/lib/browser/login-detection.js +274 -0
  45. package/dist/lib/browser/profiles.d.ts +17 -8
  46. package/dist/lib/browser/profiles.js +27 -8
  47. package/dist/lib/browser/refs.js +1 -5
  48. package/dist/lib/browser/secret-ref.d.ts +10 -0
  49. package/dist/lib/browser/secret-ref.js +14 -0
  50. package/dist/lib/browser/service.js +14 -12
  51. package/dist/lib/cli-resources.d.ts +0 -2
  52. package/dist/lib/cli-resources.js +30 -13
  53. package/dist/lib/cloud/rush.d.ts +15 -24
  54. package/dist/lib/cloud/rush.js +7 -32
  55. package/dist/lib/crabbox/cli.js +4 -1
  56. package/dist/lib/crabbox/lease.d.ts +6 -0
  57. package/dist/lib/crabbox/lease.js +40 -10
  58. package/dist/lib/crabbox/runtimes.d.ts +38 -1
  59. package/dist/lib/crabbox/runtimes.js +98 -5
  60. package/dist/lib/daemon.d.ts +12 -9
  61. package/dist/lib/daemon.js +73 -17
  62. package/dist/lib/events.d.ts +31 -5
  63. package/dist/lib/events.js +288 -101
  64. package/dist/lib/exec.js +32 -14
  65. package/dist/lib/feed.d.ts +56 -0
  66. package/dist/lib/feed.js +251 -0
  67. package/dist/lib/format.d.ts +38 -0
  68. package/dist/lib/format.js +108 -0
  69. package/dist/lib/git.d.ts +21 -0
  70. package/dist/lib/git.js +92 -0
  71. package/dist/lib/hooks/cache.d.ts +9 -2
  72. package/dist/lib/hooks/cache.js +220 -8
  73. package/dist/lib/hooks.js +24 -10
  74. package/dist/lib/hosts/passthrough.js +1 -0
  75. package/dist/lib/platform/exec.d.ts +4 -1
  76. package/dist/lib/platform/exec.js +8 -2
  77. package/dist/lib/resources.d.ts +0 -8
  78. package/dist/lib/resources.js +0 -10
  79. package/dist/lib/rotate.js +2 -0
  80. package/dist/lib/runner.js +10 -2
  81. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  82. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  83. package/dist/lib/secrets/agent.d.ts +21 -0
  84. package/dist/lib/secrets/agent.js +63 -1
  85. package/dist/lib/secrets/bundles.d.ts +33 -1
  86. package/dist/lib/secrets/bundles.js +38 -8
  87. package/dist/lib/secrets/icloud-import.d.ts +70 -0
  88. package/dist/lib/secrets/icloud-import.js +173 -0
  89. package/dist/lib/secrets/index.d.ts +36 -0
  90. package/dist/lib/secrets/index.js +99 -9
  91. package/dist/lib/secrets/remote.js +1 -1
  92. package/dist/lib/secrets/sync.js +1 -1
  93. package/dist/lib/session/active.d.ts +11 -1
  94. package/dist/lib/session/active.js +3 -0
  95. package/dist/lib/session/db.d.ts +1 -4
  96. package/dist/lib/session/db.js +20 -25
  97. package/dist/lib/session/discover.d.ts +2 -2
  98. package/dist/lib/session/discover.js +60 -48
  99. package/dist/lib/session/parse.js +35 -34
  100. package/dist/lib/session/render.d.ts +7 -3
  101. package/dist/lib/session/render.js +15 -9
  102. package/dist/lib/session/state.d.ts +55 -0
  103. package/dist/lib/session/state.js +100 -11
  104. package/dist/lib/session/types.d.ts +9 -0
  105. package/dist/lib/shims.d.ts +9 -3
  106. package/dist/lib/shims.js +44 -8
  107. package/dist/lib/ssh-tunnel.d.ts +33 -2
  108. package/dist/lib/ssh-tunnel.js +94 -7
  109. package/dist/lib/staleness/types.d.ts +0 -1
  110. package/dist/lib/startup/command-registry.d.ts +1 -0
  111. package/dist/lib/startup/command-registry.js +2 -0
  112. package/dist/lib/state.d.ts +2 -0
  113. package/dist/lib/state.js +25 -8
  114. package/dist/lib/teams/agents.js +6 -3
  115. package/dist/lib/types.d.ts +24 -1
  116. package/dist/lib/versions.d.ts +0 -26
  117. package/dist/lib/versions.js +2 -145
  118. package/dist/lib/warn-unpushed.d.ts +40 -0
  119. package/dist/lib/warn-unpushed.js +128 -0
  120. package/dist/lib/whats-new.d.ts +5 -3
  121. package/dist/lib/whats-new.js +25 -5
  122. package/package.json +3 -1
  123. package/dist/lib/resources/index.d.ts +0 -53
  124. package/dist/lib/resources/index.js +0 -76
@@ -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
  }
@@ -1,7 +1,9 @@
1
1
  import chalk from 'chalk';
2
+ import { truncate } from '../lib/format.js';
2
3
  import ora from 'ora';
3
4
  import { checkbox } from '@inquirer/prompts';
4
5
  import { capableAgents, isCapable } from '../lib/capabilities.js';
6
+ import { emit } from '../lib/events.js';
5
7
  import { AGENTS, getAllCliStates, resolveAgentName, formatAgentError, registerMcpToTargets, unregisterMcpFromTargets, listInstalledMcpsWithScope, parseMcpConfig, getMcpConfigPathForHome, agentLabel, } from '../lib/agents.js';
6
8
  import { readManifest, writeManifest, createDefaultManifest } from '../lib/manifest.js';
7
9
  import { listMcpServerConfigs, discoverMcpConfigsFromRepo, installMcpConfigCentrally, } from '../lib/mcp.js';
@@ -292,6 +294,7 @@ Examples:
292
294
  };
293
295
  }
294
296
  writeManifest(localPath, manifest);
297
+ emit('mcp.add', { module: 'mcp', server: name });
295
298
  console.log(chalk.green(`Added MCP server '${name}' to manifest`));
296
299
  console.log(chalk.gray('Run: agents mcp register to apply'));
297
300
  });
@@ -440,6 +443,8 @@ Examples:
440
443
  console.log(chalk.yellow('No MCP servers removed.'));
441
444
  }
442
445
  else {
446
+ for (const n of mcpsToRemove)
447
+ emit('mcp.remove', { module: 'mcp', server: n });
443
448
  console.log(chalk.green(`\nRemoved ${removed} MCP server(s).`));
444
449
  }
445
450
  });
@@ -574,6 +579,7 @@ Examples:
574
579
  targets = resolveConfiguredAgentTargets(config.agents, config.agentVersions, capableAgents('mcp'));
575
580
  }
576
581
  const results = await registerMcpToTargets(targets, mcpName, commandOrUrl, config.scope || 'user', transport, { headers: config.headers });
582
+ const applied = results.filter(r => r.success).length;
577
583
  for (const result of results) {
578
584
  if (result.success) {
579
585
  console.log(` ${chalk.green('+')} ${formatTargetLabel(result.agentId, result.version)}`);
@@ -585,6 +591,8 @@ Examples:
585
591
  console.log(` ${chalk.red('x')} ${formatTargetLabel(result.agentId, result.version)}: ${result.error}`);
586
592
  }
587
593
  }
594
+ if (applied > 0)
595
+ emit('mcp.register', { module: 'mcp', server: mcpName, applied });
588
596
  }
589
597
  });
590
598
  }
@@ -764,7 +772,7 @@ function buildMcpRows(opts) {
764
772
  const displayCommand = declaredCommand || firstCommand;
765
773
  rows.push({
766
774
  name,
767
- description: displayCommand ? truncateString(displayCommand, 60) : '',
775
+ description: displayCommand ? truncate(displayCommand, 60) : '',
768
776
  extra: source,
769
777
  targets,
770
778
  buildDetail: () => formatMcpDetail(name, source, centralConfig, manifestConfig, displayCommand, targets),
@@ -811,8 +819,3 @@ function formatMcpDetail(name, source, centralConfig, manifestConfig, command, t
811
819
  lines.push(buildTargetsSection(targets));
812
820
  return lines.join('\n');
813
821
  }
814
- function truncateString(s, max) {
815
- if (s.length <= max)
816
- return s;
817
- return s.slice(0, max - 1) + '…';
818
- }
@@ -1,13 +1,10 @@
1
1
  import chalk from 'chalk';
2
+ import { die } from '../lib/format.js';
2
3
  import { getActiveSessions } from '../lib/session/active.js';
3
4
  import { getTaskById, updateTaskStatus } from '../lib/cloud/store.js';
4
5
  import { resolveProvider } from '../lib/cloud/registry.js';
5
6
  import { mailboxDir, enqueue } from '../lib/mailbox.js';
6
7
  import { resolveMessageTarget } from '../lib/mailbox-target.js';
7
- function die(msg, code = 1) {
8
- console.error(chalk.red(msg));
9
- process.exit(code);
10
- }
11
8
  export function registerMessageCommand(program) {
12
9
  program
13
10
  .command('message <target> <text>')
@@ -7,7 +7,7 @@
7
7
  * through a standard agent CLI with no local proxy.
8
8
  */
9
9
  import chalk from 'chalk';
10
- import * as fs from 'fs';
10
+ import { readStdinSync } from '../lib/format.js';
11
11
  import { spawn } from 'child_process';
12
12
  import { listProfiles, readProfile, writeProfile, deleteProfile, profileExists, profileFromPreset, validateProfileName, getPresetForProfile, } from '../lib/profiles.js';
13
13
  import { getPreset, listPresets, expandPreset } from '../lib/profiles-presets.js';
@@ -41,23 +41,6 @@ async function promptForSecret(message) {
41
41
  return await password({ message, mask: true });
42
42
  }
43
43
  /** Read all available data from stdin synchronously, trimmed. */
44
- function readStdinSync() {
45
- const chunks = [];
46
- const buf = Buffer.alloc(65536);
47
- while (true) {
48
- let bytesRead;
49
- try {
50
- bytesRead = fs.readSync(0, buf, 0, buf.length, null);
51
- }
52
- catch {
53
- break;
54
- }
55
- if (bytesRead === 0)
56
- break;
57
- chunks.push(Buffer.from(buf.subarray(0, bytesRead)));
58
- }
59
- return Buffer.concat(chunks).toString('utf-8').trim();
60
- }
61
44
  /** Ensure a provider API key exists in keychain, prompting or reading stdin if missing. */
62
45
  async function ensureProviderToken(provider, signupUrl, fromStdin) {
63
46
  const item = keychainItemName(provider);
@@ -1,4 +1,5 @@
1
1
  import chalk from 'chalk';
2
+ import { visibleWidth, padVisible } from '../lib/format.js';
2
3
  import ora from 'ora';
3
4
  import * as fs from 'fs';
4
5
  import * as path from 'path';
@@ -26,7 +27,7 @@ function resolveRepoPath(target) {
26
27
  return path.join(HOME, `.agents-${trimmed}`);
27
28
  }
28
29
  import { applyExtraAliasToVersions, ensureAgentsDir, getExtraRepoDir, getSystemAgentsDir, getUserAgentsDir, readMeta, resolveExtraRepoDir, updateMeta, } from '../lib/state.js';
29
- import { parseSource, pullRepo, commitAndPush, isGitRepo, isSystemRepoOrigin } from '../lib/git.js';
30
+ import { parseSource, pullRepo, commitAndPush, isGitRepo, isSystemRepoOrigin, adoptRepo } from '../lib/git.js';
30
31
  import { DEFAULT_SYSTEM_REPO } from '../lib/types.js';
31
32
  import { ALL_AGENT_IDS, isAgentName, resolveAgentName } from '../lib/agents.js';
32
33
  import { refresh } from '../lib/refresh.js';
@@ -218,14 +219,6 @@ async function diffResourceEntries(git, range) {
218
219
  }
219
220
  return out;
220
221
  }
221
- /** Visible character width of a string with embedded ANSI color codes. */
222
- function visibleWidth(s) {
223
- return s.replace(/\[[0-9;]*m/g, '').length;
224
- }
225
- /** Pad string with trailing spaces to a target visible column width. */
226
- function padVisible(s, width) {
227
- return s + ' '.repeat(Math.max(0, width - visibleWidth(s)));
228
- }
229
222
  /**
230
223
  * Read one repo's status into structured data: branch, resource-level sync (what
231
224
  * a pull/push would move), resource-level local edits, and remote URL + commit.
@@ -752,9 +745,9 @@ export function registerRepoCommands(program) {
752
745
  await toggle(alias, false);
753
746
  });
754
747
  repoCmd
755
- .command('pull [alias]')
756
- .description('Pull updates. Aliases: "system" (~/.agents/.system/), "user" (~/.agents/), or any registered extra. No arg pulls all.')
757
- .action(async (alias) => {
748
+ .command('pull [alias] [url]')
749
+ .description('Pull updates. Aliases: "system" (~/.agents/.system/), "user" (~/.agents/), or any registered extra. No arg pulls all. Pass a git URL to git-back a not-yet-cloned user repo: "agents repo pull user <url>".')
750
+ .action(async (alias, url) => {
758
751
  const targets = collectRepoTargets(alias);
759
752
  if (!targets) {
760
753
  process.exitCode = 1;
@@ -766,8 +759,34 @@ export function registerRepoCommands(program) {
766
759
  }
767
760
  for (const t of targets) {
768
761
  if (!fs.existsSync(t.dir) || !isGitRepo(t.dir)) {
769
- console.log(chalk.yellow(` ${t.alias}: not a git repo, skipping`));
770
- continue;
762
+ // A plain (never-cloned) user repo — setup makes ~/.agents a bare dir and
763
+ // never git-clones it (state.ts ensureAgentsDir), so a fresh/Windows box
764
+ // silently falls out of config sync. If a URL was passed, git-back it in
765
+ // place (keeping local files, backing up any that differ); else guide the
766
+ // user. Only the USER repo gets this — system is cloned by setup, extras
767
+ // by `repo add`.
768
+ if (t.alias === 'user' && url) {
769
+ const spinner = ora(`Git-backing ${t.dir} from ${url}...`).start();
770
+ const res = await adoptRepo(url, t.dir);
771
+ if (!res.success) {
772
+ spinner.fail(`user: could not git-back — ${res.error}`);
773
+ process.exitCode = 1;
774
+ continue;
775
+ }
776
+ spinner.succeed(`user: git-backed -> ${res.commit}`);
777
+ if (res.backedUp.length > 0) {
778
+ console.log(chalk.yellow(` backed up ${res.backedUp.length} locally-modified file(s) to ${res.backupDir}`));
779
+ console.log(chalk.gray(` (${res.backedUp.slice(0, 5).join(', ')}${res.backedUp.length > 5 ? ', …' : ''})`));
780
+ }
781
+ // Now a git repo — fall through to the normal pull below.
782
+ }
783
+ else {
784
+ console.log(chalk.yellow(` ${t.alias}: not a git repo, skipping`));
785
+ if (t.alias === 'user') {
786
+ console.log(chalk.gray(' git-back it from your config remote: agents repo pull user <git-url>'));
787
+ }
788
+ continue;
789
+ }
771
790
  }
772
791
  if (t.alias === 'system') {
773
792
  // Skip system repo unless explicitly requested
@@ -58,3 +58,4 @@ export declare function resourceLayout(cols: number, o: {
58
58
  }): ResourceLayout;
59
59
  /** Build the sync targets section showing version pills grouped by agent. */
60
60
  export declare function buildTargetsSection(targets: SyncTarget[]): string;
61
+ /** Pad a string to a fixed width, accounting for ANSI escape codes in length calculation. */
@@ -7,6 +7,7 @@
7
7
  * filtering, and paging.
8
8
  */
9
9
  import chalk from 'chalk';
10
+ import { truncate, padVisible } from '../lib/format.js';
10
11
  import { agentLabel } from '../lib/agents.js';
11
12
  import { itemPicker } from '../lib/picker.js';
12
13
  import { isInteractiveTerminal, isPromptCancelled, printWithPager } from './utils.js';
@@ -65,15 +66,15 @@ function filterRows(rows, query) {
65
66
  }
66
67
  /** Row label rendered inside the picker list. */
67
68
  function formatPickerRow(row, opts) {
68
- const name = chalk.cyan(padRight(row.name, 22));
69
+ const name = chalk.cyan(padVisible(row.name, 22));
69
70
  const extra = opts.extraLabel
70
- ? chalk.gray(padRight(row.extra ?? '-', 10))
71
+ ? chalk.gray(padVisible(row.extra ?? '-', 10))
71
72
  : '';
72
73
  const extra2 = opts.extra2Label
73
- ? chalk.gray(padRight(row.extra2 ?? '-', 16))
74
+ ? chalk.gray(padVisible(row.extra2 ?? '-', 16))
74
75
  : '';
75
76
  const descRaw = row.description ? truncate(row.description, 40) : '';
76
- const desc = padRight(chalk.gray(descRaw), 42);
77
+ const desc = padVisible(chalk.gray(descRaw), 42);
77
78
  const sync = formatSyncSummary(row.targets, opts);
78
79
  return `${name} ${extra}${extra2}${desc} ${sync}`;
79
80
  }
@@ -252,16 +253,3 @@ function formatVersionPill(t) {
252
253
  }
253
254
  }
254
255
  /** Pad a string to a fixed width, accounting for ANSI escape codes in length calculation. */
255
- function padRight(s, width) {
256
- // Strip ANSI for length calc
257
- const raw = s.replace(/\x1b\[[0-9;]*m/g, '');
258
- if (raw.length >= width)
259
- return s;
260
- return s + ' '.repeat(width - raw.length);
261
- }
262
- /** Truncate a string to a maximum length, appending an ellipsis if needed. */
263
- function truncate(s, max) {
264
- if (s.length <= max)
265
- return s;
266
- return s.slice(0, max - 1) + '…';
267
- }
@@ -9,6 +9,7 @@ import { type Command } from 'commander';
9
9
  import { SSH_TARGET_RE, assertValidSshTarget } from '../lib/ssh-exec.js';
10
10
  import { quoteWin32ExecArg } from '../lib/platform/index.js';
11
11
  import { type SecretsBundle, type SecretsPolicy } from '../lib/secrets/bundles.js';
12
+ /** Read all available data from stdin synchronously, trimmed. */
12
13
  /**
13
14
  * Read the raw `.env` text for `import --from <path|->`. A `-` reads the .env
14
15
  * from stdin (the SSH push path: `export --host` pipes the resolved dotenv over
@@ -16,6 +17,28 @@ import { type SecretsBundle, type SecretsPolicy } from '../lib/secrets/bundles.j
16
17
  * a filesystem path.
17
18
  */
18
19
  export declare function readImportDotenv(from: string): string;
20
+ /** Where `secrets import` pulls keys from, parsed off the unified `--from`. */
21
+ export type ImportSource = {
22
+ kind: 'dotenv';
23
+ path: string;
24
+ } | {
25
+ kind: '1password';
26
+ vault?: string;
27
+ } | {
28
+ kind: 'icloud';
29
+ };
30
+ /**
31
+ * Parse the unified `--from <source>` value: a .env path (`-` reads stdin),
32
+ * `1password:<vault>` (bare `1password` prompts for the vault), or `icloud`
33
+ * (legacy iCloud Keychain bundles). The deprecated `--from-1password --vault`
34
+ * pair maps onto the 1password source. A file literally named `icloud` or
35
+ * `1password` can still be imported via an explicit path (`./icloud`).
36
+ */
37
+ export declare function parseImportSource(opts: {
38
+ from?: string;
39
+ from1password?: boolean;
40
+ vault?: string;
41
+ }): ImportSource;
19
42
  /**
20
43
  * Build the remote `agents secrets unlock` argv for `unlock --host`. `--all`
21
44
  * forwards verbatim; otherwise the explicit bundle names. A `--ttl` is passed