@phnx-labs/agents-cli 1.19.1 → 1.20.0

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 (109) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +70 -10
  3. package/dist/commands/browser.js +88 -16
  4. package/dist/commands/cli.d.ts +14 -0
  5. package/dist/commands/cli.js +244 -0
  6. package/dist/commands/commands.js +3 -3
  7. package/dist/commands/computer.js +18 -1
  8. package/dist/commands/doctor.d.ts +1 -1
  9. package/dist/commands/doctor.js +2 -2
  10. package/dist/commands/exec.js +3 -3
  11. package/dist/commands/factory.d.ts +3 -14
  12. package/dist/commands/factory.js +3 -3
  13. package/dist/commands/hooks.js +3 -3
  14. package/dist/commands/mcp.js +29 -0
  15. package/dist/commands/plugins.js +11 -4
  16. package/dist/commands/profiles.js +1 -1
  17. package/dist/commands/prune.js +39 -160
  18. package/dist/commands/pull.js +56 -3
  19. package/dist/commands/routines.js +106 -13
  20. package/dist/commands/secrets.js +6 -8
  21. package/dist/commands/sessions.d.ts +36 -7
  22. package/dist/commands/sessions.js +130 -53
  23. package/dist/commands/setup.d.ts +1 -0
  24. package/dist/commands/setup.js +37 -28
  25. package/dist/commands/skills.js +3 -3
  26. package/dist/commands/teams.js +13 -0
  27. package/dist/commands/versions.d.ts +4 -3
  28. package/dist/commands/versions.js +147 -124
  29. package/dist/commands/view.js +12 -12
  30. package/dist/index.js +34 -6
  31. package/dist/lib/acp/harnesses.js +8 -0
  32. package/dist/lib/agents.js +162 -9
  33. package/dist/lib/browser/cdp.d.ts +8 -1
  34. package/dist/lib/browser/cdp.js +40 -3
  35. package/dist/lib/browser/chrome.d.ts +13 -0
  36. package/dist/lib/browser/chrome.js +42 -3
  37. package/dist/lib/browser/domain-skills.d.ts +51 -0
  38. package/dist/lib/browser/domain-skills.js +157 -0
  39. package/dist/lib/browser/drivers/local.js +45 -4
  40. package/dist/lib/browser/drivers/ssh.js +1 -1
  41. package/dist/lib/browser/ipc.d.ts +8 -1
  42. package/dist/lib/browser/ipc.js +37 -28
  43. package/dist/lib/browser/profiles.d.ts +13 -0
  44. package/dist/lib/browser/profiles.js +41 -1
  45. package/dist/lib/browser/service.d.ts +3 -0
  46. package/dist/lib/browser/service.js +21 -5
  47. package/dist/lib/browser/types.d.ts +7 -0
  48. package/dist/lib/cli-resources.d.ts +109 -0
  49. package/dist/lib/cli-resources.js +255 -0
  50. package/dist/lib/cloud/rush.js +5 -5
  51. package/dist/lib/command-skills.js +0 -2
  52. package/dist/lib/computer-rpc.d.ts +3 -0
  53. package/dist/lib/computer-rpc.js +53 -0
  54. package/dist/lib/daemon.js +20 -0
  55. package/dist/lib/exec.d.ts +3 -2
  56. package/dist/lib/exec.js +62 -6
  57. package/dist/lib/hooks.js +182 -0
  58. package/dist/lib/mcp.js +6 -0
  59. package/dist/lib/migrate.js +1 -1
  60. package/dist/lib/overdue.d.ts +26 -0
  61. package/dist/lib/overdue.js +101 -0
  62. package/dist/lib/permissions.js +5 -1
  63. package/dist/lib/plugin-marketplace.js +1 -1
  64. package/dist/lib/profiles-presets.js +37 -0
  65. package/dist/lib/registry.d.ts +18 -0
  66. package/dist/lib/registry.js +44 -0
  67. package/dist/lib/resources/mcp.js +43 -1
  68. package/dist/lib/resources/types.d.ts +1 -1
  69. package/dist/lib/resources.d.ts +1 -1
  70. package/dist/lib/rotate.js +10 -4
  71. package/dist/lib/routines-format.d.ts +35 -0
  72. package/dist/lib/routines-format.js +173 -0
  73. package/dist/lib/routines.d.ts +7 -1
  74. package/dist/lib/routines.js +32 -12
  75. package/dist/lib/runner.js +19 -5
  76. package/dist/lib/scheduler.js +8 -1
  77. package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/CodeResources +0 -0
  78. package/dist/lib/secrets/{AgentsKeychain.app/Contents/Info.plist → Agents CLI.app/Contents/Info.plist } +4 -2
  79. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  80. package/dist/lib/secrets/bundles.d.ts +33 -2
  81. package/dist/lib/secrets/bundles.js +249 -26
  82. package/dist/lib/secrets/index.d.ts +10 -1
  83. package/dist/lib/secrets/index.js +143 -48
  84. package/dist/lib/session/active.d.ts +8 -0
  85. package/dist/lib/session/active.js +3 -2
  86. package/dist/lib/session/db.d.ts +10 -4
  87. package/dist/lib/session/db.js +16 -16
  88. package/dist/lib/session/parse.d.ts +1 -0
  89. package/dist/lib/session/parse.js +44 -0
  90. package/dist/lib/session/types.d.ts +1 -1
  91. package/dist/lib/session/types.js +1 -1
  92. package/dist/lib/shims.d.ts +6 -2
  93. package/dist/lib/shims.js +88 -10
  94. package/dist/lib/state.d.ts +0 -1
  95. package/dist/lib/state.js +2 -15
  96. package/dist/lib/teams/agents.js +1 -1
  97. package/dist/lib/teams/parsers.d.ts +1 -1
  98. package/dist/lib/teams/parsers.js +153 -3
  99. package/dist/lib/teams/summarizer.js +18 -2
  100. package/dist/lib/teams/worktree.js +14 -3
  101. package/dist/lib/types.d.ts +7 -4
  102. package/dist/lib/types.js +6 -3
  103. package/dist/lib/versions.d.ts +10 -2
  104. package/dist/lib/versions.js +227 -35
  105. package/package.json +9 -9
  106. package/dist/lib/secrets/AgentsKeychain.app/Contents/MacOS/AgentsKeychain +0 -0
  107. package/npm-shrinkwrap.json +0 -3162
  108. /package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/_CodeSignature/CodeResources +0 -0
  109. /package/dist/lib/secrets/{AgentsKeychain.app → Agents CLI.app}/Contents/embedded.provisionprofile +0 -0
