@phnx-labs/agents-cli 1.20.4 → 1.20.6

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 (207) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +49 -18
  3. package/dist/commands/browser.js +31 -4
  4. package/dist/commands/cli.js +1 -1
  5. package/dist/commands/cloud.js +1 -1
  6. package/dist/commands/commands.js +2 -0
  7. package/dist/commands/computer.js +10 -2
  8. package/dist/commands/defaults.d.ts +7 -0
  9. package/dist/commands/defaults.js +89 -0
  10. package/dist/commands/doctor.js +1 -1
  11. package/dist/commands/exec.js +73 -19
  12. package/dist/commands/hooks.js +6 -6
  13. package/dist/commands/inspect.d.ts +26 -0
  14. package/dist/commands/inspect.js +590 -0
  15. package/dist/commands/mcp.js +17 -16
  16. package/dist/commands/models.js +1 -1
  17. package/dist/commands/packages.js +6 -4
  18. package/dist/commands/permissions.js +13 -12
  19. package/dist/commands/plugins.d.ts +13 -0
  20. package/dist/commands/plugins.js +100 -11
  21. package/dist/commands/prune.js +3 -2
  22. package/dist/commands/pull.d.ts +12 -5
  23. package/dist/commands/pull.js +26 -422
  24. package/dist/commands/push.d.ts +14 -0
  25. package/dist/commands/push.js +30 -0
  26. package/dist/commands/repo.d.ts +1 -1
  27. package/dist/commands/repo.js +155 -112
  28. package/dist/commands/resource-view.d.ts +2 -0
  29. package/dist/commands/resource-view.js +12 -3
  30. package/dist/commands/routines.js +32 -7
  31. package/dist/commands/rules.js +4 -4
  32. package/dist/commands/secrets.js +46 -9
  33. package/dist/commands/sessions.js +1 -0
  34. package/dist/commands/setup.d.ts +3 -3
  35. package/dist/commands/setup.js +17 -17
  36. package/dist/commands/skills.js +6 -5
  37. package/dist/commands/subagents.js +5 -4
  38. package/dist/commands/sync.d.ts +18 -5
  39. package/dist/commands/sync.js +251 -65
  40. package/dist/commands/teams.js +109 -11
  41. package/dist/commands/tmux.d.ts +25 -0
  42. package/dist/commands/tmux.js +415 -0
  43. package/dist/commands/trash.d.ts +2 -2
  44. package/dist/commands/trash.js +1 -1
  45. package/dist/commands/versions.js +2 -2
  46. package/dist/commands/view.d.ts +12 -1
  47. package/dist/commands/view.js +128 -40
  48. package/dist/commands/workflows.js +4 -3
  49. package/dist/commands/worktree.d.ts +4 -5
  50. package/dist/commands/worktree.js +4 -4
  51. package/dist/index.js +106 -41
  52. package/dist/lib/agents.d.ts +23 -10
  53. package/dist/lib/agents.js +88 -25
  54. package/dist/lib/auto-pull-worker.d.ts +1 -1
  55. package/dist/lib/auto-pull-worker.js +2 -2
  56. package/dist/lib/auto-pull.d.ts +1 -1
  57. package/dist/lib/auto-pull.js +1 -1
  58. package/dist/lib/beta.d.ts +1 -1
  59. package/dist/lib/beta.js +1 -1
  60. package/dist/lib/browser/chrome.d.ts +10 -0
  61. package/dist/lib/browser/chrome.js +84 -3
  62. package/dist/lib/capabilities.js +2 -0
  63. package/dist/lib/commands.d.ts +28 -1
  64. package/dist/lib/commands.js +125 -20
  65. package/dist/lib/doctor-diff.js +2 -2
  66. package/dist/lib/exec.d.ts +14 -0
  67. package/dist/lib/exec.js +59 -5
  68. package/dist/lib/fuzzy.d.ts +12 -2
  69. package/dist/lib/fuzzy.js +29 -4
  70. package/dist/lib/git.js +8 -1
  71. package/dist/lib/hooks.d.ts +2 -2
  72. package/dist/lib/hooks.js +97 -10
  73. package/dist/lib/mcp.js +32 -2
  74. package/dist/lib/migrate.d.ts +51 -0
  75. package/dist/lib/migrate.js +233 -5
  76. package/dist/lib/models.js +62 -15
  77. package/dist/lib/permissions.d.ts +59 -2
  78. package/dist/lib/permissions.js +299 -7
  79. package/dist/lib/plugin-marketplace.d.ts +98 -40
  80. package/dist/lib/plugin-marketplace.js +196 -93
  81. package/dist/lib/plugins.d.ts +21 -4
  82. package/dist/lib/plugins.js +130 -49
  83. package/dist/lib/profiles-presets.js +12 -12
  84. package/dist/lib/project-launch.d.ts +70 -0
  85. package/dist/lib/project-launch.js +404 -0
  86. package/dist/lib/pty-client.js +1 -1
  87. package/dist/lib/pty-server.d.ts +1 -1
  88. package/dist/lib/pty-server.js +8 -5
  89. package/dist/lib/refresh.d.ts +26 -0
  90. package/dist/lib/refresh.js +315 -0
  91. package/dist/lib/resource-patterns.d.ts +1 -1
  92. package/dist/lib/resource-patterns.js +1 -1
  93. package/dist/lib/resources/commands.js +2 -2
  94. package/dist/lib/resources/hooks.d.ts +1 -1
  95. package/dist/lib/resources/hooks.js +1 -1
  96. package/dist/lib/resources/mcp.d.ts +1 -1
  97. package/dist/lib/resources/mcp.js +5 -6
  98. package/dist/lib/resources/permissions.js +5 -2
  99. package/dist/lib/resources/rules.js +3 -2
  100. package/dist/lib/resources/skills.js +3 -2
  101. package/dist/lib/resources/types.d.ts +1 -1
  102. package/dist/lib/resources.d.ts +2 -0
  103. package/dist/lib/resources.js +4 -3
  104. package/dist/lib/rotate.d.ts +1 -1
  105. package/dist/lib/rotate.js +7 -19
  106. package/dist/lib/routines.d.ts +16 -4
  107. package/dist/lib/routines.js +67 -17
  108. package/dist/lib/rules/compile.js +22 -10
  109. package/dist/lib/rules/rules.js +3 -3
  110. package/dist/lib/run-config.d.ts +9 -0
  111. package/dist/lib/run-config.js +35 -0
  112. package/dist/lib/run-defaults.d.ts +42 -0
  113. package/dist/lib/run-defaults.js +180 -0
  114. package/dist/lib/runner.js +16 -3
  115. package/dist/lib/scheduler.js +15 -1
  116. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  117. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  118. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +9 -1
  119. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  120. package/dist/lib/secrets/install-helper.d.ts +11 -3
  121. package/dist/lib/secrets/install-helper.js +48 -6
  122. package/dist/lib/secrets/linux.d.ts +56 -9
  123. package/dist/lib/secrets/linux.js +327 -59
  124. package/dist/lib/session/db.js +15 -2
  125. package/dist/lib/session/discover.js +118 -3
  126. package/dist/lib/session/parse.js +3 -0
  127. package/dist/lib/session/types.d.ts +1 -1
  128. package/dist/lib/session/types.js +1 -1
  129. package/dist/lib/shims.d.ts +18 -9
  130. package/dist/lib/shims.js +133 -50
  131. package/dist/lib/skills.d.ts +1 -1
  132. package/dist/lib/skills.js +10 -9
  133. package/dist/lib/staleness/detectors/commands.d.ts +3 -0
  134. package/dist/lib/staleness/detectors/commands.js +46 -0
  135. package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
  136. package/dist/lib/staleness/detectors/hooks.js +44 -0
  137. package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
  138. package/dist/lib/staleness/detectors/mcp.js +31 -0
  139. package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
  140. package/dist/lib/staleness/detectors/permissions.js +201 -0
  141. package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
  142. package/dist/lib/staleness/detectors/plugins.js +23 -0
  143. package/dist/lib/staleness/detectors/rules.d.ts +3 -0
  144. package/dist/lib/staleness/detectors/rules.js +34 -0
  145. package/dist/lib/staleness/detectors/skills.d.ts +3 -0
  146. package/dist/lib/staleness/detectors/skills.js +71 -0
  147. package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
  148. package/dist/lib/staleness/detectors/subagents.js +50 -0
  149. package/dist/lib/staleness/detectors/types.d.ts +22 -0
  150. package/dist/lib/staleness/detectors/types.js +1 -0
  151. package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
  152. package/dist/lib/staleness/detectors/workflows.js +28 -0
  153. package/dist/lib/staleness/registry.d.ts +26 -0
  154. package/dist/lib/staleness/registry.js +123 -0
  155. package/dist/lib/staleness/writers/commands.d.ts +3 -0
  156. package/dist/lib/staleness/writers/commands.js +111 -0
  157. package/dist/lib/staleness/writers/hooks.d.ts +3 -0
  158. package/dist/lib/staleness/writers/hooks.js +47 -0
  159. package/dist/lib/staleness/writers/kinds.d.ts +10 -0
  160. package/dist/lib/staleness/writers/kinds.js +15 -0
  161. package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
  162. package/dist/lib/staleness/writers/lazy-map.js +19 -0
  163. package/dist/lib/staleness/writers/mcp.d.ts +10 -0
  164. package/dist/lib/staleness/writers/mcp.js +19 -0
  165. package/dist/lib/staleness/writers/permissions.d.ts +13 -0
  166. package/dist/lib/staleness/writers/permissions.js +26 -0
  167. package/dist/lib/staleness/writers/plugins.d.ts +7 -0
  168. package/dist/lib/staleness/writers/plugins.js +31 -0
  169. package/dist/lib/staleness/writers/rules.d.ts +7 -0
  170. package/dist/lib/staleness/writers/rules.js +55 -0
  171. package/dist/lib/staleness/writers/skills.d.ts +3 -0
  172. package/dist/lib/staleness/writers/skills.js +81 -0
  173. package/dist/lib/staleness/writers/sources.d.ts +16 -0
  174. package/dist/lib/staleness/writers/sources.js +72 -0
  175. package/dist/lib/staleness/writers/subagents.d.ts +3 -0
  176. package/dist/lib/staleness/writers/subagents.js +53 -0
  177. package/dist/lib/staleness/writers/types.d.ts +36 -0
  178. package/dist/lib/staleness/writers/types.js +1 -0
  179. package/dist/lib/staleness/writers/workflows.d.ts +7 -0
  180. package/dist/lib/staleness/writers/workflows.js +31 -0
  181. package/dist/lib/state.d.ts +34 -11
  182. package/dist/lib/state.js +58 -13
  183. package/dist/lib/subagents.d.ts +0 -2
  184. package/dist/lib/subagents.js +6 -6
  185. package/dist/lib/teams/agents.js +1 -1
  186. package/dist/lib/teams/api.d.ts +67 -0
  187. package/dist/lib/teams/api.js +78 -0
  188. package/dist/lib/teams/parsers.d.ts +1 -1
  189. package/dist/lib/tmux/binary.d.ts +67 -0
  190. package/dist/lib/tmux/binary.js +141 -0
  191. package/dist/lib/tmux/index.d.ts +8 -0
  192. package/dist/lib/tmux/index.js +8 -0
  193. package/dist/lib/tmux/paths.d.ts +17 -0
  194. package/dist/lib/tmux/paths.js +30 -0
  195. package/dist/lib/tmux/session.d.ts +122 -0
  196. package/dist/lib/tmux/session.js +305 -0
  197. package/dist/lib/types.d.ts +73 -13
  198. package/dist/lib/types.js +1 -1
  199. package/dist/lib/usage.js +1 -1
  200. package/dist/lib/versions.d.ts +4 -4
  201. package/dist/lib/versions.js +138 -496
  202. package/dist/lib/workflows.d.ts +2 -4
  203. package/dist/lib/workflows.js +3 -4
  204. package/package.json +6 -3
  205. package/scripts/postinstall.js +16 -63
  206. package/dist/commands/status.d.ts +0 -9
  207. package/dist/commands/status.js +0 -25
