@phnx-labs/agents-cli 1.14.7 → 1.16.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 (105) hide show
  1. package/CHANGELOG.md +78 -39
  2. package/README.md +74 -7
  3. package/dist/commands/alias.js +2 -2
  4. package/dist/commands/beta.js +6 -1
  5. package/dist/commands/browser-picker.d.ts +21 -0
  6. package/dist/commands/browser-picker.js +114 -0
  7. package/dist/commands/browser.js +546 -75
  8. package/dist/commands/commands.js +72 -22
  9. package/dist/commands/daemon.js +2 -2
  10. package/dist/commands/exec.js +9 -2
  11. package/dist/commands/fork.js +2 -2
  12. package/dist/commands/hooks.js +71 -26
  13. package/dist/commands/mcp.js +85 -43
  14. package/dist/commands/plugins.js +48 -15
  15. package/dist/commands/prune.d.ts +0 -20
  16. package/dist/commands/prune.js +291 -16
  17. package/dist/commands/pull.js +3 -3
  18. package/dist/commands/repo.js +1 -1
  19. package/dist/commands/routines.js +2 -2
  20. package/dist/commands/secrets.js +37 -1
  21. package/dist/commands/sessions.js +62 -19
  22. package/dist/commands/{init.d.ts → setup.d.ts} +7 -6
  23. package/dist/commands/{init.js → setup.js} +32 -21
  24. package/dist/commands/skills.js +60 -19
  25. package/dist/commands/subagents.js +41 -13
  26. package/dist/commands/teams.js +2 -3
  27. package/dist/commands/usage.js +6 -0
  28. package/dist/commands/utils.d.ts +16 -0
  29. package/dist/commands/utils.js +32 -0
  30. package/dist/commands/versions.js +8 -6
  31. package/dist/commands/view.js +61 -16
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +17 -20
  34. package/dist/lib/agents.js +2 -2
  35. package/dist/lib/auto-pull-worker.js +2 -3
  36. package/dist/lib/auto-pull.js +2 -2
  37. package/dist/lib/browser/cdp.d.ts +7 -1
  38. package/dist/lib/browser/cdp.js +29 -1
  39. package/dist/lib/browser/chrome.js +6 -3
  40. package/dist/lib/browser/devices.d.ts +4 -0
  41. package/dist/lib/browser/devices.js +27 -0
  42. package/dist/lib/browser/drivers/local.js +9 -4
  43. package/dist/lib/browser/drivers/ssh.d.ts +1 -0
  44. package/dist/lib/browser/drivers/ssh.js +32 -4
  45. package/dist/lib/browser/ipc.js +145 -23
  46. package/dist/lib/browser/profiles.d.ts +5 -2
  47. package/dist/lib/browser/profiles.js +77 -37
  48. package/dist/lib/browser/service.d.ts +84 -13
  49. package/dist/lib/browser/service.js +806 -122
  50. package/dist/lib/browser/types.d.ts +81 -3
  51. package/dist/lib/browser/types.js +16 -0
  52. package/dist/lib/cloud/rush.js +2 -2
  53. package/dist/lib/cloud/store.js +2 -2
  54. package/dist/lib/commands.d.ts +1 -0
  55. package/dist/lib/commands.js +6 -2
  56. package/dist/lib/daemon.js +6 -7
  57. package/dist/lib/doctor-diff.js +4 -4
  58. package/dist/lib/events.d.ts +94 -1
  59. package/dist/lib/events.js +264 -6
  60. package/dist/lib/exec.js +16 -10
  61. package/dist/lib/hooks.d.ts +11 -7
  62. package/dist/lib/hooks.js +125 -49
  63. package/dist/lib/migrate.d.ts +1 -1
  64. package/dist/lib/migrate.js +1178 -21
  65. package/dist/lib/models.js +2 -2
  66. package/dist/lib/permissions.d.ts +14 -11
  67. package/dist/lib/permissions.js +46 -42
  68. package/dist/lib/plugins.d.ts +30 -1
  69. package/dist/lib/plugins.js +75 -3
  70. package/dist/lib/pty-server.js +9 -10
  71. package/dist/lib/resources/hooks.d.ts +5 -1
  72. package/dist/lib/resources/hooks.js +21 -4
  73. package/dist/lib/rotate.js +3 -4
  74. package/dist/lib/routines.d.ts +15 -0
  75. package/dist/lib/routines.js +68 -0
  76. package/dist/lib/runner.js +9 -5
  77. package/dist/lib/secrets/index.d.ts +14 -11
  78. package/dist/lib/secrets/index.js +49 -21
  79. package/dist/lib/secrets/linux.d.ts +27 -0
  80. package/dist/lib/secrets/linux.js +161 -0
  81. package/dist/lib/session/active.d.ts +3 -0
  82. package/dist/lib/session/active.js +92 -6
  83. package/dist/lib/session/cloud.js +2 -2
  84. package/dist/lib/session/db.d.ts +4 -0
  85. package/dist/lib/session/db.js +34 -3
  86. package/dist/lib/session/discover.js +30 -15
  87. package/dist/lib/session/team-filter.js +2 -2
  88. package/dist/lib/shims.d.ts +2 -2
  89. package/dist/lib/shims.js +6 -6
  90. package/dist/lib/skills.js +6 -2
  91. package/dist/lib/state.d.ts +86 -14
  92. package/dist/lib/state.js +150 -23
  93. package/dist/lib/subagents.d.ts +28 -0
  94. package/dist/lib/subagents.js +98 -1
  95. package/dist/lib/sync-manifest.d.ts +1 -1
  96. package/dist/lib/sync-manifest.js +3 -3
  97. package/dist/lib/teams/persistence.js +15 -5
  98. package/dist/lib/teams/registry.js +2 -2
  99. package/dist/lib/types.d.ts +32 -3
  100. package/dist/lib/types.js +3 -3
  101. package/dist/lib/usage.d.ts +1 -1
  102. package/dist/lib/usage.js +15 -48
  103. package/dist/lib/versions.js +31 -21
  104. package/package.json +1 -1
  105. package/scripts/postinstall.js +37 -9