@@ -9,6 +9,7 @@ import type { Command } from 'commander';
9
9
  export declare function runSetup(program: Command, options?: {
10
10
  force?: boolean;
11
11
  suppressFooter?: boolean;
12
+ systemRepo?: boolean;
12
13
  }): Promise<void>;
13
14
  /**
14
15
  * Ensure the system repo exists before running a command that needs it.
@@ -68,38 +68,46 @@ export async function runSetup(program, options = {}) {
68
68
  for (const install of unmanaged) {
69
69
  sessionCounts[install.agentId] = countSessionFiles(install.agentId);
70
70
  }
71
+ const systemRepo = process.env.AGENTS_SYSTEM_REPO || DEFAULT_SYSTEM_REPO;
71
72
  console.log(chalk.bold('\nWelcome to agents-cli.'));
72
- console.log(chalk.gray(`Cloning the system repo from ${systemRepoSlug(DEFAULT_SYSTEM_REPO)} into ~/.agents-system/.\n`));
73
- ensureAgentsDir();
74
- const spinner = ora('Cloning system repo...').start();
75
- if (isGitRepo(agentsDir)) {
76
- // --force on an existing repo: pull instead of re-clone
77
- const result = await pullRepo(agentsDir);
78
- if (!result.success) {
79
- spinner.fail(`Pull failed: ${result.error}`);
80
- console.log(chalk.gray('Fix the issue and re-run: agents setup --force'));
81
- process.exit(1);
82
- }
83
- spinner.succeed(`Updated to ${result.commit}`);
73
+ if (options.systemRepo === false) {
74
+ ensureAgentsDir();
75
+ console.log(chalk.gray('Skipping system repo clone (--no-system-repo).'));
76
+ console.log(chalk.gray(`Populate ~/.agents-system/ yourself before running other commands that depend on it.\n`));
84
77
  }
85
78
  else {
86
- // Check git is available
87
- try {
88
- const { execSync } = await import('child_process');
89
- execSync('which git', { stdio: 'ignore' });
90
- }
91
- catch {
92
- spinner.fail('git is not installed');
93
- console.log(chalk.gray('Install git first: https://git-scm.com/downloads'));
94
- process.exit(1);
79
+ console.log(chalk.gray(`Cloning the system repo from ${systemRepoSlug(systemRepo)} into ~/.agents-system/.\n`));
80
+ ensureAgentsDir();
81
+ const spinner = ora('Cloning system repo...').start();
82
+ if (isGitRepo(agentsDir)) {
83
+ // --force on an existing repo: pull instead of re-clone
84
+ const result = await pullRepo(agentsDir);
85
+ if (!result.success) {
86
+ spinner.fail(`Pull failed: ${result.error}`);
87
+ console.log(chalk.gray('Fix the issue and re-run: agents setup --force'));
88
+ process.exit(1);
89
+ }
90
+ spinner.succeed(`Updated to ${result.commit}`);
95
91
  }
96
- const result = await cloneIntoExisting(DEFAULT_SYSTEM_REPO, agentsDir);
97
- if (!result.success) {
98
- spinner.fail(`Clone failed: ${result.error}`);
99
- console.log(chalk.gray('Fix the issue and re-run: agents setup --force'));
100
- process.exit(1);
92
+ else {
93
+ // Check git is available
94
+ try {
95
+ const { execSync } = await import('child_process');
96
+ execSync('which git', { stdio: 'ignore' });
97
+ }
98
+ catch {
99
+ spinner.fail('git is not installed');
100
+ console.log(chalk.gray('Install git first: https://git-scm.com/downloads'));
101
+ process.exit(1);
102
+ }
103
+ const result = await cloneIntoExisting(systemRepo, agentsDir);
104
+ if (!result.success) {
105
+ spinner.fail(`Clone failed: ${result.error}`);
106
+ console.log(chalk.gray('Fix the issue and re-run: agents setup --force'));
107
+ process.exit(1);
108
+ }
109
+ spinner.succeed(`Cloned ${systemRepoSlug(systemRepo)} (${result.commit})`);
101
110
  }
102
- spinner.succeed(`Cloned ${systemRepoSlug(DEFAULT_SYSTEM_REPO)} (${result.commit})`);
103
111
  }
104
112
  // Offer to import existing unmanaged installations
105
113
  if (unmanaged.length > 0 && isInteractiveTerminal()) {
@@ -195,7 +203,8 @@ export function registerSetupCommand(program) {
195
203
  const setupCmd = program
196
204
  .command('setup')
197
205
  .description('First-time setup. Clones a config repo and installs agent CLIs.')
198
- .option('-f, --force', 'Re-run setup even if ~/.agents-system/ already exists (use with caution)');
206
+ .option('-f, --force', 'Re-run setup even if ~/.agents-system/ already exists (use with caution)')
207
+ .option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents-system/ yourself)');
199
208
  setHelpSections(setupCmd, {
200
209
  examples: `
201
210
  # First-time setup (clones the system repo into ~/.agents-system/)
@@ -394,17 +394,17 @@ Examples:
394
394
  .action(() => {
395
395
  console.error(chalk.red('"agents skills sync" is gone.'));
396
396
  console.error(chalk.gray('Sync runs automatically when you launch the agent.'));
397
- console.error(chalk.gray('To remove orphans, use: agents prune skills'));
397
+ console.error(chalk.gray('To remove orphans, use: agents prune cleanup skills'));
398
398
  process.exit(1);
399
399
  });
400
- // `skills prune` moved to the top-level `agents prune` command.
400
+ // `skills prune` moved to the top-level `agents prune cleanup` command.
401
401
  skillsCmd
402
402
  .command('prune', { hidden: true })
403
403
  .allowUnknownOption()
404
404
  .allowExcessArguments()
405
405
  .action(() => {
406
406
  console.error(chalk.red('"agents skills prune" moved.'));
407
- console.error(chalk.gray('Use: agents prune skills (or `agents prune` for everything)'));
407
+ console.error(chalk.gray('Use: agents prune cleanup skills (or `agents prune cleanup` for everything)'));
408
408
  process.exit(1);
409
409
  });
410
410
  skillsCmd
@@ -21,6 +21,8 @@ const AGENT_NAMES = {
21
21
  gemini: 'Gemini',
22
22
  cursor: 'Cursor',
23
23
  opencode: 'OpenCode',
24
+ grok: 'Grok',
25
+ antigravity: 'Antigravity',
24
26
  };
25
27
  const VALID_AGENTS = Object.keys(AGENT_NAMES);
26
28
  const VALID_MODES = ['plan', 'edit', 'full'];
@@ -756,6 +758,11 @@ export function registerTeamsCommands(program) {
756
758
  die(`Invalid teammate name '${opts.name}'. Use letters, numbers, '-', or '_'.`);
757
759
  }
758
760
  }
761
+ if (opts.worktree !== undefined) {
762
+ if (!opts.worktree || !/^[A-Za-z0-9_-]+$/.test(opts.worktree)) {
763
+ die(`Invalid worktree name '${opts.worktree}'. Use letters, numbers, '-', or '_'.`);
764
+ }
765
+ }
759
766
  const after = opts.after
760
767
  ? opts.after.split(',').map((s) => s.trim()).filter(Boolean)
761
768
  : [];
@@ -899,6 +906,12 @@ export function registerTeamsCommands(program) {
899
906
  console.log();
900
907
  if (staged) {
901
908
  console.log(chalk.gray(`Start the ready teammates: agents teams start ${team}`));
909
+ if (after.length > 0) {
910
+ process.stderr.write(chalk.yellow(`\nWarning: this teammate has --after dependencies and will NEVER start on its own.\n` +
911
+ ` A supervisor watch process is required to launch it when its deps complete.\n` +
912
+ ` Run this in another terminal:\n` +
913
+ ` agents teams start ${team} --watch\n`));
914
+ }
902
915
  }
903
916
  else {
904
917
  console.log(chalk.gray(`Check in later: agents teams status ${team}`));
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Version management commands for installing, switching, and removing agent CLIs.
3
3
  *
4
- * Implements `agents add`, `agents remove`, `agents use`, and the deprecated
5
- * `agents list`. Handles npm-based installation, shim creation, config symlink
4
+ * Implements `agents add`, `agents prune`, `agents remove` (alias),
5
+ * `agents use`, and the deprecated `agents list`. Handles npm-based installation,
6
+ * shim creation, config symlink
6
7
  * switching, resource sync prompts, and project-level version pinning.
7
8
  */