@@ -1,45 +1,134 @@
1
1
  /**
2
- * Native plugin marketplace install path for Claude / OpenClaw.
2
+ * Native plugin marketplaces for Claude / OpenClaw — one per DotAgents repo.
3
3
  *
4
- * Plugins managed by agents-cli are exposed as a synthetic local marketplace
5
- * named "agents-cli" under each version's plugin directory:
4
+ * Every DotAgents repo that holds plugins synthesizes its OWN synthetic local
5
+ * marketplace under each version's plugin directory, named after the repo:
6
6
  *
7
7
  * <versionHome>/.{claude,openclaw}/plugins/
8
- * known_marketplaces.json # registers the "agents-cli" marketplace
9
- * marketplaces/agents-cli/
10
- * .claude-plugin/marketplace.json # synthesized catalog
11
- * plugins/<plugin>/ # copied plugin source
8
+ * known_marketplaces.json # registers each repo's marketplace
9
+ * marketplaces/agents-cli/ # ~/.agents/plugins/* (user repo)
10
+ * marketplaces/agents-<alias>/ # ~/.agents-<alias>/plugins/* (extra repo)
11
+ * marketplaces/agents-project/ # <cwd>/.agents/plugins/* (project repo)
12
+ * .claude-plugin/marketplace.json # synthesized catalog
13
+ * plugins/<plugin>/ # copied plugin source
12
14
  *
13
- * Plus the version's settings.json gets `enabledPlugins["<plugin>@agents-cli"] = true`.
15
+ * Plus the version's settings.json gets
16
+ * `enabledPlugins["<plugin>@<marketplace>"] = true`.
14
17
  *
15
- * This produces native `/plugin:skill` slash namespacing, visibility in `/plugins`,
16
- * and `/plugin enable|disable` support matching the Claude Code spec at
17
- * https://code.claude.com/docs/en/plugins and /plugin-marketplaces.
18
+ * This produces native `/plugin:skill` slash namespacing, visibility in
19
+ * `/plugins`, `/plugin enable|disable` support, AND honest attribution (the
20
+ * user can see which repo each plugin came from) — matching the Claude Code
21
+ * spec at https://code.claude.com/docs/en/plugins and /plugin-marketplaces.
22
+ *
23
+ * The naming policy lives in one place — marketplaceNameFor(). Source-side
24
+ * discovery (discoverMarketplaces) and per-version synthesis (syncMarketplaceManifest
25
+ * / registerMarketplace / syncAllMarketplaces) all key off a MarketplaceSpec so
26
+ * the catalog name and on-disk layout are derived, never hard-coded per call.
18
27
  */
