@phnx-labs/agents-cli 1.22.74 → 1.22.76

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 (148) hide show
  1. package/CHANGELOG.md +152 -0
  2. package/README.md +21 -9
  3. package/dist/bootstrap.js +7 -7
  4. package/dist/cli/command-registry.js +5 -0
  5. package/dist/commands/artifacts-setup.js +1 -1
  6. package/dist/commands/artifacts.js +1 -1
  7. package/dist/commands/auth.js +7 -1
  8. package/dist/commands/browser.js +104 -10
  9. package/dist/commands/commands.js +7 -6
  10. package/dist/commands/computer.d.ts +1 -0
  11. package/dist/commands/computer.js +26 -7
  12. package/dist/commands/config.js +27 -4
  13. package/dist/commands/cost.js +6 -4
  14. package/dist/commands/doctor.d.ts +6 -5
  15. package/dist/commands/doctor.js +32 -274
  16. package/dist/commands/exec.d.ts +2 -0
  17. package/dist/commands/exec.js +9 -2
  18. package/dist/commands/harness.d.ts +1 -0
  19. package/dist/commands/harness.js +11 -3
  20. package/dist/commands/hooks.js +7 -6
  21. package/dist/commands/mcp.js +7 -6
  22. package/dist/commands/memory.js +7 -7
  23. package/dist/commands/monitors.js +3 -2
  24. package/dist/commands/open.d.ts +25 -12
  25. package/dist/commands/open.js +24 -10
  26. package/dist/commands/permissions.js +7 -6
  27. package/dist/commands/plugins.js +21 -17
  28. package/dist/commands/route.js +33 -16
  29. package/dist/commands/rules.js +7 -12
  30. package/dist/commands/sessions-share.js +1 -1
  31. package/dist/commands/setup-watchdog.js +2 -2
  32. package/dist/commands/setup.js +22 -1
  33. package/dist/commands/share.js +26 -10
  34. package/dist/commands/skills.js +7 -6
  35. package/dist/commands/subagents.js +7 -6
  36. package/dist/commands/sync.js +81 -10
  37. package/dist/commands/view.js +4 -1
  38. package/dist/commands/watchdog.d.ts +1 -1
  39. package/dist/commands/watchdog.js +10 -10
  40. package/dist/commands/webhook.d.ts +4 -0
  41. package/dist/commands/webhook.js +22 -4
  42. package/dist/commands/workflows.js +7 -6
  43. package/dist/lib/account-registry.js +27 -6
  44. package/dist/lib/accounting/rotate.d.ts +3 -1
  45. package/dist/lib/accounting/rotate.js +8 -4
  46. package/dist/lib/auth-health.d.ts +2 -0
  47. package/dist/lib/auth-health.js +2 -0
  48. package/dist/lib/browser/chrome.d.ts +21 -0
  49. package/dist/lib/browser/chrome.js +60 -3
  50. package/dist/lib/browser/drivers/local.d.ts +21 -0
  51. package/dist/lib/browser/drivers/local.js +102 -9
  52. package/dist/lib/browser/profiles.d.ts +29 -1
  53. package/dist/lib/browser/profiles.js +50 -1
  54. package/dist/lib/browser/types.d.ts +18 -0
  55. package/dist/lib/computer/computer-rpc.d.ts +6 -1
  56. package/dist/lib/computer/computer-rpc.js +23 -3
  57. package/dist/lib/computer/des.d.ts +1 -0
  58. package/dist/lib/computer/des.js +114 -0
  59. package/dist/lib/computer/rfb-client.d.ts +53 -0
  60. package/dist/lib/computer/rfb-client.js +562 -0
  61. package/dist/lib/config-keys.d.ts +7 -2
  62. package/dist/lib/config-keys.js +17 -2
  63. package/dist/lib/daemon/auth-sync-service.js +3 -0
  64. package/dist/lib/daemon/daemon.js +17 -10
  65. package/dist/lib/daemon/session-summarizer-service.d.ts +24 -0
  66. package/dist/lib/daemon/session-summarizer-service.js +39 -0
  67. package/dist/lib/daemon/usage-sync-service.js +3 -0
  68. package/dist/lib/daemon-services.d.ts +1 -1
  69. package/dist/lib/daemon-services.js +5 -0
  70. package/dist/lib/daemon-ticks.d.ts +2 -2
  71. package/dist/lib/daemon-ticks.js +2 -1
  72. package/dist/lib/daemon-webhooks.js +15 -2
  73. package/dist/lib/deeplink/register.js +10 -9
  74. package/dist/lib/deeplink/url.d.ts +4 -4
  75. package/dist/lib/deeplink/url.js +4 -4
  76. package/dist/lib/device-config.js +25 -0
  77. package/dist/lib/devices/doctor-findings.d.ts +4 -4
  78. package/dist/lib/devices/doctor-findings.js +14 -8
  79. package/dist/lib/devices/registry.js +2 -0
  80. package/dist/lib/devices/stats-cache.d.ts +4 -0
  81. package/dist/lib/devices/stats-cache.js +19 -0
  82. package/dist/lib/drift-sync.d.ts +3 -1
  83. package/dist/lib/drift-sync.js +16 -5
  84. package/dist/lib/exec.d.ts +2 -0
  85. package/dist/lib/exec.js +16 -1
  86. package/dist/lib/fleet-shared-repo-sync.d.ts +12 -0
  87. package/dist/lib/fleet-shared-repo-sync.js +101 -4
  88. package/dist/lib/fleet-shared-state.d.ts +8 -0
  89. package/dist/lib/heal.d.ts +4 -3
  90. package/dist/lib/heal.js +5 -4
  91. package/dist/lib/hosts/ready.d.ts +1 -1
  92. package/dist/lib/hosts/ready.js +16 -4
  93. package/dist/lib/hosts/reconnect.js +4 -2
  94. package/dist/lib/identity/client.d.ts +6 -0
  95. package/dist/lib/identity/index.d.ts +16 -0
  96. package/dist/lib/identity/index.js +25 -1
  97. package/dist/lib/profiles.d.ts +2 -0
  98. package/dist/lib/profiles.js +28 -9
  99. package/dist/lib/reconcile-and-repair.d.ts +109 -0
  100. package/dist/lib/reconcile-and-repair.js +267 -0
  101. package/dist/lib/routers.d.ts +12 -1
  102. package/dist/lib/routers.js +30 -1
  103. package/dist/lib/scheduling/routines.js +8 -2
  104. package/dist/lib/session/active.d.ts +13 -0
  105. package/dist/lib/session/db.d.ts +47 -7
  106. package/dist/lib/session/db.js +114 -12
  107. package/dist/lib/session/mirror.js +58 -0
  108. package/dist/lib/session/remote/remote-list.d.ts +2 -0
  109. package/dist/lib/session/remote/remote-list.js +4 -0
  110. package/dist/lib/session/remote/watch.js +22 -2
  111. package/dist/lib/session/session-cache.d.ts +19 -0
  112. package/dist/lib/session/session-cache.js +46 -0
  113. package/dist/lib/session/types.d.ts +34 -0
  114. package/dist/lib/share/backend.d.ts +6 -4
  115. package/dist/lib/share/backend.js +10 -8
  116. package/dist/lib/share/config.d.ts +4 -3
  117. package/dist/lib/share/config.js +10 -1
  118. package/dist/lib/share/delete.d.ts +1 -1
  119. package/dist/lib/share/delete.js +1 -1
  120. package/dist/lib/share/html.d.ts +1 -1
  121. package/dist/lib/share/html.js +1 -1
  122. package/dist/lib/share/provision.d.ts +1 -1
  123. package/dist/lib/share/provision.js +2 -2
  124. package/dist/lib/share/publish.d.ts +23 -7
  125. package/dist/lib/share/publish.js +58 -12
  126. package/dist/lib/share/worker-template.js +221 -60
  127. package/dist/lib/startup/command-registry.js +2 -2
  128. package/dist/lib/state.d.ts +15 -0
  129. package/dist/lib/state.js +29 -7
  130. package/dist/lib/summarizer/config.d.ts +46 -0
  131. package/dist/lib/summarizer/config.js +83 -0
  132. package/dist/lib/summarizer/pass.d.ts +45 -0
  133. package/dist/lib/summarizer/pass.js +112 -0
  134. package/dist/lib/summarizer/summarize.d.ts +68 -0
  135. package/dist/lib/summarizer/summarize.js +120 -0
  136. package/dist/lib/teams/agents.d.ts +4 -3
  137. package/dist/lib/teams/agents.js +12 -4
  138. package/dist/lib/teams/scheduler.d.ts +4 -2
  139. package/dist/lib/teams/scheduler.js +6 -6
  140. package/dist/lib/tmux/session.d.ts +2 -0
  141. package/dist/lib/tmux/session.js +7 -1
  142. package/dist/lib/types.d.ts +20 -0
  143. package/dist/lib/verbs.d.ts +23 -0
  144. package/dist/lib/verbs.js +24 -0
  145. package/dist/lib/view-types.d.ts +4 -0
  146. package/dist/lib/watchdog/rotate.d.ts +1 -1
  147. package/dist/lib/watchdog/rotate.js +1 -1
  148. package/package.json +1 -1