8
9
  import type { Command } from 'commander';
9
- /** Register `agents add`, `agents remove`, `agents use`, and `agents list` (deprecated). */
10
+ /** Register `agents add`, `agents prune`, `agents remove`, `agents use`, and `agents list` (deprecated). */
10
11
  export declare function registerVersionsCommands(program: Command): void;
@@ -7,12 +7,27 @@ import { AGENTS, ALL_AGENT_IDS, getAccountEmail, getAccountInfo, agentLabel, } f
7
7
  import { formatUsageSummary, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
8
8
  import { viewAction } from './view.js';
9
9
  import { readManifest, writeManifest, createDefaultManifest } from '../lib/manifest.js';
10
- import { installVersion, removeVersion, listInstalledVersions, isVersionInstalled, isLatestInstalled, getGlobalDefault, setGlobalDefault, getVersionHomePath, syncResourcesToVersion, parseAgentSpec, promptResourceSelection, promptNewResourceSelection, getAvailableResources, getActuallySyncedResources, getNewResources, hasNewResources, } from '../lib/versions.js';
10
+ import { installVersion, removeVersion, listInstalledVersions, isVersionInstalled, isLatestInstalled, getGlobalDefault, setGlobalDefault, getVersionHomePath, getVersionDir, syncResourcesToVersion, parseAgentSpec, promptResourceSelection, promptNewResourceSelection, getAvailableResources, getActuallySyncedResources, getNewResources, hasNewResources, printTrashFooter, } from '../lib/versions.js';
11
11
  import { createShim, createVersionedAlias, removeShim, shimExists, getShimsDir, getShimPath, getPathShadowingExecutable, isShimsInPath, getPathSetupInstructions, addShimsToPath, switchConfigSymlink, switchHomeFileSymlinks, } from '../lib/shims.js';
12
12
  import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection } from './utils.js';
