@phnx-labs/agents-cli 1.20.91 → 1.20.93

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 (105) hide show
  1. package/CHANGELOG.md +276 -0
  2. package/README.md +1 -1
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +72 -6
  5. package/dist/commands/activity.js +198 -49
  6. package/dist/commands/beta.js +1 -0
  7. package/dist/commands/doctor.js +4 -2
  8. package/dist/commands/events.js +91 -1
  9. package/dist/commands/exec.d.ts +14 -0
  10. package/dist/commands/exec.js +144 -14
  11. package/dist/commands/projects.d.ts +22 -0
  12. package/dist/commands/projects.js +539 -0
  13. package/dist/commands/secrets.d.ts +17 -0
  14. package/dist/commands/secrets.js +198 -7
  15. package/dist/commands/send.d.ts +14 -12
  16. package/dist/commands/send.js +105 -35
  17. package/dist/commands/sessions-picker.d.ts +15 -0
  18. package/dist/commands/sessions-picker.js +37 -12
  19. package/dist/commands/sessions-resume.d.ts +2 -0
  20. package/dist/commands/sessions-resume.js +9 -1
  21. package/dist/commands/sessions.d.ts +10 -5
  22. package/dist/commands/sessions.js +65 -27
  23. package/dist/commands/sync.js +9 -3
  24. package/dist/commands/view.js +4 -0
  25. package/dist/index.js +18 -1
  26. package/dist/lib/activity.d.ts +77 -12
  27. package/dist/lib/activity.js +424 -74
  28. package/dist/lib/beta.d.ts +1 -1
  29. package/dist/lib/beta.js +1 -1
  30. package/dist/lib/channels/send.d.ts +83 -0
  31. package/dist/lib/channels/send.js +112 -0
  32. package/dist/lib/devices/registry.d.ts +14 -0
  33. package/dist/lib/devices/registry.js +37 -0
  34. package/dist/lib/events-ingest.d.ts +46 -0
  35. package/dist/lib/events-ingest.js +182 -0
  36. package/dist/lib/events.d.ts +15 -3
  37. package/dist/lib/events.js +55 -3
  38. package/dist/lib/feed-post.js +8 -2
  39. package/dist/lib/hosts/remote-cmd.js +4 -0
  40. package/dist/lib/linear-project-counts.d.ts +62 -0
  41. package/dist/lib/linear-project-counts.js +122 -0
  42. package/dist/lib/linear-projects.d.ts +50 -0
  43. package/dist/lib/linear-projects.js +114 -0
  44. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  45. package/dist/lib/menubar/install-menubar.d.ts +14 -4
  46. package/dist/lib/menubar/install-menubar.js +20 -6
  47. package/dist/lib/menubar/notify-desktop.d.ts +17 -2
  48. package/dist/lib/menubar/notify-desktop.js +8 -2
  49. package/dist/lib/project-key.d.ts +44 -0
  50. package/dist/lib/project-key.js +79 -0
  51. package/dist/lib/project-probe.d.ts +75 -0
  52. package/dist/lib/project-probe.js +160 -0
  53. package/dist/lib/project-resources.d.ts +8 -0
  54. package/dist/lib/project-resources.js +31 -3
  55. package/dist/lib/project-root.js +16 -0
  56. package/dist/lib/project-status.d.ts +100 -0
  57. package/dist/lib/project-status.js +182 -0
  58. package/dist/lib/projects.d.ts +144 -0
  59. package/dist/lib/projects.js +313 -0
  60. package/dist/lib/remote-agents-json.d.ts +9 -0
  61. package/dist/lib/remote-agents-json.js +11 -5
  62. package/dist/lib/routine-notify.d.ts +11 -0
  63. package/dist/lib/routine-notify.js +22 -0
  64. package/dist/lib/run-notify.js +3 -0
  65. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  66. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  67. package/dist/lib/secrets/audit.d.ts +1 -1
  68. package/dist/lib/secrets/audit.js +53 -10
  69. package/dist/lib/secrets/list-filter.d.ts +20 -5
  70. package/dist/lib/secrets/list-filter.js +22 -6
  71. package/dist/lib/secrets/usage-db.d.ts +106 -0
  72. package/dist/lib/secrets/usage-db.js +236 -0
  73. package/dist/lib/session/bash-command.d.ts +53 -0
  74. package/dist/lib/session/bash-command.js +364 -0
  75. package/dist/lib/session/digest.d.ts +6 -0
  76. package/dist/lib/session/digest.js +19 -0
  77. package/dist/lib/session/relative-time.d.ts +23 -0
  78. package/dist/lib/session/relative-time.js +60 -8
  79. package/dist/lib/session/remote-active.d.ts +5 -1
  80. package/dist/lib/session/remote-active.js +4 -1
  81. package/dist/lib/session/remote-list.js +5 -2
  82. package/dist/lib/session/render.d.ts +2 -9
  83. package/dist/lib/session/render.js +25 -56
  84. package/dist/lib/sqlite.js +28 -1
  85. package/dist/lib/ssh-exec.d.ts +6 -0
  86. package/dist/lib/ssh-exec.js +10 -1
  87. package/dist/lib/startup/command-registry.d.ts +1 -0
  88. package/dist/lib/startup/command-registry.js +2 -0
  89. package/dist/lib/state.d.ts +14 -0
  90. package/dist/lib/state.js +19 -0
  91. package/dist/lib/terminal/backends/index.d.ts +10 -2
  92. package/dist/lib/terminal/backends/index.js +14 -2
  93. package/dist/lib/terminal/backends/terminal-app.d.ts +13 -0
  94. package/dist/lib/terminal/backends/terminal-app.js +73 -0
  95. package/dist/lib/terminal/index.d.ts +2 -1
  96. package/dist/lib/terminal/index.js +2 -1
  97. package/dist/lib/terminal/preferred.d.ts +89 -0
  98. package/dist/lib/terminal/preferred.js +87 -0
  99. package/dist/lib/terminal/run-surface.d.ts +82 -0
  100. package/dist/lib/terminal/run-surface.js +146 -0
  101. package/dist/lib/terminal/types.d.ts +1 -1
  102. package/dist/lib/types.d.ts +6 -5
  103. package/dist/lib/versions.d.ts +6 -0
  104. package/dist/lib/versions.js +6 -4
  105. package/package.json +2 -1
