@phnx-labs/agents-cli 1.20.3 → 1.20.5

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 (193) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +48 -17
  3. package/dist/commands/cli.js +1 -1
  4. package/dist/commands/cloud.js +1 -1
  5. package/dist/commands/commands.js +2 -0
  6. package/dist/commands/doctor.js +1 -1
  7. package/dist/commands/exec.js +52 -16
  8. package/dist/commands/hooks.js +6 -6
  9. package/dist/commands/import.js +90 -37
  10. package/dist/commands/inspect.d.ts +26 -0
  11. package/dist/commands/inspect.js +590 -0
  12. package/dist/commands/mcp.js +17 -16
  13. package/dist/commands/models.js +1 -1
  14. package/dist/commands/packages.js +6 -4
  15. package/dist/commands/permissions.js +13 -12
  16. package/dist/commands/plugins.d.ts +13 -0
  17. package/dist/commands/plugins.js +100 -11
  18. package/dist/commands/prune.js +3 -2
  19. package/dist/commands/pull.d.ts +12 -5
  20. package/dist/commands/pull.js +26 -422
  21. package/dist/commands/push.d.ts +14 -0
  22. package/dist/commands/push.js +30 -0
  23. package/dist/commands/repo.d.ts +1 -1
  24. package/dist/commands/repo.js +155 -112
  25. package/dist/commands/resource-view.d.ts +2 -0
  26. package/dist/commands/resource-view.js +12 -3
  27. package/dist/commands/routines.js +32 -7
  28. package/dist/commands/rules.js +1 -1
  29. package/dist/commands/sessions.js +1 -0
  30. package/dist/commands/setup.d.ts +3 -3
  31. package/dist/commands/setup.js +15 -15
  32. package/dist/commands/skills.js +6 -5
  33. package/dist/commands/subagents.js +5 -4
  34. package/dist/commands/sync.d.ts +18 -5
  35. package/dist/commands/sync.js +251 -65
  36. package/dist/commands/teams.js +1 -0
  37. package/dist/commands/tmux.d.ts +25 -0
  38. package/dist/commands/tmux.js +415 -0
  39. package/dist/commands/trash.d.ts +2 -2
  40. package/dist/commands/trash.js +1 -1
  41. package/dist/commands/versions.js +2 -2
  42. package/dist/commands/view.js +14 -4
  43. package/dist/commands/workflows.js +4 -3
  44. package/dist/commands/worktree.d.ts +4 -5
  45. package/dist/commands/worktree.js +4 -4
  46. package/dist/index.js +68 -20
  47. package/dist/lib/agents.d.ts +19 -10
  48. package/dist/lib/agents.js +102 -28
  49. package/dist/lib/auto-pull-worker.d.ts +1 -1
  50. package/dist/lib/auto-pull-worker.js +2 -2
  51. package/dist/lib/auto-pull.d.ts +1 -1
  52. package/dist/lib/auto-pull.js +1 -1
  53. package/dist/lib/beta.d.ts +1 -1
  54. package/dist/lib/beta.js +1 -1
  55. package/dist/lib/capabilities.js +2 -0
  56. package/dist/lib/commands.d.ts +28 -1
  57. package/dist/lib/commands.js +125 -20
  58. package/dist/lib/doctor-diff.js +2 -2
  59. package/dist/lib/exec.d.ts +14 -0
  60. package/dist/lib/exec.js +39 -5
  61. package/dist/lib/fuzzy.d.ts +12 -2
  62. package/dist/lib/fuzzy.js +29 -4
  63. package/dist/lib/git.js +8 -1
  64. package/dist/lib/hooks.d.ts +2 -2
  65. package/dist/lib/hooks.js +97 -10
  66. package/dist/lib/import.d.ts +21 -0
  67. package/dist/lib/import.js +55 -2
  68. package/dist/lib/mcp.js +32 -2
  69. package/dist/lib/migrate.d.ts +51 -0
  70. package/dist/lib/migrate.js +227 -1
  71. package/dist/lib/models.js +62 -15
  72. package/dist/lib/permissions.d.ts +36 -2
  73. package/dist/lib/permissions.js +217 -7
  74. package/dist/lib/plugin-marketplace.d.ts +108 -40
  75. package/dist/lib/plugin-marketplace.js +243 -94
  76. package/dist/lib/plugins.d.ts +21 -4
  77. package/dist/lib/plugins.js +130 -49
  78. package/dist/lib/profiles-presets.js +12 -12
  79. package/dist/lib/project-launch.d.ts +65 -0
  80. package/dist/lib/project-launch.js +367 -0
  81. package/dist/lib/pty-client.js +1 -1
  82. package/dist/lib/pty-server.d.ts +1 -1
  83. package/dist/lib/pty-server.js +28 -4
  84. package/dist/lib/refresh.d.ts +26 -0
  85. package/dist/lib/refresh.js +315 -0
  86. package/dist/lib/resource-patterns.d.ts +1 -1
  87. package/dist/lib/resource-patterns.js +1 -1
  88. package/dist/lib/resources/commands.js +2 -2
  89. package/dist/lib/resources/hooks.d.ts +1 -1
  90. package/dist/lib/resources/hooks.js +1 -1
  91. package/dist/lib/resources/mcp.d.ts +1 -1
  92. package/dist/lib/resources/mcp.js +5 -6
  93. package/dist/lib/resources/permissions.js +5 -2
  94. package/dist/lib/resources/rules.js +3 -2
  95. package/dist/lib/resources/skills.js +3 -2
  96. package/dist/lib/resources/types.d.ts +1 -1
  97. package/dist/lib/resources.js +2 -2
  98. package/dist/lib/rotate.d.ts +1 -1
  99. package/dist/lib/rotate.js +1 -1
  100. package/dist/lib/routines.d.ts +16 -4
  101. package/dist/lib/routines.js +67 -17
  102. package/dist/lib/rules/compile.js +22 -10
  103. package/dist/lib/rules/rules.js +3 -3
  104. package/dist/lib/runner.js +16 -3
  105. package/dist/lib/scheduler.js +15 -1
  106. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  107. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  108. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
  109. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  110. package/dist/lib/secrets/linux.d.ts +44 -9
  111. package/dist/lib/secrets/linux.js +302 -48
  112. package/dist/lib/session/db.js +15 -2
  113. package/dist/lib/session/discover.js +118 -3
  114. package/dist/lib/session/parse.js +3 -0
  115. package/dist/lib/session/types.d.ts +1 -1
  116. package/dist/lib/session/types.js +1 -1
  117. package/dist/lib/shims.d.ts +10 -9
  118. package/dist/lib/shims.js +101 -50
  119. package/dist/lib/skills.d.ts +1 -1
  120. package/dist/lib/skills.js +10 -9
  121. package/dist/lib/staleness/detectors/commands.d.ts +3 -0
  122. package/dist/lib/staleness/detectors/commands.js +46 -0
  123. package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
  124. package/dist/lib/staleness/detectors/hooks.js +44 -0
  125. package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
  126. package/dist/lib/staleness/detectors/mcp.js +31 -0
  127. package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
  128. package/dist/lib/staleness/detectors/permissions.js +201 -0
  129. package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
  130. package/dist/lib/staleness/detectors/plugins.js +23 -0
  131. package/dist/lib/staleness/detectors/rules.d.ts +3 -0
  132. package/dist/lib/staleness/detectors/rules.js +34 -0
  133. package/dist/lib/staleness/detectors/skills.d.ts +3 -0
  134. package/dist/lib/staleness/detectors/skills.js +71 -0
  135. package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
  136. package/dist/lib/staleness/detectors/subagents.js +50 -0
  137. package/dist/lib/staleness/detectors/types.d.ts +22 -0
  138. package/dist/lib/staleness/detectors/types.js +1 -0
  139. package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
  140. package/dist/lib/staleness/detectors/workflows.js +28 -0
  141. package/dist/lib/staleness/registry.d.ts +26 -0
  142. package/dist/lib/staleness/registry.js +123 -0
  143. package/dist/lib/staleness/writers/commands.d.ts +3 -0
  144. package/dist/lib/staleness/writers/commands.js +111 -0
  145. package/dist/lib/staleness/writers/hooks.d.ts +3 -0
  146. package/dist/lib/staleness/writers/hooks.js +47 -0
  147. package/dist/lib/staleness/writers/kinds.d.ts +10 -0
  148. package/dist/lib/staleness/writers/kinds.js +15 -0
  149. package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
  150. package/dist/lib/staleness/writers/lazy-map.js +19 -0
  151. package/dist/lib/staleness/writers/mcp.d.ts +10 -0
  152. package/dist/lib/staleness/writers/mcp.js +19 -0
  153. package/dist/lib/staleness/writers/permissions.d.ts +13 -0
  154. package/dist/lib/staleness/writers/permissions.js +26 -0
  155. package/dist/lib/staleness/writers/plugins.d.ts +7 -0
  156. package/dist/lib/staleness/writers/plugins.js +31 -0
  157. package/dist/lib/staleness/writers/rules.d.ts +7 -0
  158. package/dist/lib/staleness/writers/rules.js +55 -0
  159. package/dist/lib/staleness/writers/skills.d.ts +3 -0
  160. package/dist/lib/staleness/writers/skills.js +81 -0
  161. package/dist/lib/staleness/writers/sources.d.ts +16 -0
  162. package/dist/lib/staleness/writers/sources.js +72 -0
  163. package/dist/lib/staleness/writers/subagents.d.ts +3 -0
  164. package/dist/lib/staleness/writers/subagents.js +53 -0
  165. package/dist/lib/staleness/writers/types.d.ts +36 -0
  166. package/dist/lib/staleness/writers/types.js +1 -0
  167. package/dist/lib/staleness/writers/workflows.d.ts +7 -0
  168. package/dist/lib/staleness/writers/workflows.js +31 -0
  169. package/dist/lib/state.d.ts +34 -11
  170. package/dist/lib/state.js +58 -13
  171. package/dist/lib/subagents.d.ts +0 -2
  172. package/dist/lib/subagents.js +6 -6
  173. package/dist/lib/teams/agents.js +1 -1
  174. package/dist/lib/teams/parsers.d.ts +1 -1
  175. package/dist/lib/tmux/binary.d.ts +67 -0
  176. package/dist/lib/tmux/binary.js +141 -0
  177. package/dist/lib/tmux/index.d.ts +8 -0
  178. package/dist/lib/tmux/index.js +8 -0
  179. package/dist/lib/tmux/paths.d.ts +17 -0
  180. package/dist/lib/tmux/paths.js +30 -0
  181. package/dist/lib/tmux/session.d.ts +122 -0
  182. package/dist/lib/tmux/session.js +305 -0
  183. package/dist/lib/types.d.ts +58 -7
  184. package/dist/lib/types.js +1 -1
  185. package/dist/lib/usage.js +1 -1
  186. package/dist/lib/versions.d.ts +4 -4
  187. package/dist/lib/versions.js +154 -491
  188. package/dist/lib/workflows.d.ts +2 -4
  189. package/dist/lib/workflows.js +3 -4
  190. package/package.json +7 -7
  191. package/scripts/postinstall.js +16 -63
  192. package/dist/commands/status.d.ts +0 -9
  193. package/dist/commands/status.js +0 -25
