@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
@@ -14,10 +14,13 @@ import { cloneRepo } from '../lib/git.js';
14
14
  import { discoverCommands, resolveCommandSource, installCommand, installCommandCentrally, } from '../lib/commands.js';
15
15
  import { discoverSkillsFromRepo, installSkill, installSkillCentrally, } from '../lib/skills.js';
16
16
  import { discoverHooksFromRepo, installHooks, installHooksCentrally, } from '../lib/hooks.js';
17
- import { listInstalledVersions, resolveInstalledAgentTargets, resolveConfiguredAgentTargets, syncResourcesToVersion, } from '../lib/versions.js';
18
- import { isInteractiveTerminal, isPromptCancelled, requireDestructiveArg, requireInteractiveSelection, } from './utils.js';
17
+ import { discoverWorkflowsFromRepo, installWorkflowCentrally, } from '../lib/workflows.js';
18
+ import { discoverSubagentsFromRepo, installSubagentCentrally, } from '../lib/subagents.js';
19
+ import { discoverPermissionsFromRepo, installPermissionSet, } from '../lib/permissions.js';
20
+ import { listInstalledVersions, resolveConfiguredAgentTargets, syncResourcesToVersion, } from '../lib/versions.js';
21
+ import { isInteractiveTerminal, isPromptCancelled, parseCommaSeparatedList, requireDestructiveArg, requireInteractiveSelection, resolveInstalledAgentTargetsAutoInstalling, } from './utils.js';
19
22
  import { itemPicker } from '../lib/picker.js';
