@phnx-labs/agents-cli 1.19.2 → 1.20.3

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 (156) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/README.md +72 -12
  3. package/dist/browser.js +0 -0
  4. package/dist/commands/browser.js +88 -16
  5. package/dist/commands/cli.d.ts +14 -0
  6. package/dist/commands/cli.js +244 -0
  7. package/dist/commands/cloud.js +1 -1
  8. package/dist/commands/commands.js +27 -10
  9. package/dist/commands/computer.js +18 -1
  10. package/dist/commands/doctor.d.ts +1 -1
  11. package/dist/commands/doctor.js +2 -2
  12. package/dist/commands/exec.js +38 -18
  13. package/dist/commands/factory.d.ts +3 -14
  14. package/dist/commands/factory.js +3 -3
  15. package/dist/commands/feedback.d.ts +7 -0
  16. package/dist/commands/feedback.js +89 -0
  17. package/dist/commands/helper.d.ts +12 -0
  18. package/dist/commands/helper.js +87 -0
  19. package/dist/commands/hooks.js +89 -10
  20. package/dist/commands/mcp.js +166 -10
  21. package/dist/commands/packages.js +196 -27
  22. package/dist/commands/permissions.js +21 -6
  23. package/dist/commands/plugins.js +11 -4
  24. package/dist/commands/profiles.d.ts +8 -0
  25. package/dist/commands/profiles.js +118 -5
  26. package/dist/commands/prune.js +39 -160
  27. package/dist/commands/pull.js +58 -5
  28. package/dist/commands/routines.js +107 -14
  29. package/dist/commands/rules.js +8 -4
  30. package/dist/commands/secrets-migrate.d.ts +24 -0
  31. package/dist/commands/secrets-migrate.js +198 -0
  32. package/dist/commands/secrets-sync.d.ts +11 -0
  33. package/dist/commands/secrets-sync.js +155 -0
  34. package/dist/commands/secrets.js +79 -46
  35. package/dist/commands/sessions.d.ts +28 -0
  36. package/dist/commands/sessions.js +98 -33
  37. package/dist/commands/setup.d.ts +1 -0
  38. package/dist/commands/setup.js +37 -28
  39. package/dist/commands/skills.js +25 -8
  40. package/dist/commands/subagents.js +69 -49
  41. package/dist/commands/teams.js +61 -10
  42. package/dist/commands/utils.d.ts +33 -0
  43. package/dist/commands/utils.js +139 -0
  44. package/dist/commands/versions.d.ts +4 -3
  45. package/dist/commands/versions.js +134 -130
  46. package/dist/commands/view.d.ts +6 -0
  47. package/dist/commands/view.js +175 -19
  48. package/dist/commands/workflows.js +29 -6
  49. package/dist/computer.js +0 -0
  50. package/dist/index.js +38 -6
  51. package/dist/lib/acp/client.js +6 -1
  52. package/dist/lib/acp/harnesses.js +8 -0
  53. package/dist/lib/agents.d.ts +4 -0
  54. package/dist/lib/agents.js +125 -34
  55. package/dist/lib/auto-pull-worker.js +18 -1
  56. package/dist/lib/browser/cdp.d.ts +8 -1
  57. package/dist/lib/browser/cdp.js +40 -3
  58. package/dist/lib/browser/chrome.d.ts +13 -0
  59. package/dist/lib/browser/chrome.js +46 -3
  60. package/dist/lib/browser/domain-skills.d.ts +51 -0
  61. package/dist/lib/browser/domain-skills.js +157 -0
  62. package/dist/lib/browser/drivers/local.js +45 -4
  63. package/dist/lib/browser/drivers/ssh.js +2 -2
  64. package/dist/lib/browser/ipc.d.ts +8 -1
  65. package/dist/lib/browser/ipc.js +37 -28
  66. package/dist/lib/browser/profiles.d.ts +16 -3
  67. package/dist/lib/browser/profiles.js +44 -4
  68. package/dist/lib/browser/service.d.ts +3 -0
  69. package/dist/lib/browser/service.js +40 -5
  70. package/dist/lib/browser/types.d.ts +11 -4
  71. package/dist/lib/cli-resources.d.ts +137 -0
  72. package/dist/lib/cli-resources.js +477 -0
  73. package/dist/lib/cloud/factory.d.ts +1 -1
  74. package/dist/lib/cloud/factory.js +1 -1
  75. package/dist/lib/cloud/rush.js +5 -5
  76. package/dist/lib/command-skills.js +0 -2
  77. package/dist/lib/computer-rpc.d.ts +3 -0
  78. package/dist/lib/computer-rpc.js +53 -0
  79. package/dist/lib/daemon.js +20 -0
  80. package/dist/lib/events.d.ts +16 -2
  81. package/dist/lib/events.js +33 -2
  82. package/dist/lib/exec.d.ts +42 -13
  83. package/dist/lib/exec.js +127 -33
  84. package/dist/lib/help.js +11 -5
  85. package/dist/lib/hooks/cache.d.ts +38 -0
  86. package/dist/lib/hooks/cache.js +242 -0
  87. package/dist/lib/hooks/profile.d.ts +33 -0
  88. package/dist/lib/hooks/profile.js +129 -0
  89. package/dist/lib/hooks.d.ts +0 -10
  90. package/dist/lib/hooks.js +246 -11
  91. package/dist/lib/mcp.d.ts +15 -0
  92. package/dist/lib/mcp.js +46 -0
  93. package/dist/lib/migrate.js +1 -1
  94. package/dist/lib/overdue.d.ts +26 -0
  95. package/dist/lib/overdue.js +101 -0
  96. package/dist/lib/permissions.d.ts +13 -0
  97. package/dist/lib/permissions.js +55 -1
  98. package/dist/lib/plugin-marketplace.js +1 -1
  99. package/dist/lib/plugins.js +15 -1
  100. package/dist/lib/profiles-presets.d.ts +26 -0
  101. package/dist/lib/profiles-presets.js +216 -0
  102. package/dist/lib/profiles.d.ts +34 -0
  103. package/dist/lib/profiles.js +112 -1
  104. package/dist/lib/resources/mcp.js +37 -0
  105. package/dist/lib/resources.d.ts +1 -1
  106. package/dist/lib/rotate.js +10 -4
  107. package/dist/lib/routines-format.d.ts +47 -0
  108. package/dist/lib/routines-format.js +194 -0
  109. package/dist/lib/routines.d.ts +8 -2
  110. package/dist/lib/routines.js +34 -14
  111. package/dist/lib/runner.js +83 -15
  112. package/dist/lib/scheduler.js +8 -1
  113. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  114. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  115. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
  116. package/dist/lib/secrets/bundles.d.ts +34 -17
  117. package/dist/lib/secrets/bundles.js +210 -36
  118. package/dist/lib/secrets/index.d.ts +49 -30
  119. package/dist/lib/secrets/index.js +126 -115
  120. package/dist/lib/secrets/install-helper.d.ts +45 -0
  121. package/dist/lib/secrets/install-helper.js +165 -0
  122. package/dist/lib/secrets/linux.js +4 -4
  123. package/dist/lib/secrets/sync.d.ts +56 -0
  124. package/dist/lib/secrets/sync.js +180 -0
  125. package/dist/lib/session/active.d.ts +8 -0
  126. package/dist/lib/session/active.js +3 -2
  127. package/dist/lib/session/db.d.ts +0 -4
  128. package/dist/lib/session/db.js +0 -26
  129. package/dist/lib/session/parse.d.ts +1 -0
  130. package/dist/lib/session/parse.js +44 -0
  131. package/dist/lib/session/render.js +4 -4
  132. package/dist/lib/session/types.d.ts +2 -2
  133. package/dist/lib/session/types.js +1 -1
  134. package/dist/lib/shims.d.ts +5 -2
  135. package/dist/lib/shims.js +70 -38
  136. package/dist/lib/state.d.ts +14 -2
  137. package/dist/lib/state.js +51 -20
  138. package/dist/lib/teams/agents.d.ts +5 -4
  139. package/dist/lib/teams/agents.js +48 -22
  140. package/dist/lib/teams/api.d.ts +2 -1
  141. package/dist/lib/teams/api.js +4 -3
  142. package/dist/lib/teams/parsers.d.ts +1 -1
  143. package/dist/lib/teams/parsers.js +153 -3
  144. package/dist/lib/teams/summarizer.js +18 -2
  145. package/dist/lib/teams/worktree.js +14 -3
  146. package/dist/lib/types.d.ts +63 -4
  147. package/dist/lib/types.js +8 -3
  148. package/dist/lib/usage.d.ts +27 -2
  149. package/dist/lib/usage.js +100 -17
  150. package/dist/lib/versions.d.ts +45 -3
  151. package/dist/lib/versions.js +455 -60
  152. package/package.json +15 -14
  153. package/scripts/install-helper.js +97 -0
  154. package/scripts/postinstall.js +16 -0
  155. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  156. package/npm-shrinkwrap.json +0 -3162