@@ -11,11 +11,12 @@
11
11
  import * as fs from 'fs';
12
12
  import * as path from 'path';
13
13
  import { execFileSync } from 'child_process';
14
- import { getPluginsDir, getTrashPluginsDir } from './state.js';
14
+ import { getPluginsDir, getTrashPluginsDir, getExtraPluginsDir, getProjectPluginsDir } from './state.js';
15
15
  import { listInstalledVersions, getVersionHomePath } from './versions.js';
16
- import { AGENTS, PLUGINS_CAPABLE_AGENTS } from './agents.js';
16
+ import { AGENTS, agentConfigDirName } from './agents.js';
17
+ import { capableAgents, isCapable } from './capabilities.js';
17
18
  import { shouldInstallCommandAsSkill, installCommandSkillToVersion } from './command-skills.js';
18
- import { copyPluginToMarketplace, syncMarketplaceManifest, registerMarketplace, unregisterMarketplace, enablePluginInSettings, disablePluginInSettings, removePluginFromMarketplace, marketplaceIsEmpty, removeEmptyMarketplaceDir, isInstalledInMarketplace, marketplaceRoot, } from './plugin-marketplace.js';
19
+ import { copyPluginToMarketplace, syncMarketplaceManifest, registerMarketplace, unregisterMarketplace, addPluginToSettings, removePluginFromSettings, removePluginFromMarketplace, marketplaceIsEmpty, removeEmptyMarketplaceDir, isInstalledInMarketplace, marketplaceRoot, discoverMarketplaces, marketplaceNameFor, MARKETPLACE_NAME, PROJECT_MARKETPLACE_NAME, } from './plugin-marketplace.js';
19
20
  const PLUGIN_MANIFEST_DIR = '.claude-plugin';