@@ -5,7 +5,7 @@
5
5
  * or headlessly. Supports profile resolution, version rotation, secrets
6
6
  * injection, and multi-agent fallback chains for rate-limit resilience.
7
7
  */
8
- import { Option } from 'commander';
8
+ import { InvalidArgumentError, Option } from 'commander';
9
9
  import chalk from 'chalk';
10
10
  import { isTierToken } from '../lib/model-tiers.js';
11
11
  import { RUN_AUTO_KEYWORD } from '../lib/types.js';
@@ -28,6 +28,13 @@ import { isSessionTrackedAgent } from '../lib/session/types.js';
28
28
  import { applyActiveRulesPresetAtRun } from '../lib/rules/run-sync.js';
29
29
  import { handleBroadcast } from './run-broadcast.js';
30
30
  import { bootMark } from '../lib/boot-profile.js';
31
+ /** Validate a caller-supplied session id before it reaches tmux, paths, indexes, or remote dispatch. */
32
+ export function parseExplicitSessionId(value) {
33
+ if (!/^[A-Za-z0-9._-]+$/.test(value)) {
34
+ throw new InvalidArgumentError('must contain only ASCII letters, digits, dots, underscores, or hyphens');
35
+ }
36
+ return value;
37
+ }
31
38
  /** Distinguish a terminal account-picker marker from an explicit @version pin. */