20
- import { registerMcpCommandToTargets } from '../lib/mcp.js';
23
+ import { registerMcpCommandToTargets, discoverMcpConfigsFromRepo, installMcpConfigCentrally, } from '../lib/mcp.js';
21
24
  export function buildMcpPackageCommand(pkg) {
22
25
  const packageName = pkg.name || pkg.registry_name;
23
26
  if (pkg.runtime === 'node') {
@@ -326,6 +329,9 @@ When to use:
326
329
  .command('install <identifier>')
327
330
  .description('Install a package by registry name (mcp:notion), GitHub URL (gh:user/repo), or skill identifier')
328
331
  .option('-a, --agents <list>', 'Targets: claude, codex@0.116.0, or gemini@default')
332
+ .option('--types <list>', 'When source is a repo: comma-separated resource types to install (skills,workflows,commands,hooks,permissions,subagents,mcp)')
333
+ .option('--names <list>', 'When source is a repo: comma-separated resource names within the selected types')
334
+ .option('-y, --yes', 'Auto-install any missing agent versions without prompting')
329
335
  .addHelpText('after', `
330
336
  Install resolves the package type (MCP server, skill, command, hook) and installs to the specified agents. Packages can come from registries (mcp:, skill:), GitHub (gh:user/repo), or direct URLs.
331
337
 
@@ -339,6 +345,12 @@ Examples:
339
345
  # Install using GitHub shorthand
340
346
  agents install gh:user/repo --agents claude@2.1.112
341
347
 
348
+ # Install only specific resource types from a multi-resource repo
349
+ agents install gh:phnx-labs/.agents-system --types skills,workflows --agents claude@all
350
+
351
+ # Install specific resources by name
352
+ agents install gh:phnx-labs/.agents-system --types skills --names animator,composer --agents claude@all
353
+
342
354
  # Install to all installed agents (uses defaults or prompts)
343
355
  agents install mcp:postgres
344
356
 
@@ -399,9 +411,18 @@ When to use:
399
411
  }
400
412
  const cliStates = await getAllCliStates();
401
413
  const installedAgents = MCP_CAPABLE_AGENTS.filter((id) => cliStates[id]?.installed || listInstalledVersions(id).length > 0);
402
- const targets = options.agents
403
- ? resolveInstalledAgentTargets(options.agents, MCP_CAPABLE_AGENTS)
404
- : resolveConfiguredAgentTargets(installedAgents, undefined, MCP_CAPABLE_AGENTS);
414
+ let targets;
415
+ if (options.agents) {
416
+ const resolved = await resolveInstalledAgentTargetsAutoInstalling(options.agents, MCP_CAPABLE_AGENTS, { yes: options.yes });
417
+ if (!resolved) {
418
+ console.log(chalk.gray('Cancelled.'));
419
+ return;
420
+ }
421
+ targets = resolved;
422
+ }
423
+ else {
424
+ targets = resolveConfiguredAgentTargets(installedAgents, undefined, MCP_CAPABLE_AGENTS);
425
+ }
405
426
  if (targets.selectedAgents.length === 0) {
406
427
  console.log(chalk.yellow('\nNo MCP-capable agents installed.'));
407
428
  process.exit(1);
@@ -420,38 +441,95 @@ When to use:
420
441
  console.log(chalk.green('\nMCP server installed.'));
421
442
  }
422
443
  else if (resolved.type === 'git' || resolved.type === 'skill') {
423
- // Install from git source (skills/commands/hooks)
444
+ // Install from git source: sniff every resource type the repo
445
+ // contains. Optional --types narrows which kinds get installed;
446
+ // --names narrows which specific resources within those kinds.
424
447
  console.log(chalk.bold(`\nInstalling from ${resolved.source}`));
425
448
  const { localPath } = await cloneRepo(resolved.source);
426
- // Discover what's in the repo
427
- const commands = discoverCommands(localPath);
428
- const skills = discoverSkillsFromRepo(localPath);
429
- const hooks = discoverHooksFromRepo(localPath);
430
- const hasCommands = commands.length > 0;
431
- const hasSkills = skills.length > 0;
432
- const hasHooks = hooks.length > 0;
433
- if (!hasCommands && !hasSkills && !hasHooks) {
449
+ const requestedTypes = new Set(parseCommaSeparatedList(options.types));
450
+ const includeType = (type) => requestedTypes.size === 0 || requestedTypes.has(type);
451
+ const requestedNames = new Set(parseCommaSeparatedList(options.names));
452
+ const nameFilter = (items) => {
453
+ if (requestedNames.size === 0)
454
+ return items;
455
+ return items.filter((item) => requestedNames.has(item.name));
456
+ };
457
+ // Discover everything; filter to requested types up front so the
458
+ // summary table reflects what will actually be installed.
459
+ let commands = includeType('commands') ? discoverCommands(localPath) : [];
460
+ let skills = includeType('skills') ? discoverSkillsFromRepo(localPath) : [];
461
+ let hooks = includeType('hooks') ? discoverHooksFromRepo(localPath) : [];
462
+ let workflows = includeType('workflows') ? discoverWorkflowsFromRepo(localPath) : [];
463
+ let subagents = includeType('subagents') ? discoverSubagentsFromRepo(localPath) : [];
464
+ let permissions = includeType('permissions') ? discoverPermissionsFromRepo(localPath) : [];
465
+ let mcpServers = includeType('mcp') ? discoverMcpConfigsFromRepo(localPath) : [];
466
+ // --names filter applies across every discovered type. If the user
467
+ // typed a name that matched nothing, fail loud so they can fix the
468
+ // typo rather than silently install zero items.
469
+ if (requestedNames.size > 0) {
470
+ const allNames = new Set([
471
+ ...commands.map((c) => c.name),
472
+ ...skills.map((s) => s.name),
473
+ ...hooks,
474
+ ...workflows.map((w) => w.name),
475
+ ...subagents.map((s) => s.name),
476
+ ...permissions.map((p) => p.name),
477
+ ...mcpServers.map((s) => s.name),
478
+ ]);
479
+ const missing = [...requestedNames].filter((n) => !allNames.has(n));
480
+ if (missing.length > 0) {
481
+ console.log(chalk.red(`\nResource(s) not found in repo: ${missing.join(', ')}`));
482
+ console.log(chalk.gray(`Available: ${[...allNames].sort().join(', ')}`));
483
+ process.exit(1);
484
+ }
485
+ commands = nameFilter(commands);
486
+ skills = nameFilter(skills);
487
+ hooks = hooks.filter((h) => requestedNames.has(h));
488
+ workflows = nameFilter(workflows);
489
+ subagents = nameFilter(subagents);
490
+ permissions = nameFilter(permissions);
491
+ mcpServers = nameFilter(mcpServers);
492
+ }
493
+ const summary = [
494
+ { kind: 'commands', count: commands.length },
495
+ { kind: 'skills', count: skills.length },
496
+ { kind: 'hooks', count: hooks.length },
497
+ { kind: 'workflows', count: workflows.length },
498
+ { kind: 'subagents', count: subagents.length },
499
+ { kind: 'permissions', count: permissions.length },
500
+ { kind: 'mcp', count: mcpServers.length },
501
+ ].filter((s) => s.count > 0);
502
+ if (summary.length === 0) {
434
503
  console.log(chalk.yellow('No installable content found in repository.'));
504
+ if (requestedTypes.size > 0 || requestedNames.size > 0) {
505
+ console.log(chalk.gray('Try removing --types/--names to see everything the repo offers.'));
506
+ }
435
507
  process.exit(1);
436
508
  }
437
509
  console.log(chalk.bold('\nFound:'));
438
- if (hasCommands)
439
- console.log(` ${commands.length} commands`);
440
- if (hasSkills)
441
- console.log(` ${skills.length} skills`);
442
- if (hasHooks)
443
- console.log(` ${hooks.length} hooks`);
510
+ for (const { kind, count } of summary) {
511
+ console.log(` ${count} ${kind}`);
512
+ }
444
513
  const gitCliStates = await getAllCliStates();
445
514
  const installedAgents = ALL_AGENT_IDS.filter((id) => gitCliStates[id]?.installed || listInstalledVersions(id).length > 0);
446
- const targets = options.agents
447
- ? resolveInstalledAgentTargets(options.agents, ALL_AGENT_IDS)
448
- : resolveConfiguredAgentTargets(installedAgents, undefined, ALL_AGENT_IDS);
515
+ let targets;
516
+ if (options.agents) {
517
+ const resolved = await resolveInstalledAgentTargetsAutoInstalling(options.agents, ALL_AGENT_IDS, { yes: options.yes });
518
+ if (!resolved) {
519
+ console.log(chalk.gray('Cancelled.'));
520
+ return;
521
+ }
522
+ targets = resolved;
523
+ }
524
+ else {
525
+ targets = resolveConfiguredAgentTargets(installedAgents, undefined, ALL_AGENT_IDS);
526
+ }
449
527
  if (targets.selectedAgents.length === 0) {
450
528
  console.log(chalk.yellow('\nNo agents selected.'));
451
529
  return;
452
530
  }
453
531
  // Install commands
454
- if (hasCommands) {
532
+ if (commands.length > 0) {
455
533
  console.log(chalk.bold('\nInstalling commands...'));
456
534
  let directInstalled = 0;
457
535
  let syncedVersions = 0;
@@ -495,7 +573,7 @@ When to use:
495
573
  }
496
574
  }
497
575
  // Install skills
498
- if (hasSkills) {
576
+ if (skills.length > 0) {
499
577
  console.log(chalk.bold('\nInstalling skills...'));
500
578
  const directAgents = targets.directAgents.filter((agentId) => AGENTS[agentId].capabilities.skills && gitCliStates[agentId]?.installed);
501
579
  let syncedVersions = 0;
@@ -522,7 +600,7 @@ When to use:
522
600
  console.log(` Synced skills to ${syncedVersions} managed version(s)`);
523
601
  }
524
602
  // Install hooks
525
- if (hasHooks) {
603
+ if (hooks.length > 0) {
526
604
  console.log(chalk.bold('\nInstalling hooks...'));
527
605
  let syncedVersions = 0;
528
606
  const directHookAgents = targets.directAgents.filter((id) => AGENTS[id].supportsHooks && gitCliStates[id]?.installed);
@@ -543,6 +621,97 @@ When to use:
543
621
  }
544
622
  console.log(` Synced hooks to ${syncedVersions} managed version(s)`);
545
623
  }
624
+ // Install workflows
625
+ if (workflows.length > 0) {
626
+ console.log(chalk.bold('\nInstalling workflows...'));
627
+ let installed = 0;
628
+ for (const w of workflows) {
629
+ const result = installWorkflowCentrally(w.path, w.name);
630
+ if (result.success) {
631
+ installed++;
632
+ }
633
+ else {
634
+ console.log(` ${chalk.red('x')} ${w.name}: ${result.error}`);
635
+ }
636
+ }
637
+ const workflowNames = workflows.map((w) => w.name);
638
+ let syncedVersions = 0;
639
+ for (const [agentId, versions] of targets.versionSelections) {
640
+ for (const version of versions) {
641
+ const result = syncResourcesToVersion(agentId, version, { workflows: workflowNames });
642
+ if (result.workflows.length > 0)
643
+ syncedVersions++;
644
+ }
645
+ }
646
+ console.log(` Installed ${installed} workflow(s) to ~/.agents/workflows/`);
647
+ console.log(` Synced workflows to ${syncedVersions} managed version(s)`);
648
+ }
649
+ // Install subagents
650
+ if (subagents.length > 0) {
651
+ console.log(chalk.bold('\nInstalling subagents...'));
652
+ let installed = 0;
653
+ for (const s of subagents) {
654
+ const result = installSubagentCentrally(s.path, s.name);
655
+ if (result.success) {
656
+ installed++;
657
+ }
658
+ else {
659
+ console.log(` ${chalk.red('x')} ${s.name}: ${result.error}`);
660
+ }
661
+ }
662
+ const subagentNames = subagents.map((s) => s.name);
663
+ let syncedVersions = 0;
664
+ for (const [agentId, versions] of targets.versionSelections) {
665
+ for (const version of versions) {
666
+ const result = syncResourcesToVersion(agentId, version, { subagents: subagentNames });
667
+ if (result.subagents.length > 0)
668
+ syncedVersions++;
669
+ }
670
+ }
671
+ console.log(` Installed ${installed} subagent(s) to ~/.agents/subagents/`);
672
+ console.log(` Synced subagents to ${syncedVersions} managed version(s)`);
673
+ }
674
+ // Install permissions
675
+ if (permissions.length > 0) {
676
+ console.log(chalk.bold('\nInstalling permission sets...'));
677
+ let installed = 0;
678
+ for (const p of permissions) {
679
+ const result = installPermissionSet(p.path, p.name);
680
+ if (result.success) {
681
+ installed++;
682
+ }
683
+ else {
684
+ console.log(` ${chalk.red('x')} ${p.name}: ${result.error}`);
685
+ }
686
+ }
687
+ console.log(` Installed ${installed} permission set(s) to ~/.agents/permissions/`);
688
+ console.log(chalk.gray(' Apply with: agents permissions apply <name> --agents <selector>'));
689
+ }
690
+ // Install MCP server configs
691
+ if (mcpServers.length > 0) {
692
+ console.log(chalk.bold('\nInstalling MCP server configs...'));
693
+ let installed = 0;
694
+ for (const s of mcpServers) {
695
+ const result = installMcpConfigCentrally(s.path);
696
+ if (result.success) {
697
+ installed++;
698
+ }
699
+ else {
700
+ console.log(` ${chalk.red('x')} ${s.name}: ${result.error}`);
701
+ }
702
+ }
703
+ const mcpNames = mcpServers.map((s) => s.name);
704
+ let syncedVersions = 0;
705
+ for (const [agentId, versions] of targets.versionSelections) {
706
+ for (const version of versions) {
707
+ const result = syncResourcesToVersion(agentId, version, { mcp: mcpNames });
708
+ if (result.mcp.length > 0)
709
+ syncedVersions++;
710
+ }
711
+ }
712
+ console.log(` Installed ${installed} MCP config(s) to ~/.agents/mcp/`);
713
+ console.log(` Synced MCP configs to ${syncedVersions} managed version(s)`);
714
+ }
546
715
  console.log(chalk.green('\nPackage installed.'));
547
716
  }
548
717
  }
@@ -7,9 +7,9 @@ import { confirm, checkbox } from '@inquirer/prompts';
7
7
  import { AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
8
8
  import { cloneRepo } from '../lib/git.js';
9
9
  import { PERMISSIONS_CAPABLE_AGENTS, listInstalledPermissions, discoverPermissionsFromRepo, installPermissionSet, removePermissionSet, applyPermissionsToVersion, readAgentPermissions, exportPermissionsFromPath, getDefaultPermissionSet, computePermissionsDiff, mergePermissionSets, saveDefaultPermissionSet, containsBroadGrants, } from '../lib/permissions.js';
10
- import { listInstalledVersions, getGlobalDefault, getVersionHomePath, promptAgentVersionSelection, resolveAgentVersionTargets, resolveVersionAlias, } from '../lib/versions.js';
10
+ import { listInstalledVersions, getGlobalDefault, getVersionHomePath, promptAgentVersionSelection, resolveVersionAlias, } 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, resolveAgentTargetsAutoInstalling, } from './utils.js';
13
13
  export function shouldRefuseBroadPermissions(permissions, allowBroadPermissions) {
14
14
  return !allowBroadPermissions && permissions.some((perm) => containsBroadGrants(perm.set) !== null);
15
15
  }
@@ -279,9 +279,14 @@ Examples:
279
279
  let selectedAgents;
280
280
  let versionSelections;
281
281
  if (options.agents) {
282
- const result = resolveAgentVersionTargets(options.agents, PERMISSIONS_CAPABLE_AGENTS, {
282
+ const result = await resolveAgentTargetsAutoInstalling(options.agents, PERMISSIONS_CAPABLE_AGENTS, {
283
+ yes: options.yes,
283
284
  allVersions: options.all,
284
285
  });
286
+ if (!result) {
287
+ console.log(chalk.gray('Cancelled.'));
288
+ return;
289
+ }
285
290
  selectedAgents = result.selectedAgents;
286
291
  versionSelections = result.versionSelections;
287
292
  }
@@ -296,7 +301,7 @@ Examples:
296
301
  }
297
302
  }
298
303
  else {
299
- const result = await promptAgentVersionSelection(PERMISSIONS_CAPABLE_AGENTS, { skipPrompts });
304
+ const result = await promptAgentVersionSelection(PERMISSIONS_CAPABLE_AGENTS, { skipPrompts: options.yes });
300
305
  selectedAgents = result.selectedAgents;
301
306
  versionSelections = result.versionSelections;
302
307
  }
@@ -412,9 +417,14 @@ Examples:
412
417
  let selectedAgents;
413
418
  let versionSelections;
414
419
  if (options.agents) {
415
- const result = resolveAgentVersionTargets(options.agents, PERMISSIONS_CAPABLE_AGENTS, {
420
+ const result = await resolveAgentTargetsAutoInstalling(options.agents, PERMISSIONS_CAPABLE_AGENTS, {
421
+ yes: options.yes,
416
422
  allVersions: options.all,
417
423
  });
424
+ if (!result) {
425
+ console.log(chalk.gray('Cancelled.'));
426
+ return;
427
+ }
418
428
  selectedAgents = result.selectedAgents;
419
429
  versionSelections = result.versionSelections;
420
430
  }
@@ -537,9 +547,14 @@ Examples:
537
547
  let selectedAgents;
538
548
  let versionSelections;
539
549
  if (options.agents) {
540
- const result = resolveAgentVersionTargets(options.agents, PERMISSIONS_CAPABLE_AGENTS, {
550
+ const result = await resolveAgentTargetsAutoInstalling(options.agents, PERMISSIONS_CAPABLE_AGENTS, {
551
+ yes: options.yes,
541
552
  allVersions: options.all,
542
553
  });
554
+ if (!result) {
555
+ console.log(chalk.gray('Cancelled.'));
556
+ return;
557
+ }
543
558
  selectedAgents = result.selectedAgents;
544
559
  versionSelections = result.versionSelections;
545
560
  }
@@ -237,6 +237,7 @@ Examples:
237
237
  pluginsCmd
238
238
  .command('sync <name> [agent]')
239
239
  .description('Apply a plugin to the default version of an agent (or all supported agents if none specified)')
240
+ .option('--allow-exec-surfaces', 'Enable the plugin even when it ships hooks/, .mcp.json, bin/, scripts/, settings.json, or permissions/')
240
241
  .addHelpText('after', `
241
242
  Examples:
242
243
  # Sync a plugin to a specific agent (default version)
@@ -244,8 +245,11 @@ Examples:
244
245
 
245
246
  # Sync to all supported agents
246
247
  agents plugins sync rush-toolkit
248
+
249
+ # Re-affirm consent for a hooks-bearing plugin
250
+ agents plugins sync hivemind claude --allow-exec-surfaces
247
251
  `)
248
- .action(async (name, agentArg) => {
252
+ .action(async (name, agentArg, options) => {
249
253
  const plugin = getPlugin(name);
250
254
  if (!plugin) {
251
255
  console.log(chalk.red(`Plugin '${name}' not found`));
@@ -268,6 +272,7 @@ Examples:
268
272
  else {
269
273
  targetAgents = PLUGINS_CAPABLE_AGENTS.filter(a => pluginSupportsAgent(plugin, a));
270
274
  }
275
+ const allowExec = options.allowExecSurfaces === true;
271
276
  for (const agentId of targetAgents) {
272
277
  const versions = listInstalledVersions(agentId);
273
278
  if (versions.length === 0)
@@ -275,9 +280,11 @@ Examples:
275
280
  const defaultVer = getGlobalDefault(agentId);
276
281
  const targetVersions = defaultVer ? [defaultVer] : [versions[versions.length - 1]];
277
282
  for (const version of targetVersions) {
278
- const syncResult = syncResourcesToVersion(agentId, version, { plugins: [name] });
279
- if (syncResult.plugins.length > 0) {
280
- console.log(chalk.green(`Synced ${name} to ${agentLabel(agentId)}@${version}`));
283
+ const didSync = allowExec
284
+ ? syncPluginToVersion(plugin, agentId, getVersionHomePath(agentId, version), { allowExecSurfaces: true, version }).success
285
+ : syncResourcesToVersion(agentId, version, { plugins: [name] }).plugins.length > 0;
286
+ if (didSync) {
287
+ console.log(chalk.green(`Synced ${name} to ${agentLabel(agentId)}@${version}${allowExec ? ' (exec surfaces enabled)' : ''}`));
281
288
  }
282
289
  else {
283
290
  console.log(chalk.gray(`${name} already synced to ${agentLabel(agentId)}@${version}`));
@@ -7,5 +7,13 @@
7
7
  * through a standard agent CLI with no local proxy.
8
8
  */
9
9
  import type { Command } from 'commander';
10
+ import { type Profile } from '../lib/profiles.js';
11
+ import { type Preset } from '../lib/profiles-presets.js';
12
+ /**
13
+ * Pure helper: builds a Profile from collected wizard inputs. Extracted so the
14
+ * shape of preset->profile mapping for the `create` wizard is unit-testable
15
+ * without mocking @inquirer/prompts.
16
+ */
17
+ export declare function buildProfileFromCollection(name: string, preset: Preset, collected: Record<string, string>, version?: string): Profile;
10
18
  /** Register the `agents profiles` command tree. */
11
19
  export declare function registerProfilesCommands(program: Command): void;
@@ -8,10 +8,30 @@
8
8
  */
9
9
  import chalk from 'chalk';
10
10
  import * as fs from 'fs';
11
- import { listProfiles, readProfile, writeProfile, deleteProfile, profileExists, profileFromPreset, getPresetForProfile, } from '../lib/profiles.js';
12
- import { getPreset, listPresets } from '../lib/profiles-presets.js';
11
+ import { spawn } from 'child_process';
12
+ import { listProfiles, readProfile, writeProfile, deleteProfile, profileExists, profileFromPreset, validateProfileName, getPresetForProfile, } from '../lib/profiles.js';
13
+ import { getPreset, listPresets, expandPreset } from '../lib/profiles-presets.js';
13
14
  import { hasKeychainToken, keychainItemName, setKeychainToken, deleteKeychainToken, } from '../lib/secrets/profiles.js';
14
15
  import { isInteractiveTerminal } from './utils.js';
16
+ /**
17
+ * Pure helper: builds a Profile from collected wizard inputs. Extracted so the
18
+ * shape of preset->profile mapping for the `create` wizard is unit-testable
19
+ * without mocking @inquirer/prompts.
20
+ */
21
+ export function buildProfileFromCollection(name, preset, collected, version) {
22
+ return {
23
+ name,
24
+ host: { agent: preset.host, version },
25
+ env: { ...preset.env, ...collected },
26
+ auth: {
27
+ envVar: preset.authEnvVar,
28
+ keychainItem: keychainItemName(preset.provider),
29
+ },
30
+ description: preset.description,
31
+ preset: preset.name,
32
+ provider: preset.provider,
33
+ };
34
+ }
15
35
  /** Prompt the user for a secret value with masked input. Requires an interactive TTY. */
16
36
  async function promptForSecret(message) {
17
37
  if (!isInteractiveTerminal()) {
@@ -86,7 +106,8 @@ Built-in presets (via OpenRouter, one shared key):
86
106
  Run 'agents profiles presets' for the full list with pricing and context sizes.
87
107
 
88
108
  Typical flow:
89
- agents profiles add kimi # prompts for OpenRouter key, stored in Keychain
109
+ agents profiles create # interactive wizard for any provider
110
+ agents profiles add kimi # one-line preset (existing)
90
111
  agents run kimi "refactor this" # Claude Code UI, Kimi model responses
91
112
  agents profiles add deepseek # reuses OpenRouter key, no re-prompt
92
113
 
@@ -116,7 +137,7 @@ Examples:
116
137
 
117
138
  # Add MiniMax for SWE-bench style fixes; reuses the same OpenRouter key
118
139
  agents profiles add minimax
119
- agents run minimax "investigate EXAMPLE-2317 and patch the off-by-one in pagination"
140
+ agents run minimax "investigate PROJ-456 and patch the off-by-one in pagination"
120
141
 
121
142
  # Add DeepSeek for cheap, fast non-reasoning work
122
143
  agents profiles add deepseek
@@ -202,6 +223,96 @@ Examples:
202
223
  process.exit(1);
203
224
  }
204
225
  });
226
+ cmd
227
+ .command('create')
228
+ .description('Interactive profile creation wizard (any provider, with prompts for endpoints + keys).')
229
+ .option('--name <name>', 'Profile name (skips the name prompt)')
230
+ .option('--provider <provider>', 'Provider preset name (skips the provider prompt)')
231
+ .option('--no-smoke-test', 'Skip the post-create smoke test prompt')
232
+ .action(async (opts) => {
233
+ if (!isInteractiveTerminal()) {
234
+ console.error(chalk.red('agents profiles create requires an interactive terminal. Use `agents profiles add <preset>` for scriptable creation.'));
235
+ process.exit(1);
236
+ }
237
+ const { input, select, confirm } = await import('@inquirer/prompts');
238
+ const name = opts.name
239
+ ? opts.name
240
+ : await input({
241
+ message: 'Profile name',
242
+ validate: (v) => /^[a-z0-9][a-z0-9-_]{0,48}$/i.test(v) || 'lowercase alphanumeric + -_ only, max 48 chars',
243
+ });
244
+ validateProfileName(name);
245
+ if (profileExists(name)) {
246
+ const overwrite = await confirm({
247
+ message: `Profile '${name}' already exists. Overwrite?`,
248
+ default: false,
249
+ });
250
+ if (!overwrite) {
251
+ console.log(chalk.gray('Cancelled.'));
252
+ return;
253
+ }
254
+ }
255
+ const presets = listPresets();
256
+ const providerName = opts.provider
257
+ ? opts.provider
258
+ : await select({
259
+ message: 'Provider',
260
+ choices: presets.map((p) => ({
261
+ name: `${p.name.padEnd(18)} ${chalk.gray(p.description.slice(0, 70))}`,
262
+ value: p.name,
263
+ })),
264
+ });
265
+ const preset = getPreset(providerName);
266
+ if (!preset) {
267
+ console.error(chalk.red(`Unknown provider '${providerName}'. Run 'agents profiles presets' for the list.`));
268
+ process.exit(1);
269
+ }
270
+ const expanded = expandPreset(preset);
271
+ const collected = {};
272
+ for (const v of expanded.prompts) {
273
+ if (v.secret) {
274
+ collected[v.envVar] = await promptForSecret(v.prompt);
275
+ }
276
+ else {
277
+ const value = await input({
278
+ message: v.hint ? `${v.prompt} ${chalk.gray('(' + v.hint + ')')}` : v.prompt,
279
+ default: v.default,
280
+ validate: v.pattern
281
+ ? (val) => new RegExp(v.pattern).test(val) || `must match ${v.pattern}`
282
+ : undefined,
283
+ });
284
+ collected[v.envVar] = value;
285
+ }
286
+ }
287
+ if (!preset.authOptional) {
288
+ await ensureProviderToken(preset.provider, preset.signupUrl);
289
+ }
290
+ const profile = buildProfileFromCollection(name, preset, collected);
291
+ writeProfile(profile);
292
+ console.log(chalk.green(`Profile '${name}' created.`));
293
+ if (preset.docPath) {
294
+ console.log(chalk.gray(`See docs/profiles/${preset.docPath}.md for provider-specific caveats.`));
295
+ }
296
+ if (opts.smokeTest !== false) {
297
+ const run = await confirm({ message: 'Run smoke test now?', default: true });
298
+ if (run) {
299
+ console.log(chalk.gray(`Spawning: agents run ${name} "say alive in one word" (60s timeout)`));
300
+ const child = spawn(process.argv[0], [
301
+ process.argv[1],
302
+ 'run',
303
+ name,
304
+ 'say alive in one word',
305
+ '--headless',
306
+ '--timeout',
307
+ '60s',
308
+ ], { stdio: 'inherit' });
309
+ child.on('exit', (code) => process.exit(code ?? 0));
310
+ }
311
+ else {
312
+ console.log(chalk.gray(`Try later: agents run ${name} "hello"`));
313
+ }
314
+ }
315
+ });
205
316
  cmd
206
317
  .command('add <name>')
207
318
  .description('Add a profile. If <name> matches a built-in preset, the preset is applied. Prompts for API key (once per provider).')
@@ -223,7 +334,9 @@ Examples:
223
334
  console.error(chalk.gray('Or pass --preset <name> to pick explicitly.'));
224
335
  process.exit(1);
225
336
  }
226
- await ensureProviderToken(preset.provider, preset.signupUrl, opts.keyStdin);
337
+ if (!preset.authOptional) {
338
+ await ensureProviderToken(preset.provider, preset.signupUrl, opts.keyStdin);
339
+ }
227
340
  const profile = profileFromPreset(name, preset, opts.version);
228
341
  writeProfile(profile);
229
342
  console.log(chalk.green(`Profile '${name}' added.`));