20
21
  const PLUGIN_MANIFEST_FILE = 'plugin.json';
21
22
  const USER_CONFIG_FILE = '.user-config.json';
@@ -43,11 +44,15 @@ function isPluginRootEntry(pluginsDir, entry) {
43
44
  }
44
45
  }
45
46
  /**
46
- * Discover all plugins in ~/.agents/plugins/.
47
+ * Discover all plugins in a given plugins directory (e.g. ~/.agents/plugins/,
48
+ * ~/.agents/.system/plugins/, <cwd>/.agents/plugins/, ~/.agents-<alias>/plugins/).
47
49
  * A valid plugin has a .claude-plugin/plugin.json manifest.
50
+ *
51
+ * `spec` stamps marketplace provenance onto each discovered plugin. Callers that
52
+ * scan a single source dir without a marketplace identity (e.g. project-launch)
53
+ * may omit it; those plugins default to the user marketplace.
48
54
  */
49
- export function discoverPlugins() {
50
- const pluginsDir = getPluginsDir();
55
+ export function discoverPluginsInDir(pluginsDir, spec = { kind: 'user' }) {
51
56
  if (!fs.existsSync(pluginsDir)) {
52
57
  return [];
53
58
  }
@@ -60,15 +65,32 @@ export function discoverPlugins() {
60
65
  const manifest = loadPluginManifest(pluginRoot);
61
66
  if (!manifest)
62
67
  continue;
63
- plugins.push(buildDiscoveredPlugin(pluginRoot, manifest));
68
+ plugins.push(buildDiscoveredPlugin(pluginRoot, manifest, spec));
64
69
  }
65
70
  return plugins;
66
71
  }
67
- export function buildDiscoveredPlugin(pluginRoot, manifest) {
72
+ /**
73
+ * Discover every plugin across ALL marketplaces — the user repo (~/.agents/),
74
+ * each enabled extra repo (~/.agents-<alias>/), and the project repo
75
+ * (<cwd>/.agents/) — stamping marketplace provenance onto each.
76
+ *
77
+ * Plugin names are NOT deduplicated across marketplaces: a `code` plugin in both
78
+ * the user repo and an extra repo yields two entries (`code@agents-cli` and
79
+ * `code@agents-<alias>`), each installing into its own marketplace directory.
80
+ */
81
+ export function discoverPlugins(opts = {}) {
82
+ const out = [];
83
+ for (const dm of discoverMarketplaces(opts)) {
84
+ out.push(...discoverPluginsInDir(dm.pluginsRoot, dm.spec));
85
+ }
86
+ return out;
87
+ }
88
+ export function buildDiscoveredPlugin(pluginRoot, manifest, spec = { kind: 'user' }) {
68
89
  return {
69
90
  name: manifest.name,
70
91
  root: pluginRoot,
71
92
  manifest,
93
+ marketplace: marketplaceNameFor(spec),
72
94
  skills: discoverPluginSkills(pluginRoot),
73
95
  hooks: discoverPluginHooks(pluginRoot),
74
96
  scripts: discoverPluginScripts(pluginRoot),
@@ -151,7 +173,7 @@ export function getPlugin(name) {
151
173
  * Otherwise defaults to all plugin-capable agents.
152
174
  */
153
175
  export function pluginSupportsAgent(plugin, agent) {
154
- if (!PLUGINS_CAPABLE_AGENTS.includes(agent))
176
+ if (!isCapable(agent, 'plugins'))
155
177
  return false;
156
178
  if (plugin.manifest.agents && plugin.manifest.agents.length > 0) {
157
179
  return plugin.manifest.agents.includes(agent);
@@ -274,7 +296,7 @@ function pluginHasNonPermissionSettings(pluginRoot) {
274
296
  * ${user_config.<key>} -> value from plugin's .user-config.json
275
297
  */
276
298
  export function expandPluginVars(str, pluginRoot, pluginName, agentId, versionHome, userConfig) {
277
- const dataDir = path.join(versionHome, `.${agentId}`, 'plugin-data', pluginName);
299
+ const dataDir = path.join(versionHome, agentConfigDirName(agentId), 'plugin-data', pluginName);
278
300
  let result = str
279
301
  .replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRoot)
280
302
  .replace(/\$\{CLAUDE_PLUGIN_DATA\}/g, dataDir);
@@ -319,6 +341,42 @@ export function checkPluginDependencies(manifest) {
319
341
  const installed = new Set(discoverPlugins().map(p => p.name));
320
342
  return manifest.dependencies.filter(dep => !installed.has(dep));
321
343
  }
344
+ // ─── Marketplace routing ──────────────────────────────────────────────────────
345
+ /**
346
+ * Reconstruct a MarketplaceSpec from a marketplace name. The inverse of
347
+ * marketplaceNameFor(): "agents-cli" → user, "agents-project" → project,
348
+ * "agents-<alias>" → extra. The per-version marketplace operations only key off
349
+ * the name (never spec.root), but we resolve the real source root anyway so the
350
+ * spec is honest for any caller that inspects it.
351
+ */
352
+ function marketplaceSpecForName(name, cwd = process.cwd()) {
353
+ if (!name || name === MARKETPLACE_NAME)
354
+ return { kind: 'user' };
355
+ if (name === PROJECT_MARKETPLACE_NAME) {
356
+ return { kind: 'project', root: getProjectPluginsDir(cwd) ?? '' };
357
+ }
358
+ const alias = name.slice('agents-'.length);
359
+ return { kind: 'extra', alias, root: getExtraPluginsDir(alias) };
360
+ }
361
+ /**
362
+ * List the marketplace names that have been synthesized under a version home
363
+ * (i.e. the directories beneath .{agent}/plugins/marketplaces/). Used by
364
+ * removal/orphan/diff passes that must touch every marketplace a version
365
+ * carries, not just the user one.
366
+ */
367
+ function listVersionMarketplaceNames(agent, versionHome) {
368
+ const dir = path.join(versionHome, `.${agent}`, 'plugins', 'marketplaces');
369
+ if (!fs.existsSync(dir))
370
+ return [];
371
+ try {
372
+ return fs.readdirSync(dir, { withFileTypes: true })
373
+ .filter(d => d.isDirectory() && !d.name.startsWith('.'))
374
+ .map(d => d.name);
375
+ }
376
+ catch {
377
+ return [];
378
+ }
379
+ }
322
380
  // ─── Main sync entry point ────────────────────────────────────────────────────
323
381
  /**
324
382
  * Sync a plugin to a specific agent version's home directory.
@@ -352,8 +410,13 @@ export function syncPluginToVersion(plugin, agent, versionHome, options = {}) {
352
410
  return result;
353
411
  }
354
412
  const userConfig = loadUserConfig(plugin.name);
413
+ // Route every marketplace op through the plugin's own marketplace, so a plugin
414
+ // discovered in an extra/project repo installs under its own
415
+ // marketplaces/<name>/ tree — never the user marketplace.
416
+ const spec = marketplaceSpecForName(plugin.marketplace);
417
+ const marketplaceName = marketplaceNameFor(spec);
355
418
  // 1. Copy plugin to native marketplace install dir.
356
- const installDir = copyPluginToMarketplace(plugin, agent, versionHome);
419
+ const installDir = copyPluginToMarketplace(plugin, spec, agent, versionHome);
357
420
  // 2. Pre-expand ${user_config.*} in the copy. Leave ${CLAUDE_PLUGIN_ROOT} /
358
421
  // ${CLAUDE_PLUGIN_DATA} alone — Claude expands those natively at runtime.
359
422
  if (Object.keys(userConfig).length > 0) {
@@ -371,16 +434,20 @@ export function syncPluginToVersion(plugin, agent, versionHome, options = {}) {
371
434
  }
372
435
  }
373
436
  // 3-5. Synthesize manifest, register marketplace, enable plugin.
374
- syncMarketplaceManifest(agent, versionHome);
375
- registerMarketplace(agent, versionHome);
376
- enablePluginInSettings(plugin.name, agent, versionHome, {
377
- allowExecSurfaces: options.allowExecSurfaces === true,
378
- });
437
+ syncMarketplaceManifest(spec, agent, versionHome);
438
+ registerMarketplace(spec, agent, versionHome);
439
+ // Trust gate: plugins with executable surfaces (hooks/, bin/, scripts/,
440
+ // .mcp.json, settings.json, permissions/) are only auto-enabled when the
441
+ // caller explicitly opts in. addPluginToSettings does no gating — that moved
442
+ // here, where plugin capabilities are inspected.
443
+ if (options.allowExecSurfaces === true || !hasPluginExecSurfaces(inspectPluginCapabilities(plugin.root))) {
444
+ addPluginToSettings(plugin.name, marketplaceName, agent, versionHome);
445
+ }
379
446
  // 5b. Convert plugin commands/ to skills for agents that dropped command support
380
447
  // (Codex >= 0.117.0). Skill name is prefixed with plugin name to avoid
381
448
  // collision with standalone command skills.
382
449
  if (options.version && shouldInstallCommandAsSkill(agent, options.version) && plugin.commands.length > 0) {
383
- const agentDir = path.join(versionHome, `.${AGENTS[agent].id}`);
450
+ const agentDir = path.join(versionHome, agentConfigDirName(agent));
384
451
  const skillSourceDirs = [path.join(agentDir, 'skills')];
385
452
  for (const cmd of plugin.commands) {
386
453
  const srcPath = path.join(plugin.root, 'commands', `${cmd}.md`);
@@ -465,7 +532,7 @@ function expandUserConfigInDir(dir, userConfig) {
465
532
  */
466
533
  function migrateLegacyFlatLayout(plugin, agent, versionHome) {
467
534
  const prefix = `${plugin.name}--`;
468
- const agentRoot = path.join(versionHome, `.${agent}`);
535
+ const agentRoot = path.join(versionHome, agentConfigDirName(agent));
469
536
  // 1. skills
470
537
  const skillsDir = path.join(agentRoot, 'skills');
471
538
  if (fs.existsSync(skillsDir)) {
@@ -598,9 +665,9 @@ function migrateLegacyFlatLayout(plugin, agent, versionHome) {
598
665
  * they're treated as stale and migrated away on the next sync.
599
666
  */
600
667
  export function isPluginSynced(plugin, agent, versionHome) {
601
- if (!PLUGINS_CAPABLE_AGENTS.includes(agent))
668
+ if (!isCapable(agent, 'plugins'))
602
669
  return false;
603
- return isInstalledInMarketplace(plugin.name, agent, versionHome);
670
+ return isInstalledInMarketplace(plugin.name, marketplaceSpecForName(plugin.marketplace), agent, versionHome);
604
671
  }
605
672
  // ─── Removal ─────────────────────────────────────────────────────────────────
606
673
  /**
@@ -619,19 +686,26 @@ export function removePluginFromVersion(pluginName, pluginRoot, agent, versionHo
619
686
  permissions: 0,
620
687
  mcp: 0,
621
688
  };
622
- // 1. Remove the plugin from the marketplace install dir + disable it.
623
- const removed = removePluginFromMarketplace(pluginName, agent, versionHome);
624
- if (removed) {
625
- result.skills.push(pluginName);
689
+ // 1. Remove the plugin from every marketplace it's installed under. A name can
690
+ // appear in more than one (collision across repos), so we sweep them all.
691
+ let removedAny = false;
692
+ for (const name of listVersionMarketplaceNames(agent, versionHome)) {
693
+ const spec = marketplaceSpecForName(name);
694
+ if (removePluginFromMarketplace(pluginName, name, agent, versionHome)) {
695
+ removedAny = true;
696
+ }
697
+ removePluginFromSettings(pluginName, name, agent, versionHome);
698
+ // Refresh marketplace.json so it reflects what's left under plugins/.
699
+ syncMarketplaceManifest(spec, agent, versionHome);
700
+ // If we just removed the last plugin, drop the marketplace dir and the
701
+ // known_marketplaces.json entry too.
702
+ if (marketplaceIsEmpty(name, agent, versionHome)) {
703
+ removeEmptyMarketplaceDir(name, agent, versionHome);
704
+ unregisterMarketplace(name, agent, versionHome);
705
+ }
626
706
  }
627
- disablePluginInSettings(pluginName, agent, versionHome);
628
- // 2. Refresh marketplace.json so it reflects what's left under plugins/.
629
- syncMarketplaceManifest(agent, versionHome);
630
- // 3. If we just removed the last plugin, drop the marketplace dir and the
631
- // known_marketplaces.json entry too.
632
- if (marketplaceIsEmpty(agent, versionHome)) {
633
- removeEmptyMarketplaceDir(agent, versionHome);
634
- unregisterMarketplace(agent, versionHome);
707
+ if (removedAny) {
708
+ result.skills.push(pluginName);
635
709
  }
636
710
  // 4. Strip any legacy dual-dash entries from prior agents-cli versions.
637
711
  cleanLegacyFlatLayout(pluginName, pluginRoot, agent, versionHome, result);
@@ -643,7 +717,7 @@ export function removePluginFromVersion(pluginName, pluginRoot, agent, versionHo
643
717
  */
644
718
  function cleanLegacyFlatLayout(pluginName, pluginRoot, agent, versionHome, result) {
645
719
  const prefix = `${pluginName}--`;
646
- const agentRoot = path.join(versionHome, `.${agent}`);
720
+ const agentRoot = path.join(versionHome, agentConfigDirName(agent));
647
721
  const skillsDir = path.join(agentRoot, 'skills');
648
722
  if (fs.existsSync(skillsDir)) {
649
723
  for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
@@ -786,9 +860,13 @@ function cleanLegacyFlatLayout(pluginName, pluginRoot, agent, versionHome, resul
786
860
  */
787
861
  export function cleanOrphanedPluginSkills(agent, versionHome, activePluginNames, version) {
788
862
  const removed = [];
789
- // 1. Walk the native marketplace install dir and trash entries no longer active.
790
- const mktPluginsDir = path.join(marketplaceRoot(agent, versionHome), 'plugins');
791
- if (fs.existsSync(mktPluginsDir)) {
863
+ // 1. Walk every marketplace's install dir and trash entries no longer active.
864
+ for (const name of listVersionMarketplaceNames(agent, versionHome)) {
865
+ const spec = marketplaceSpecForName(name);
866
+ const mktPluginsDir = path.join(marketplaceRoot(name, agent, versionHome), 'plugins');
867
+ if (!fs.existsSync(mktPluginsDir))
868
+ continue;
869
+ let trashedHere = false;
792
870
  for (const entry of fs.readdirSync(mktPluginsDir, { withFileTypes: true })) {
793
871
  if (!entry.isDirectory() || entry.name.startsWith('.'))
794
872
  continue;
@@ -800,22 +878,23 @@ export function cleanOrphanedPluginSkills(agent, versionHome, activePluginNames,
800
878
  const trashDest = path.join(trashDir, stamp);
801
879
  fs.mkdirSync(trashDir, { recursive: true, mode: 0o700 });
802
880
  fs.renameSync(path.join(mktPluginsDir, entry.name), trashDest);
803
- disablePluginInSettings(entry.name, agent, versionHome);
881
+ removePluginFromSettings(entry.name, name, agent, versionHome);
804
882
  removed.push(entry.name);
883
+ trashedHere = true;
805
884
  }
806
885
  catch { /* skip on error */ }
807
886
  }
808
887
  // Keep manifest in sync with on-disk state and drop the marketplace if empty.
809
- if (removed.length > 0) {
810
- syncMarketplaceManifest(agent, versionHome);
811
- if (marketplaceIsEmpty(agent, versionHome)) {
812
- removeEmptyMarketplaceDir(agent, versionHome);
813
- unregisterMarketplace(agent, versionHome);
888
+ if (trashedHere) {
889
+ syncMarketplaceManifest(spec, agent, versionHome);
890
+ if (marketplaceIsEmpty(name, agent, versionHome)) {
891
+ removeEmptyMarketplaceDir(name, agent, versionHome);
892
+ unregisterMarketplace(name, agent, versionHome);
814
893
  }
815
894
  }
816
895
  }
817
896
  // 2. Sweep legacy dual-dash skills directories from older agents-cli versions.
818
- const skillsDir = path.join(versionHome, `.${agent}`, 'skills');
897
+ const skillsDir = path.join(versionHome, agentConfigDirName(agent), 'skills');
819
898
  if (fs.existsSync(skillsDir)) {
820
899
  for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
821
900
  if (!entry.isDirectory())
@@ -843,8 +922,10 @@ export function diffVersionPlugins(agent, version) {
843
922
  const versionHome = getVersionHomePath(agent, version);
844
923
  const activePlugins = new Set(discoverPlugins().map(p => p.name));
845
924
  const orphans = [];
846
- const mktPluginsDir = path.join(marketplaceRoot(agent, versionHome), 'plugins');
847
- if (fs.existsSync(mktPluginsDir)) {
925
+ for (const name of listVersionMarketplaceNames(agent, versionHome)) {
926
+ const mktPluginsDir = path.join(marketplaceRoot(name, agent, versionHome), 'plugins');
927
+ if (!fs.existsSync(mktPluginsDir))
928
+ continue;
848
929
  for (const entry of fs.readdirSync(mktPluginsDir, { withFileTypes: true })) {
849
930
  if (!entry.isDirectory() || entry.name.startsWith('.'))
850
931
  continue;
@@ -854,7 +935,7 @@ export function diffVersionPlugins(agent, version) {
854
935
  }
855
936
  }
856
937
  // Also surface legacy dual-dash skill dirs as orphans during migration period.
857
- const skillsDir = path.join(versionHome, `.${agent}`, 'skills');
938
+ const skillsDir = path.join(versionHome, agentConfigDirName(agent), 'skills');
858
939
  if (fs.existsSync(skillsDir)) {
859
940
  for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
860
941
  if (!entry.isDirectory())
@@ -872,9 +953,9 @@ export function diffVersionPlugins(agent, version) {
872
953
  }
873
954
  export function iterPluginsCapableVersions(filter) {
874
955
  const pairs = [];
875
- const agents = filter?.agent ? [filter.agent] : PLUGINS_CAPABLE_AGENTS;
956
+ const agents = filter?.agent ? [filter.agent] : capableAgents('plugins');
876
957
  for (const agent of agents) {
877
- if (!PLUGINS_CAPABLE_AGENTS.includes(agent))
958
+ if (!isCapable(agent, 'plugins'))
878
959
  continue;
879
960
  const versions = listInstalledVersions(agent);
880
961
  for (const version of versions) {
@@ -887,7 +968,7 @@ export function iterPluginsCapableVersions(filter) {
887
968
  }
888
969
  export function removePluginSkillFromVersion(agent, version, skillName) {
889
970
  const versionHome = getVersionHomePath(agent, version);
890
- const skillPath = path.join(versionHome, `.${agent}`, 'skills', skillName);
971
+ const skillPath = path.join(versionHome, agentConfigDirName(agent), 'skills', skillName);
891
972
  if (!fs.existsSync(skillPath)) {
892
973
  return { success: true };
893
974
  }
@@ -10,13 +10,13 @@
10
10
  //
11
11
  // Important limitation of Claude Code + non-Anthropic models via OpenRouter:
12
12
  // Claude Code sends `thinking:{type:"enabled"}` in its Anthropic payload by
13
- // default, and its `--print` consolidation returns empty text when a response
14
- // contains thinking/redacted_thinking blocks — even when the model *also*
15
- // emits a text block. This means reasoning models work fine in interactive
16
- // `claude` mode (same env vars) but `agents run <profile> --print` sees
17
- // empty stdout.
13
+ // default, and its headless output consolidation returns empty text when a
14
+ // response contains thinking/redacted_thinking blocks — even when the model
15
+ // *also* emits a text block. This means reasoning models work fine in
16
+ // interactive `claude` mode (same env vars) but headless invocations
17
+ // (`agents run <profile> "<prompt>"`) see empty stdout.
18
18
  //
19
- // Presets flagged "print-safe" use non-reasoning variants that ignore
19
+ // Presets flagged "headless-safe" use non-reasoning variants that ignore
20
20
  // thinking:enabled. Presets flagged "reasoning" are the leaderboard leaders
21
21
  // but are best invoked interactively.
22
22
  const OPENROUTER_BASE = 'https://openrouter.ai/api';
@@ -30,7 +30,7 @@ export const PRESETS = [
30
30
  // ----- Top coding (via OpenRouter) -----
31
31
  {
32
32
  name: 'kimi',
33
- description: 'Kimi K2.5 via OpenRouter (262K ctx, $0.38/$1.72 per 1M). Top Kimi: 99% HumanEval, 76.8% SWE-bench. REASONING — works interactively, but `agents run --print` returns empty stdout. Use `kimi-chat` preset for scripting.',
33
+ description: 'Kimi K2.5 via OpenRouter (262K ctx, $0.38/$1.72 per 1M). Top Kimi: 99% HumanEval, 76.8% SWE-bench. REASONING — works interactively, but `agents run kimi "<prompt>"` (headless) returns empty stdout. Use `kimi-chat` preset for scripting.',
34
34
  ...OPENROUTER_AUTH,
35
35
  env: {
36
36
  ANTHROPIC_BASE_URL: OPENROUTER_BASE,
@@ -40,7 +40,7 @@ export const PRESETS = [
40
40
  },
41
41
  {
42
42
  name: 'kimi-chat',
43
- description: 'Kimi K2 0905 via OpenRouter (262K ctx, $0.40/$2.00 per 1M). Non-reasoning sibling of K2.5 — slightly older but PRINT-SAFE, works end-to-end with `agents run --print` and in scripts/automation.',
43
+ description: 'Kimi K2 0905 via OpenRouter (262K ctx, $0.40/$2.00 per 1M). Non-reasoning sibling of K2.5 — slightly older but HEADLESS-SAFE, works end-to-end with `agents run kimi-chat "<prompt>"` and in scripts/automation.',
44
44
  ...OPENROUTER_AUTH,
45
45
  env: {
46
46
  ANTHROPIC_BASE_URL: OPENROUTER_BASE,
@@ -50,7 +50,7 @@ export const PRESETS = [
50
50
  },
51
51
  {
52
52
  name: 'minimax',
53
- description: 'MiniMax M2.5 via OpenRouter (230B params). #1 SWE-bench Verified (80.2%) on Apr 2026 leaderboards. REASONING — works interactively, --print returns empty.',
53
+ description: 'MiniMax M2.5 via OpenRouter (230B params). #1 SWE-bench Verified (80.2%) on Apr 2026 leaderboards. REASONING — works interactively, headless `agents run` returns empty stdout.',
54
54
  ...OPENROUTER_AUTH,
55
55
  env: {
56
56
  ANTHROPIC_BASE_URL: OPENROUTER_BASE,
@@ -60,7 +60,7 @@ export const PRESETS = [
60
60
  },
61
61
  {
62
62
  name: 'glm',
63
- description: 'GLM 5 via OpenRouter (80K ctx, $0.72/$2.30 per 1M). #1 Chatbot Arena ELO (1451) among open-weight models on BenchLM.ai (Apr 2026). Prompt-complexity-dependent reasoning — Claude Code\'s 38K system prompt typically triggers thinking blocks, so --print is unreliable. Interactive use is fine.',
63
+ description: 'GLM 5 via OpenRouter (80K ctx, $0.72/$2.30 per 1M). #1 Chatbot Arena ELO (1451) among open-weight models on BenchLM.ai (Apr 2026). Prompt-complexity-dependent reasoning — Claude Code\'s 38K system prompt typically triggers thinking blocks, so headless invocations are unreliable. Interactive use is fine.',
64
64
  ...OPENROUTER_AUTH,
65
65
  env: {
66
66
  ANTHROPIC_BASE_URL: OPENROUTER_BASE,
@@ -70,7 +70,7 @@ export const PRESETS = [
70
70
  },
71
71
  {
72
72
  name: 'qwen',
73
- description: 'Qwen3 Coder Next via OpenRouter (256K ctx, $0.15/$0.80 per 1M, sparse MoE 80B/3B active). Latest coding-specific Qwen (Feb 2026). PRINT-SAFE — works with `agents run --print`.',
73
+ description: 'Qwen3 Coder Next via OpenRouter (256K ctx, $0.15/$0.80 per 1M, sparse MoE 80B/3B active). Latest coding-specific Qwen (Feb 2026). HEADLESS-SAFE — works with `agents run qwen "<prompt>"`.',
74
74
  ...OPENROUTER_AUTH,
75
75
  env: {
76
76
  ANTHROPIC_BASE_URL: OPENROUTER_BASE,
@@ -80,7 +80,7 @@ export const PRESETS = [
80
80
  },
81
81
  {
82
82
  name: 'deepseek',
83
- description: 'DeepSeek Chat V3 (0324) via OpenRouter. Latest DeepSeek Chat variant that ignores thinking:enabled. PRINT-SAFE. The newer V3.2 / V3.1-Terminus / V3.2-Speciale are reasoning variants — use `--model deepseek/deepseek-v3.2` to override if you want those for interactive use.',
83
+ description: 'DeepSeek Chat V3 (0324) via OpenRouter. Latest DeepSeek Chat variant that ignores thinking:enabled. HEADLESS-SAFE. The newer V3.2 / V3.1-Terminus / V3.2-Speciale are reasoning variants — use `--model deepseek/deepseek-v3.2` to override if you want those for interactive use.',
84
84
  ...OPENROUTER_AUTH,
85
85
  env: {
86
86
  ANTHROPIC_BASE_URL: OPENROUTER_BASE,
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Launch-time project compile. Invoked by the agent shim's hot path (via
3
+ * `agents sync --launch`) between version resolve and binary exec.
4
+ *
5
+ * Three responsibilities, all skip-fast when there's nothing to do:
6
+ *
7
+ * 1. Compile project rules from `<cwd>/.agents/rules/` into `<cwd>/AGENTS.md`
8
+ * (+ per-agent symlinks). Delegates to compileRulesForProject, which is
9
+ * the same helper management-side `agents sync` uses.
10
+ *
11
+ * 2. Mirror project resources from `<cwd>/.agents/{subagents,commands,skills}`
12
+ * into the agent's workspace-local discovery dirs (`<cwd>/.claude/agents/`,
13
+ * `<cwd>/.claude/commands/`, `<cwd>/.claude/skills/`). File-level symlinks
14
+ * so edits in the source dir are live without re-sync. Skips any dest
15
+ * entry that already exists and isn't one of our symlinks (don't-clobber).
16
+ * v1: claude-only. Other agents have varying workspace conventions (amp
17
+ * uses `~/.config/amp`, antigravity uses `~/.gemini/antigravity-cli`,
18
+ * codex/gemini/cursor lack subagent support entirely) — they're follow-up
19
+ * material. NOTE: `.mcp.json` is intentionally NOT auto-symlinked from
20
+ * the launch path — that's a supply-chain surface (cloning a hostile
21
+ * repo would auto-register an attacker MCP server). Belongs to full
22
+ * `agents sync` with explicit opt-in, not the hot path.
23
+ *
24
+ * 3. Synthesize four scope-grouped plugin marketplaces under the version's
25
+ * `<versionHome>/.{agent}/plugins/marketplaces/` (for plugin-capable agents):
26
+ * - agents-cli ← ~/.agents/plugins/* (user scope, legacy name)
27
+ * - agents-system ← ~/.agents/.system/plugins/*
28
+ * - extras-<alias> ← ~/.agents-<alias>/plugins/* (per enabled extra)
29
+ * - agents-project ← <cwd>/.agents/plugins/*
30
+ * Each plugin is copied in (skip-fast via mtime cache), the marketplace
31
+ * catalog is rewritten only when contents change, and the marketplace is
32
+ * registered in known_marketplaces.json. Upstream marketplaces like
33
+ * "claude-plugins-official" are left untouched. Project- and extras-
34
+ * scope plugins do NOT auto-enable exec surfaces (.mcp.json, hooks, bin/,
35
+ * scripts/) — user must explicitly `agents plugins enable` them.
36
+ *
37
+ * Heavy work (version-home reconciliation, hook registration, MCP merging)
38
+ * stays in `agents sync` without --launch and is NOT touched here. The
39
+ * launch path is filesystem-only and skip-fast: sub-50ms when no source
40
+ * has changed, scales linearly only with newly-modified plugins on the
41
+ * change path.
42
+ */
43
+ import type { AgentId } from './types.js';
44
+ import { pluginInstallDir } from './plugin-marketplace.js';
45
+ export interface LaunchSyncOptions {
46
+ agent: AgentId;
47
+ version: string;
48
+ cwd: string;
49
+ }
50
+ export interface LaunchSyncResult {
51
+ /** Project rules were re-compiled into cwd/AGENTS.md (+ per-agent symlinks). */
52
+ rulesCompiled: boolean;
53
+ /** Number of workspace resource symlinks created or refreshed. */
54
+ workspaceLinks: number;
55
+ /** Workspace resource paths we left alone because they exist and aren't ours. */
56
+ workspaceSkipped: string[];
57
+ /** Map of marketplace name → plugin names installed under it. */
58
+ marketplaces: Record<string, string[]>;
59
+ }
60
+ /**
61
+ * Run the launch-time project compile. Safe to call on every agent launch:
62
+ * each step is idempotent and skips when its inputs are missing.
63
+ */
64
+ export declare function runLaunchSync(opts: LaunchSyncOptions): LaunchSyncResult;
65
+ export { pluginInstallDir };