13
13
  import { tryAutoPull } from '../lib/git.js';
14
- import { getAgentsDir } from '../lib/state.js';
14
+ import { getAgentsDir, getTrashVersionsDir } from '../lib/state.js';
15
15
  import { setHelpSections } from '../lib/help.js';
16
+ import { updateSessionFilePaths } from '../lib/session/db.js';
17
+ /**
18
+ * After removeVersion soft-deletes a version dir to trash, rewrite session
19
+ * file_path entries in the DB so reads still work from the new trash location.
20
+ */
21
+ function fixSessionFilePaths(agent, version, oldVersionDir) {
22
+ const trashAgentDir = path.join(getTrashVersionsDir(), agent, version);
23
+ if (!fs.existsSync(trashAgentDir))
24
+ return;
25
+ const stamps = fs.readdirSync(trashAgentDir).sort().reverse();
26
+ if (stamps.length === 0)
27
+ return;
28
+ const trashPath = path.join(trashAgentDir, stamps[0]);
29
+ updateSessionFilePaths(oldVersionDir, trashPath);
30
+ }
16
31
  /**
17
32
  * Helper to get actual installed version for an agent.
18
33
  * Returns the latest installed version, or throws if none installed.
@@ -78,7 +93,130 @@ function warnIfShimShadowed(agent) {
78
93
  console.log(chalk.gray(` Managed shim: ${getShimPath(agent)}`));
79
94
  console.log(chalk.gray(` ${getPathSetupInstructions().split('\n').join('\n ')}`));
80
95
  }
81
- /** Register `agents add`, `agents remove`, `agents use`, and `agents list` (deprecated). */
96
+ async function versionPruneAction(specs, options, commandName) {
97
+ const isProject = options.project;
98
+ const moved = [];
99
+ for (const spec of specs) {
100
+ const parsed = parseAgentSpec(spec);
101
+ if (!parsed) {
102
+ console.log(chalk.red(`Invalid agent: ${spec}`));
103
+ console.log(chalk.gray(`Format: <agent>[@version]. Available: ${ALL_AGENT_IDS.join(', ')}`));
104
+ continue;
105
+ }
106
+ const { agent, version } = parsed;
107
+ const agentConfig = AGENTS[agent];
108
+ if (version === 'latest' || !spec.includes('@')) {
109
+ const versions = listInstalledVersions(agent);
110
+ if (versions.length === 0) {
111
+ console.log(chalk.gray(`No versions of ${agentLabel(agentConfig.id)} installed`));
112
+ continue;
113
+ }
114
+ if (!isInteractiveTerminal()) {
115
+ requireInteractiveSelection(`Selecting ${agentLabel(agentConfig.id)} versions to ${commandName}`, [
116
+ `agents ${commandName} ${agent}@${versions[0]}`,
117
+ ]);
118
+ }
119
+ const globalDefault = getGlobalDefault(agent);
120
+ const sortedVersions = [...versions].sort((a, b) => {
121
+ if (a === globalDefault)
122
+ return -1;
123
+ if (b === globalDefault)
124
+ return 1;
125
+ return 0;
126
+ });
127
+ try {
128
+ const toRemove = await checkbox({
129
+ message: `Select ${agentLabel(agentConfig.id)} versions to ${commandName}:`,
130
+ choices: sortedVersions.map((v) => ({
131
+ name: v === globalDefault ? `${v} ${chalk.green('(default)')}` : v,
132
+ value: v,
133
+ checked: false,
134
+ })),
135
+ });
136
+ if (toRemove.length === 0) {
137
+ console.log(chalk.gray('No versions selected'));
138
+ continue;
139
+ }
140
+ for (const v of toRemove) {
141
+ const versionDir = getVersionDir(agent, v);
142
+ removeVersion(agent, v);
143
+ fixSessionFilePaths(agent, v, versionDir);
144
+ console.log(chalk.green(`Moved ${agentLabel(agentConfig.id)}@${v} to trash`));
145
+ moved.push({ agent, version: v });
146
+ }
147
+ if (globalDefault && toRemove.includes(globalDefault)) {
148
+ setGlobalDefault(agent, undefined);
149
+ console.log(chalk.yellow(`Default version removed. Run: agents use ${agent}@<version> to set a new default`));
150
+ }
151
+ const remaining = listInstalledVersions(agent);
152
+ if (remaining.length === 0) {
153
+ removeShim(agent);
154
+ }
155
+ }
156
+ catch (err) {
157
+ if (isPromptCancelled(err)) {
158
+ console.log(chalk.gray('Cancelled'));
159
+ continue;
160
+ }
161
+ throw err;
162
+ }
163
+ }
164
+ else if (!isVersionInstalled(agent, version)) {
165
+ console.log(chalk.gray(`${agentLabel(agentConfig.id)}@${version} not installed`));
166
+ }
167
+ else {
168
+ const versionDir = getVersionDir(agent, version);
169
+ removeVersion(agent, version);
170
+ fixSessionFilePaths(agent, version, versionDir);
171
+ console.log(chalk.green(`Moved ${agentLabel(agentConfig.id)}@${version} to trash`));
172
+ moved.push({ agent, version });
173
+ const remaining = listInstalledVersions(agent);
174
+ if (remaining.length === 0) {
175
+ removeShim(agent);
176
+ }
177
+ }
178
+ if (isProject) {
179
+ const projectManifestPath = path.join(process.cwd(), '.agents', 'agents.yaml');
180
+ if (fs.existsSync(projectManifestPath)) {
181
+ const manifest = readManifest(process.cwd());
182
+ if (manifest?.agents?.[agent]) {
183
+ delete manifest.agents[agent];
184
+ writeManifest(process.cwd(), manifest);
185
+ console.log(chalk.gray(` Removed from .agents/agents.yaml`));
186
+ }
187
+ }
188
+ }
189
+ }
190
+ printTrashFooter(moved);
191
+ }
192
+ function configureVersionPruneCommand(cmd, commandName) {
193
+ const isAlias = commandName === 'remove';
194
+ cmd
195
+ .description(isAlias
196
+ ? 'Alias for agents prune. Uninstalls agent CLI versions.'
197
+ : 'Uninstall agent CLI versions. Moves version data to trash for recovery.')
198
+ .option('-p, --project', 'Also clear the pinned version from .agents/agents.yaml in the current project');
199
+ setHelpSections(cmd, {
200
+ examples: `
201
+ # Prune a specific version
202
+ agents ${commandName} claude@2.0.50
203
+
204
+ # Pick interactively if you omit the version
205
+ agents ${commandName} claude
206
+
207
+ # Prune and also clear the project pin
208
+ agents ${commandName} claude@2.0.50 --project
209
+ `,
210
+ notes: `
211
+ - Pruned version directories move to trash with their home/ data intact.
212
+ - Session file paths are rewritten so session history remains readable.
213
+ - Removing the default version unsets the default; run 'agents use' to pick a new one.
214
+ - Reinstall any time with 'agents add'.
215
+ `,
216
+ });
217
+ cmd.action((specs, options) => versionPruneAction(specs, options, commandName));
218
+ }
219
+ /** Register `agents add`, `agents prune`, `agents remove`, `agents use`, and `agents list` (deprecated). */
82
220
  export function registerVersionsCommands(program) {
83
221
  const addCmd = program
84
222
  .command('add <specs...>')
@@ -180,9 +318,9 @@ export function registerVersionsCommands(program) {
180
318
  selection = userSelection;
181
319
  }
182
320
  }