@@ -7,9 +7,9 @@ import { select, checkbox } from '@inquirer/prompts';
7
7
  import { AGENTS, SKILLS_CAPABLE_AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
8
8
  import { cloneRepo } from '../lib/git.js';
9
9
  import { discoverSkillsFromRepo, installSkillCentrally, listInstalledSkills, listInstalledSkillsWithScope, getSkillInfo, getSkillRules, getSkillsDir, countSkillFiles, tryParseSkillMetadata, diffVersionSkills, iterSkillsCapableVersions, removeSkillFromVersion, } from '../lib/skills.js';
10
- import { getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, resolveAgentVersionTargets, } from '../lib/versions.js';
10
+ import { getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, } from '../lib/versions.js';
11
11
  import { recordVersionResources } from '../lib/state.js';
12
- import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, promptRemovalTargets, } from './utils.js';
12
+ import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, resolveAgentTargetsAutoInstalling, promptRemovalTargets, } from './utils.js';
13
13
  import { showResourceList, buildTargetsSection, } from './resource-view.js';
14
14
  /** Register the `agents skills` command tree (list, add, remove, sync, prune, view). */
15
15
  export function registerSkillsCommands(program) {
@@ -202,11 +202,24 @@ Examples:
202
202
  spinner.succeed('Using local path');
203
203
  }
204
204
  }
205
- console.log(chalk.bold(`\nFound ${discoveredSkills.length} skill(s):`));
206
205
  if (discoveredSkills.length === 0) {
207
206
  console.log(chalk.yellow('No skills found (looking for SKILL.md files)'));
208
207
  return;
209
208
  }