32
39
  export function parseRunAccountPickerRequest(agentSpec) {
33
40
  const requested = agentSpec.endsWith('@');
@@ -514,7 +521,7 @@ export function registerRunCommand(program) {
514
521
  .option('--results [run-id]', 'With --broadcast: show one saved matrix run, or list saved runs newest first')
515
522
  .option('--concurrency <n>', 'With --broadcast: maximum cells running at once', '3')
516
523
  .option('--resume [id]', 'Recover a previous conversation on its origin device. The exact healthy origin uses native resume; otherwise a healthy version of the same harness replays via /continue. Pair with a prompt to continue headlessly.')
517
- .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.')
524
+ .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.', parseExplicitSessionId)
518
525
  .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 --device runs) instead of an opaque id. An agent-generated title later refines the label; your name shows until then. Optional.')
519
526
  .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).')
520
527
  .option('--no-trace-sync', 'Skip the run-exit trace auto-sync for this run. Auto-sync fires by default only for local runs and only once you have run `agents traces sync` at least once (also silenced by AGENTS_NO_TRACE_SYNC=1).')
@@ -22,6 +22,7 @@ import { type HarnessDraft } from './harness-wizard.js';
22
22
  export declare function renderHarnessDetail(name: string): void;
23
23
  /** Options accepted by `agents harness fork`. */