183
- else if (hasNewResources(newResources, agent)) {
321
+ else if (hasNewResources(newResources, agent, installedVersion)) {
184
322
  // Some synced, but NEW resources available - prompt for new only
185
- const userSelection = await promptNewResourceSelection(agent, newResources);
323
+ const userSelection = await promptNewResourceSelection(agent, newResources, installedVersion);
186
324
  if (userSelection) {
187
325
  selection = userSelection;
188
326
  }
@@ -296,123 +434,8 @@ export function registerVersionsCommands(program) {
296
434
  }
297
435
  }
298
436
  });
299
- const removeCmd = program
300
- .command('remove <specs...>')
301
- .description('Uninstall agent CLI versions. Frees disk space and removes the version\'s auth token.')
302
- .option('-p, --project', 'Also clear the pinned version from .agents/agents.yaml in the current project');
303
- setHelpSections(removeCmd, {
304
- examples: `
305
- # Remove a specific version
306
- agents remove claude@2.0.50
307
-
308
- # Pick interactively if you omit the version
309
- agents remove claude
310
-
311
- # Remove and also clear the project pin
312
- agents remove claude@2.0.50 --project
313
- `,
314
- notes: `
315
- - Removing the default version unsets the default; run 'agents use' to pick a new one.
316
- - Reinstall any time with 'agents add'.
317
- `,
318
- });
319
- removeCmd.action(async (specs, options) => {
320
- const isProject = options.project;
321
- for (const spec of specs) {
322
- const parsed = parseAgentSpec(spec);
323
- if (!parsed) {
324
- console.log(chalk.red(`Invalid agent: ${spec}`));
325
- console.log(chalk.gray(`Format: <agent>[@version]. Available: ${ALL_AGENT_IDS.join(', ')}`));
326
- continue;
327
- }
328
- const { agent, version } = parsed;
329
- const agentConfig = AGENTS[agent];
330
- if (version === 'latest' || !spec.includes('@')) {
331
- // Show picker for which versions to remove
332
- const versions = listInstalledVersions(agent);
333
- if (versions.length === 0) {
334
- console.log(chalk.gray(`No versions of ${agentLabel(agentConfig.id)} installed`));
335
- continue;
336
- }
337
- if (!isInteractiveTerminal()) {
338
- requireInteractiveSelection(`Selecting ${agentLabel(agentConfig.id)} versions to remove`, [
339
- `agents remove ${agent}@${versions[0]}`,
340
- ]);
341
- }
342
- const globalDefault = getGlobalDefault(agent);
343
- // Sort versions with default first
344
- const sortedVersions = [...versions].sort((a, b) => {
345
- if (a === globalDefault)
346
- return -1;
347
- if (b === globalDefault)
348
- return 1;
349
- return 0;
350
- });
351
- try {
352
- const toRemove = await checkbox({
353
- message: `Select ${agentLabel(agentConfig.id)} versions to remove:`,
354
- choices: sortedVersions.map((v) => ({
355
- name: v === globalDefault ? `${v} ${chalk.green('(default)')}` : v,
356
- value: v,
357
- checked: false, // All unchecked by default
358
- })),
359
- });
360
- if (toRemove.length === 0) {
361
- console.log(chalk.gray('No versions selected'));
362
- continue;
363
- }
364
- for (const v of toRemove) {
365
- removeVersion(agent, v);
366
- console.log(chalk.green(`Removed ${agentLabel(agentConfig.id)}@${v}`));
367
- }
368
- // Check if default was removed
369
- if (globalDefault && toRemove.includes(globalDefault)) {
370
- setGlobalDefault(agent, undefined);
371
- console.log(chalk.yellow(`Default version removed. Run: agents use ${agent}@<version> to set a new default`));
372
- }
373
- // Remove shim if no versions left
374
- const remaining = listInstalledVersions(agent);
375
- if (remaining.length === 0) {
376
- removeShim(agent);
377
- }
378
- }
379
- catch (err) {
380
- if (isPromptCancelled(err)) {
381
- console.log(chalk.gray('Cancelled'));
382
- continue;
383
- }
384
- throw err;
385
- }
386
- }
387
- else {
388
- // Remove specific version
389
- if (!isVersionInstalled(agent, version)) {
390
- console.log(chalk.gray(`${agentLabel(agentConfig.id)}@${version} not installed`));
391
- }
392
- else {
393
- removeVersion(agent, version);
394
- console.log(chalk.green(`Removed ${agentLabel(agentConfig.id)}@${version}`));
395
- // Remove shim if no versions left
396
- const remaining = listInstalledVersions(agent);
397
- if (remaining.length === 0) {
398
- removeShim(agent);
399
- }
400
- }
401
- }
402
- // Update project manifest if -p flag
403
- if (isProject) {
404
- const projectManifestPath = path.join(process.cwd(), '.agents', 'agents.yaml');
405
- if (fs.existsSync(projectManifestPath)) {
406
- const manifest = readManifest(process.cwd());
407
- if (manifest?.agents?.[agent]) {
408
- delete manifest.agents[agent];
409
- writeManifest(process.cwd(), manifest);
410
- console.log(chalk.gray(` Removed from .agents/agents.yaml`));
411
- }
412
- }
413
- }
414
- }
415
- });
437
+ configureVersionPruneCommand(program.command('prune <specs...>'), 'prune');
438
+ configureVersionPruneCommand(program.command('remove <specs...>', { hidden: true }), 'remove');
416
439
  const useCmd = program