19
28
  import * as fs from 'fs';
29
+ import { agentConfigDirName } from './agents.js';
20
30
  import * as path from 'path';
31
+ import { getPluginsDir, getEnabledExtraRepos, getProjectPluginsDir } from './state.js';
32
+ /**
33
+ * Canonical name for the user-repo marketplace (~/.agents/plugins/). Kept as an
34
+ * exported constant for callers that operate on the user repo directly and for
35
+ * the `marketplaces/agents-cli/` on-disk path that existing installs already have.
36
+ */
21
37
  export const MARKETPLACE_NAME = 'agents-cli';
38
+ export const SYSTEM_MARKETPLACE_NAME = 'agents-system';
39
+ /** Marketplace name for <cwd>/.agents/plugins/*. */
40
+ export const PROJECT_MARKETPLACE_NAME = 'agents-project';
41
+ // ─── Naming policy (single source of truth) ──────────────────────────────────
42
+ /**
43
+ * Map a MarketplaceSpec to its catalog name. This is the ONLY place that
44
+ * encodes the repo → name policy; every other function derives the name here.
45
+ */
46
+ export function marketplaceNameFor(spec) {
47
+ switch (spec.kind) {
48
+ case 'user': return MARKETPLACE_NAME; // "agents-cli"
49
+ case 'extra': return `agents-${spec.alias}`; // e.g. "agents-extras"
50
+ case 'project': return PROJECT_MARKETPLACE_NAME; // "agents-project"
51
+ case 'system': return SYSTEM_MARKETPLACE_NAME; // "agents-system"
52
+ }
53
+ }
54
+ /** Resolve a spec-or-name argument to the bare marketplace name string. */
55
+ function nameOf(specOrName) {
56
+ return typeof specOrName === 'string' ? specOrName : marketplaceNameFor(specOrName);
57
+ }
58
+ function descriptionFor(spec) {
59
+ switch (spec.kind) {
60
+ case 'user': return 'Plugins from the user repo (~/.agents/plugins/)';
61
+ case 'extra': return `Plugins from extra repo "${spec.alias}" (~/.agents-${spec.alias}/plugins/)`;
62
+ case 'project': return 'Project-scoped plugins from <cwd>/.agents/plugins/';
63
+ case 'system': return 'Plugins from the system repo (~/.agents/.system/plugins/)';
64
+ }
65
+ }
66
+ // ─── Source-side discovery ────────────────────────────────────────────────────
67
+ /**
68
+ * Discover every DotAgents repo that contributes plugins, in precedence order
69
+ * (user, then each enabled extra repo, then the project repo when cwd has one).
70
+ * No agent / version is involved — this walks source-side plugin roots only.
71
+ *
72
+ * A repo is included when its plugins/ directory exists on disk. The user repo
73
+ * is always probed; extras come from getEnabledExtraRepos() (already filtered to
74
+ * enabled + on-disk repos); the project repo is included only when
75
+ * <cwd>/.agents/plugins/ exists.
76
+ */
77
+ export function discoverMarketplaces(opts = {}) {
78
+ const out = [];
79
+ // User repo — always the canonical "agents-cli" marketplace.
80
+ const userRoot = getPluginsDir();
81
+ if (dirExists(userRoot)) {
82
+ const spec = { kind: 'user' };
83
+ out.push({ spec, name: marketplaceNameFor(spec), pluginsRoot: userRoot, description: descriptionFor(spec) });
84
+ }
85
+ // Extra repos — peer ~/.agents-<alias>/ clones (and user-owned path:-repos).
86
+ for (const extra of getEnabledExtraRepos()) {
87
+ const pluginsRoot = path.join(extra.dir, 'plugins');
88
+ if (!dirExists(pluginsRoot))
89
+ continue;
90
+ const spec = { kind: 'extra', alias: extra.alias, root: pluginsRoot };
91
+ out.push({ spec, name: marketplaceNameFor(spec), pluginsRoot, description: descriptionFor(spec) });
92
+ }
93
+ // Project repo — <cwd>/.agents/plugins/.
94
+ const projectRoot = getProjectPluginsDir(opts.cwd ?? process.cwd());
95
+ if (projectRoot && dirExists(projectRoot)) {
96
+ const spec = { kind: 'project', root: projectRoot };
97
+ out.push({ spec, name: marketplaceNameFor(spec), pluginsRoot: projectRoot, description: descriptionFor(spec) });
98
+ }
99
+ return out;
100
+ }
101
+ function dirExists(p) {
102
+ try {
103
+ return fs.statSync(p).isDirectory();
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ }
109
+ // ─── Per-version paths ────────────────────────────────────────────────────────
22
110
  function pluginsRootForVersion(agent, versionHome) {
23
- return path.join(versionHome, `.${agent}`, 'plugins');
111
+ return path.join(versionHome, agentConfigDirName(agent), 'plugins');
24
112
  }
25
- export function marketplaceRoot(agent, versionHome) {
26
- return path.join(pluginsRootForVersion(agent, versionHome), 'marketplaces', MARKETPLACE_NAME);
113
+ export function marketplaceRoot(specOrName, agent, versionHome) {
114
+ return path.join(pluginsRootForVersion(agent, versionHome), 'marketplaces', nameOf(specOrName));
27
115
  }
28
- export function marketplaceManifestPath(agent, versionHome) {
29
- return path.join(marketplaceRoot(agent, versionHome), '.claude-plugin', 'marketplace.json');
116
+ export function marketplaceManifestPath(specOrName, agent, versionHome) {
117
+ return path.join(marketplaceRoot(specOrName, agent, versionHome), '.claude-plugin', 'marketplace.json');
30
118
  }
31
- export function pluginInstallDir(plugin, agent, versionHome) {
32
- return path.join(marketplaceRoot(agent, versionHome), 'plugins', plugin.name);
119
+ export function pluginInstallDir(plugin, specOrName, agent, versionHome) {
120
+ return path.join(marketplaceRoot(specOrName, agent, versionHome), 'plugins', plugin.name);
33
121
  }
34
122
  export function knownMarketplacesPath(agent, versionHome) {
35
123
  return path.join(pluginsRootForVersion(agent, versionHome), 'known_marketplaces.json');
36
124
  }
37
125
  function settingsPath(agent, versionHome) {
38
- return path.join(versionHome, `.${agent}`, 'settings.json');
126
+ return path.join(versionHome, agentConfigDirName(agent), 'settings.json');
39
127
  }
128
+ // ─── Copy plugin source into a marketplace ────────────────────────────────────
40
129
  /**
41
- * Copy plugin source into marketplace install dir.
42
- * Source of truth remains ~/.agents/plugins/<name>/ — this is a per-version snapshot.
130
+ * Copy plugin source into the marketplace install dir for the given spec.
131
+ * Source of truth remains the plugin's source dir — this is a per-version snapshot.
43
132
  *
44
133
  * Symlinks pointing OUTSIDE the plugin source root are dropped. They show up
45
134
  * when plugin authors (legitimately) link prompt-side references to sibling
@@ -51,8 +140,8 @@ function settingsPath(agent, versionHome) {
51
140
  *
52
141
  * Internal symlinks (target stays inside the plugin root) are preserved.
53
142
  */
54
- export function copyPluginToMarketplace(plugin, agent, versionHome) {
55
- const dest = pluginInstallDir(plugin, agent, versionHome);
143
+ export function copyPluginToMarketplace(plugin, spec, agent, versionHome) {
144
+ const dest = pluginInstallDir(plugin, spec, agent, versionHome);
56
145
  fs.mkdirSync(path.dirname(dest), { recursive: true });
57
146
  if (fs.existsSync(dest)) {
58
147
  fs.rmSync(dest, { recursive: true, force: true });
@@ -96,21 +185,38 @@ export function copyPluginToMarketplace(plugin, agent, versionHome) {
96
185
  }
97
186
  return dest;
98
187
  }
188
+ // ─── Catalog synthesis ──────────────────────────────────────────────────────
99
189
  /**
100
- * Re-synthesize <marketplace>/.claude-plugin/marketplace.json from the list of
101
- * plugins installed under <marketplace>/plugins/. Always run after add or remove
102
- * so the manifest stays in lockstep with on-disk contents.
190
+ * Re-synthesize <marketplace>/.claude-plugin/marketplace.json from the plugins
191
+ * already installed under <marketplace>/plugins/. Always run after add or remove
192
+ * so the manifest stays in lockstep with on-disk contents. Returns the manifest
193
+ * it wrote, or null when the marketplace has no plugins dir yet.
103
194
  */
104
- export function syncMarketplaceManifest(agent, versionHome) {
105
- const root = marketplaceRoot(agent, versionHome);
195
+ export function syncMarketplaceManifest(spec, agent, versionHome) {
196
+ const name = marketplaceNameFor(spec);
197
+ const root = marketplaceRoot(spec, agent, versionHome);
106
198
  const pluginsDir = path.join(root, 'plugins');
107
199
  if (!fs.existsSync(pluginsDir))
108
200
  return null;
109
201
  const entries = [];
110
202
  for (const entry of fs.readdirSync(pluginsDir, { withFileTypes: true })) {
111
- if (!entry.isDirectory() || entry.name.startsWith('.'))
203
+ if (entry.name.startsWith('.'))
112
204
  continue;
113
- const manifestFile = path.join(pluginsDir, entry.name, '.claude-plugin', 'plugin.json');
205
+ // Follow symlinks: Dirent.isDirectory() is false for a symlink even when the
206
+ // target is a directory. statSync follows the link.
207
+ const entryPath = path.join(pluginsDir, entry.name);
208
+ let isDir = entry.isDirectory();
209
+ if (!isDir && entry.isSymbolicLink()) {
210
+ try {
211
+ isDir = fs.statSync(entryPath).isDirectory();
212
+ }
213
+ catch {
214
+ isDir = false;
215
+ }
216
+ }
217
+ if (!isDir)
218
+ continue;
219
+ const manifestFile = path.join(entryPath, '.claude-plugin', 'plugin.json');
114
220
  if (!fs.existsSync(manifestFile))
115
221
  continue;
116
222
  let manifest;
@@ -130,22 +236,25 @@ export function syncMarketplaceManifest(agent, versionHome) {
130
236
  }
131
237
  const manifest = {
132
238
  $schema: 'https://anthropic.com/claude-code/marketplace.schema.json',
133
- name: MARKETPLACE_NAME,
134
- description: 'Plugins managed by agents-cli',
239
+ name,
240
+ description: descriptionFor(spec),
135
241
  owner: { name: 'agents-cli' },
136
242
  plugins: entries.sort((a, b) => a.name.localeCompare(b.name)),
137
243
  };
138
- const manifestPath = marketplaceManifestPath(agent, versionHome);
244
+ const manifestPath = marketplaceManifestPath(spec, agent, versionHome);
139
245
  fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
140
246
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
141
247
  return manifest;
142
248
  }
249
+ // ─── Registration in known_marketplaces.json ──────────────────────────────────
143
250
  /**
144
- * Register the agents-cli marketplace in known_marketplaces.json so Claude Code
145
- * discovers it on startup. Idempotent: re-running just refreshes lastUpdated.
251
+ * Register a marketplace in known_marketplaces.json so Claude Code discovers
252
+ * it on startup. Idempotent: re-running just refreshes lastUpdated. Other
253
+ * marketplaces' entries are preserved untouched.
146
254
  */
147
- export function registerMarketplace(agent, versionHome) {
148
- const root = marketplaceRoot(agent, versionHome);
255
+ export function registerMarketplace(spec, agent, versionHome) {
256
+ const name = marketplaceNameFor(spec);
257
+ const root = marketplaceRoot(spec, agent, versionHome);
149
258
  const knownPath = knownMarketplacesPath(agent, versionHome);
150
259
  let known = {};
151
260
  if (fs.existsSync(knownPath)) {
@@ -156,7 +265,7 @@ export function registerMarketplace(agent, versionHome) {
156
265
  known = {};
157
266
  }
158
267
  }
159
- known[MARKETPLACE_NAME] = {
268
+ known[name] = {
160
269
  source: { source: 'directory', path: root },
161
270
  installLocation: root,
162
271
  lastUpdated: new Date().toISOString(),
@@ -165,10 +274,12 @@ export function registerMarketplace(agent, versionHome) {
165
274
  fs.writeFileSync(knownPath, JSON.stringify(known, null, 2) + '\n', 'utf-8');
166
275
  }
167
276
  /**
168
- * Drop the agents-cli marketplace entry from known_marketplaces.json.
169
- * Called when the last plugin under it is removed.
277
+ * Drop a marketplace entry from known_marketplaces.json. Called when the last
278
+ * plugin under it is removed. Removes only its own entry; deletes the file only
279
+ * when no entries remain.
170
280
  */
171
- export function unregisterMarketplace(agent, versionHome) {
281
+ export function unregisterMarketplace(specOrName, agent, versionHome) {
282
+ const name = nameOf(specOrName);
172
283
  const knownPath = knownMarketplacesPath(agent, versionHome);
173
284
  if (!fs.existsSync(knownPath))
174
285
  return;
@@ -179,9 +290,9 @@ export function unregisterMarketplace(agent, versionHome) {
179
290
  catch {
180
291
  return;
181
292
  }
182
- if (!(MARKETPLACE_NAME in known))
293
+ if (!(name in known))
183
294
  return;
184
- delete known[MARKETPLACE_NAME];
295
+ delete known[name];
185
296
  if (Object.keys(known).length === 0) {
186
297
  try {
187
298
  fs.unlinkSync(knownPath);
@@ -192,15 +303,38 @@ export function unregisterMarketplace(agent, versionHome) {
192
303
  fs.writeFileSync(knownPath, JSON.stringify(known, null, 2) + '\n', 'utf-8');
193
304
  }
194
305
  }
306
+ // ─── Top-level orchestration ──────────────────────────────────────────────────
195
307
  /**
196
- * Mark a plugin as enabled in <versionHome>/.{agent}/settings.json under
197
- * enabledPlugins["<plugin>@agents-cli"]: true. Reads, mutates, writes
198
- * preserving every other key.
308
+ * Discover every source-side marketplace, then for each one re-synthesize its
309
+ * catalog from the plugins already copied under the version home and register
310
+ * it in known_marketplaces.json. Returns one result per marketplace that has at
311
+ * least one plugin installed.
312
+ *
313
+ * Copying plugin source into a marketplace is the caller's responsibility
314
+ * (copyPluginToMarketplace / syncPluginToVersion) — this reconciles catalogs +
315
+ * registrations across all repos once the copies are in place. Marketplaces
316
+ * whose version-home plugins dir is empty or absent are skipped, so we never
317
+ * register a known_marketplace pointing at a directory with no catalog.
199
318
  */
200
- export function enablePluginInSettings(pluginName, agent, versionHome, options = {}) {
201
- if (!options.allowExecSurfaces && marketplacePluginHasExecSurfaces(pluginName, agent, versionHome)) {
202
- return;
319
+ export function syncAllMarketplaces(agent, versionHome, opts = {}) {
320
+ const results = [];
321
+ for (const dm of discoverMarketplaces(opts)) {
322
+ const manifest = syncMarketplaceManifest(dm.spec, agent, versionHome);
323
+ if (!manifest || manifest.plugins.length === 0)
324
+ continue;
325
+ registerMarketplace(dm.spec, agent, versionHome);
326
+ results.push({ spec: dm.spec, name: dm.name, plugins: manifest.plugins.length });
203
327
  }
328
+ return results;
329
+ }
330
+ // ─── Per-plugin settings ops ──────────────────────────────────────────────────
331
+ /**
332
+ * Mark a plugin as enabled in <versionHome>/.{agent}/settings.json under
333
+ * enabledPlugins["<plugin>@<marketplace>"]: true. Reads, mutates, writes —
334
+ * preserving every other key. Trust/exec-surface gating is the caller's
335
+ * responsibility (plugins.ts owns plugin capability inspection).
336
+ */
337
+ export function addPluginToSettings(pluginName, marketplaceName, agent, versionHome) {
204
338
  const sPath = settingsPath(agent, versionHome);
205
339
  let settings = {};
206
340
  if (fs.existsSync(sPath)) {
@@ -215,49 +349,17 @@ export function enablePluginInSettings(pluginName, agent, versionHome, options =
215
349
  settings.enabledPlugins = {};
216
350
  }
217
351
  const enabled = settings.enabledPlugins;
218
- const key = `${pluginName}@${MARKETPLACE_NAME}`;
352
+ const key = `${pluginName}@${marketplaceName}`;
219
353
  if (enabled[key] === true)
220
354
  return;
221
355
  enabled[key] = true;
222
356
  fs.mkdirSync(path.dirname(sPath), { recursive: true });
223
357
  fs.writeFileSync(sPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
224
358
  }
225
- function marketplacePluginHasExecSurfaces(pluginName, agent, versionHome) {
226
- const root = path.join(marketplaceRoot(agent, versionHome), 'plugins', pluginName);
227
- if (fs.existsSync(path.join(root, '.mcp.json')))
228
- return true;
229
- for (const dir of ['bin', 'scripts', 'permissions']) {
230
- if (fs.existsSync(path.join(root, dir)))
231
- return true;
232
- }
233
- const hooksFile = path.join(root, 'hooks', 'hooks.json');
234
- if (fs.existsSync(hooksFile))
235
- return true;
236
- const hooksDir = path.join(root, 'hooks');
237
- if (fs.existsSync(hooksDir)) {
238
- try {
239
- if (fs.readdirSync(hooksDir).some((entry) => !entry.startsWith('.')))
240
- return true;
241
- }
242
- catch {
243
- return true;
244
- }
245
- }
246
- const settingsFile = path.join(root, 'settings.json');
247
- if (!fs.existsSync(settingsFile))
248
- return false;
249
- try {
250
- const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf-8'));
251
- return Object.keys(settings).some((key) => key !== 'permissions') || 'permissions' in settings;
252
- }
253
- catch {
254
- return true;
255
- }
256
- }
257
359
  /**
258
- * Remove the enabledPlugins key for this plugin. Inverse of enablePluginInSettings.
360
+ * Remove the enabledPlugins key for this plugin. Inverse of addPluginToSettings.
259
361
  */
260
- export function disablePluginInSettings(pluginName, agent, versionHome) {
362
+ export function removePluginFromSettings(pluginName, marketplaceName, agent, versionHome) {
261
363
  const sPath = settingsPath(agent, versionHome);
262
364
  if (!fs.existsSync(sPath))
263
365
  return;
@@ -271,7 +373,7 @@ export function disablePluginInSettings(pluginName, agent, versionHome) {
271
373
  const enabled = settings.enabledPlugins;
272
374
  if (!enabled)
273
375
  return;
274
- const key = `${pluginName}@${MARKETPLACE_NAME}`;
376
+ const key = `${pluginName}@${marketplaceName}`;
275
377
  if (!(key in enabled))
276
378
  return;
277
379
  delete enabled[key];
@@ -280,12 +382,13 @@ export function disablePluginInSettings(pluginName, agent, versionHome) {
280
382
  }
281
383
  fs.writeFileSync(sPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
282
384
  }
385
+ // ─── Marketplace teardown helpers ─────────────────────────────────────────────
283
386
  /**
284
387
  * Remove a plugin's installed marketplace directory. Returns true if the dir
285
388
  * existed and was removed.
286
389
  */
287
- export function removePluginFromMarketplace(pluginName, agent, versionHome) {
288
- const installed = path.join(marketplaceRoot(agent, versionHome), 'plugins', pluginName);
390
+ export function removePluginFromMarketplace(pluginName, specOrName, agent, versionHome) {
391
+ const installed = path.join(marketplaceRoot(specOrName, agent, versionHome), 'plugins', pluginName);
289
392
  if (!fs.existsSync(installed))
290
393
  return false;
291
394
  fs.rmSync(installed, { recursive: true, force: true });
@@ -294,8 +397,8 @@ export function removePluginFromMarketplace(pluginName, agent, versionHome) {
294
397
  /**
295
398
  * Return true if the marketplace has no plugins left under it.
296
399
  */
297
- export function marketplaceIsEmpty(agent, versionHome) {
298
- const pluginsDir = path.join(marketplaceRoot(agent, versionHome), 'plugins');
400
+ export function marketplaceIsEmpty(specOrName, agent, versionHome) {
401
+ const pluginsDir = path.join(marketplaceRoot(specOrName, agent, versionHome), 'plugins');
299
402
  if (!fs.existsSync(pluginsDir))
300
403
  return true;
301
404
  const remaining = fs.readdirSync(pluginsDir, { withFileTypes: true })
@@ -305,8 +408,8 @@ export function marketplaceIsEmpty(agent, versionHome) {
305
408
  /**
306
409
  * Drop the entire marketplace directory. Called after the last plugin removal.
307
410
  */
308
- export function removeEmptyMarketplaceDir(agent, versionHome) {
309
- const root = marketplaceRoot(agent, versionHome);
411
+ export function removeEmptyMarketplaceDir(specOrName, agent, versionHome) {
412
+ const root = marketplaceRoot(specOrName, agent, versionHome);
310
413
  if (!fs.existsSync(root))
311
414
  return;
312
415
  fs.rmSync(root, { recursive: true, force: true });
@@ -314,7 +417,7 @@ export function removeEmptyMarketplaceDir(agent, versionHome) {
314
417
  /**
315
418
  * Detect whether a plugin is installed via the native marketplace path.
316
419
  */
317
- export function isInstalledInMarketplace(pluginName, agent, versionHome) {
318
- const installed = path.join(marketplaceRoot(agent, versionHome), 'plugins', pluginName);
420
+ export function isInstalledInMarketplace(pluginName, specOrName, agent, versionHome) {
421
+ const installed = path.join(marketplaceRoot(specOrName, agent, versionHome), 'plugins', pluginName);
319
422
  return fs.existsSync(path.join(installed, '.claude-plugin', 'plugin.json'));
320
423
  }
@@ -8,7 +8,7 @@
8
8
  * module discovers plugins, validates their manifests, and syncs their
9
9
  * contents into agent version homes.
10
10
  */
11
- import type { AgentId, DiscoveredPlugin, PluginManifest } from './types.js';
11
+ import type { AgentId, DiscoveredPlugin, PluginManifest, MarketplaceSpec } from './types.js';
12
12
  export interface PluginCapabilities {
13
13
  hasHooks: boolean;
14
14
  hasMcp: boolean;
@@ -19,11 +19,28 @@ export interface PluginCapabilities {
19
19
  }
20
20
  export declare const PLUGIN_EXEC_SURFACE_LABELS: Record<keyof PluginCapabilities, string>;
21
21
  /**
22
- * Discover all plugins in ~/.agents/plugins/.
22
+ * Discover all plugins in a given plugins directory (e.g. ~/.agents/plugins/,
23
+ * ~/.agents/.system/plugins/, <cwd>/.agents/plugins/, ~/.agents-<alias>/plugins/).
23
24
  * A valid plugin has a .claude-plugin/plugin.json manifest.
25
+ *
26
+ * `spec` stamps marketplace provenance onto each discovered plugin. Callers that
27
+ * scan a single source dir without a marketplace identity (e.g. project-launch)
28
+ * may omit it; those plugins default to the user marketplace.
24
29
  */
25
- export declare function discoverPlugins(): DiscoveredPlugin[];
26
- export declare function buildDiscoveredPlugin(pluginRoot: string, manifest: PluginManifest): DiscoveredPlugin;
30
+ export declare function discoverPluginsInDir(pluginsDir: string, spec?: MarketplaceSpec): DiscoveredPlugin[];
31
+ /**
32
+ * Discover every plugin across ALL marketplaces — the user repo (~/.agents/),
33
+ * each enabled extra repo (~/.agents-<alias>/), and the project repo
34
+ * (<cwd>/.agents/) — stamping marketplace provenance onto each.
35
+ *
36
+ * Plugin names are NOT deduplicated across marketplaces: a `code` plugin in both
37
+ * the user repo and an extra repo yields two entries (`code@agents-cli` and
38
+ * `code@agents-<alias>`), each installing into its own marketplace directory.
39
+ */
40
+ export declare function discoverPlugins(opts?: {
41
+ cwd?: string;
42
+ }): DiscoveredPlugin[];
43
+ export declare function buildDiscoveredPlugin(pluginRoot: string, manifest: PluginManifest, spec?: MarketplaceSpec): DiscoveredPlugin;
27
44
  export declare function inspectPluginCapabilities(pluginRoot: string): PluginCapabilities;
28
45
  export declare function hasPluginExecSurfaces(capabilities: PluginCapabilities): boolean;
29
46
  export declare function pluginCapabilityLabels(capabilities: PluginCapabilities): string[];