@@ -34,6 +34,17 @@ export function parseRunAccountPickerRequest(agentSpec) {
34
34
  };
35
35
  }
36
36
  /** Return every option whose routing semantics conflict with a local account choice. */
37
+ /**
38
+ * The `--host` alias family — the flags that mean "dispatch this run to another
39
+ * machine over SSH". `--host` is canonical; `--device`/`--on`/`--computer` are
40
+ * aliases. Returns the values actually given (so callers can both test presence
41
+ * and read the target). Kept in ONE place because a guard that listed only a
42
+ * subset silently let `--terminal --device` open a local tab and drop the remote
43
+ * target — the drift this predicate exists to prevent.
44
+ */
45
+ export function hostTargetGiven(options) {
46
+ return [options.host, options.device, options.on, options.computer].filter((v) => !!v);
47
+ }
37
48
  export function runAccountPickerConflicts(options) {
38
49
  const conflicts = [];
39
50
  if (options.resume !== undefined)
@@ -46,7 +57,7 @@ export function runAccountPickerConflicts(options) {
46
57
  conflicts.push('--lease');
47
58
  if (options.box)
48
59
  conflicts.push('--box');
49
- if (options.host || options.device || options.on || options.computer)
60
+ if (hostTargetGiven(options).length)
50
61
  conflicts.push('--host/--device');
51
62
  return conflicts;
52
63
  }
@@ -349,6 +360,121 @@ export async function runWorkflowForEach(spec, opts) {
349
360
  process.stderr.write(chalk.yellow(`[for_each] stopped by ${result.stoppedBy} after ${result.waves} waves. Team: ${team}\n`));
350
361
  return 1;
351
362
  }
363
+ /**
364
+ * The run's working directory from `--cwd` / `--project`. `--project <slug>` owns
365
+ * the directory and is mutually exclusive with `--cwd`/`--remote-cwd`; both the
366
+ * main dispatch and the `--terminal` handoff need the same answer, so the rule
367
+ * and its error live here once instead of in two places that can drift.
368
+ */
369
+ async function resolveRunCwd(options, opts) {
370
+ if (!options.project)
371
+ return options.cwd;
372
+ if (options.cwd || options.remoteCwd) {
373
+ console.error(chalk.red('Pass --project alone — not with --cwd or --remote-cwd.'));
374
+ process.exit(1);
375
+ }
376
+ const { resolveProjectRef } = await import('../lib/project-root.js');
377
+ try {
378
+ return await resolveProjectRef(options.project, { forRemote: opts.forRemote });
379
+ }
380
+ catch (err) {
381
+ console.error(chalk.red(err.message));
382
+ process.exit(1);
383
+ }
384
+ }
385
+ /**
386
+ * `--terminal`: hand this run to a real terminal tab and exit.
387
+ *
388
+ * The terminal is detected from the user's live sessions, so a run started from
389
+ * a surface that cannot host a TUI (the menu bar's "New Session", a script) lands
390
+ * in the terminal they actually work in instead of a hardcoded Terminal.app.
391
+ * Exits non-zero when no terminal could be opened — the caller must not believe
392
+ * a session started when none did.
393
+ */
394
+ async function handleTerminalHandoff(agentSpec, options, prompt) {
395
+ const { parseTerminalFlag, openRunInTerminal, toHostSamples } = await import('../lib/terminal/run-surface.js');
396
+ const { currentContext } = await import('../lib/terminal/index.js');
397
+ const parsed = parseTerminalFlag(options.terminal);
398
+ if (parsed.error) {
399
+ console.error(chalk.red(parsed.error));
400
+ process.exit(1);
401
+ }
402
+ // Reject an unrunnable target HERE, where the person can read it. The tab would
403
+ // otherwise open, print the same error, and close — the failure lands in a
404
+ // window that is gone before it can be read, which reads as "nothing happened".
405
+ //
406
+ // `agents run <thing>` takes an agent id, a PROFILE, or a WORKFLOW (see the
407
+ // isValidAgent / profileExists / resolveWorkflowRef chain below), so this must
408
+ // accept all three. Gating on the agent table alone rejected every profile —
409
+ // the whole Kimi/DeepSeek/Qwen/GLM path — for `--terminal` runs only.
410
+ const rawTarget = parseRunAccountPickerRequest(agentSpec).normalizedAgentSpec.split('@')[0];
411
+ const knownAgent = resolveAgentName(rawTarget);
412
+ if (knownAgent && isAgentHardDeprecated(knownAgent)) {
413
+ console.error(chalk.red(hardDeprecationError(knownAgent)));
414
+ process.exit(1);
415
+ }
416
+ if (!knownAgent) {
417
+ const [{ profileExists }, { resolveWorkflowRef }] = await Promise.all([
418
+ import('../lib/profiles.js'),
419
+ import('../lib/workflows.js'),
420
+ ]);
421
+ const probeCwd = options.cwd ?? process.cwd();
422
+ if (!profileExists(rawTarget) && !resolveWorkflowRef(rawTarget, probeCwd)) {
423
+ console.error(chalk.red(`Unknown agent, profile, or workflow: ${rawTarget}. See \`agents list\` for the installed harnesses.`));
424
+ process.exit(1);
425
+ }
426
+ }
427
+ // --host and its aliases (--device/--on/--computer) all mean "dispatch this
428
+ // run to another machine over SSH", which is incompatible with opening a
429
+ // terminal tab on THIS machine — so reject the whole alias family, not just
430
+ // the canonical flag. The rule and its wording live once, in the --host
431
+ // forwarding table, so the classification a reviewer reads and the error a
432
+ // user sees can't drift.
433
+ if (hostTargetGiven(options).length) {
434
+ const { RUN_OPTION_REJECT_MESSAGES } = await import('../lib/hosts/remote-cmd.js');
435
+ console.error(chalk.red(RUN_OPTION_REJECT_MESSAGES.terminal));
436
+ process.exit(1);
437
+ }
438
+ // Machine-readable output would land in the tab, where the caller that asked
439
+ // for it can never read it. Same class of failure as --host: refuse, don't
440
+ // hand back a stream that goes nowhere.
441
+ const streamFlag = options.json ? '--json' : options.emitSessionId ? '--emit-session-id' : undefined;
442
+ if (streamFlag) {
443
+ console.error(chalk.red(`${streamFlag} streams to stdout, but --terminal moves the run into a tab where you cannot read it. Drop one.`));
444
+ process.exit(1);
445
+ }
446
+ // `--project` owns the working directory, but the main action resolves it far
447
+ // below this handoff — so without this the tab would open in THIS process's
448
+ // cwd (launchd's `/` for a menu-bar click) while the run inside it moved to
449
+ // the project. `forRemote: false` because --terminal is always local (--host
450
+ // is rejected above).
451
+ const cwd = await resolveRunCwd(options, { forRemote: false });
452
+ const { getActiveSessions } = await import('../lib/session/active.js');
453
+ let sessions = [];
454
+ try {
455
+ sessions = await toHostSamples(await getActiveSessions());
456
+ }
457
+ catch {
458
+ // Detection is best-effort — an unreadable session index must not block the
459
+ // launch; resolution falls through to the available-backend floor.
460
+ }
461
+ const result = await openRunInTerminal({
462
+ argv: process.argv.slice(2),
463
+ forced: parsed.backend,
464
+ consumedValue: typeof options.terminal === 'string' ? options.terminal : undefined,
465
+ cwd: cwd ?? process.cwd(),
466
+ sessions,
467
+ ctx: currentContext(),
468
+ });
469
+ if (!result.ok) {
470
+ console.error(chalk.red(`Could not open a terminal: ${result.error ?? 'unknown error'}`));
471
+ process.exit(1);
472
+ }
473
+ if (!options.quiet) {
474
+ const what = prompt === undefined ? 'session' : 'run';
475
+ console.log(chalk.gray(`Opened the ${what} in ${result.description}.`));
476
+ }
477
+ }
352
478
  /** Register the `agents run <agent> [prompt]` command. */
353
479
  export function registerRunCommand(program) {
354
480
  const runCmd = program
@@ -374,6 +500,7 @@ export function registerRunCommand(program) {
374
500
  .option('--session-id <id>', 'Force a NEW conversation to use this exact session UUID (Claude only). This CREATES a session — to resume an existing one, use --resume.')
375
501
  .option('--name <slug>', 'Name the run — seeds the session label so it shows up as `<name>` in `agents sessions` and resolves by it (and `agents hosts logs <name>` for --host runs) instead of an opaque id. An agent-generated title later refines the label; your name shows until then. Optional.')
376
502
  .option('--notify', 'Post a desktop notification when a headless run finishes. Fired by this process on exit, so it survives whatever launched the run (the menu bar dispatching it, a terminal you closed).')
503
+ .option('--terminal [backend]', "Open this run in a real terminal tab instead of here. Without a value the terminal is detected from your live sessions (`agents sessions --active` host), so it lands where you already work — Ghostty for a Ghostty user, iTerm for an iTerm user. Name one to force it: iterm | ghostty | terminal | tmux | vscodium-agent. This is how the menu bar's New Session opens.")
377
504
  .option('--verbose', 'Show detailed execution logs')
378
505
  .option('--raw', 'Interactive runs on macOS/Linux launch inside a shared tmux session (for %pane addressing + re-attach). Pass --raw to spawn the agent directly instead. Also disabled by AGENTS_NO_TMUX=1.')
379
506
  .option('--no-tmux', 'Spawn the agent directly instead of wrapping it in the shared tmux session. Same effect as --raw / AGENTS_NO_TMUX=1. Use this to see the agent\'s full startup output when a launch is failing.')
@@ -433,6 +560,11 @@ export function registerRunCommand(program) {
433
560
  # Pick a signed-in account/version for only this run
434
561
  agents run claude@
435
562
 
563
+ # Open the session in a terminal tab — detected from where your sessions
564
+ # already run (Ghostty / iTerm / Terminal.app); force one with a value
565
+ agents run claude --terminal
566
+ agents run claude --terminal ghostty
567
+
436
568
  # Pipe JSON events to a parser (--quiet drops the preamble)
437
569
  agents run claude "..." --json --quiet | jq
438
570
 
@@ -499,6 +631,15 @@ export function registerRunCommand(program) {
499
631
  // a native flag, not a prompt. Run interactively.
500
632
  prompt = undefined;
501
633
  }
634
+ // --terminal: this process can't host the TUI (a menu-bar click, a script),
635
+ // so hand the run to a real terminal and exit. Resolved from the user's own
636
+ // live sessions, so it opens where they already work. Done before every
637
+ // other dispatch path because the tab re-runs this same argv without the
638
+ // flag — arming --notify or picking a version here would happen twice.
639
+ if (options.terminal) {
640
+ await handleTerminalHandoff(agentSpec, options, prompt);
641
+ return;
642
+ }
502
643
  // --notify: post a desktop notification when this run finishes. Armed on
503
644
  // process exit so it covers EVERY dispatch path below (local, --host,
504
645
  // --lease, the error path) instead of one branch. Only for headless runs
@@ -906,24 +1047,13 @@ export function registerRunCommand(program) {
906
1047
  }
907
1048
  // --host/--on/--computer: offload this run onto a registered agent host
908
1049
  // over SSH instead of running locally. The three flags are aliases.
909
- const hostGiven = [options.host, options.device, options.on, options.computer].filter((v) => !!v);
1050
+ const hostGiven = hostTargetGiven(options);
910
1051
  // --project <slug>[@worktree]: resolve the projects-root shorthand into a
911
1052
  // cwd. On a host run it resolves home-relative (`~/…`, so the host expands
912
1053
  // it); locally it becomes an absolute path. It owns the working directory,
913
1054
  // so it is mutually exclusive with both --cwd and --remote-cwd.
914
1055
  if (options.project) {
915
- if (options.cwd || options.remoteCwd) {
916
- console.error(chalk.red('Pass --project alone — not with --cwd or --remote-cwd.'));
917
- process.exit(1);
918
- }
919
- const { resolveProjectRef } = await import('../lib/project-root.js');
920
- try {
921
- options.cwd = await resolveProjectRef(options.project, { forRemote: hostGiven.length > 0 });
922
- }
923
- catch (err) {
924
- console.error(chalk.red(err.message));
925
- process.exit(1);
926
- }
1056
+ options.cwd = await resolveRunCwd(options, { forRemote: hostGiven.length > 0 });
927
1057
  }
928
1058
  if (hostGiven.length > 0) {
929
1059
  if (new Set(hostGiven).size > 1) {
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `agents projects` — named, multi-repo projects and the progress
3
+ * rollup. Definitions live in `~/.agents/projects/<name>.yaml` (see
4
+ * `lib/projects.ts`); this registers the command tree over them. Beta-gated on
5
+ * `isBetaEnabled('projects')`, mirroring `agents factory`.
6
+ *
7
+ * The headline is `status`: instead of the vague per-agent activity line, it
8
+ * rolls every session up by project (matched on cwd) into one card — agents by
9
+ * lifecycle state, plan completion, open PRs, and tickets in flight.
10
+ */
11
+ import type { Command } from 'commander';
12
+ /** Recursion guard: a peer answering a probe fan-out never re-fans-out itself. */
13
+ export declare const PROJECTS_NO_FANOUT_ENV = "AGENTS_PROJECTS_LOCAL";
14
+ /**
15
+ * One compact trailing note for peers that didn't answer the `--fleet`
16
+ * fan-out — unreachable, running an agents-cli too old to carry `projects
17
+ * probe`, or too slow to finish inside the 12s SSH budget. Mirrors
18
+ * `formatUnreachableNote` (activity) with the probe-specific reasons; empty
19
+ * string when everything answered.
20
+ */
21
+ export declare function formatFleetSkippedNote(skipped: string[]): string;
22
+ export declare function registerProjectsCommands(program: Command): void;