@@ -1,7 +1,7 @@
1
1
  /**
2
- * First-run initialization command.
2
+ * First-run setup command.
3
3
  *
4
- * Registers the `agents init` command which clones the system repo into
4
+ * Registers the `agents setup` command which clones the system repo into
5
5
  * ~/.agents-system/ and installs agent CLIs with resource syncing.
6
6
  */
7
7
  import chalk from 'chalk';
@@ -52,13 +52,13 @@ async function importAgent(agentId, version) {
52
52
  }
53
53
  }
54
54
  /** First-run setup. Clones ~/.agents-system/ from the system repo if needed. */
55
- export async function runInit(program, options = {}) {
55
+ export async function runSetup(program, options = {}) {
56
56
  const agentsDir = getAgentsDir();
57
57
  const alreadyConfigured = isGitRepo(agentsDir);
58
58
  if (alreadyConfigured && !options.force) {
59
59
  console.log(chalk.gray('~/.agents-system/ is already set up.'));
60
60
  console.log(chalk.gray('\nTo sync updates: agents repo pull system'));
61
- console.log(chalk.gray('To re-initialize: agents init --force'));
61
+ console.log(chalk.gray('To re-run setup: agents setup --force'));
62
62
  return;
63
63
  }
64
64
  // Detect existing installations BEFORE cloning (they won't exist after if we import)
@@ -76,16 +76,26 @@ export async function runInit(program, options = {}) {
76
76
  const result = await pullRepo(agentsDir);
77
77
  if (!result.success) {
78
78
  spinner.fail(`Pull failed: ${result.error}`);
79
- console.log(chalk.gray('Fix the issue and re-run: agents init --force'));
79
+ console.log(chalk.gray('Fix the issue and re-run: agents setup --force'));
80
80
  process.exit(1);
81
81
  }
82
82
  spinner.succeed(`Updated to ${result.commit}`);
83
83
  }
84
84
  else {
85
+ // Check git is available
86
+ try {
87
+ const { execSync } = await import('child_process');
88
+ execSync('which git', { stdio: 'ignore' });
89
+ }
90
+ catch {
91
+ spinner.fail('git is not installed');
92
+ console.log(chalk.gray('Install git first: https://git-scm.com/downloads'));
93
+ process.exit(1);
94
+ }
85
95
  const result = await cloneIntoExisting(DEFAULT_SYSTEM_REPO, agentsDir);
86
96
  if (!result.success) {
87
97
  spinner.fail(`Clone failed: ${result.error}`);
88
- console.log(chalk.gray('Fix the issue and re-run: agents init --force'));
98
+ console.log(chalk.gray('Fix the issue and re-run: agents setup --force'));
89
99
  process.exit(1);
90
100
  }
91
101
  spinner.succeed(`Cloned ${systemRepoSlug(DEFAULT_SYSTEM_REPO)} (${result.commit})`);
@@ -146,6 +156,8 @@ export async function runInit(program, options = {}) {
146
156
  }
147
157
  }
148
158
  }
159
+ if (options.suppressFooter)
160
+ return;
149
161
  console.log(chalk.bold('\nSetup complete. Try:'));
150
162
  console.log(chalk.cyan(' agents view ') + chalk.gray(' # see what\'s installed'));
151
163
  console.log(chalk.cyan(' agents run <agent> "hello" ') + chalk.gray(' # run an agent'));
@@ -155,7 +167,7 @@ export async function runInit(program, options = {}) {
155
167
  /**
156
168
  * Ensure the system repo exists before running a command that needs it.
157
169
  * If ~/.agents-system/ is not a git repo AND we're in an interactive TTY,
158
- * prompt the user to run init now. In non-interactive mode, print a clear
170
+ * prompt the user to run setup now. In non-interactive mode, print a clear
159
171
  * error and exit.
160
172
  */
161
173
  export async function ensureInitialized(program) {
@@ -163,34 +175,33 @@ export async function ensureInitialized(program) {
163
175
  if (isGitRepo(agentsDir))
164
176
  return;
165
177
  if (!isInteractiveTerminal()) {
166
- console.error(chalk.red('agents-cli is not initialized. Run: agents init'));
178
+ console.error(chalk.red('agents-cli is not set up. Run: agents setup'));
167
179
  process.exit(1);
168
180
  }
169
181
  console.log(chalk.yellow('\nagents-cli has not been set up yet.'));
170
182
  const proceed = await confirm({
171
- message: 'Run `agents init` now?',
183
+ message: 'Run `agents setup` now?',
172
184
  default: true,
173
185
  }).catch(() => false);
174
186
  if (!proceed) {
175
- console.log(chalk.gray('Skipped. Run `agents init` when ready.'));
187
+ console.log(chalk.gray('Skipped. Run `agents setup` when ready.'));
176
188
  process.exit(0);
177
189
  }
178
- await runInit(program);
179
- process.exit(0);
190
+ await runSetup(program, { suppressFooter: true });
180
191
  }
181
- /** Register the `agents init` command. */
182
- export function registerInitCommand(program) {
192
+ /** Register the `agents setup` command. */
193
+ export function registerSetupCommand(program) {
183
194
  program
184
- .command('init')
195
+ .command('setup')
185
196
  .description('Set up agents-cli for the first time. Clones a config repo and installs agent CLIs.')
186
- .option('-f, --force', 'Reinitialize even if ~/.agents-system/ already exists (use with caution)')
197
+ .option('-f, --force', 'Re-run setup even if ~/.agents-system/ already exists (use with caution)')
187
198
  .addHelpText('after', `
188
199
  Examples:
189
200
  # First-time setup (clones the system repo into ~/.agents-system/)
190
- agents init
201
+ agents setup
191
202
 
192
- # Re-initialize after corruption
193
- agents init --force
203
+ # Re-run setup after corruption
204
+ agents setup --force
194
205
 
195
206
  When to use:
196
207
  - First time running agents-cli: this is your starting point
@@ -203,12 +214,12 @@ What it does:
203
214
  3. Syncs commands, skills, hooks, and MCP servers to each version
204
215
 
205
216
  Non-interactive alternative:
206
- Skip 'init' and run:
217
+ Skip 'setup' and run:
207
218
  agents pull
208
219
  `)
209
220
  .action(async (options) => {
210
221
  try {
211
- await runInit(program, options);
222
+ await runSetup(program, options);
212
223
  }
213
224
  catch (err) {
214
225
  if (isPromptCancelled(err)) {
@@ -4,12 +4,12 @@ import * as fs from 'fs';
4
4
  import * as os from 'os';
5
5
  import * as path from 'path';
6
6
  import { select, checkbox } from '@inquirer/prompts';
7
- import { SKILLS_CAPABLE_AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
7
+ import { AGENTS, SKILLS_CAPABLE_AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
8
8
  import { cloneRepo } from '../lib/git.js';
9
- import { discoverSkillsFromRepo, installSkillCentrally, uninstallSkill, listInstalledSkills, listInstalledSkillsWithScope, getSkillInfo, getSkillRules, getSkillsDir, countSkillFiles, tryParseSkillMetadata, diffVersionSkills, iterSkillsCapableVersions, } from '../lib/skills.js';
10
- import { getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, resolveAgentVersionTargets, } from '../lib/versions.js';
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';
11
11
  import { recordVersionResources } from '../lib/state.js';
12
- import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, } from './utils.js';
12
+ import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, 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) {
@@ -294,15 +294,29 @@ Examples:
294
294
  agents skills remove
295
295
  `)
296
296
  .action(async (name) => {
297
+ const skillTargetMap = new Map();
298
+ for (const { agent, version } of iterSkillsCapableVersions()) {
299
+ const home = getVersionHomePath(agent, version);
300
+ const skills = listInstalledSkillsWithScope(agent, process.cwd(), { home });
301
+ for (const skill of skills) {
302
+ if (skill.scope !== 'user')
303
+ continue;
304
+ const existing = skillTargetMap.get(skill.name);
305
+ if (existing) {
306
+ existing.targets.push({ agent, version });
307
+ }
308
+ else {
309
+ skillTargetMap.set(skill.name, { name: skill.name, targets: [{ agent, version }] });
310
+ }
311
+ }
312
+ }
297
313
  let skillsToRemove;
298
314
  if (name) {
299
315
  skillsToRemove = [name];
300
316
  }
301
317
  else {
302
- // Interactive picker
303
- const installedSkills = listInstalledSkills();
304
- if (installedSkills.size === 0) {
305
- console.log(chalk.yellow('No skills installed.'));
318
+ if (skillTargetMap.size === 0) {
319
+ console.log(chalk.yellow('No skills installed in any version.'));
306
320
  return;
307
321
  }
308
322
  if (!isInteractiveTerminal()) {
@@ -311,12 +325,13 @@ Examples:
311
325
  ]);
312
326
  }
313
327
  try {
314
- const choices = Array.from(installedSkills.entries()).map(([skillName, skill]) => ({
315
- value: skillName,
316
- name: skill.metadata.description
317
- ? `${skillName} - ${skill.metadata.description}`
318
- : skillName,
319
- }));
328
+ const choices = Array.from(skillTargetMap.values()).map((skill) => {
329
+ const agents = [...new Set(skill.targets.map((t) => AGENTS[t.agent].name))];
330
+ return {
331
+ value: skill.name,
332
+ name: `${skill.name} (${agents.join(', ')})`,
333
+ };
334
+ });
320
335
  const selected = await checkbox({
321
336
  message: 'Select skills to remove',
322
337
  choices,
@@ -335,14 +350,40 @@ Examples:
335
350
  throw err;
336
351
  }
337
352
  }
353
+ let removed = 0;
338
354
  for (const skillName of skillsToRemove) {
339
- const result = uninstallSkill(skillName);
340
- if (result.success) {
341
- console.log(chalk.green(`Removed skill '${skillName}'`));
355
+ const skillInfo = skillTargetMap.get(skillName);
356
+ if (!skillInfo || skillInfo.targets.length === 0) {
357
+ console.log(chalk.yellow(` Skill '${skillName}' not found in any version.`));
358
+ continue;
342
359
  }
343
- else {
344
- console.log(chalk.red(result.error || `Failed to remove skill '${skillName}'`));
360
+ const removalTargets = skillInfo.targets.map((t) => ({
361
+ agent: t.agent,
362
+ version: t.version,
363
+ label: `${agentLabel(t.agent)}@${t.version}`,
364
+ }));
365
+ const selectedTargets = await promptRemovalTargets(skillName, removalTargets);
366
+ if (selectedTargets.length === 0) {
367
+ console.log(chalk.gray(` Skipped '${skillName}'.`));
368
+ continue;
345
369
  }
370
+ for (const target of selectedTargets) {
371
+ const result = removeSkillFromVersion(target.agent, target.version, skillName);
372
+ if (result.success) {
373
+ console.log(` ${chalk.red('-')} ${target.label}: ${skillName}`);
374
+ removed++;
375
+ }
376
+ else if (result.error) {
377
+ console.log(` ${chalk.yellow('!')} ${target.label}: ${result.error}`);
378
+ }
379
+ }
380
+ }
381
+ if (removed === 0) {
382
+ console.log(chalk.yellow('No skills removed.'));
383
+ }
384
+ else {
385
+ console.log(chalk.green(`\nRemoved ${removed} skill(s) from version homes.`));
386
+ console.log(chalk.gray('Central source unchanged. Skills will re-sync on next agent launch.'));
346
387
  }
347
388
  });
348
389
  // `skills sync` is gone — sync runs automatically when the agent launches.
@@ -12,10 +12,10 @@ import * as path from 'path';
12
12
  import { checkbox } from '@inquirer/prompts';
13
13
  import { AGENTS, agentLabel } from '../lib/agents.js';
14
14
  import { cloneRepo } from '../lib/git.js';
15
- import { discoverSubagentsFromRepo, installSubagentCentrally, removeSubagent, listInstalledSubagents, getInstalledSubagent, listSubagentsForAgent, SUBAGENT_CAPABLE_AGENTS, } from '../lib/subagents.js';
15
+ import { discoverSubagentsFromRepo, installSubagentCentrally, listInstalledSubagents, getInstalledSubagent, listSubagentsForAgent, SUBAGENT_CAPABLE_AGENTS, iterSubagentsCapableVersions, removeSubagentFromVersion, } from '../lib/subagents.js';
16
16
  import { listInstalledVersions, syncResourcesToVersion, getGlobalDefault, getVersionHomePath, } from '../lib/versions.js';
17
17
  import { getSubagentsDir } from '../lib/state.js';
18
- import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection, requireDestructiveArg, } from './utils.js';
18
+ import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection, requireDestructiveArg, promptRemovalTargets, } from './utils.js';
19
19
  import { showResourceList, buildTargetsSection, } from './resource-view.js';
20
20
  /** Replace the home directory prefix with ~ for display. */
21
21
  function formatPath(p) {
@@ -240,20 +240,48 @@ Examples:
240
240
  console.log(chalk.red(`Subagent '${name}' not found`));
241
241
  process.exit(1);
242
242
  }
243
- const spinner = ora({ text: `Removing ${name}...`, isSilent: !process.stdout.isTTY }).start();
244
- const result = removeSubagent(name);
245
- if (!result.success) {
246
- spinner.fail(`Failed to remove: ${result.error}`);
247
- process.exit(1);
243
+ // Build list of targets that have this subagent synced
244
+ const availableTargets = [];
245
+ for (const { agent, version } of iterSubagentsCapableVersions()) {
246
+ const home = getVersionHomePath(agent, version);
247
+ const installed = listSubagentsForAgent(agent, home).some((s) => s.name === name);
248
+ if (installed) {
249
+ availableTargets.push({ agent, version });
250
+ }
251
+ }
252
+ if (availableTargets.length === 0) {
253
+ console.log(chalk.yellow(`Subagent '${name}' not synced to any version.`));
254
+ return;
255
+ }
256
+ // Show multi-select picker for targets
257
+ const removalTargets = availableTargets.map((t) => ({
258
+ agent: t.agent,
259
+ version: t.version,
260
+ label: `${agentLabel(t.agent)}@${t.version}`,
261
+ }));
262
+ const selectedTargets = await promptRemovalTargets(name, removalTargets);
263
+ if (selectedTargets.length === 0) {
264
+ console.log(chalk.gray('Cancelled.'));
265
+ return;
248
266
  }
249
- // Re-sync all installed versions to remove from agent homes
250
- for (const agentId of SUBAGENT_CAPABLE_AGENTS) {
251
- const versions = listInstalledVersions(agentId);
252
- for (const version of versions) {
253
- syncResourcesToVersion(agentId, version);
267
+ let removed = 0;
268
+ for (const target of selectedTargets) {
269
+ const result = removeSubagentFromVersion(target.agent, target.version, name);
270
+ if (result.success) {
271
+ console.log(` ${chalk.red('-')} ${target.label}: ${name}`);
272
+ removed++;
273
+ }
274
+ else if (result.error) {
275
+ console.log(` ${chalk.yellow('!')} ${target.label}: ${result.error}`);
254
276
  }
255
277
  }
256
- spinner.succeed(`Removed subagent '${name}'`);
278
+ if (removed === 0) {
279
+ console.log(chalk.yellow('No subagents removed.'));
280
+ }
281
+ else {
282
+ console.log(chalk.green(`\nRemoved ${removed} subagent(s) from version homes.`));
283
+ console.log(chalk.gray('Central source unchanged. Subagents will re-sync on next agent launch.'));
284
+ }
257
285
  });
258
286
  }
259
287
  /** Every (agent, version) that supports subagents and is installed. */
@@ -922,13 +922,12 @@ Name teammates with --name alice to refer to them as 'alice' instead of a UUID.
922
922
  .command('status [team]')
923
923
  .aliases(['s', 'st', 'check'])
924
924
  .description("Check in on a team: who's working, what files they touched, recent commands, last output. Pass --since for efficient delta polling.")
925
- .option('-f, --filter <state>', 'Show only teammates in this state: working, completed, failed, stopped, or all (default: all)', 'all')
925
+ .option('-f, --filter <state>', 'Show only teammates in this state: running, completed, failed, stopped, or all (default: all)', 'all')
926
926
  .option('-s, --since <iso>', 'Cursor from a previous status call; only show updates after this timestamp (enables efficient polling)')
927
927
  .option('--agent-id <id>', 'Show only this one teammate (by UUID or UUID prefix)')
928
928
  .option('--json', 'Output machine-readable JSON')
929
929
  .action(async (team, opts) => {
930
- // Map friendly 'working' → internal 'running' for filter.
931
- const filter = opts.filter === 'working' ? 'running' : opts.filter;
930
+ const filter = opts.filter;
932
931
  const mgr = mkManager();
933
932
  // No team given → drop into the picker (TTY) or fail clearly (script).
934
933
  if (!team) {
@@ -8,6 +8,12 @@ export function registerUsageCommand(program) {
8
8
  program
9
9
  .command('usage [agent]')
10
10
  .description('Show rate-limit / quota usage per agent')
11
+ .addHelpText('after', `
12
+ Examples:
13
+ agents usage Show usage for all installed agents
14
+ agents usage claude Show usage for Claude only
15
+ agents usage codex Show usage for Codex only
16
+ `)
11
17
  .action(async (agentFilter) => {
12
18
  const filter = agentFilter;
13
19
  const targets = filter
@@ -38,6 +38,22 @@ export declare function printWithPager(output: string, lineCount: number): void;
38
38
  * Parse a comma-separated CLI list, trimming whitespace and dropping empties.
39
39
  */
40
40
  export declare function parseCommaSeparatedList(value: string | undefined): string[];
41
+ /**
42
+ * A target for resource removal: agent + version.
43
+ */
44
+ export interface RemovalTarget {
45
+ agent: string;
46
+ version: string;
47
+ label: string;
48
+ }
49
+ /**
50
+ * Prompt user to select which agent/version targets to remove a resource from.
51
+ * If only one target, returns it without prompting. If multiple, shows checkbox.
52
+ * Returns empty array if user cancels or selects nothing.
53
+ */
54
+ export declare function promptRemovalTargets(resourceName: string, targets: RemovalTarget[], options?: {
55
+ skipPrompt?: boolean;
56
+ }): Promise<RemovalTarget[]>;
41
57
  /**
42
58
  * Format a path for display, using ~ for home directory
43
59
  */
@@ -89,6 +89,38 @@ export function parseCommaSeparatedList(value) {
89
89
  .map((item) => item.trim())
90
90
  .filter(Boolean);
91
91
  }
92
+ /**
93
+ * Prompt user to select which agent/version targets to remove a resource from.
94
+ * If only one target, returns it without prompting. If multiple, shows checkbox.
95
+ * Returns empty array if user cancels or selects nothing.
96
+ */
97
+ export async function promptRemovalTargets(resourceName, targets, options) {
98
+ if (targets.length === 0)
99
+ return [];
100
+ if (targets.length === 1 || options?.skipPrompt)
101
+ return targets;
102
+ if (!isInteractiveTerminal()) {
103
+ return targets;
104
+ }
105
+ const { checkbox } = await import('@inquirer/prompts');
106
+ try {
107
+ const selected = await checkbox({
108
+ message: `Select targets to remove '${resourceName}' from`,
109
+ choices: targets.map((t) => ({
110
+ value: t,
111
+ name: t.label,
112
+ checked: true,
113
+ })),
114
+ });
115
+ return selected;
116
+ }
117
+ catch (err) {
118
+ if (isPromptCancelled(err)) {
119
+ return [];
120
+ }
121
+ throw err;
122
+ }
123
+ }
92
124
  /**
93
125
  * Format a path for display, using ~ for home directory
94
126
  */
@@ -104,7 +104,7 @@ When to use:
104
104
  - Multi-account: install different versions for different accounts (each version has its own auth)
105
105
  - Project-specific: lock a version for a repo with --project
106
106
 
107
- Note: The first version you install is NOT set as default automatically. Run 'agents use' to set it.
107
+ Note: The first version you install becomes the default automatically.
108
108
  `)
109
109
  .action(async (specs, options) => {
110
110
  const isProject = options.project;
@@ -220,10 +220,14 @@ Note: The first version you install is NOT set as default automatically. Run 'ag
220
220
  console.log(chalk.green(` Synced: ${synced.join(', ')}`));
221
221
  }
222
222
  }
223
- // Prompt to set as default
223
+ // Set as default: auto-set if no default exists, otherwise prompt
224
224
  const currentDefault = getGlobalDefault(agent);
225
225
  if (currentDefault !== installedVersion) {
226
- if (skipPrompts) {
226
+ if (!currentDefault) {
227
+ // First install for this agent - auto-set without prompting
228
+ await setDefaultVersion(agent, installedVersion);
229
+ }
230
+ else if (skipPrompts) {
227
231
  await setDefaultVersion(agent, installedVersion);
228
232
  }
229
233
  else {
@@ -238,9 +242,7 @@ Note: The first version you install is NOT set as default automatically. Run 'ag
238
242
  info,
239
243
  });
240
244
  const accountHint = formatAccountHint(info, usage.snapshot);
241
- const message = currentDefault
242
- ? `Switch default from ${agentLabel(agentConfig.id)}@${currentDefault} to ${agentLabel(agentConfig.id)}@${installedVersion}${accountHint}?`
243
- : `Set ${agentLabel(agentConfig.id)}@${installedVersion}${accountHint} as default?`;
245
+ const message = `Switch default from ${agentLabel(agentConfig.id)}@${currentDefault} to ${agentLabel(agentConfig.id)}@${installedVersion}${accountHint}?`;
244
246
  const setAsDefault = await confirm({
245
247
  message,
246
248
  default: true,
@@ -2,16 +2,16 @@ import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import * as fs from 'fs';
4
4
  import * as path from 'path';
5
- import * as yaml from 'yaml';
6
5
  import { AGENTS, ALL_AGENT_IDS, getAllCliStates, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
7
6
  import { formatUsageSection, formatUsageSummary, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
8
7
  import { readManifest } from '../lib/manifest.js';
9
8
  import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, resolveVersionAlias, getAvailableResources, getActuallySyncedResources, getNewResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, } from '../lib/versions.js';
10
9
  import { getShimsDir, isShimsInPath, ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
11
10
  import { getAgentResources } from '../lib/resources.js';
12
- import { getAgentsDir, getUserAgentsDir, getPromptcutsPath } from '../lib/state.js';
11
+ import { getAgentsDir, getUserAgentsDir, getEffectivePromptcutsPath, readMergedPromptcuts } from '../lib/state.js';
13
12
  import { isGitRepo, getGitSyncStatus } from '../lib/git.js';
14
13
  import { getCentralRulesFileName } from '../lib/rules/rules.js';
14
+ import { composeRulesFromState } from '../lib/rules/compose.js';
15
15
  import { getConfiguredRunStrategy } from '../lib/rotate.js';
16
16
  import { confirm } from '@inquirer/prompts';
17
17
  import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js';
@@ -512,25 +512,19 @@ async function showAgentResources(agentId, requestedVersion) {
512
512
  console.log(` ${display.padEnd(38)} ${pathStr}${syncStr}`);
513
513
  }
514
514
  }
515
- // Render the single ~/.agents/promptcuts.yaml (cross-agent, not per-version).
516
- // Reads the file to surface the shortcut count cheap (<1KB typical).
515
+ // Render promptcuts (cross-agent, not per-version). Shortcuts are layered
516
+ // across system + user files with user precedence; the displayed file path
517
+ // is whichever is "live" — user if it exists, else system.
517
518
  function renderPromptcuts() {
518
519
  console.log(chalk.bold(`\nPromptcuts\n`));
519
- const promptcutsPath = getPromptcutsPath();
520
- if (!fs.existsSync(promptcutsPath)) {
520
+ const merged = readMergedPromptcuts();
521
+ const count = Object.keys(merged).length;
522
+ if (count === 0) {
521
523
  console.log(` ${chalk.gray('none')}`);
522
524
  return;
523
525
  }
524
- let count = 0;
525
- try {
526
- const parsed = yaml.parse(fs.readFileSync(promptcutsPath, 'utf-8'));
527
- count = parsed?.shortcuts ? Object.keys(parsed.shortcuts).length : 0;
528
- }
529
- catch {
530
- count = 0;
531
- }
532
526
  const label = `${count} shortcut${count === 1 ? '' : 's'}`;
533
- console.log(` ${chalk.green(label).padEnd(24)} ${chalk.gray(formatPath(promptcutsPath, cwd))}`);
527
+ console.log(` ${chalk.green(label).padEnd(24)} ${chalk.gray(formatPath(getEffectivePromptcutsPath(), cwd))}`);
534
528
  }
535
529
  // 1. Agent CLI info
536
530
  console.log(chalk.bold('Agent CLIs\n'));
@@ -565,7 +559,58 @@ async function showAgentResources(agentId, requestedVersion) {
565
559
  }
566
560
  }
567
561
  renderSection('MCP Servers', agentData.mcp);
568
- renderSection('Rules', agentData.memory);
562
+ // Rules section with subrules breakdown
563
+ function renderRulesSection() {
564
+ console.log(chalk.bold('\nRules\n'));
565
+ const items = agentData.memory;
566
+ if (items.length === 0) {
567
+ console.log(` ${chalk.gray('none')}`);
568
+ return;
569
+ }
570
+ const versionStr = agentData.version ? ` (${agentData.version})` : '';
571
+ console.log(` ${chalk.bold(agentData.agentName)}${chalk.gray(versionStr)}:`);
572
+ // Get composed subrules for the user scope
573
+ let composedSubrules = [];
574
+ try {
575
+ const composed = composeRulesFromState({ cwd });
576
+ composedSubrules = composed.subrules;
577
+ }
578
+ catch {
579
+ // No preset configured or rules.yaml missing — show rules without subrule breakdown
580
+ }
581
+ for (const r of items) {
582
+ let nameColor = chalk.cyan;
583
+ if (r.syncState === 'synced')
584
+ nameColor = chalk.green;
585
+ else if (r.syncState === 'new')
586
+ nameColor = chalk.blue;
587
+ else if (r.syncState === 'modified')
588
+ nameColor = chalk.yellow;
589
+ else if (r.syncState === 'deleted')
590
+ nameColor = chalk.red;
591
+ let display = nameColor(r.name);
592
+ if (r.ruleCount !== undefined)
593
+ display += chalk.gray(` (${r.ruleCount} rules)`);
594
+ const sourceTag = r.scope === 'project' ? chalk.blue('[project]')
595
+ : r.scope === 'user' ? chalk.cyan('[user]')
596
+ : chalk.gray('[system]');
597
+ display += ` ${sourceTag}`;
598
+ const pathStr = r.path ? chalk.gray(formatPath(r.path, cwd)) : '';
599
+ const syncStr = r.syncState ? chalk.gray(` [${r.syncState}]`) : '';
600
+ console.log(` ${display.padEnd(38)} ${pathStr}${syncStr}`);
601
+ // Show subrules for user-scope rules (the compiled CLAUDE.md)
602
+ if (r.scope === 'user' && composedSubrules.length > 0) {
603
+ for (const sub of composedSubrules) {
604
+ const scopeLabel = sub.layerScope === 'project' ? chalk.blue('[project]')
605
+ : sub.layerScope === 'user' ? chalk.cyan('[user]')
606
+ : sub.layerScope === 'extra' ? chalk.magenta(`[${sub.layerAlias || 'extra'}]`)
607
+ : chalk.gray('[system]');
608
+ console.log(` ${chalk.gray('-')} ${sub.name} ${scopeLabel}`);
609
+ }
610
+ }
611
+ }
612
+ }
613
+ renderRulesSection();
569
614
  renderSection('Hooks', agentData.hooks);
570
615
  renderPromptcuts();
571
616
  // Show legend at the end if git repo exists
package/dist/index.d.ts CHANGED
@@ -3,6 +3,6 @@
3
3
  * CLI entry point for agents-cli.
4
4
  *
5
5
  * Registers all commands, handles update checks, auto-corrects typos,
6
- * and launches the first-run interactive init when appropriate.
6
+ * and launches the first-run interactive setup when appropriate.
7
7
  */
8
8
  export {};