209
+ // Filter by --names if provided. Mirrors the no-source path's behavior
210
+ // so users can pluck specific skills from a multi-skill repo.
211
+ const requestedNames = parseCommaSeparatedList(options.names);
212
+ if (requestedNames.length > 0) {
213
+ const discoveredNames = new Set(discoveredSkills.map((s) => s.name));
214
+ const missing = requestedNames.filter((n) => !discoveredNames.has(n));
215
+ if (missing.length > 0) {
216
+ console.log(chalk.red(`\nSkill(s) not found in source: ${missing.join(', ')}`));
217
+ console.log(chalk.gray(`Available: ${[...discoveredNames].join(', ')}`));
218
+ process.exit(1);
219
+ }
220
+ discoveredSkills = discoveredSkills.filter((s) => requestedNames.includes(s.name));
221
+ }
222
+ console.log(chalk.bold(`\nFound ${discoveredSkills.length} skill(s):`));
210
223
  for (const skill of discoveredSkills) {
211
224
  const nameColor = skill.parseError ? chalk.yellow : chalk.cyan;
212
225
  console.log(`\n ${nameColor(skill.name)}: ${skill.metadata.description || 'no description'}`);
@@ -238,13 +251,17 @@ Examples:
238
251
  let selectedAgents;
239
252
  let versionSelections;
240
253
  if (options.agents) {
241
- const result = resolveAgentVersionTargets(options.agents, SKILLS_CAPABLE_AGENTS);
254
+ const result = await resolveAgentTargetsAutoInstalling(options.agents, SKILLS_CAPABLE_AGENTS, { yes: options.yes });
255
+ if (!result) {
256
+ console.log(chalk.gray('Cancelled.'));
257
+ return;
258
+ }
242
259
  selectedAgents = result.selectedAgents;
243
260
  versionSelections = result.versionSelections;
244
261
  }
245
262
  else {
246
263
  const result = await promptAgentVersionSelection(SKILLS_CAPABLE_AGENTS, {
247
- skipPrompts: options.yes || !isInteractiveTerminal(),
264
+ skipPrompts: options.yes,
248
265
  });
249
266
  selectedAgents = result.selectedAgents;
250
267
  versionSelections = result.versionSelections;
@@ -394,17 +411,17 @@ Examples:
394
411
  .action(() => {
395
412
  console.error(chalk.red('"agents skills sync" is gone.'));
396
413
  console.error(chalk.gray('Sync runs automatically when you launch the agent.'));
397
- console.error(chalk.gray('To remove orphans, use: agents prune skills'));
414
+ console.error(chalk.gray('To remove orphans, use: agents prune cleanup skills'));
398
415
  process.exit(1);
399
416
  });
400
- // `skills prune` moved to the top-level `agents prune` command.
417
+ // `skills prune` moved to the top-level `agents prune cleanup` command.
401
418
  skillsCmd
402
419
  .command('prune', { hidden: true })
403
420
  .allowUnknownOption()
404
421
  .allowExcessArguments()
405
422
  .action(() => {
406
423
  console.error(chalk.red('"agents skills prune" moved.'));
407
- console.error(chalk.gray('Use: agents prune skills (or `agents prune` for everything)'));
424
+ console.error(chalk.gray('Use: agents prune cleanup skills (or `agents prune cleanup` for everything)'));
408
425
  process.exit(1);
409
426
  });
410
427
  skillsCmd
@@ -9,13 +9,12 @@ import chalk from 'chalk';
9
9
  import ora from 'ora';
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
- import { checkbox } from '@inquirer/prompts';
13
- import { AGENTS, agentLabel } from '../lib/agents.js';
12
+ import { agentLabel } from '../lib/agents.js';
14
13
  import { cloneRepo } from '../lib/git.js';
15
14
  import { discoverSubagentsFromRepo, installSubagentCentrally, listInstalledSubagents, getInstalledSubagent, listSubagentsForAgent, SUBAGENT_CAPABLE_AGENTS, iterSubagentsCapableVersions, removeSubagentFromVersion, } from '../lib/subagents.js';
16
- import { listInstalledVersions, syncResourcesToVersion, getGlobalDefault, getVersionHomePath, } from '../lib/versions.js';
17
- import { getSubagentsDir } from '../lib/state.js';
18
- import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection, requireDestructiveArg, promptRemovalTargets, } from './utils.js';
15
+ import { listInstalledVersions, syncResourcesToVersion, getGlobalDefault, getVersionHomePath, promptAgentVersionSelection, } from '../lib/versions.js';
16
+ import { getSubagentsDir, recordVersionResources } from '../lib/state.js';
17
+ import { requireDestructiveArg, promptRemovalTargets, parseCommaSeparatedList, resolveAgentTargetsAutoInstalling, } from './utils.js';
19
18
  import { showResourceList, buildTargetsSection, } from './resource-view.js';
20
19
  /** Replace the home directory prefix with ~ for display. */
21
20
  function formatPath(p) {
@@ -102,13 +101,20 @@ Examples:
102
101
  subagentsCmd
103
102
  .command('add <source>')
104
103
  .description('Install subagents from a source (GitHub, local path) and sync to agent versions')
105
- .option('-a, --agents <agents...>', 'Targets: claude, openclaw (defaults to all subagent-capable agents)')
104
+ .option('-a, --agents <list>', 'Targets: claude, openclaw, claude@2.1.141, claude@all, all')
105
+ .option('--names <list>', 'Subagent names from the source (comma-separated)')
106
106
  .option('-y, --yes', 'Skip all prompts and confirmations')
107
107
  .addHelpText('after', `
108
108
  Examples:
109
109
  # Install from GitHub
110
110
  agents subagents add gh:team/subagents --agents claude,openclaw
111
111
 
112
+ # Pluck specific subagents from a multi-subagent repo
113
+ agents subagents add gh:team/subagents --names code-reviewer,planner
114
+
115
+ # Install across every installed Claude version
116
+ agents subagents add gh:team/subagents --agents claude@all
117
+
112
118
  # Install from local directory (must contain subagents/*/AGENT.md)
113
119
  agents subagents add ~/my-subagent --agents claude
114
120
 
@@ -117,9 +123,13 @@ Examples:
117
123
  `)
118
124
  .action(async (source, options) => {
119
125
  const spinner = ora({ text: 'Fetching source...', isSilent: !process.stdout.isTTY }).start();
120
- // Clone or use local source
126
+ // Clone or use local source. Accept any git-like scheme to match the
127
+ // other <resource> add commands (skills, workflows, commands, hooks).
121
128
  let sourcePath;
122
- if (source.startsWith('gh:') || source.startsWith('http')) {
129
+ const isGitRepo = source.startsWith('gh:') || source.startsWith('git:') ||
130
+ source.startsWith('ssh:') || source.startsWith('https://') ||
131
+ source.startsWith('http://');
132
+ if (isGitRepo) {
123
133
  try {
124
134
  const cloneResult = await cloneRepo(source);
125
135
  sourcePath = cloneResult.localPath;
@@ -138,12 +148,24 @@ Examples:
138
148
  }
139
149
  // Discover subagents
140
150
  spinner.text = 'Discovering subagents...';
141
- const discovered = discoverSubagentsFromRepo(sourcePath);
151
+ let discovered = discoverSubagentsFromRepo(sourcePath);
142
152
  if (discovered.length === 0) {
143
153
  spinner.fail('No subagents found in source');
144
154
  console.log(chalk.gray(`Expected: subagents/*/AGENT.md`));
145
155
  process.exit(1);
146
156
  }
157
+ // --names filter: pluck specific subagents from a multi-subagent source.
158
+ const requestedNames = parseCommaSeparatedList(options.names);
159
+ if (requestedNames.length > 0) {
160
+ const discoveredNames = new Set(discovered.map((s) => s.name));
161
+ const missing = requestedNames.filter((n) => !discoveredNames.has(n));
162
+ if (missing.length > 0) {
163
+ spinner.fail(`Subagent(s) not found in source: ${missing.join(', ')}`);
164
+ console.log(chalk.gray(`Available: ${[...discoveredNames].join(', ')}`));
165
+ process.exit(1);
166
+ }
167
+ discovered = discovered.filter((s) => requestedNames.includes(s.name));
168
+ }
147
169
  spinner.succeed(`Found ${discovered.length} subagent(s)`);
148
170
  // Show what we found
149
171
  console.log();
@@ -151,42 +173,29 @@ Examples:
151
173
  console.log(` ${chalk.cyan(sub.name)}: ${chalk.gray(sub.frontmatter.description)}`);
152
174
  }
153
175
  console.log();
154
- // Determine target agents
155
- let targetAgents = options.agents || [];
156
- if (targetAgents.length === 0 && !options.yes) {
157
- // Prompt for target agents
158
- const installedAgents = SUBAGENT_CAPABLE_AGENTS.filter(id => {
159
- const versions = listInstalledVersions(id);
160
- return versions.length > 0;
161
- });
162
- if (installedAgents.length === 0) {
163
- console.log(chalk.yellow('No subagent-capable agents installed'));
164
- console.log(chalk.gray('Subagents will be stored centrally and synced when you install claude or openclaw'));
165
- targetAgents = [];
166
- }
167
- else {
168
- if (!isInteractiveTerminal()) {
169
- requireInteractiveSelection('Selecting target agents for subagents', [
170
- 'agents subagents add <source> --agents claude openclaw',
171
- 'agents subagents add <source> --yes',
172
- ]);
173
- }
174
- try {
175
- targetAgents = await checkbox({
176
- message: 'Install to which agents?',
177
- choices: installedAgents.map(id => ({
178
- name: AGENTS[id].name,
179
- value: id,
180
- checked: true,
181
- })),
182
- });
183
- }
184
- catch (err) {
185
- if (isPromptCancelled(err))
186
- return;
187
- throw err;
188
- }
176
+ // Determine target agent versions, using the same path skills/workflows use.
177
+ // Back-compat: commander's old `--agents <agents...>` shape arrives as an array;
178
+ // join it with commas so resolveAgentVersionTargets can parse it.
179
+ const agentsArg = Array.isArray(options.agents)
180
+ ? options.agents.join(',')
181
+ : options.agents;
182
+ let selectedAgents;
183
+ let versionSelections;
184
+ if (agentsArg) {
185
+ const result = await resolveAgentTargetsAutoInstalling(agentsArg, SUBAGENT_CAPABLE_AGENTS, { yes: options.yes });
186
+ if (!result) {
187
+ console.log(chalk.gray('Cancelled.'));
188
+ return;
189
189
  }
190
+ selectedAgents = result.selectedAgents;
191
+ versionSelections = result.versionSelections;
192
+ }
193
+ else {
194
+ const result = await promptAgentVersionSelection(SUBAGENT_CAPABLE_AGENTS, {
195
+ skipPrompts: options.yes,
196
+ });
197
+ selectedAgents = result.selectedAgents;
198
+ versionSelections = result.versionSelections;
190
199
  }
191
200
  // Install centrally
192
201
  const installSpinner = ora({ text: 'Installing subagents...', isSilent: !process.stdout.isTTY }).start();
@@ -198,16 +207,27 @@ Examples:
198
207
  }
199
208
  }
200
209
  installSpinner.succeed(`Installed ${discovered.length} subagent(s) to ${formatPath(getSubagentsDir())}`);
201
- // Sync to target agents
202
- if (targetAgents.length > 0) {
210
+ // Sync to selected versions
211
+ if (versionSelections.size > 0) {
203
212
  const syncSpinner = ora({ text: 'Syncing to agents...', isSilent: !process.stdout.isTTY }).start();
204
- for (const agentId of targetAgents) {
205
- const versions = listInstalledVersions(agentId);
213
+ const subagentNames = discovered.map((s) => s.name);
214
+ let synced = 0;
215
+ for (const [agentId, versions] of versionSelections) {
206
216
  for (const version of versions) {
207
217
  syncResourcesToVersion(agentId, version);
218
+ recordVersionResources(agentId, version, 'subagents', subagentNames);
219
+ synced++;
208
220
  }
209
221
  }
210
- syncSpinner.succeed(`Synced to ${targetAgents.map(id => agentLabel(id)).join(', ')}`);
222
+ if (synced > 0) {
223
+ syncSpinner.succeed(`Synced to ${synced} agent version(s) across ${selectedAgents.map((id) => agentLabel(id)).join(', ')}`);
224
+ }
225
+ else {
226
+ syncSpinner.info('No version-managed agents to sync');
227
+ }
228
+ }
229
+ else {
230
+ console.log(chalk.gray('Stored centrally; no agent versions selected for sync.'));
211
231
  }
212
232
  console.log();
213
233
  });
@@ -14,6 +14,7 @@ import { buildPreview as buildSessionPreview } from './sessions-picker.js';
14
14
  import { parseExecEnv } from '../lib/exec.js';
15
15
  import { teamPicker, printTeamTable } from './teams-picker.js';
16
16
  import { itemPicker } from '../lib/picker.js';
17
+ import { profileExists, readProfile } from '../lib/profiles.js';
17
18
  import { isPromptCancelled, isInteractiveTerminal, requireDestructiveArg, requireInteractiveSelection, } from './utils.js';
18
19
  const AGENT_NAMES = {
19
20
  claude: 'Claude',
@@ -21,9 +22,12 @@ const AGENT_NAMES = {
21
22
  gemini: 'Gemini',
22
23
  cursor: 'Cursor',
23
24
  opencode: 'OpenCode',
25
+ grok: 'Grok',
26
+ antigravity: 'Antigravity',
24
27
  };
25
28
  const VALID_AGENTS = Object.keys(AGENT_NAMES);
26
- const VALID_MODES = ['plan', 'edit', 'full'];
29
+ // 'full' kept as historical alias for 'skip'; normalized to 'skip' downstream.
30
+ const VALID_MODES = ['plan', 'edit', 'auto', 'skip', 'full'];
27
31
  const VALID_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'auto'];
28
32
  const VALID_CLOUD_PROVIDERS = ['rush', 'codex', 'factory'];
29
33
  // Auto-enable JSON mode when piped / not a TTY so AI agent consumers get
@@ -85,14 +89,48 @@ function fullName(type, version) {
85
89
  const name = AGENT_NAMES[type];
86
90
  return version ? `${name} ${version}` : name;
87
91
  }
92
+ /**
93
+ * Resolve a teammate spec to its execution target.
94
+ *
95
+ * Accepts:
96
+ * - `claude` — default version of an installed agent
97
+ * - `claude@2.1.112` — pinned version of an installed agent
98
+ * - `<profile-name>` — runs through `agents run <profile>`, with the
99
+ * profile's host agent used as the underlying
100
+ * AgentType for event parsing and CLI checks.
101
+ *
102
+ * `agent` is always the underlying harness so event parsers, CLI-availability
103
+ * checks, and version pins keep working. `profileName` is set only when the
104
+ * spec resolved through a profile.
105
+ */
88
106
  function parseTeammate(spec) {
89
107
  const [name, version] = spec.split('@');
90
- if (!VALID_AGENTS.includes(name)) {
91
- die(`Unknown teammate '${name}'. Available: ${VALID_AGENTS.join(', ')}.\n` +
92
- ` Use the form 'claude' or 'claude@2.1.112' (see 'agents view' for installed versions).`);
108
+ if (VALID_AGENTS.includes(name)) {
109
+ const agent = name;
110
+ return {
111
+ agent,
112
+ version: resolveVersionAlias(agent, version) ?? null,
113
+ profileName: null,
114
+ };
115
+ }
116
+ // Not a built-in agent id — try resolving as a profile name. A profile
117
+ // pinning a version is allowed; `profile@<override>` is not (would conflict
118
+ // with the profile's own host.version).
119
+ if (!version && profileExists(name)) {
120
+ try {
121
+ const profile = readProfile(name);
122
+ return {
123
+ agent: profile.host.agent,
124
+ version: profile.host.version ?? null,
125
+ profileName: profile.name,
126
+ };
127
+ }
128
+ catch (err) {
129
+ die(`Profile '${name}' is malformed: ${err.message}`);
130
+ }
93
131
  }
94
- const agent = name;
95
- return { agent, version: resolveVersionAlias(agent, version) ?? null };
132
+ die(`Unknown teammate '${spec}'. Available agents: ${VALID_AGENTS.join(', ')}.\n` +
133
+ ` Use 'claude', 'claude@2.1.112', or the name of a profile from 'agents view'.`);
96
134
  }
97
135
  function shortId(id) {
98
136
  return id.slice(0, 8);
@@ -545,6 +583,8 @@ export function registerTeamsCommands(program) {
545
583
  Teammate syntax:
546
584
  'claude' the default Claude version on this machine
547
585
  'claude@2.1.112' a specific installed version (see 'agents view')
586
+ '<profile>' a profile from 'agents view' — runs through 'agents
587
+ run <profile>' with the profile's host harness
548
588
 
549
589
  Short aliases:
550
590
  teams c = create teams a = add teams s = status
@@ -709,7 +749,7 @@ export function registerTeamsCommands(program) {
709
749
  .alias('a')
710
750
  .description("Add a teammate to work on a task. Runs in background; returns immediately. Use 'status' to check in.")
711
751
  .option('-n, --name <name>', 'Friendly name for this teammate (e.g. alice). Required if using --after. Unique within team.')
712
- .option('-m, --mode <mode>', `Permissions: plan (read-only) | edit (can write files) | full (write + skip permission prompts)`, 'edit')
752
+ .option('-m, --mode <mode>', `Permissions: plan (read-only) | edit (can write files) | auto (smart classifier auto-approves safe ops) | skip (bypass all permission prompts). 'full' accepted as alias for skip.`, 'edit')
713
753
  .option('-e, --effort <effort>', `Reasoning intensity: ${VALID_EFFORTS.join('|')}`, 'medium')
714
754
  .option('--model <model>', 'Override the effort tier and use this specific model (e.g. claude-opus-4-6)')
715
755
  .option('--env <key=value>', 'Set an environment variable for this teammate (repeatable for multiple vars)', (val, prev) => [...prev, val], [])
@@ -745,7 +785,7 @@ export function registerTeamsCommands(program) {
745
785
  die(`--cloud rush requires --repo <owner/repo>`);
746
786
  }
747
787
  }
748
- const { agent, version } = parseTeammate(teammate);
788
+ const { agent, version, profileName } = parseTeammate(teammate);
749
789
  if (version && !isVersionInstalled(agent, version)) {
750
790
  die(`${AGENT_NAMES[agent]} ${version} isn't installed.\n` +
751
791
  ` Install it: agents add ${agent}@${version}\n` +
@@ -756,6 +796,11 @@ export function registerTeamsCommands(program) {
756
796
  die(`Invalid teammate name '${opts.name}'. Use letters, numbers, '-', or '_'.`);
757
797
  }
758
798
  }
799
+ if (opts.worktree !== undefined) {
800
+ if (!opts.worktree || !/^[A-Za-z0-9_-]+$/.test(opts.worktree)) {
801
+ die(`Invalid worktree name '${opts.worktree}'. Use letters, numbers, '-', or '_'.`);
802
+ }
803
+ }
759
804
  const after = opts.after
760
805
  ? opts.after.split(',').map((s) => s.trim()).filter(Boolean)
761
806
  : [];
@@ -865,12 +910,12 @@ export function registerTeamsCommands(program) {
865
910
  }
866
911
  }
867
912
  try {
868
- const result = await handleSpawn(mgr, team, agent, effectiveTask, cwd, opts.mode, opts.effort, null, cwd, version, opts.name ?? null, after, opts.model ?? null, envOverrides ?? null, taskType, cloudProviderId, cloudSessionId, opts.repo ?? null, opts.branch ?? null, worktreeName, worktreePath);
913
+ const result = await handleSpawn(mgr, team, agent, effectiveTask, cwd, opts.mode, opts.effort, null, cwd, version, opts.name ?? null, after, opts.model ?? null, envOverrides ?? null, taskType, cloudProviderId, cloudSessionId, opts.repo ?? null, opts.branch ?? null, worktreeName, worktreePath, profileName);
869
914
  if (isJsonMode(opts)) {
870
915
  console.log(JSON.stringify(result, null, 2));
871
916
  return;
872
917
  }
873
- const who = fullName(agent, version);
918
+ const who = profileName ? `${profileName} (via ${fullName(agent, version)})` : fullName(agent, version);
874
919
  const staged = result.status === 'pending';
875
920
  const verb = staged ? 'Staged' : 'Welcomed';
876
921
  const greeting = result.name
@@ -899,6 +944,12 @@ export function registerTeamsCommands(program) {
899
944
  console.log();
900
945
  if (staged) {
901
946
  console.log(chalk.gray(`Start the ready teammates: agents teams start ${team}`));
947
+ if (after.length > 0) {
948
+ process.stderr.write(chalk.yellow(`\nWarning: this teammate has --after dependencies and will NEVER start on its own.\n` +
949
+ ` A supervisor watch process is required to launch it when its deps complete.\n` +
950
+ ` Run this in another terminal:\n` +
951
+ ` agents teams start ${team} --watch\n`));
952
+ }
902
953
  }
903
954
  else {
904
955
  console.log(chalk.gray(`Check in later: agents teams status ${team}`));
@@ -4,6 +4,8 @@
4
4
  * Small helpers used across multiple commands: prompt cancellation detection,
5
5
  * table formatting, spinner management, and platform-specific workarounds.
6
6
  */
7
+ import type { AgentId } from '../lib/types.js';
8
+ import { type InstalledAgentTargetResult, type VersionSelectionResult } from '../lib/versions.js';
7
9
  /**
8
10
  * Check if an error is from user cancelling a prompt (Ctrl+C)
9
11
  */
@@ -58,3 +60,34 @@ export declare function promptRemovalTargets(resourceName: string, targets: Remo
58
60
  * Format a path for display, using ~ for home directory
59
61
  */
60
62
  export declare function formatPath(fullPath: string, cwd?: string): string;
63
+ /**
64
+ * Make sure every specific `agent@x.y.z` the user typed is installed before
65
+ * the caller resolves targets. Returns true if the caller should continue,
66
+ * false if the user declined the prompt. Exported so non-standard caller
67
+ * shapes (e.g. mcp.ts's manifest-shaped parser) can run the pre-flight
68
+ * without going through resolveAgentVersionTargets first.
69
+ */
70
+ export declare function ensureAgentVersionsInstalled(value: string, availableAgents: readonly AgentId[], options?: {
71
+ yes?: boolean;
72
+ }): Promise<boolean>;
73
+ /**
74
+ * Resolve a `--agents` selector and, if any requested `agent@version` isn't
75
+ * installed yet, prompt to install it (or auto-install with --yes) before
76
+ * delegating to resolveAgentVersionTargets. Returns null when the user
77
+ * declines the install prompt — callers should treat that as a clean cancel.
78
+ */
79
+ export declare function resolveAgentTargetsAutoInstalling(value: string, availableAgents: readonly AgentId[], options?: {
80
+ yes?: boolean;
81
+ allVersions?: boolean;
82
+ }): Promise<VersionSelectionResult | null>;
83
+ /**
84
+ * Same as resolveAgentTargetsAutoInstalling but returns the broader
85
+ * InstalledAgentTargetResult that includes `directAgents` (for paths like
86
+ * `agents install` and `mcp register` that fall through to unmanaged homes
87
+ * when no managed version is installed).
88
+ */
89
+ export declare function resolveInstalledAgentTargetsAutoInstalling(value: string, availableAgents: readonly AgentId[], options?: {
90
+ yes?: boolean;
91
+ allVersions?: boolean;
92
+ }): Promise<InstalledAgentTargetResult | null>;
93
+ export { VersionNotInstalledError } from '../lib/versions.js';
@@ -7,6 +7,10 @@
7
7
  import * as os from 'os';
8
8
  import { spawnSync } from 'child_process';
9
9
  import chalk from 'chalk';
10
+ import ora from 'ora';
11
+ import { confirm } from '@inquirer/prompts';
12
+ import { agentLabel, resolveAgentName } from '../lib/agents.js';
13
+ import { installVersion, listInstalledVersions, resolveAgentVersionTargets, resolveInstalledAgentTargets, } from '../lib/versions.js';
10
14
  /**
11
15
  * Check if an error is from user cancelling a prompt (Ctrl+C)
12
16
  */
@@ -135,3 +139,138 @@ export function formatPath(fullPath, cwd) {
135
139
  }
136
140
  return fullPath;
137
141
  }
142
+ /**
143
+ * Parse a --agents selector and collect every (agentId, specificVersion) pair
144
+ * the user requested where the version is a concrete x.y.z (not `default`,
145
+ * not `all`, not `latest`) and is NOT currently installed.
146
+ *
147
+ * This is the lookahead the auto-install wrappers use to decide whether to
148
+ * prompt + install before delegating to resolveAgentVersionTargets.
149
+ */
150
+ function collectMissingVersions(value, availableAgents) {
151
+ const missing = [];
152
+ const seen = new Set();
153
+ for (const raw of value.split(',').map((s) => s.trim()).filter(Boolean)) {
154
+ // Literal `all` / `all@all` expand to per-agent — never missing.
155
+ if (raw === 'all' || raw === 'all@all')
156
+ continue;
157
+ const atIndex = raw.indexOf('@');
158
+ if (atIndex === -1)
159
+ continue; // bare agent → resolves to default; never missing in this sense
160
+ const agentToken = raw.slice(0, atIndex).trim();
161
+ const versionToken = raw.slice(atIndex + 1).trim();
162
+ // Non-specific selectors handled by the underlying resolver.
163
+ if (!versionToken || versionToken === 'default' || versionToken === 'all')
164
+ continue;
165
+ const agentId = resolveAgentName(agentToken);
166
+ if (!agentId || !availableAgents.includes(agentId))
167
+ continue;
168
+ const installed = listInstalledVersions(agentId);
169
+ if (installed.includes(versionToken))
170
+ continue;
171
+ const key = `${agentId}@${versionToken}`;
172
+ if (seen.has(key))
173
+ continue;
174
+ seen.add(key);
175
+ missing.push({ agentId, version: versionToken });
176
+ }
177
+ return missing;
178
+ }
179
+ /**
180
+ * Sequentially install every requested missing version with a per-version
181
+ * spinner. Aborts via process.exit(1) on the first failure — the user
182
+ * already approved the install so a partial-install outcome is worse than
183
+ * a hard stop.
184
+ */
185
+ async function installMissingVersions(missing) {
186
+ for (const { agentId, version } of missing) {
187
+ const label = `${agentLabel(agentId)}@${version}`;
188
+ const spinner = ora(`Installing ${label}...`).start();
189
+ try {
190
+ const result = await installVersion(agentId, version, (msg) => {
191
+ spinner.text = msg;
192
+ });
193
+ if (!result.success) {
194
+ spinner.fail(`Failed to install ${label}: ${result.error ?? 'unknown error'}`);
195
+ process.exit(1);
196
+ }
197
+ spinner.succeed(`Installed ${label}`);
198
+ }
199
+ catch (err) {
200
+ spinner.fail(`Failed to install ${label}: ${err.message}`);
201
+ process.exit(1);
202
+ }
203
+ }
204
+ }
205
+ /**
206
+ * Make sure every specific `agent@x.y.z` the user typed is installed before
207
+ * the caller resolves targets. Returns true if the caller should continue,
208
+ * false if the user declined the prompt. Exported so non-standard caller
209
+ * shapes (e.g. mcp.ts's manifest-shaped parser) can run the pre-flight
210
+ * without going through resolveAgentVersionTargets first.
211
+ */
212
+ export async function ensureAgentVersionsInstalled(value, availableAgents, options = {}) {
213
+ const missing = collectMissingVersions(value, availableAgents);
214
+ if (missing.length === 0)
215
+ return true;
216
+ const summary = missing.map((m) => `${agentLabel(m.agentId)}@${m.version}`).join(', ');
217
+ if (!options.yes) {
218
+ if (!isInteractiveTerminal()) {
219
+ console.error(chalk.red(`Missing agent version(s): ${summary}`));
220
+ console.error(chalk.gray('In a scripted shell, opt in to auto-install:'));
221
+ console.error(chalk.cyan(` rerun with --yes`));
222
+ console.error(chalk.gray('Or pre-install:'));
223
+ for (const m of missing) {
224
+ console.error(chalk.cyan(` agents add ${m.agentId}@${m.version}`));
225
+ }
226
+ process.exit(1);
227
+ }
228
+ console.log(chalk.yellow(`\nThe following agent version(s) are not installed:`));
229
+ for (const m of missing) {
230
+ console.log(` ${chalk.cyan(`${agentLabel(m.agentId)}@${m.version}`)}`);
231
+ }
232
+ let proceed;
233
+ try {
234
+ proceed = await confirm({
235
+ message: `Install ${missing.length} missing version${missing.length === 1 ? '' : 's'}?`,
236
+ default: true,
237
+ });
238
+ }
239
+ catch (err) {
240
+ if (isPromptCancelled(err))
241
+ return false;
242
+ throw err;
243
+ }
244
+ if (!proceed)
245
+ return false;
246
+ }
247
+ await installMissingVersions(missing);
248
+ return true;
249
+ }
250
+ /**
251
+ * Resolve a `--agents` selector and, if any requested `agent@version` isn't
252
+ * installed yet, prompt to install it (or auto-install with --yes) before
253
+ * delegating to resolveAgentVersionTargets. Returns null when the user
254
+ * declines the install prompt — callers should treat that as a clean cancel.
255
+ */
256
+ export async function resolveAgentTargetsAutoInstalling(value, availableAgents, options = {}) {
257
+ const ok = await ensureAgentVersionsInstalled(value, availableAgents, options);
258
+ if (!ok)
259
+ return null;
260
+ return resolveAgentVersionTargets(value, availableAgents, { allVersions: options.allVersions });
261
+ }
262
+ /**
263
+ * Same as resolveAgentTargetsAutoInstalling but returns the broader
264
+ * InstalledAgentTargetResult that includes `directAgents` (for paths like
265
+ * `agents install` and `mcp register` that fall through to unmanaged homes
266
+ * when no managed version is installed).
267
+ */
268
+ export async function resolveInstalledAgentTargetsAutoInstalling(value, availableAgents, options = {}) {
269
+ const ok = await ensureAgentVersionsInstalled(value, availableAgents, options);
270
+ if (!ok)
271
+ return null;
272
+ return resolveInstalledAgentTargets(value, availableAgents, { allVersions: options.allVersions });
273
+ }
274
+ // Re-export so callers can `catch (err) { if (err instanceof VersionNotInstalledError) … }`
275
+ // without reaching into ../lib/versions directly.
276
+ export { VersionNotInstalledError } from '../lib/versions.js';
@@ -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;