417
440
  .command('use <agent> [version]')
418
441
  .description('Switch the active version for an agent. This is the only command that sets the default.')
@@ -619,9 +642,9 @@ export function registerVersionsCommands(program) {
619
642
  }
620
643
  }
621
644
  }
622
- else if (hasNewResources(newResources, agentId)) {
645
+ else if (hasNewResources(newResources, agentId, finalVersion)) {
623
646
  // Has synced before, but NEW items available
624
- const userSelection = await promptNewResourceSelection(agentId, newResources);
647
+ const userSelection = await promptNewResourceSelection(agentId, newResources, finalVersion);
625
648
  if (userSelection && Object.keys(userSelection).length > 0) {
626
649
  const syncResult = syncResourcesToVersion(agentId, finalVersion, userSelection);
627
650
  const syncedTypes = [];
@@ -5,7 +5,7 @@ import * as path from 'path';
5
5
  import { AGENTS, ALL_AGENT_IDS, getAllCliStates, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
6
6
  import { formatUsageSection, formatUsageSummary, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
7
7
  import { readManifest } from '../lib/manifest.js';
8
- import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, resolveVersionAlias, getAvailableResources, getActuallySyncedResources, getNewResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, } from '../lib/versions.js';
8
+ import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, resolveVersionAlias, getAvailableResources, getActuallySyncedResources, getNewResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, } from '../lib/versions.js';
9
9
  import { getShimsDir, isShimsInPath, ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
10
10
  import { getAgentResources } from '../lib/resources.js';
11
11
  import { WORKFLOW_CAPABLE_AGENTS } from '../lib/workflows.js';
@@ -366,7 +366,7 @@ async function showInstalledVersions(filterAgentId) {
366
366
  const newResources = getNewResources(available, synced);
367
367
  if (hasNewResources(newResources, filterAgentId, defaultVersion)) {
368
368
  try {
369
- const selection = await promptNewResourceSelection(filterAgentId, newResources);
369
+ const selection = await promptNewResourceSelection(filterAgentId, newResources, defaultVersion);
370
370
  if (selection && Object.keys(selection).length > 0) {
371
371
  const result = syncResourcesToVersion(filterAgentId, defaultVersion, selection);
372
372
  const synced = [];
@@ -833,13 +833,12 @@ async function buildAgentPrunePlan(agentId) {
833
833
  return { agentId, toPrune, skippedDefaults };
834
834
  }
835
835
  async function executePrunePlan(plan) {
836
- let removed = 0;
836
+ const moved = [];
837
837
  for (const p of plan.toPrune) {
838
- console.log(chalk.gray(`Removing ${agentLabel(p.agentId)}@${p.version}...`));
839
838
  const ok = removeVersion(p.agentId, p.version);
840
839
  if (ok) {
841
- console.log(chalk.green(`Removed ${agentLabel(p.agentId)}@${p.version}`));
842
- removed++;
840
+ console.log(chalk.green(`Moved ${agentLabel(p.agentId)}@${p.version} to trash`));
841
+ moved.push({ agent: p.agentId, version: p.version });
843
842
  }
844
843
  else {
845
844
  console.log(chalk.yellow(`Already gone: ${agentLabel(p.agentId)}@${p.version}`));
@@ -848,7 +847,7 @@ async function executePrunePlan(plan) {
848
847
  if (listInstalledVersions(plan.agentId).length === 0) {
849
848
  removeShim(plan.agentId);
850
849
  }
851
- return removed;
850
+ return moved;
852
851
  }
853
852
  function printPrunePlan(plan, isFirst) {
854
853
  if (plan.skippedDefaults.length > 0) {
@@ -897,7 +896,7 @@ export async function pruneDuplicates(filterAgentId, yes, dryRun) {
897
896
  return;
898
897
  }
899
898
  const totalCandidates = actionable.reduce((n, plan) => n + plan.toPrune.length, 0);
900
- let totalRemoved = 0;
899
+ const allMoved = [];
901
900
  let isFirst = true;
902
901
  let processedAny = false;
903
902
  for (const plan of actionable) {
@@ -916,10 +915,10 @@ export async function pruneDuplicates(filterAgentId, yes, dryRun) {
916
915
  if (!isInteractiveTerminal()) {
917
916
  console.log(chalk.red('Refusing to prune in a non-interactive shell without --yes.'));
918
917
  if (filterAgentId) {
919
- console.log(chalk.gray(`Re-run with: agents prune ${filterAgentId} --dry-run`));
918
+ console.log(chalk.gray(`Re-run with: agents prune cleanup ${filterAgentId} --dry-run`));
920
919
  }
921
920
  else {
922
- console.log(chalk.gray('Re-run with: agents prune --dry-run'));
921
+ console.log(chalk.gray('Re-run with: agents prune cleanup --dry-run'));
923
922
  }
924
923
  process.exit(1);
925
924
  }
@@ -943,7 +942,7 @@ export async function pruneDuplicates(filterAgentId, yes, dryRun) {
943
942
  break;
944
943
  }
945
944
  }
946
- totalRemoved += await executePrunePlan(plan);
945
+ allMoved.push(...(await executePrunePlan(plan)));
947
946
  processedAny = true;
948
947
  isFirst = false;
949
948
  console.log();
@@ -953,7 +952,8 @@ export async function pruneDuplicates(filterAgentId, yes, dryRun) {
953
952
  return;
954
953
  }
955
954
  if (processedAny) {
956
- console.log(chalk.bold(`Pruned ${totalRemoved} version${totalRemoved === 1 ? '' : 's'}.`));
955
+ console.log(chalk.bold(`Pruned ${allMoved.length} version${allMoved.length === 1 ? '' : 's'}.`));
956
+ printTrashFooter(allMoved);
957
957
  }
958
958
  }
959
959
  /**
package/dist/index.js CHANGED
@@ -66,6 +66,7 @@ import { registerSkillsCommands } from './commands/skills.js';
66
66
  import { registerRulesCommands } from './commands/rules.js';
67
67
  import { registerPermissionsCommands } from './commands/permissions.js';
68
68
  import { registerMcpCommands } from './commands/mcp.js';
69
+ import { registerCliCommands } from './commands/cli.js';
69
70
  import { registerVersionsCommands } from './commands/versions.js';
70
71
  import { registerImportCommand } from './commands/import.js';
71
72
  import { registerPackagesCommands } from './commands/packages.js';
@@ -120,11 +121,12 @@ Quick start:
120
121
  agents sessions Browse past sessions across all agents
121
122
 
122
123
  Agent versions:
123
- add <agent>[@version] Install an agent CLI (e.g. agents add codex)
124
+ add <agent>[@version] Install an agent CLI (e.g. agents add grok or agents add codex)
124
125
  import <agent> Adopt an existing global install (npm/homebrew) into agents-cli
125
- remove <agent>[@version] Uninstall a version
126
+ prune <agent>[@version] Uninstall a version
127
+ remove <agent>[@version] Alias for prune
126
128
  use <agent>@<version> Set the default version
127
- prune [target] Remove orphan resources and older duplicate version installs (targets: commands, sessions, runs, trash)
129
+ prune cleanup [target] Remove orphan resources and older duplicate version installs
128
130
  trash Inspect and restore soft-deleted version directories
129
131
  view [agent[@version]] List versions, or inspect one in detail
130
132
 
@@ -171,7 +173,7 @@ Automation tips:
171
173
  Pass explicit names/IDs Avoid pickers: agents sessions <id> --markdown
172
174
  Use --yes for defaults Auto-accept sync/default prompts on add/use/pull
173
175
  Use --names for central items e.g. agents commands add --names review-pr,debug
174
- Use agent@version targets e.g. --agents claude@2.1.79,codex@default
176
+ Use agent@version targets e.g. --agents grok@0.1.218,claude@2.1.79,codex@default
175
177
  Non-TTY shells apply defaults Omitted required selections fail with a plain hint
176
178
 
177
179
  Options:
@@ -361,7 +363,13 @@ async function promptUpgrade(latestVersion) {
361
363
  console.log();
362
364
  }
363
365
  }
364
- /** Fire-and-forget: refresh the registry cache in background. Never blocks the command. */
366
+ /**
367
+ * Background update check — fires once per 24h cache window.
368
+ * Network: GET registry.npmjs.org/@phnx-labs/agents-cli/latest.
369
+ * Disable: set AGENTS_CLI_DISABLE_AUTO_UPDATE=1 in shell rc.
370
+ *
371
+ * Fire-and-forget; never blocks the CLI's foreground operation.
372
+ */
365
373
  function refreshUpdateCacheInBackground() {
366
374
  fetch('https://registry.npmjs.org/@phnx-labs/agents-cli/latest', {
367
375
  signal: AbortSignal.timeout(2000),
@@ -529,6 +537,7 @@ program
529
537
  await program.parseAsync(['node', 'agents', ...args]);
530
538
  });
531
539
  registerMcpCommands(program);
540
+ registerCliCommands(program);
532
541
  registerSubagentsCommands(program);
533
542
  registerPluginsCommands(program);
534
543
  registerWorkflowsCommands(program);
@@ -726,7 +735,13 @@ if (firstRun) {
726
735
  // Every command requires the system repo to be cloned first. `setup` is the
727
736
  // only exemption — it's the command that does the cloning.
728
737
  const SETUP_EXEMPT_COMMANDS = new Set(['setup', 'help']);
729
- if (!firstRun && requestedCommand && !SETUP_EXEMPT_COMMANDS.has(requestedCommand)) {
738
+ // Help and version output are pure documentation — they must never gate on
739
+ // setup, otherwise `agents <cmd> --help` becomes useless on a fresh box.
740
+ const helpOrVersionRequested = passedArgs.some((arg) => arg === '--help' || arg === '-h' || arg === '--version' || arg === '-V');
741
+ if (!firstRun &&
742
+ requestedCommand &&
743
+ !SETUP_EXEMPT_COMMANDS.has(requestedCommand) &&
744
+ !helpOrVersionRequested) {
730
745
  const { ensureInitialized } = await import('./commands/setup.js');
731
746
  await ensureInitialized(program);
732
747
  }
@@ -771,5 +786,18 @@ catch (err) {
771
786
  if (err instanceof Error && err.name === 'ExitPromptError') {
772
787
  process.exit(130);
773
788
  }
789
+ // Browser-daemon-not-running and CDP-not-reachable surface as typed errors
790
+ // from src/lib/browser/. Don't dump a Node stacktrace for these — they are
791
+ // user-actionable, not engineering bugs. See issues #41 and #43.
792
+ if (err instanceof Error) {
793
+ const isBrowserDaemonNotRunning = err.name === 'BrowserDaemonNotRunningError';
794
+ const isBrowserCdpUnreachable = err.name === 'BrowserCdpConnectionError';
795
+ const isBrowserIpcDown = err.message.startsWith('IPC error:') &&
796
+ (err.message.includes('ECONNREFUSED') || err.message.includes('ENOENT'));
797
+ if (isBrowserDaemonNotRunning || isBrowserCdpUnreachable || isBrowserIpcDown) {
798
+ console.error(err.message);
799
+ process.exit(1);
800
+ }
801
+ }
774
802
  throw err;
775
803
  }