24
24
  export interface ForkOptions {
25
+ toHost?: string;
25
26
  model?: string;
26
27
  baseUrl?: string;
27
28
  authProvider?: string;
@@ -62,7 +62,11 @@ export function buildFork(source, name, opts) {
62
62
  if (opts.authProvider || opts.fromSecrets)
63
63
  throw new Error("Harnesses no longer own credentials. Add one with 'agents accounts add <name> --provider <provider> --auth <type>', then pass --account <name>.");
64
64
  if (profileExists(source)) {
65
+ const targetHost = opts.toHost ? (resolveAgentName(opts.toHost) ?? undefined) : undefined;
66
+ if (opts.toHost && !targetHost)
67
+ throw new Error(`Unknown target host '${opts.toHost}'.`);
65
68
  const profile = forkProfile(readProfile(source), name, {
69
+ host: targetHost,
66
70
  model: opts.model,
67
71
  baseUrl: opts.baseUrl,
68
72
  provider: opts.authProvider,
@@ -78,14 +82,17 @@ export function buildFork(source, name, opts) {
78
82
  }
79
83
  return profile;
80
84
  }
81
- const host = resolveAgentName(source);
82
- if (!host) {
85
+ const sourceHost = resolveAgentName(source);
86
+ if (!sourceHost) {
83
87
  throw new Error(`No harness or agent named '${source}'.\n` +
84
88
  `Fork from a custom harness (agents harness list) or a native one: ${ALL_AGENT_IDS.join(', ')}.`);
85
89
  }
86
90
  if (!opts.model) {
87
- throw new Error(`--model <id> is required when forking the native '${host}' harness (there is no model to inherit).`);
91
+ throw new Error(`--model <id> is required when forking the native '${sourceHost}' harness (there is no model to inherit).`);
88
92
  }
93
+ const host = opts.toHost ? resolveAgentName(opts.toHost) : sourceHost;
94
+ if (!host)
95
+ throw new Error(`Unknown target host '${opts.toHost}'.`);
89
96
  const profile = profileFromHostModel(name, host, opts.model, {
90
97
  version: opts.version,
91
98
  baseUrl: opts.baseUrl,
@@ -461,6 +468,7 @@ Examples:
461
468
  .command('fork [source] [name]')
462
469
  .description('Fork a native harness (claude, opencode, ...) or an existing custom one into a new named harness. Omit args in a terminal for the interactive wizard.')
463
470
  .option('--model <id>', 'Model to pin on the fork (required when forking a native harness)')
471
+ .option('--to-host <agent>', 'Translate the fork onto another native harness host (for example claude to codex)')
464
472
  .option('--base-url <url>', 'Custom endpoint base URL (claude/codex hosts)')
465
473
  .option('--account <name>', 'Default durable credential account')
466
474
  .option('--auth-provider <provider>', 'Removed: use agents accounts add, then --account')
@@ -1,3 +1,4 @@
1
+ import { withAliases } from '../lib/verbs.js';
1
2
  import chalk from 'chalk';
2
3
  import ora from 'ora';
3
4
  import * as fs from 'fs';
@@ -40,8 +41,8 @@ When to use:
40
41
  - Notifications: ping Slack when agents complete long tasks
41
42
  - Team workflows: sync hooks via 'agents hooks add gh:team/hooks'
42
43
  `);
43
- hooksCmd
44
- .command('list [agent]')
44
+ withAliases(hooksCmd
45
+ .command('list [agent]'), 'list')
45
46
  .description('Show which hooks are installed and which events they respond to')
46
47
  .option('-a, --agent <agent>', 'Filter to a specific agent (alternative to positional arg)')
47
48
  .option('-s, --scope <scope>', 'user (global), project (repo), or all', 'all')
@@ -387,8 +388,8 @@ Examples:
387
388
  process.exit(1);
388
389
  }
389
390
  });
390
- hooksCmd
391
- .command('remove [name]')
391
+ withAliases(hooksCmd
392
+ .command('remove [name]'), 'remove')
392
393
  .description('Delete a hook from agents (interactive picker if no name given)')
393
394
  .option('-a, --agents <list>', 'Limit removal to specific agents')
394
395
  .addHelpText('after', `
@@ -527,8 +528,8 @@ Examples:
527
528
  console.error(chalk.gray('Use: agents prune cleanup hooks (or `agents prune cleanup` for everything)'));
528
529
  process.exit(1);
529
530
  });
530
- hooksCmd
531
- .command('view [name]')
531
+ withAliases(hooksCmd
532
+ .command('view [name]'), 'view')
532
533
  .description('Read the shell script content for a hook')
533
534
  .addHelpText('after', `
534
535
  Examples:
@@ -1,3 +1,4 @@
1
+ import { withAliases } from '../lib/verbs.js';
1
2
  import chalk from 'chalk';
2
3
  import { truncate } from '../lib/format.js';
3
4
  import ora from 'ora';
@@ -143,8 +144,8 @@ When to use:
143
144
  - Version upgrade: 'agents mcp register' to sync servers to the new version
144
145
  - Team setup: commit mcp config to .agents and run 'agents mcp register'
145
146
  `);
146
- mcpCmd
147
- .command('list [agent]')
147
+ withAliases(mcpCmd
148
+ .command('list [agent]'), 'list')
148
149
  .description('Show which MCP servers are registered and which agent versions they are synced to')
149
150
  .option('-a, --agent <agent>', 'Filter to a specific agent (alternative to positional arg)')
150
151
  .option('--json', 'Emit machine-readable JSON instead of the table/picker')
@@ -337,8 +338,8 @@ Examples:
337
338
  console.log(chalk.green(`Added MCP server '${name}' to manifest`));
338
339
  console.log(chalk.gray('Run: agents mcp register to apply'));
339
340
  });
340
- mcpCmd
341
- .command('remove [name]')
341
+ withAliases(mcpCmd
342
+ .command('remove [name]'), 'remove')
342
343
  .description('Unregister an MCP server from agents (interactive picker if no name given)')
343
344
  .option('-a, --agents <list>', 'Limit removal to specific agents')
344
345
  .addHelpText('after', `
@@ -487,8 +488,8 @@ Examples:
487
488
  console.log(chalk.green(`\nRemoved ${removed} MCP server(s).`));
488
489
  }
489
490
  });
490
- mcpCmd
491
- .command('view [name]')
491
+ withAliases(mcpCmd
492
+ .command('view [name]'), 'view')
492
493
  .description('Show MCP server configuration (command, scope, registered agents)')
493
494
  .addHelpText('after', `
494
495
  Examples:
@@ -4,6 +4,7 @@
4
4
  * Mirrors the skills surface at list / add / remove / view / sync scale.
5
5
  * Canonical storage: ~/.agents/memory/ (project > user > system layering).
6
6
  */
7
+ import { withAliases } from '../lib/verbs.js';
7
8
  import chalk from 'chalk';
8
9
  import * as fs from 'fs';
9
10
  import { listMemoryFacts, addMemoryFact, removeMemoryFact, readMemoryFact, ensureUserMemoryDir, getUserMemoryDir, syncMemoryToVersionHome, } from '../lib/memory.js';
@@ -42,8 +43,8 @@ export function registerMemoryCommands(program) {
42
43
  Format: MEMORY.md index + one <slug>.md file per fact.
43
44
  `,
44
45
  });
45
- memoryCmd
46
- .command('list')
46
+ withAliases(memoryCmd
47
+ .command('list'), 'list')
47
48
  .description('List memory facts from project, user, and system layers')
48
49
  .option('--json', 'Emit machine-readable JSON')
49
50
  .action((options) => {
@@ -86,9 +87,8 @@ export function registerMemoryCommands(program) {
86
87
  console.log(chalk.gray(' Run: agents memory sync (or agents sync) to fan out'));
87
88
  }
88
89
  });
89
- memoryCmd
90
- .command('remove <name>')
91
- .alias('rm')
90
+ withAliases(memoryCmd
91
+ .command('remove <name>'), 'remove')
92
92
  .description('Remove a user-layer memory fact')
93
93
  .action((name) => {
94
94
  if (removeMemoryFact(name)) {
@@ -99,8 +99,8 @@ export function registerMemoryCommands(program) {
99
99
  process.exit(1);
100
100
  }
101
101
  });
102
- memoryCmd
103
- .command('view <name>')
102
+ withAliases(memoryCmd
103
+ .command('view <name>'), 'view')
104
104
  .description('Print a memory fact (winning layer)')
105
105
  .action((name) => {
106
106
  const fact = readMemoryFact(name, process.cwd());
@@ -8,6 +8,7 @@
8
8
  * source instead of a clock.
9
9
  */
10
10
  import chalk from 'chalk';
11
+ import { withAliases } from '../lib/verbs.js';
11
12
  import * as fs from 'fs';
12
13
  import * as path from 'path';
13
14
  import * as yaml from 'yaml';
@@ -1184,8 +1185,8 @@ export function registerMonitorsCommands(program) {
1184
1185
  console.log(chalk.gray(' Re-pin with: agents monitors device ' + name + ' --set <device>'));
1185
1186
  });
1186
1187
  // ─── remove ────────────────────────────────────────────────────────────────────
1187
- monitorsCmd
1188
- .command('remove [name]')
1188
+ withAliases(monitorsCmd
1189
+ .command('remove [name]'), 'remove')
1189
1190
  .description('Delete a monitor. Stops watching; past fire history remains on disk.')
1190
1191
  .action(async (name) => {
1191
1192
  if (!name) {
@@ -1,18 +1,31 @@
1
1
  /**
2
- * `agents open` — resolve an `agents://` deep link, and manage the OS handler
3
- * that routes such links here.
2
+ * `agents _callback` — the machine-only OS callback for `agents://` deep links.
4
3
  *
5
- * agents open agents://session/<id> resume that session in a terminal
6
- * agents open register register the agents:// scheme with the OS
7
- * agents open unregister remove the handler
8
- * agents open status report whether the handler is registered
4
+ * agents _callback agents://session/<id> resume that session in a terminal
9
5
  *
10
- * A rendered artifact (plan/report) embeds `agents://session/<id>` in its
11
- * provenance line; clicking it hands the URL to the OS, which invokes
12
- * `agents open <url>`. This command parses it (lib/deeplink/url.ts) and hands the
13
- * session id to the existing resume dispatcher, which resolves the owning host
14
- * and opens the terminal with the cursor in the input bar. The id is passed as
15
- * argv, never interpolated into a shell.
6
+ * This is NOT a user command: humans resume with `agents sessions resume`, and
7
+ * manage the OS URL-scheme handler with `agents setup url-scheme`. The bare-URL
8
+ * verb exists only because a rendered artifact (plan/report) embeds
9
+ * `agents://session/<id>` in its provenance line; clicking it hands the URL to
10
+ * the OS, which invokes the registered handler — `agents _callback <url>`. This
11
+ * command parses it (lib/deeplink/url.ts) and hands the session id to the
12
+ * existing resume dispatcher, which resolves the owning host and opens the
13
+ * terminal with the cursor in the input bar. The id is passed as argv, never
14
+ * interpolated into a shell.
15
+ *
16
+ * The command is hidden. `open` is kept as a HIDDEN alias so machines whose OS
17
+ * handler was written by an older CLI (which emitted `agents open <url>`) keep
18
+ * resolving until `agents setup url-scheme register` re-writes the handler to the
19
+ * `_callback` verb. Dropping `open` would break every previously-registered link.
16
20
  */
17
21
  import type { Command } from 'commander';
18
22
  export declare function registerOpenCommand(program: Command): void;
23
+ /**
24
+ * Attach `register` / `unregister` / `status` subcommands that manage the
25
+ * `agents://` OS URL-scheme handler onto `parent`. One implementation, mounted
26
+ * both under the hidden `_callback` command (back-compat) and under the visible
27
+ * `agents setup url-scheme` group.
28
+ */
29
+ export declare function addUrlSchemeSubcommands(parent: Command, opts?: {
30
+ hidden?: boolean;
31
+ }): void;
@@ -2,18 +2,32 @@ import chalk from 'chalk';
2
2
  import { parseAgentsUrl } from '../lib/deeplink/url.js';
3
3
  import { registerAgentsUrlScheme, unregisterAgentsUrlScheme, agentsUrlSchemeStatus, } from '../lib/deeplink/register.js';
4
4
  export function registerOpenCommand(program) {
5
- const open = program
6
- .command('open [url]')
7
- .description('Resume a session from an agents:// deep link, or register/unregister/status the OS URL-scheme handler.')
5
+ const callback = program
6
+ .command('_callback [url]', { hidden: true })
7
+ .alias('open')
8
+ .description('OS callback that resumes a session from an agents:// deep link (machine-only; humans use `agents sessions resume`).')
8
9
  .action(async (url) => {
9
10
  if (!url) {
10
- open.help();
11
+ callback.help();
11
12
  return;
12
13
  }
13
14
  await handleUrl(url);
14
15
  });
15
- open
16
- .command('register')
16
+ // Back-compat: `agents open register|unregister|status` still work as HIDDEN
17
+ // subcommands (muscle memory + docs). The canonical, visible home is
18
+ // `agents setup url-scheme <verb>`, which reuses the SAME builder below.
19
+ addUrlSchemeSubcommands(callback, { hidden: true });
20
+ }
21
+ /**
22
+ * Attach `register` / `unregister` / `status` subcommands that manage the
23
+ * `agents://` OS URL-scheme handler onto `parent`. One implementation, mounted
24
+ * both under the hidden `_callback` command (back-compat) and under the visible
25
+ * `agents setup url-scheme` group.
26
+ */
27
+ export function addUrlSchemeSubcommands(parent, opts = {}) {
28
+ const hidden = opts.hidden ?? false;
29
+ parent
30
+ .command('register', { hidden })
17
31
  .description('Register the agents:// URL scheme with the OS so artifact links resume sessions (idempotent).')
18
32
  .action(() => {
19
33
  const status = registerAgentsUrlScheme();
@@ -25,15 +39,15 @@ export function registerOpenCommand(program) {
25
39
  process.exitCode = 1;
26
40
  }
27
41
  });
28
- open
29
- .command('unregister')
42
+ parent
43
+ .command('unregister', { hidden })
30
44
  .description('Remove the agents:// URL scheme handler.')
31
45
  .action(() => {
32
46
  const status = unregisterAgentsUrlScheme();
33
47
  console.log(chalk.gray(status.detail));
34
48
  });
35
- open
36
- .command('status')
49
+ parent
50
+ .command('status', { hidden })
37
51
  .description('Report whether the agents:// URL scheme handler is registered.')
38
52
  .action(() => {
39
53
  const status = agentsUrlSchemeStatus();
@@ -1,3 +1,4 @@
1
+ import { withAliases } from '../lib/verbs.js';
1
2
  import chalk from 'chalk';
2
3
  import ora from 'ora';
3
4
  import { terminalWidth, truncateToWidth, stringWidth } from '../lib/session/width.js';
@@ -44,8 +45,8 @@ When to use:
44
45
  - Team sync: share permission sets via 'agents permissions add gh:team/perms'
45
46
  - Version isolation: apply stricter permissions to experimental versions
46
47
  `);
47
- permissionsCmd
48
- .command('list [agent]')
48
+ withAliases(permissionsCmd
49
+ .command('list [agent]'), 'list')
49
50
  .description('Show permission sets in storage or active permissions for an agent version')
50
51
  .option('-s, --scope <scope>', 'user (global) or project (repo-specific)', 'user')
51
52
  .action(async (agentArg, options) => {
@@ -642,8 +643,8 @@ Examples:
642
643
  console.error(chalk.red(`Error: ${err.message}`));
643
644
  }
644
645
  });
645
- permissionsCmd
646
- .command('remove [name]')
646
+ withAliases(permissionsCmd
647
+ .command('remove [name]'), 'remove')
647
648
  .description('Delete a permission set from ~/.agents/permissions/ (interactive picker if no name given)')
648
649
  .addHelpText('after', `
649
650
  Examples:
@@ -704,8 +705,8 @@ Examples:
704
705
  }
705
706
  }
706
707
  });
707
- permissionsCmd
708
- .command('view [name]')
708
+ withAliases(permissionsCmd
709
+ .command('view [name]'), 'view')
709
710
  .description('Read the allow and deny rules for a stored permission set')
710
711
  .addHelpText('after', `
711
712
  Examples:
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
+ import { withAliases } from '../lib/verbs.js';
10
11
  import chalk from 'chalk';
11
12
  import { homeDir } from '../lib/platform/index.js';
12
13
  import { input } from '@inquirer/prompts';
@@ -86,8 +87,8 @@ When to use:
86
87
  // flag to the parent for `plugins list --json`, dropping it from the subcommand.)
87
88
  pluginsCmd.action(runList);
88
89
  // agents plugins list
89
- pluginsCmd
90
- .command('list')
90
+ withAliases(pluginsCmd
91
+ .command('list'), 'list')
91
92
  .description('Show plugins in a table with sync status across agent versions')
92
93
  .option('--json', 'Emit machine-readable JSON')
93
94
  .action(runList);
@@ -127,10 +128,10 @@ When to use:
127
128
  .description(`Redirects to 'agents repo ${verb}' — marketplaces follow repos`)
128
129
  .action(marketplaceRedirect(verb));
129
130
  }
130
- // agents plugins info [name]
131
- pluginsCmd
132
- .command('info [name]')
133
- .alias('view')
131
+ // agents plugins view [name]
132
+ withAliases(pluginsCmd
133
+ .command('view [name]'), 'view')
134
+ .alias('info')
134
135
  .description('Show plugin metadata, resources, and installation status across agent versions')
135
136
  .addHelpText('after', `
136
137
  Examples:
@@ -150,8 +151,8 @@ Examples:
150
151
  return;
151
152
  }
152
153
  if (!isInteractiveTerminal()) {
153
- requireInteractiveSelection('Picking a plugin for `agents plugins info`', [
154
- 'agents plugins info <name>',
154
+ requireInteractiveSelection('Picking a plugin for `agents plugins view`', [
155
+ 'agents plugins view <name>',
155
156
  'agents plugins # to see installed plugins',
156
157
  ]);
157
158
  }
@@ -375,8 +376,8 @@ Examples:
375
376
  }
376
377
  });
377
378
  // agents plugins remove [name]
378
- pluginsCmd
379
- .command('remove [name]')
379
+ withAliases(pluginsCmd
380
+ .command('remove [name]'), 'remove')
380
381
  .description('Unsync a plugin from all agent versions and optionally delete its source directory')
381
382
  .option('--keep-source', 'Keep the directory at ~/.agents/plugins/<name> (only unsync from agents)')
382
383
  .addHelpText('after', `
@@ -493,22 +494,25 @@ Examples:
493
494
  console.log(chalk.gray(`Kept source at ${formatPath(pluginRoot)}`));
494
495
  }
495
496
  });
496
- // agents plugins install <spec>
497
+ // agents plugins add <spec>
497
498
  pluginsCmd
498
- .command('install <spec>')
499
- .alias('add')
499
+ .command('add <spec>')
500
+ .alias('install')
500
501
  .description('Install a plugin from a git URL or local path (format: name@source or source)')
501
502
  .option('--allow-exec-surfaces', 'Allow installing plugins that ship executable surfaces')
502
503
  .addHelpText('after', `
503
504
  Examples:
504
505
  # Install from a git URL
505
- agents plugins install my-plugin@https://github.com/user/my-plugin.git
506
+ agents plugins add my-plugin@https://github.com/user/my-plugin.git
506
507
 
507
508
  # Install from a local path
508
- agents plugins install /path/to/plugin
509
+ agents plugins add /path/to/plugin
509
510
 
510
511
  # Named install from a local path
511
- agents plugins install rush-toolkit@~/Projects/rush-toolkit
512
+ agents plugins add rush-toolkit@~/Projects/rush-toolkit
513
+
514
+ # 'install' is kept as an alias
515
+ agents plugins install /path/to/plugin
512
516
  `)
513
517
  .action(async (spec, options) => {
514
518
  console.log(chalk.gray(`Installing plugin from: ${spec}`));
@@ -545,7 +549,7 @@ Examples:
545
549
  const missingDeps = checkPluginDependencies(plugin.manifest);
546
550
  if (missingDeps.length > 0) {
547
551
  console.log(chalk.yellow(`Warning: missing dependencies: ${missingDeps.join(', ')}`));
548
- console.log(chalk.gray('Install them with: agents plugins install <name>@<source>'));
552
+ console.log(chalk.gray('Install them with: agents plugins add <name>@<source>'));
549
553
  }
550
554
  // Prompt for userConfig fields
551
555
  if (plugin.manifest.userConfig && plugin.manifest.userConfig.length > 0 && isInteractiveTerminal()) {
@@ -9,7 +9,8 @@
9
9
  * invoking a router to route a task is a separate ticket.
10
10
  */
11
11
  import chalk from 'chalk';
12
- import { listRouters, readRouter, writeRouter, deleteRouter, routerExists, routerSource, routersDir, validateRouter, } from '../lib/routers.js';
12
+ import { withAliases } from '../lib/verbs.js';
13
+ import { listRouters, readRouter, writeRouter, deleteRouter, renameRouter, routerExists, routerSource, routersDir, validateRouter, } from '../lib/routers.js';
13
14
  import { MODEL_TIERS, isTierToken } from '../lib/model-tiers.js';
14
15
  import { die } from '../lib/format.js';
15
16
  import { setHelpSections } from '../lib/help.js';
@@ -89,11 +90,12 @@ function printRouterDetail(router) {
89
90
  export function registerRouteCommands(program) {
90
91
  const routeCmd = program
91
92
  .command('route')
93
+ .alias('routes')
92
94
  .description('Named routers -- reusable, task-typed allowlists of harnesses x models/tiers x linked accounts.');
93
95
  setHelpSections(routeCmd, {
94
96
  examples: `
95
97
  # Create a router scoped to two harnesses, capped at a tier
96
- agents route create research --harness gemini,kimi --tier cheap,default
98
+ agents route add research --harness gemini,kimi --tier cheap,default
97
99
 
98
100
  # Narrow one harness's model set
99
101
  agents route allow research kimi kimi-k2
@@ -102,8 +104,9 @@ export function registerRouteCommands(program) {
102
104
  agents route link-account research gemini personal
103
105
  agents route link-account research kimi work
104
106
 
105
- # Inspect it
106
- agents route show research
107
+ # Inspect, rename, remove it
108
+ agents route view research
109
+ agents route rename research deep-research
107
110
  agents route list --json
108
111
  `,
109
112
  notes: `
@@ -112,12 +115,16 @@ export function registerRouteCommands(program) {
112
115
  outside its declared harness/model/tier allowlist or linked accounts.
113
116
 
114
117
  Routers are a layered resource (project > user > system, like commands,
115
- skills, and profiles) but 'agents route create'/'allow'/'link-account'
118
+ skills, and profiles) but 'agents route add'/'allow'/'link-account'
116
119
  always write to the user layer (~/.agents/routers/<name>.yml).
120
+
121
+ The older verb names still work as aliases: 'create' for 'add',
122
+ 'show' for 'view'.
117
123
  `,
118
124
  });
119
125
  routeCmd
120
- .command('create <name>')
126
+ .command('add <name>')
127
+ .alias('create')
121
128
  .description('Create a named router with an initial harness + tier allowlist.')
122
129
  .option('--harness <list>', 'Comma-separated harness ids to allow, e.g. gemini,kimi')
123
130
  .option('--tier <list>', 'Comma-separated tier tokens applied to every listed harness, e.g. cheap,default')
@@ -127,7 +134,7 @@ export function registerRouteCommands(program) {
127
134
  die(`Router '${name}' already exists. Use 'agents route allow ${name} <harness> <model|tier>...' to edit it.`);
128
135
  }
129
136
  if (!opts.harness) {
130
- die("Missing --harness. Example: agents route create research --harness gemini,kimi --tier cheap,default");
137
+ die("Missing --harness. Example: agents route add research --harness gemini,kimi --tier cheap,default");
131
138
  }
132
139
  const harnesses = opts.harness.split(',').map((h) => h.trim()).filter(Boolean);
133
140
  const tiers = (opts.tier ?? '').split(',').map((t) => t.trim()).filter(Boolean);
@@ -145,8 +152,8 @@ export function registerRouteCommands(program) {
145
152
  writeRouter(router);
146
153
  console.log(chalk.green(`+ created router "${name}"`) + chalk.gray(` (${path.join(routersDir(), `${name}.yml`)})`));
147
154
  });
148
- routeCmd
149
- .command('list')
155
+ withAliases(routeCmd
156
+ .command('list'), 'list')
150
157
  .description('List every configured router.')
151
158
  .option('--json', 'Emit machine-readable JSON')
152
159
  .action((opts) => {
@@ -163,15 +170,14 @@ export function registerRouteCommands(program) {
163
170
  }
164
171
  if (routers.length === 0) {
165
172
  console.log(chalk.gray('No routers configured.'));
166
- console.log(chalk.gray('Try: agents route create research --harness gemini,kimi --tier cheap,default'));
173
+ console.log(chalk.gray('Try: agents route add research --harness gemini,kimi --tier cheap,default'));
167
174
  return;
168
175
  }
169
176
  for (const router of routers)
170
177
  console.log(renderRouterRow(router));
171
178
  });
172
- routeCmd
173
- .command('show <name>')
174
- .alias('view')
179
+ withAliases(routeCmd
180
+ .command('view <name>'), 'view')
175
181
  .description("Show a router's harness/model/account allowlist, weights, and hijack flag.")
176
182
  .option('--json', 'Emit machine-readable JSON')
177
183
  .action((name, opts) => {
@@ -242,9 +248,20 @@ export function registerRouteCommands(program) {
242
248
  writeRouter(router);
243
249
  console.log(chalk.green(`Router '${name}': unlinked ${harness} account '${account}'`));
244
250
  });
245
- routeCmd
246
- .command('remove <name>')
247
- .alias('rm')
251
+ withAliases(routeCmd
252
+ .command('rename <old-name> <new-name>'), 'rename')
253
+ .description('Rename a router, preserving every field (allowlists, weights, accounts, hijack). Errors on a name collision.')
254
+ .action((oldName, newName) => {
255
+ try {
256
+ renameRouter(oldName, newName);
257
+ console.log(chalk.green(`Router '${oldName}' renamed to '${newName}'.`));
258
+ }
259
+ catch (err) {
260
+ die(err.message);
261
+ }
262
+ });
263
+ withAliases(routeCmd
264
+ .command('remove <name>'), 'remove')
248
265
  .description('Remove a router.')
249
266
  .action((name) => {
250
267
  const source = routerSource(name);
@@ -1,3 +1,4 @@
1
+ import { withAliases } from '../lib/verbs.js';
1
2
  import chalk from 'chalk';
2
3
  import ora from 'ora';
3
4
  import * as fs from 'fs';
@@ -57,8 +58,8 @@ Project rules & @-imports:
57
58
  For rules that need to work across all agents, inline the content rather than using
58
59
  @-imports — the second group will load '@path/to/file.md' as a literal string.
59
60
  `);
60
- rulesCmd
61
- .command('list [agent]')
61
+ withAliases(rulesCmd
62
+ .command('list [agent]'), 'list')
62
63
  .description('Show which rule files are installed. Pass agent@version to see a specific version.')
63
64
  .option('-a, --agent <agent>', 'Filter to a specific agent (alternative to positional arg)')
64
65
  .action(async (agentArg, options) => {
@@ -364,8 +365,8 @@ Examples:
364
365
  process.exit(1);
365
366
  }
366
367
  });
367
- rulesCmd
368
- .command('view [agent]')
368
+ withAliases(rulesCmd
369
+ .command('view [agent]'), 'view')
369
370
  .description('Read the full content of a rule file with markdown rendering')
370
371
  .option('-s, --scope <scope>', 'user (global) or project (repo-specific)', 'user')
371
372
  .addHelpText('after', `
@@ -446,8 +447,8 @@ Examples:
446
447
  const filePath = instr?.path || '';
447
448
  await displayContent(content, `${agentLabel(agentId)} Rules (${scope})`, filePath);
448
449
  });
449
- rulesCmd
450
- .command('remove [agent]')
450
+ withAliases(rulesCmd
451
+ .command('remove [agent]'), 'remove')
451
452
  .description('Delete user-level rule files for an agent or version')
452
453
  .addHelpText('after', `
453
454
  Examples:
@@ -605,10 +606,4 @@ Examples:
605
606
  process.exit(1);
606
607
  }
607
608
  });
608
- rulesCmd
609
- .command('show [agent]', { hidden: true })
610
- .action(async (agentArg) => {
611
- console.log(chalk.yellow('Deprecated: Use "agents rules view" instead of "agents rules show"\n'));
612
- await rulesCmd.commands.find((c) => c.name() === 'view')?.parseAsync(['view', ...(agentArg ? [agentArg] : [])], { from: 'user' });
613
- });
614
609
  }
@@ -68,7 +68,7 @@ export function registerSessionsShareCommand(sessionsCmd) {
68
68
  examples: `# Share a session — prints an unlisted, redacted link
69
69
  agents sessions share a1b2c3d4
70
70
 
71
- # Put it in your public gallery at share.agents-cli.sh/<you>
71
+ # Put it in your public gallery at share.getrush.ai/<you>
72
72
  agents sessions share a1b2c3d4 --public --label "How the retry bug got fixed"
73
73
 
74
74
  # Keep the model's reasoning in collapsible sections