@kolisachint/hoocode-agent 0.4.162 → 0.4.163

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 (293) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/cli/args.d.ts.map +1 -1
  3. package/dist/cli/args.js +0 -9
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/config.d.ts +0 -40
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +4 -50
  8. package/dist/config.js.map +1 -1
  9. package/dist/core/agent-frontmatter.d.ts +0 -28
  10. package/dist/core/agent-frontmatter.d.ts.map +1 -1
  11. package/dist/core/agent-frontmatter.js.map +1 -1
  12. package/dist/core/auth-guidance.d.ts +0 -1
  13. package/dist/core/auth-guidance.d.ts.map +1 -1
  14. package/dist/core/auth-guidance.js +1 -1
  15. package/dist/core/auth-guidance.js.map +1 -1
  16. package/dist/core/embsearch/chunker.d.ts +0 -2
  17. package/dist/core/embsearch/chunker.d.ts.map +1 -1
  18. package/dist/core/embsearch/chunker.js +1 -1
  19. package/dist/core/embsearch/chunker.js.map +1 -1
  20. package/dist/core/embsearch/index-meta.d.ts +0 -2
  21. package/dist/core/embsearch/index-meta.d.ts.map +1 -1
  22. package/dist/core/embsearch/index-meta.js +0 -6
  23. package/dist/core/embsearch/index-meta.js.map +1 -1
  24. package/dist/core/export-html/ansi-to-html.d.ts +0 -4
  25. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
  26. package/dist/core/export-html/ansi-to-html.js +1 -1
  27. package/dist/core/export-html/ansi-to-html.js.map +1 -1
  28. package/dist/core/extensions/index.d.ts +3 -3
  29. package/dist/core/extensions/index.d.ts.map +1 -1
  30. package/dist/core/extensions/index.js +1 -1
  31. package/dist/core/extensions/index.js.map +1 -1
  32. package/dist/core/extensions/plugins/authoring.d.ts +2 -2
  33. package/dist/core/extensions/plugins/authoring.d.ts.map +1 -1
  34. package/dist/core/extensions/plugins/authoring.js +0 -0
  35. package/dist/core/extensions/plugins/authoring.js.map +1 -1
  36. package/dist/core/extensions/plugins/formats/index.d.ts +2 -6
  37. package/dist/core/extensions/plugins/formats/index.d.ts.map +1 -1
  38. package/dist/core/extensions/plugins/formats/index.js +2 -3
  39. package/dist/core/extensions/plugins/formats/index.js.map +1 -1
  40. package/dist/core/extensions/plugins/formats/shared.d.ts +0 -2
  41. package/dist/core/extensions/plugins/formats/shared.d.ts.map +1 -1
  42. package/dist/core/extensions/plugins/formats/shared.js +1 -1
  43. package/dist/core/extensions/plugins/formats/shared.js.map +1 -1
  44. package/dist/core/extensions/plugins/formats/types.d.ts +4 -3
  45. package/dist/core/extensions/plugins/formats/types.d.ts.map +1 -1
  46. package/dist/core/extensions/plugins/formats/types.js.map +1 -1
  47. package/dist/core/extensions/plugins/install.d.ts +0 -2
  48. package/dist/core/extensions/plugins/install.d.ts.map +1 -1
  49. package/dist/core/extensions/plugins/install.js +1 -1
  50. package/dist/core/extensions/plugins/install.js.map +1 -1
  51. package/dist/core/extensions/plugins/manifest.d.ts +0 -6
  52. package/dist/core/extensions/plugins/manifest.d.ts.map +1 -1
  53. package/dist/core/extensions/plugins/manifest.js +0 -6
  54. package/dist/core/extensions/plugins/manifest.js.map +1 -1
  55. package/dist/core/extensions/plugins/marketplace.d.ts +3 -3
  56. package/dist/core/extensions/plugins/marketplace.d.ts.map +1 -1
  57. package/dist/core/extensions/plugins/marketplace.js.map +1 -1
  58. package/dist/core/extensions/types.d.ts +28 -28
  59. package/dist/core/extensions/types.d.ts.map +1 -1
  60. package/dist/core/extensions/types.js.map +1 -1
  61. package/dist/core/keybindings.d.ts +3 -3
  62. package/dist/core/keybindings.d.ts.map +1 -1
  63. package/dist/core/keybindings.js.map +1 -1
  64. package/dist/core/model-resolver.d.ts +0 -7
  65. package/dist/core/model-resolver.d.ts.map +1 -1
  66. package/dist/core/model-resolver.js +0 -56
  67. package/dist/core/model-resolver.js.map +1 -1
  68. package/dist/core/package-resource-discovery.d.ts +0 -10
  69. package/dist/core/package-resource-discovery.d.ts.map +1 -1
  70. package/dist/core/package-resource-discovery.js +7 -7
  71. package/dist/core/package-resource-discovery.js.map +1 -1
  72. package/dist/core/provider-health.d.ts +0 -2
  73. package/dist/core/provider-health.d.ts.map +1 -1
  74. package/dist/core/provider-health.js +1 -1
  75. package/dist/core/provider-health.js.map +1 -1
  76. package/dist/core/resolve-config-value.d.ts +0 -4
  77. package/dist/core/resolve-config-value.d.ts.map +1 -1
  78. package/dist/core/resolve-config-value.js +0 -15
  79. package/dist/core/resolve-config-value.js.map +1 -1
  80. package/dist/core/sdk.d.ts +3 -7
  81. package/dist/core/sdk.d.ts.map +1 -1
  82. package/dist/core/sdk.js +3 -3
  83. package/dist/core/sdk.js.map +1 -1
  84. package/dist/core/search/eval.d.ts +0 -3
  85. package/dist/core/search/eval.d.ts.map +1 -1
  86. package/dist/core/search/eval.js +1 -1
  87. package/dist/core/search/eval.js.map +1 -1
  88. package/dist/core/search/types.d.ts +2 -1
  89. package/dist/core/search/types.d.ts.map +1 -1
  90. package/dist/core/search/types.js.map +1 -1
  91. package/dist/core/session-cwd.d.ts +0 -1
  92. package/dist/core/session-cwd.d.ts.map +1 -1
  93. package/dist/core/session-cwd.js +1 -1
  94. package/dist/core/session-cwd.js.map +1 -1
  95. package/dist/core/settings-manager.d.ts +2 -3
  96. package/dist/core/settings-manager.d.ts.map +1 -1
  97. package/dist/core/settings-manager.js +0 -1
  98. package/dist/core/settings-manager.js.map +1 -1
  99. package/dist/core/settings-types.d.ts +9 -8
  100. package/dist/core/settings-types.d.ts.map +1 -1
  101. package/dist/core/settings-types.js.map +1 -1
  102. package/dist/core/subagent-inbox.d.ts +1 -3
  103. package/dist/core/subagent-inbox.d.ts.map +1 -1
  104. package/dist/core/subagent-inbox.js +1 -1
  105. package/dist/core/subagent-inbox.js.map +1 -1
  106. package/dist/core/subagent-pool.d.ts +0 -13
  107. package/dist/core/subagent-pool.d.ts.map +1 -1
  108. package/dist/core/subagent-pool.js +2 -2
  109. package/dist/core/subagent-pool.js.map +1 -1
  110. package/dist/core/task-store.d.ts +3 -3
  111. package/dist/core/task-store.d.ts.map +1 -1
  112. package/dist/core/task-store.js.map +1 -1
  113. package/dist/core/team-auto.d.ts +0 -2
  114. package/dist/core/team-auto.d.ts.map +1 -1
  115. package/dist/core/team-auto.js +1 -1
  116. package/dist/core/team-auto.js.map +1 -1
  117. package/dist/core/token-budget.d.ts +0 -13
  118. package/dist/core/token-budget.d.ts.map +1 -1
  119. package/dist/core/token-budget.js.map +1 -1
  120. package/dist/core/tools/browser/browser-continue.d.ts +1 -2
  121. package/dist/core/tools/browser/browser-continue.d.ts.map +1 -1
  122. package/dist/core/tools/browser/browser-continue.js.map +1 -1
  123. package/dist/core/tools/browser/browser-run.d.ts +1 -2
  124. package/dist/core/tools/browser/browser-run.d.ts.map +1 -1
  125. package/dist/core/tools/browser/browser-run.js.map +1 -1
  126. package/dist/core/tools/browser/browsertools-shared.d.ts +1 -14
  127. package/dist/core/tools/browser/browsertools-shared.d.ts.map +1 -1
  128. package/dist/core/tools/browser/browsertools-shared.js +4 -4
  129. package/dist/core/tools/browser/browsertools-shared.js.map +1 -1
  130. package/dist/core/tools/browser/index.d.ts +2 -2
  131. package/dist/core/tools/browser/index.d.ts.map +1 -1
  132. package/dist/core/tools/browser/index.js +2 -2
  133. package/dist/core/tools/browser/index.js.map +1 -1
  134. package/dist/core/tools/doc/docedit.d.ts +1 -2
  135. package/dist/core/tools/doc/docedit.d.ts.map +1 -1
  136. package/dist/core/tools/doc/docedit.js.map +1 -1
  137. package/dist/core/tools/doc/docgrep.d.ts +1 -2
  138. package/dist/core/tools/doc/docgrep.d.ts.map +1 -1
  139. package/dist/core/tools/doc/docgrep.js.map +1 -1
  140. package/dist/core/tools/doc/docpeek.d.ts +1 -2
  141. package/dist/core/tools/doc/docpeek.d.ts.map +1 -1
  142. package/dist/core/tools/doc/docpeek.js.map +1 -1
  143. package/dist/core/tools/doc/docread.d.ts +1 -2
  144. package/dist/core/tools/doc/docread.d.ts.map +1 -1
  145. package/dist/core/tools/doc/docread.js.map +1 -1
  146. package/dist/core/tools/doc/docscan.d.ts +1 -2
  147. package/dist/core/tools/doc/docscan.d.ts.map +1 -1
  148. package/dist/core/tools/doc/docscan.js.map +1 -1
  149. package/dist/core/tools/doc/docwrite.d.ts +1 -2
  150. package/dist/core/tools/doc/docwrite.d.ts.map +1 -1
  151. package/dist/core/tools/doc/docwrite.js.map +1 -1
  152. package/dist/core/tools/doc/index.d.ts +6 -6
  153. package/dist/core/tools/doc/index.d.ts.map +1 -1
  154. package/dist/core/tools/doc/index.js +6 -6
  155. package/dist/core/tools/doc/index.js.map +1 -1
  156. package/dist/core/tools/edit-diff.d.ts +0 -38
  157. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  158. package/dist/core/tools/edit-diff.js +2 -9
  159. package/dist/core/tools/edit-diff.js.map +1 -1
  160. package/dist/core/tools/filetools-shared.d.ts +7 -10
  161. package/dist/core/tools/filetools-shared.d.ts.map +1 -1
  162. package/dist/core/tools/filetools-shared.js +2 -2
  163. package/dist/core/tools/filetools-shared.js.map +1 -1
  164. package/dist/core/tools/index.d.ts +0 -4
  165. package/dist/core/tools/index.d.ts.map +1 -1
  166. package/dist/core/tools/index.js +0 -7
  167. package/dist/core/tools/index.js.map +1 -1
  168. package/dist/core/tools/native-search.d.ts +3 -2
  169. package/dist/core/tools/native-search.d.ts.map +1 -1
  170. package/dist/core/tools/native-search.js.map +1 -1
  171. package/dist/core/tools/path-utils.d.ts +0 -10
  172. package/dist/core/tools/path-utils.d.ts.map +1 -1
  173. package/dist/core/tools/path-utils.js +2 -2
  174. package/dist/core/tools/path-utils.js.map +1 -1
  175. package/dist/core/tools/plugins.d.ts +1 -20
  176. package/dist/core/tools/plugins.d.ts.map +1 -1
  177. package/dist/core/tools/plugins.js +2 -2
  178. package/dist/core/tools/plugins.js.map +1 -1
  179. package/dist/core/tools/propose-plugin.d.ts +0 -12
  180. package/dist/core/tools/propose-plugin.d.ts.map +1 -1
  181. package/dist/core/tools/propose-plugin.js +0 -1
  182. package/dist/core/tools/propose-plugin.js.map +1 -1
  183. package/dist/core/tools/read-dedup.d.ts +0 -2
  184. package/dist/core/tools/read-dedup.d.ts.map +1 -1
  185. package/dist/core/tools/read-dedup.js +1 -1
  186. package/dist/core/tools/read-dedup.js.map +1 -1
  187. package/dist/core/tools/render-utils.d.ts +0 -5
  188. package/dist/core/tools/render-utils.d.ts.map +1 -1
  189. package/dist/core/tools/render-utils.js.map +1 -1
  190. package/dist/core/tools/search.d.ts +1 -4
  191. package/dist/core/tools/search.d.ts.map +1 -1
  192. package/dist/core/tools/search.js +0 -4
  193. package/dist/core/tools/search.js.map +1 -1
  194. package/dist/core/tools/webfetch.d.ts +1 -2
  195. package/dist/core/tools/webfetch.d.ts.map +1 -1
  196. package/dist/core/tools/webfetch.js.map +1 -1
  197. package/dist/core/tools/websearch.d.ts +1 -2
  198. package/dist/core/tools/websearch.d.ts.map +1 -1
  199. package/dist/core/tools/websearch.js.map +1 -1
  200. package/dist/core/tools/webtools-shared.d.ts +3 -5
  201. package/dist/core/tools/webtools-shared.d.ts.map +1 -1
  202. package/dist/core/tools/webtools-shared.js +1 -1
  203. package/dist/core/tools/webtools-shared.js.map +1 -1
  204. package/dist/core/warm-subagent-pool-instance.d.ts +0 -4
  205. package/dist/core/warm-subagent-pool-instance.d.ts.map +1 -1
  206. package/dist/core/warm-subagent-pool-instance.js +0 -10
  207. package/dist/core/warm-subagent-pool-instance.js.map +1 -1
  208. package/dist/core/wordmark.d.ts +1 -9
  209. package/dist/core/wordmark.d.ts.map +1 -1
  210. package/dist/core/wordmark.js +2 -26
  211. package/dist/core/wordmark.js.map +1 -1
  212. package/dist/extensions/core/config.d.ts +4 -3
  213. package/dist/extensions/core/config.d.ts.map +1 -1
  214. package/dist/extensions/core/config.js.map +1 -1
  215. package/dist/extensions/core/mcp-loader.d.ts +0 -18
  216. package/dist/extensions/core/mcp-loader.d.ts.map +1 -1
  217. package/dist/extensions/core/mcp-loader.js.map +1 -1
  218. package/dist/extensions/core/prompt-reactive/policy.d.ts +0 -8
  219. package/dist/extensions/core/prompt-reactive/policy.d.ts.map +1 -1
  220. package/dist/extensions/core/prompt-reactive/policy.js +1 -1
  221. package/dist/extensions/core/prompt-reactive/policy.js.map +1 -1
  222. package/dist/migrations.d.ts +0 -16
  223. package/dist/migrations.d.ts.map +1 -1
  224. package/dist/migrations.js +2 -2
  225. package/dist/migrations.js.map +1 -1
  226. package/dist/modes/interactive/brand.d.ts +0 -4
  227. package/dist/modes/interactive/brand.d.ts.map +1 -1
  228. package/dist/modes/interactive/brand.js +0 -4
  229. package/dist/modes/interactive/brand.js.map +1 -1
  230. package/dist/modes/interactive/components/index.d.ts +0 -4
  231. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  232. package/dist/modes/interactive/components/index.js +0 -5
  233. package/dist/modes/interactive/components/index.js.map +1 -1
  234. package/dist/modes/interactive/components/read-output.d.ts +0 -9
  235. package/dist/modes/interactive/components/read-output.d.ts.map +1 -1
  236. package/dist/modes/interactive/components/read-output.js +0 -20
  237. package/dist/modes/interactive/components/read-output.js.map +1 -1
  238. package/dist/modes/interactive/components/session-selector-search.d.ts +0 -17
  239. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  240. package/dist/modes/interactive/components/session-selector-search.js +2 -2
  241. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  242. package/dist/modes/interactive/components/settings-selector.d.ts +4 -13
  243. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  244. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  245. package/dist/modes/interactive/login-controller.d.ts +1 -1
  246. package/dist/modes/interactive/login-controller.d.ts.map +1 -1
  247. package/dist/modes/interactive/login-controller.js +1 -1
  248. package/dist/modes/interactive/login-controller.js.map +1 -1
  249. package/dist/modes/interactive/resource-display.d.ts +0 -20
  250. package/dist/modes/interactive/resource-display.d.ts.map +1 -1
  251. package/dist/modes/interactive/resource-display.js +7 -7
  252. package/dist/modes/interactive/resource-display.js.map +1 -1
  253. package/dist/modes/interactive/theme/theme.d.ts +0 -4
  254. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  255. package/dist/modes/interactive/theme/theme.js +0 -7
  256. package/dist/modes/interactive/theme/theme.js.map +1 -1
  257. package/dist/modes/rpc/rpc-mode.d.ts +0 -1
  258. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  259. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  260. package/dist/modes/rpc/rpc-types.d.ts +0 -1
  261. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  262. package/dist/modes/rpc/rpc-types.js +3 -0
  263. package/dist/modes/rpc/rpc-types.js.map +1 -1
  264. package/dist/package-manager-cli.d.ts +0 -1
  265. package/dist/package-manager-cli.d.ts.map +1 -1
  266. package/dist/package-manager-cli.js.map +1 -1
  267. package/dist/utils/changelog.d.ts +0 -4
  268. package/dist/utils/changelog.d.ts.map +1 -1
  269. package/dist/utils/changelog.js +1 -1
  270. package/dist/utils/changelog.js.map +1 -1
  271. package/dist/utils/clipboard-native.d.ts +1 -1
  272. package/dist/utils/clipboard-native.d.ts.map +1 -1
  273. package/dist/utils/clipboard-native.js.map +1 -1
  274. package/dist/utils/paths.d.ts +0 -2
  275. package/dist/utils/paths.d.ts.map +1 -1
  276. package/dist/utils/paths.js +1 -1
  277. package/dist/utils/paths.js.map +1 -1
  278. package/dist/utils/plan/parser.d.ts +5 -4
  279. package/dist/utils/plan/parser.d.ts.map +1 -1
  280. package/dist/utils/plan/parser.js.map +1 -1
  281. package/dist/utils/tools-manager.d.ts +0 -1
  282. package/dist/utils/tools-manager.d.ts.map +1 -1
  283. package/dist/utils/tools-manager.js +1 -1
  284. package/dist/utils/tools-manager.js.map +1 -1
  285. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  286. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  287. package/examples/extensions/sandbox/package.json +1 -1
  288. package/examples/extensions/with-deps/package.json +1 -1
  289. package/package.json +4 -7
  290. package/dist/core/index.d.ts +0 -12
  291. package/dist/core/index.d.ts.map +0 -1
  292. package/dist/core/index.js +0 -12
  293. package/dist/core/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG","sourcesContent":["/**\n * Plugin format adapter contract.\n *\n * A `PluginFormatAdapter` owns *everything* format-specific for one vendor\n * layout — how a plugin directory is detected and parsed (the reader), how a\n * marketplace index file is located, and how a plugin is scaffolded from a\n * normalized draft (the writer). The rest of the plugin subsystem is written\n * against {@link NormalizedPlugin} / {@link PluginDraft} and never branches on a\n * format again.\n *\n * This is the extension point the plugin system is designed around: Claude Code\n * and GitHub Copilot conventions evolve independently, so each lives in its own\n * adapter file ({@link ./claude.ts}, {@link ./copilot.ts}, {@link ./agents.ts}).\n * Supporting a new vendor — or tracking a change in an existing one — means\n * editing (or adding) exactly one adapter and registering it; no other module\n * changes.\n */\n\nimport type { NormalizedPlugin } from \"../manifest.js\";\n\n/** Internal format id — matches `NormalizedPlugin.format` / `NormalizedMarketplace.format`. */\nexport type PluginFormatId = \"agents\" | \"claude\" | \"copilot\";\n\n/**\n * Public platform token surfaced to users. The Copilot format lives under\n * `.github/`, so it is surfaced as `\"github\"` rather than the internal\n * `\"copilot\"`.\n */\nexport type MarketplacePlatform = \"agents\" | \"claude\" | \"github\";\n\n/** A file produced by a format's scaffolder. `path` is relative to the plugin root (POSIX). */\nexport interface EmittedFile {\n\tpath: string;\n\tcontent: string;\n}\n\n/** An authored skill: passive instructions loaded lazily (SKILL.md-style). */\nexport interface AuthoredSkill {\n\tname: string;\n\tdescription?: string;\n\t/** Instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored slash command: a passive prompt template. */\nexport interface AuthoredCommand {\n\tname: string;\n\tdescription?: string;\n\t/** Prompt template body (markdown). */\n\tbody: string;\n}\n\n/** An authored subagent definition. `tools` is a comma-separated allowlist (Claude Code convention). */\nexport interface AuthoredAgent {\n\tname: string;\n\tdescription?: string;\n\t/** Comma-separated `allowed-tools` string, e.g. \"read, grep, glob\". */\n\ttools?: string;\n\tmodel?: string;\n\t/** System-prompt / instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored shell hook wired to a tool event. Active code — gated on authoring. */\nexport interface AuthoredHook {\n\t/** Claude Code event name: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ... */\n\tevent: string;\n\t/** Regex matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\tcommand: string;\n\t/** Timeout in seconds. */\n\ttimeout?: number;\n}\n\n/** An authored MCP server. Active code — gated on authoring. */\nexport interface AuthoredMcpServer {\n\tname: string;\n\tcommand: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n}\n\n/**\n * Format-agnostic authoring spec consumed by {@link PluginFormatAdapter.emit}.\n * `ProposePlugin` builds one of these and asks each requested adapter to render\n * it, so the same authored capability lands correctly in Claude Code and\n * Copilot layouts without the authoring code knowing either.\n */\nexport interface PluginDraft {\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/**\n\t * Which platform layouts to scaffold this draft into (e.g. `[\"claude\",\n\t * \"github\"]`). Consumed as the default target set by `emitForPlatforms`;\n\t * omit to render every registered format.\n\t */\n\tsupportPlatform?: MarketplacePlatform[];\n\tskills?: AuthoredSkill[];\n\tcommands?: AuthoredCommand[];\n\tagents?: AuthoredAgent[];\n\thooks?: AuthoredHook[];\n\tmcpServers?: AuthoredMcpServer[];\n}\n\n/**\n * Writer for a platform's *workspace-level* (non-plugin) artifact conventions —\n * where a standalone skill, subagent, or command lives inside a repository for\n * that vendor (e.g. Copilot reads `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, `.github/prompts/<name>.prompt.md`).\n * Unlike {@link PluginFormatAdapter.emit}, the returned paths are relative to\n * the **workspace root** (cwd), marker directory included.\n */\nexport interface WorkspaceLayout {\n\t/** Marker root the artifacts land under, e.g. \".github\", \".claude\", \".agents\". */\n\treadonly root: string;\n\temitSkill(skill: AuthoredSkill): EmittedFile;\n\temitAgent(agent: AuthoredAgent): EmittedFile;\n\temitCommand(command: AuthoredCommand): EmittedFile;\n}\n\n/** Reader + writer for a single plugin format. */\nexport interface PluginFormatAdapter {\n\t/** Internal format id (stable; used in `NormalizedPlugin.format`). */\n\treadonly id: PluginFormatId;\n\t/** Public platform token (Copilot → `\"github\"`). */\n\treadonly platform: MarketplacePlatform;\n\t/**\n\t * Precedence when several formats coexist in one directory (lower wins).\n\t * Native `.agents-plugin` beats Claude beats Copilot.\n\t */\n\treadonly precedence: number;\n\t/** Short human label for messages and scaffolding summaries. */\n\treadonly label: string;\n\t/**\n\t * Candidate locations of this format's marketplace index file, relative to a\n\t * marketplace root (POSIX), in precedence order. Formats with more than one\n\t * real-world convention (Copilot uses both `.github/plugin/marketplace.json`\n\t * and `.github/marketplace.json`) list every location they accept.\n\t */\n\treadonly marketplaceFiles: readonly string[];\n\n\t/** True if `root` carries this format's plugin manifest. */\n\tdetectPlugin(root: string): boolean;\n\t/** Parse `root` as a plugin of this format. Returns null when unparseable. */\n\tparsePlugin(root: string): NormalizedPlugin | null;\n\t/** Render `draft` into this format's on-disk layout (paths relative to the plugin root). */\n\temit(draft: PluginDraft): EmittedFile[];\n\t/** This platform's workspace-level artifact conventions (paths relative to the workspace root). */\n\treadonly workspace: WorkspaceLayout;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG","sourcesContent":["/**\n * Plugin format adapter contract.\n *\n * A `PluginFormatAdapter` owns *everything* format-specific for one vendor\n * layout — how a plugin directory is detected and parsed (the reader), how a\n * marketplace index file is located, and how a plugin is scaffolded from a\n * normalized draft (the writer). The rest of the plugin subsystem is written\n * against {@link NormalizedPlugin} / {@link PluginDraft} and never branches on a\n * format again.\n *\n * This is the extension point the plugin system is designed around: Claude Code\n * and GitHub Copilot conventions evolve independently, so each lives in its own\n * adapter file ({@link ./claude.ts}, {@link ./copilot.ts}, {@link ./agents.ts}).\n * Supporting a new vendor — or tracking a change in an existing one — means\n * editing (or adding) exactly one adapter and registering it; no other module\n * changes.\n */\n\nimport type { NormalizedPlugin } from \"../manifest.js\";\n\n/** Internal format id — matches `NormalizedPlugin.format` / `NormalizedMarketplace.format`. */\nexport type PluginFormatId = \"agents\" | \"claude\" | \"copilot\";\n\n/**\n * Public platform token surfaced to users. The Copilot format lives under\n * `.github/`, so it is surfaced as `\"github\"` rather than the internal\n * `\"copilot\"`.\n */\nexport type MarketplacePlatform = \"agents\" | \"claude\" | \"github\";\n\n/** A file produced by a format's scaffolder. `path` is relative to the plugin root (POSIX). */\nexport interface EmittedFile {\n\tpath: string;\n\tcontent: string;\n}\n\n/** An authored skill: passive instructions loaded lazily (SKILL.md-style). */\ninterface AuthoredSkill {\n\tname: string;\n\tdescription?: string;\n\t/** Instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored slash command: a passive prompt template. */\ninterface AuthoredCommand {\n\tname: string;\n\tdescription?: string;\n\t/** Prompt template body (markdown). */\n\tbody: string;\n}\n\n/** An authored subagent definition. `tools` is a comma-separated allowlist (Claude Code convention). */\ninterface AuthoredAgent {\n\tname: string;\n\tdescription?: string;\n\t/** Comma-separated `allowed-tools` string, e.g. \"read, grep, glob\". */\n\ttools?: string;\n\tmodel?: string;\n\t/** System-prompt / instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored shell hook wired to a tool event. Active code — gated on authoring. */\nexport interface AuthoredHook {\n\t/** Claude Code event name: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ... */\n\tevent: string;\n\t/** Regex matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\tcommand: string;\n\t/** Timeout in seconds. */\n\ttimeout?: number;\n}\n\n/** An authored MCP server. Active code — gated on authoring. */\nexport interface AuthoredMcpServer {\n\tname: string;\n\tcommand: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n}\n\n/**\n * Format-agnostic authoring spec consumed by {@link PluginFormatAdapter.emit}.\n * `ProposePlugin` builds one of these and asks each requested adapter to render\n * it, so the same authored capability lands correctly in Claude Code and\n * Copilot layouts without the authoring code knowing either.\n */\nexport interface PluginDraft {\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/**\n\t * Which platform layouts to scaffold this draft into (e.g. `[\"claude\",\n\t * \"github\"]`). Consumed as the default target set by `emitForPlatforms`;\n\t * omit to render every registered format.\n\t */\n\tsupportPlatform?: MarketplacePlatform[];\n\tskills?: AuthoredSkill[];\n\tcommands?: AuthoredCommand[];\n\tagents?: AuthoredAgent[];\n\thooks?: AuthoredHook[];\n\tmcpServers?: AuthoredMcpServer[];\n}\n\n/**\n * Writer for a platform's *workspace-level* (non-plugin) artifact conventions —\n * where a standalone skill, subagent, or command lives inside a repository for\n * that vendor (e.g. Copilot reads `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, `.github/prompts/<name>.prompt.md`).\n * Unlike {@link PluginFormatAdapter.emit}, the returned paths are relative to\n * the **workspace root** (cwd), marker directory included.\n */\nexport interface WorkspaceLayout {\n\t/** Marker root the artifacts land under, e.g. \".github\", \".claude\", \".agents\". */\n\treadonly root: string;\n\temitSkill(skill: AuthoredSkill): EmittedFile;\n\temitAgent(agent: AuthoredAgent): EmittedFile;\n\temitCommand(command: AuthoredCommand): EmittedFile;\n}\n\n/** Reader + writer for a single plugin format. */\nexport interface PluginFormatAdapter {\n\t/** Internal format id (stable; used in `NormalizedPlugin.format`). */\n\treadonly id: PluginFormatId;\n\t/** Public platform token (Copilot → `\"github\"`). */\n\treadonly platform: MarketplacePlatform;\n\t/**\n\t * Precedence when several formats coexist in one directory (lower wins).\n\t * Native `.agents-plugin` beats Claude beats Copilot.\n\t */\n\treadonly precedence: number;\n\t/** Short human label for messages and scaffolding summaries. */\n\treadonly label: string;\n\t/**\n\t * Candidate locations of this format's marketplace index file, relative to a\n\t * marketplace root (POSIX), in precedence order. Formats with more than one\n\t * real-world convention (Copilot uses both `.github/plugin/marketplace.json`\n\t * and `.github/marketplace.json`) list every location they accept.\n\t */\n\treadonly marketplaceFiles: readonly string[];\n\n\t/** True if `root` carries this format's plugin manifest. */\n\tdetectPlugin(root: string): boolean;\n\t/** Parse `root` as a plugin of this format. Returns null when unparseable. */\n\tparsePlugin(root: string): NormalizedPlugin | null;\n\t/** Render `draft` into this format's on-disk layout (paths relative to the plugin root). */\n\temit(draft: PluginDraft): EmittedFile[];\n\t/** This platform's workspace-level artifact conventions (paths relative to the workspace root). */\n\treadonly workspace: WorkspaceLayout;\n}\n"]}
@@ -17,8 +17,6 @@ import { type MarketplacePluginSource, type MarketplaceRecord } from "./marketpl
17
17
  export declare function installedPluginsDir(cwd: string): string;
18
18
  /** Filesystem-safe directory name derived from a plugin name (matches the `/plugin` command). */
19
19
  export declare function sanitizeForDir(s: string): string;
20
- /** Absolute path to the curated default marketplace bundled with hoocode. */
21
- export declare function defaultMarketplaceDir(): string;
22
20
  /** The default marketplace record — always present so source-level trust is meaningful out of the box. */
23
21
  export declare function defaultMarketplaceRecord(): MarketplaceRecord;
24
22
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EACN,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EAItB,MAAM,kBAAkB,CAAC;AAE1B,6GAA6G;AAC7G,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEvD;AAUD,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,4GAA0G;AAC1G,wBAAgB,wBAAwB,IAAI,iBAAiB,CAE5D;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAMhF,CAAC;AAEF,iHAAiH;AACjH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAahF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAuBvE;AAED,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,MAAM,EAAE,uBAAuB,CAAC;IAChC,oDAAoD;IACpD,UAAU,EAAE,OAAO,GAAG,KAAK,GAAG,YAAY,GAAG,KAAK,CAAC;IACnD,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,eAAe,EAAE,mBAAmB,EAAE,CAAC;CACvC;AAED,sGAAsG;AACtG,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAqBnE;AAED,oDAAoD;AACpD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAE1F;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAsB,GAAG,gBAAgB,EAAE,CAEtG;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAsB,GAAG,OAAO,CAEtG;AAqED,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,eAAe,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACxC,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA6D/F;AAED,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,6FAA6F;AAC7F,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAS3E","sourcesContent":["/**\n * Plugin install/uninstall/discovery — the shared engine behind both the\n * `/plugin` slash command (human-driven) and the model-facing lifecycle tools\n * (`SearchPlugins`, `InstallPlugin`, ...). Keeping it in one place means the two\n * surfaces can never drift on where plugins live or how sources resolve.\n *\n * Trust model (see docs/plugin-system-spec.md): *adding* a marketplace is the\n * human trust boundary; *installing* from an already-trusted marketplace is the\n * model's discretion (package-manager model), and must stay transparent and\n * reversible. This module performs the mechanical install/remove; the gating\n * (announce, injection carve-out) lives with the callers.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../../config.js\";\nimport { defaultPluginDirs } from \"../loader.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\nimport { discoverPlugins } from \"./index.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\nimport { parsePluginDir } from \"./manifest.js\";\nimport {\n\ttype MarketplacePluginSource,\n\ttype MarketplaceRecord,\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n} from \"./marketplace.js\";\n\n/** `.agents/` is the primary, cross-vendor home for installed plugins and the added-marketplace registry. */\nexport function installedPluginsDir(cwd: string): string {\n\treturn path.join(cwd, \".agents\", \"plugins\");\n}\n\nfunction marketplaceStorePath(cwd: string): string {\n\treturn path.join(cwd, \".agents\", \"marketplaces.json\");\n}\n\nfunction legacyStorePath(cwd: string): string {\n\treturn path.join(cwd, \".hoocode\", \"marketplaces.json\");\n}\n\n/** Filesystem-safe directory name derived from a plugin name (matches the `/plugin` command). */\nexport function sanitizeForDir(s: string): string {\n\treturn s.replace(/[^a-zA-Z0-9._-]+/g, \"_\").slice(0, 80);\n}\n\n/** Absolute path to the curated default marketplace bundled with hoocode. */\nexport function defaultMarketplaceDir(): string {\n\treturn fileURLToPath(new URL(\"./default-marketplace/\", import.meta.url));\n}\n\n/** The default marketplace record — always present so source-level trust is meaningful out of the box. */\nexport function defaultMarketplaceRecord(): MarketplaceRecord {\n\treturn { location: \"hoocode-default\", dir: defaultMarketplaceDir() };\n}\n\n/**\n * Curated well-known marketplaces, trusted out of the box. Shipping an entry\n * here is a maintainer-level trust decision (the human half of the \"adding a\n * marketplace is the human trust boundary\" rule) — keep this list short and\n * high-trust. Indices are cloned lazily into the marketplace cache on first\n * search and never auto-updated afterwards (plugin sources inside the official\n * index are sha-pinned).\n */\nexport const WELL_KNOWN_MARKETPLACES: ReadonlyArray<{ name: string; url: string }> = [\n\t{ name: \"claude-plugins-official\", url: \"https://github.com/anthropics/claude-plugins-official\" },\n\t// The Copilot plugin directory ships both a `.github/plugin/marketplace.json`\n\t// and a `.claude-plugin/marketplace.json`, so it parses with\n\t// supportPlatform [\"claude\", \"github\"] — the default `github` source.\n\t{ name: \"copilot-plugins\", url: \"https://github.com/github/copilot-plugins\" },\n];\n\n/** Local cache directory for a marketplace fetched from `url` (same convention as `/plugin marketplace add`). */\nexport function marketplaceCacheDir(cwd: string, url: string): string {\n\treturn path.join(cwd, \".agents\", \"marketplace-cache\", sanitizeForDir(url));\n}\n\n/**\n * Clone any well-known marketplace index that is not in the local cache yet.\n * No-op (and no network) when every index is already cached. Returns one error\n * string per marketplace that could not be fetched (offline is non-fatal —\n * search degrades to the marketplaces already available).\n */\nexport async function ensureWellKnownMarketplaces(cwd: string): Promise<string[]> {\n\tconst errors: string[] = [];\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(cwd, wk.url);\n\t\tif (existsSync(dir)) continue;\n\t\tmkdirSync(path.dirname(dir), { recursive: true });\n\t\tconst res = await cloneGitRepo(wk.url, dir);\n\t\tif (res.code !== 0) {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\terrors.push(`${wk.name}: ${(res.stderr || res.stdout).trim()}`);\n\t\t}\n\t}\n\treturn errors;\n}\n\n/**\n * All marketplace records in effect: the bundled default first (curated,\n * trusted), then user-added ones from `.agents/` (falling back to legacy\n * `.hoocode/`). Deduplicated by directory.\n */\nexport function readMarketplaceRecords(cwd: string): MarketplaceRecord[] {\n\tconst records: MarketplaceRecord[] = [];\n\tconst def = defaultMarketplaceRecord();\n\tif (existsSync(def.dir)) records.push(def);\n\n\t// Well-known marketplaces participate once their index is cached locally\n\t// (see ensureWellKnownMarketplaces; SearchPlugins fetches lazily).\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(cwd, wk.url);\n\t\tif (existsSync(dir) && !records.some((x) => x.dir === dir)) {\n\t\t\trecords.push({ location: wk.url, dir });\n\t\t}\n\t}\n\n\tconst primary = readMarketplaceStore(marketplaceStorePath(cwd));\n\tconst user =\n\t\tprimary.length > 0 || existsSync(marketplaceStorePath(cwd))\n\t\t\t? primary\n\t\t\t: readMarketplaceStore(legacyStorePath(cwd));\n\tfor (const r of user) {\n\t\tif (!records.some((x) => x.dir === r.dir)) records.push(r);\n\t}\n\treturn records;\n}\n\n/** A plugin offered by some registered marketplace (not necessarily installed). */\nexport interface AvailablePlugin {\n\tname: string;\n\tdescription?: string;\n\t/** Relative path, git URL, `npm:<spec>`, or structured source object. */\n\tsource: MarketplacePluginSource;\n\t/** Resolved source kind, for display and gating. */\n\tsourceKind: \"local\" | \"git\" | \"git-subdir\" | \"npm\";\n\tmarketplaceName: string;\n\tmarketplaceRoot: string;\n\t/** Platforms this entry targets (per-entry hint, else the marketplace's). */\n\tsupportPlatform: MarketplacePlatform[];\n}\n\n/** Every plugin offered across all registered marketplaces (first marketplace wins on name clash). */\nexport function listAvailablePlugins(cwd: string): AvailablePlugin[] {\n\tconst out: AvailablePlugin[] = [];\n\tconst seen = new Set<string>();\n\tfor (const rec of readMarketplaceRecords(cwd)) {\n\t\tconst market = parseMarketplaceDir(rec.dir);\n\t\tif (!market) continue;\n\t\tfor (const entry of market.plugins) {\n\t\t\tif (seen.has(entry.name)) continue;\n\t\t\tseen.add(entry.name);\n\t\t\tout.push({\n\t\t\t\tname: entry.name,\n\t\t\t\tdescription: entry.description,\n\t\t\t\tsource: entry.source,\n\t\t\t\tsourceKind: resolvePluginSource(entry.source, market.root).kind,\n\t\t\t\tmarketplaceName: market.name,\n\t\t\t\tmarketplaceRoot: market.root,\n\t\t\t\tsupportPlatform: entry.supportPlatform ?? market.supportPlatform,\n\t\t\t});\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Find a single available plugin by exact name. */\nexport function findAvailablePlugin(cwd: string, name: string): AvailablePlugin | undefined {\n\treturn listAvailablePlugins(cwd).find((p) => p.name === name);\n}\n\n/** All currently installed plugins (project + global plugin dirs). */\nexport function listInstalledPlugins(cwd: string, agentDir: string = getAgentDir()): NormalizedPlugin[] {\n\treturn discoverPlugins(defaultPluginDirs(cwd, agentDir));\n}\n\n/** Whether a plugin with `name` (by id) is already installed. */\nexport function isPluginInstalled(cwd: string, name: string, agentDir: string = getAgentDir()): boolean {\n\treturn listInstalledPlugins(cwd, agentDir).some((p) => p.id === name);\n}\n\nfunction execGit(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(\"git\", args);\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout?.on(\"data\", (d) => {\n\t\t\tstdout += d.toString();\n\t\t});\n\t\tchild.stderr?.on(\"data\", (d) => {\n\t\t\tstderr += d.toString();\n\t\t});\n\t\tchild.on(\"error\", (e) => resolve({ code: 1, stdout, stderr: stderr || String(e) }));\n\t\tchild.on(\"close\", (code) => resolve({ code: code ?? 1, stdout, stderr }));\n\t});\n}\n\n/**\n * Clone a git repository into `dest`.\n * If `ref` is provided, does a shallow clone of that branch/tag.\n * If only `sha` is provided, does a full clone and checks out the commit.\n * Returns the clone result; on failure `dest` may be partially created.\n */\nasync function cloneGitRepo(\n\turl: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ code: number; stdout: string; stderr: string }> {\n\tif (ref) {\n\t\treturn execGit([\"clone\", \"--branch\", ref, \"--depth\", \"1\", \"--\", url, dest]);\n\t}\n\tif (sha) {\n\t\tconst cloneRes = await execGit([\"clone\", \"--\", url, dest]);\n\t\tif (cloneRes.code !== 0) return cloneRes;\n\t\treturn execGit([\"-C\", dest, \"checkout\", \"--quiet\", sha]);\n\t}\n\treturn execGit([\"clone\", \"--depth\", \"1\", \"--\", url, dest]);\n}\n\n/**\n * Resolve a git-subdir source: clone the repo, checkout the requested ref/sha,\n * and copy the subdirectory to `dest`. Cleans up the temporary clone on failure.\n */\nasync function installGitSubdir(\n\turl: string,\n\tsubdir: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ ok: true } | { ok: false; message: string }> {\n\tconst tmpDir = mkdtempSync(path.join(os.tmpdir(), \"hoo-plugin-clone-\"));\n\ttry {\n\t\tconst cloneRes = await cloneGitRepo(url, tmpDir, ref, sha);\n\t\tif (cloneRes.code !== 0) {\n\t\t\treturn { ok: false, message: `git clone failed: ${cloneRes.stderr || cloneRes.stdout}`.trim() };\n\t\t}\n\t\tconst src = path.resolve(tmpDir, subdir);\n\t\tif (!existsSync(src)) {\n\t\t\treturn { ok: false, message: `Plugin subdirectory not found in cloned repo: ${subdir}` };\n\t\t}\n\t\tcpSync(src, dest, { recursive: true });\n\t\treturn { ok: true };\n\t} finally {\n\t\trmSync(tmpDir, { recursive: true, force: true });\n\t}\n}\n\nexport interface InstallOutcome {\n\tinstalled: boolean;\n\t/** Install destination directory (when installed). */\n\tdest?: string;\n\t/** Platforms the installed plugin supports (read back from disk). */\n\tsupportPlatform?: MarketplacePlatform[];\n\t/** Human-readable summary suitable for a tool result or notification. */\n\tmessage: string;\n}\n\n/**\n * Install an available plugin by name into `.agents/plugins`. Copies local\n * sources; clones git sources. Transparent + reversible by construction — the\n * plugin lands in a named directory and {@link uninstallPlugin} removes it.\n * Callers activate the result (live activation via AgentSession.activatePlugin,\n * or a reload).\n */\nexport async function installAvailablePlugin(cwd: string, name: string): Promise<InstallOutcome> {\n\tconst found = findAvailablePlugin(cwd, name);\n\tif (!found) return { installed: false, message: `Plugin \"${name}\" not found in any registered marketplace.` };\n\n\tconst resolved = resolvePluginSource(found.source, found.marketplaceRoot);\n\tconst dest = path.join(installedPluginsDir(cwd), sanitizeForDir(name));\n\trmSync(dest, { recursive: true, force: true });\n\tmkdirSync(installedPluginsDir(cwd), { recursive: true });\n\n\tif (resolved.kind === \"local\") {\n\t\tif (!existsSync(resolved.path)) {\n\t\t\treturn { installed: false, message: `Plugin source path not found: ${resolved.path}` };\n\t\t}\n\t\tcpSync(resolved.path, dest, { recursive: true });\n\t} else if (resolved.kind === \"git\") {\n\t\tconst res = await cloneGitRepo(resolved.url, dest, resolved.ref, resolved.sha);\n\t\tif (res.code !== 0) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: `git clone failed: ${res.stderr || res.stdout}`.trim() };\n\t\t}\n\t} else if (resolved.kind === \"git-subdir\") {\n\t\tconst res = await installGitSubdir(resolved.url, resolved.path, dest, resolved.ref, resolved.sha);\n\t\tif (!res.ok) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: res.message };\n\t\t}\n\t} else {\n\t\treturn { installed: false, message: `npm plugin sources are not supported yet (${resolved.spec}).` };\n\t}\n\n\tlet parsed = parsePluginDir(dest);\n\tif (!parsed) {\n\t\t// Manifest-less plugin dir (some marketplaces index bare capability trees,\n\t\t// e.g. a plugin that is just a `skills/` directory): synthesize a native\n\t\t// manifest from the marketplace entry so the standard loader can carry it.\n\t\tconst manifestDir = path.join(dest, \".agents-plugin\");\n\t\tmkdirSync(manifestDir, { recursive: true });\n\t\twriteFileSync(\n\t\t\tpath.join(manifestDir, \"plugin.json\"),\n\t\t\t`${JSON.stringify(\n\t\t\t\t{ name: found.name, ...(found.description ? { description: found.description } : {}) },\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t)}\\n`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tparsed = parsePluginDir(dest);\n\t}\n\tif (!parsed) {\n\t\trmSync(dest, { recursive: true, force: true });\n\t\treturn { installed: false, message: `Installed source for \"${name}\" has no recognizable plugin manifest.` };\n\t}\n\treturn {\n\t\tinstalled: true,\n\t\tdest,\n\t\tsupportPlatform: parsed.supportPlatform,\n\t\tmessage:\n\t\t\t`Installed \"${name}\" from marketplace \"${found.marketplaceName}\" ` +\n\t\t\t`(${parsed.supportPlatform.join(\", \")}) to ${path.relative(cwd, dest) || dest}. ` +\n\t\t\t`Remove it with UninstallPlugin.`,\n\t};\n}\n\nexport interface UninstallOutcome {\n\tremoved: boolean;\n\tmessage: string;\n}\n\n/** Remove an installed plugin from `.agents/plugins` (and the legacy `.hoocode/plugins`). */\nexport function uninstallPlugin(cwd: string, name: string): UninstallOutcome {\n\tconst candidates = [\n\t\tpath.join(installedPluginsDir(cwd), sanitizeForDir(name)),\n\t\tpath.join(cwd, \".hoocode\", \"plugins\", sanitizeForDir(name)),\n\t];\n\tconst present = candidates.filter((p) => existsSync(p));\n\tif (present.length === 0) return { removed: false, message: `Plugin \"${name}\" is not installed.` };\n\tfor (const p of present) rmSync(p, { recursive: true, force: true });\n\treturn { removed: true, message: `Removed \"${name}\".` };\n}\n"]}
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EACN,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EAItB,MAAM,kBAAkB,CAAC;AAE1B,6GAA6G;AAC7G,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEvD;AAUD,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhD;AAOD,4GAA0G;AAC1G,wBAAgB,wBAAwB,IAAI,iBAAiB,CAE5D;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAMhF,CAAC;AAEF,iHAAiH;AACjH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAahF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAuBvE;AAED,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,MAAM,EAAE,uBAAuB,CAAC;IAChC,oDAAoD;IACpD,UAAU,EAAE,OAAO,GAAG,KAAK,GAAG,YAAY,GAAG,KAAK,CAAC;IACnD,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,eAAe,EAAE,mBAAmB,EAAE,CAAC;CACvC;AAED,sGAAsG;AACtG,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAqBnE;AAED,oDAAoD;AACpD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAE1F;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAsB,GAAG,gBAAgB,EAAE,CAEtG;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAsB,GAAG,OAAO,CAEtG;AAqED,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,eAAe,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACxC,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA6D/F;AAED,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,6FAA6F;AAC7F,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAS3E","sourcesContent":["/**\n * Plugin install/uninstall/discovery — the shared engine behind both the\n * `/plugin` slash command (human-driven) and the model-facing lifecycle tools\n * (`SearchPlugins`, `InstallPlugin`, ...). Keeping it in one place means the two\n * surfaces can never drift on where plugins live or how sources resolve.\n *\n * Trust model (see docs/plugin-system-spec.md): *adding* a marketplace is the\n * human trust boundary; *installing* from an already-trusted marketplace is the\n * model's discretion (package-manager model), and must stay transparent and\n * reversible. This module performs the mechanical install/remove; the gating\n * (announce, injection carve-out) lives with the callers.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../../config.js\";\nimport { defaultPluginDirs } from \"../loader.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\nimport { discoverPlugins } from \"./index.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\nimport { parsePluginDir } from \"./manifest.js\";\nimport {\n\ttype MarketplacePluginSource,\n\ttype MarketplaceRecord,\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n} from \"./marketplace.js\";\n\n/** `.agents/` is the primary, cross-vendor home for installed plugins and the added-marketplace registry. */\nexport function installedPluginsDir(cwd: string): string {\n\treturn path.join(cwd, \".agents\", \"plugins\");\n}\n\nfunction marketplaceStorePath(cwd: string): string {\n\treturn path.join(cwd, \".agents\", \"marketplaces.json\");\n}\n\nfunction legacyStorePath(cwd: string): string {\n\treturn path.join(cwd, \".hoocode\", \"marketplaces.json\");\n}\n\n/** Filesystem-safe directory name derived from a plugin name (matches the `/plugin` command). */\nexport function sanitizeForDir(s: string): string {\n\treturn s.replace(/[^a-zA-Z0-9._-]+/g, \"_\").slice(0, 80);\n}\n\n/** Absolute path to the curated default marketplace bundled with hoocode. */\nfunction defaultMarketplaceDir(): string {\n\treturn fileURLToPath(new URL(\"./default-marketplace/\", import.meta.url));\n}\n\n/** The default marketplace record — always present so source-level trust is meaningful out of the box. */\nexport function defaultMarketplaceRecord(): MarketplaceRecord {\n\treturn { location: \"hoocode-default\", dir: defaultMarketplaceDir() };\n}\n\n/**\n * Curated well-known marketplaces, trusted out of the box. Shipping an entry\n * here is a maintainer-level trust decision (the human half of the \"adding a\n * marketplace is the human trust boundary\" rule) — keep this list short and\n * high-trust. Indices are cloned lazily into the marketplace cache on first\n * search and never auto-updated afterwards (plugin sources inside the official\n * index are sha-pinned).\n */\nexport const WELL_KNOWN_MARKETPLACES: ReadonlyArray<{ name: string; url: string }> = [\n\t{ name: \"claude-plugins-official\", url: \"https://github.com/anthropics/claude-plugins-official\" },\n\t// The Copilot plugin directory ships both a `.github/plugin/marketplace.json`\n\t// and a `.claude-plugin/marketplace.json`, so it parses with\n\t// supportPlatform [\"claude\", \"github\"] — the default `github` source.\n\t{ name: \"copilot-plugins\", url: \"https://github.com/github/copilot-plugins\" },\n];\n\n/** Local cache directory for a marketplace fetched from `url` (same convention as `/plugin marketplace add`). */\nexport function marketplaceCacheDir(cwd: string, url: string): string {\n\treturn path.join(cwd, \".agents\", \"marketplace-cache\", sanitizeForDir(url));\n}\n\n/**\n * Clone any well-known marketplace index that is not in the local cache yet.\n * No-op (and no network) when every index is already cached. Returns one error\n * string per marketplace that could not be fetched (offline is non-fatal —\n * search degrades to the marketplaces already available).\n */\nexport async function ensureWellKnownMarketplaces(cwd: string): Promise<string[]> {\n\tconst errors: string[] = [];\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(cwd, wk.url);\n\t\tif (existsSync(dir)) continue;\n\t\tmkdirSync(path.dirname(dir), { recursive: true });\n\t\tconst res = await cloneGitRepo(wk.url, dir);\n\t\tif (res.code !== 0) {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\terrors.push(`${wk.name}: ${(res.stderr || res.stdout).trim()}`);\n\t\t}\n\t}\n\treturn errors;\n}\n\n/**\n * All marketplace records in effect: the bundled default first (curated,\n * trusted), then user-added ones from `.agents/` (falling back to legacy\n * `.hoocode/`). Deduplicated by directory.\n */\nexport function readMarketplaceRecords(cwd: string): MarketplaceRecord[] {\n\tconst records: MarketplaceRecord[] = [];\n\tconst def = defaultMarketplaceRecord();\n\tif (existsSync(def.dir)) records.push(def);\n\n\t// Well-known marketplaces participate once their index is cached locally\n\t// (see ensureWellKnownMarketplaces; SearchPlugins fetches lazily).\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(cwd, wk.url);\n\t\tif (existsSync(dir) && !records.some((x) => x.dir === dir)) {\n\t\t\trecords.push({ location: wk.url, dir });\n\t\t}\n\t}\n\n\tconst primary = readMarketplaceStore(marketplaceStorePath(cwd));\n\tconst user =\n\t\tprimary.length > 0 || existsSync(marketplaceStorePath(cwd))\n\t\t\t? primary\n\t\t\t: readMarketplaceStore(legacyStorePath(cwd));\n\tfor (const r of user) {\n\t\tif (!records.some((x) => x.dir === r.dir)) records.push(r);\n\t}\n\treturn records;\n}\n\n/** A plugin offered by some registered marketplace (not necessarily installed). */\nexport interface AvailablePlugin {\n\tname: string;\n\tdescription?: string;\n\t/** Relative path, git URL, `npm:<spec>`, or structured source object. */\n\tsource: MarketplacePluginSource;\n\t/** Resolved source kind, for display and gating. */\n\tsourceKind: \"local\" | \"git\" | \"git-subdir\" | \"npm\";\n\tmarketplaceName: string;\n\tmarketplaceRoot: string;\n\t/** Platforms this entry targets (per-entry hint, else the marketplace's). */\n\tsupportPlatform: MarketplacePlatform[];\n}\n\n/** Every plugin offered across all registered marketplaces (first marketplace wins on name clash). */\nexport function listAvailablePlugins(cwd: string): AvailablePlugin[] {\n\tconst out: AvailablePlugin[] = [];\n\tconst seen = new Set<string>();\n\tfor (const rec of readMarketplaceRecords(cwd)) {\n\t\tconst market = parseMarketplaceDir(rec.dir);\n\t\tif (!market) continue;\n\t\tfor (const entry of market.plugins) {\n\t\t\tif (seen.has(entry.name)) continue;\n\t\t\tseen.add(entry.name);\n\t\t\tout.push({\n\t\t\t\tname: entry.name,\n\t\t\t\tdescription: entry.description,\n\t\t\t\tsource: entry.source,\n\t\t\t\tsourceKind: resolvePluginSource(entry.source, market.root).kind,\n\t\t\t\tmarketplaceName: market.name,\n\t\t\t\tmarketplaceRoot: market.root,\n\t\t\t\tsupportPlatform: entry.supportPlatform ?? market.supportPlatform,\n\t\t\t});\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Find a single available plugin by exact name. */\nexport function findAvailablePlugin(cwd: string, name: string): AvailablePlugin | undefined {\n\treturn listAvailablePlugins(cwd).find((p) => p.name === name);\n}\n\n/** All currently installed plugins (project + global plugin dirs). */\nexport function listInstalledPlugins(cwd: string, agentDir: string = getAgentDir()): NormalizedPlugin[] {\n\treturn discoverPlugins(defaultPluginDirs(cwd, agentDir));\n}\n\n/** Whether a plugin with `name` (by id) is already installed. */\nexport function isPluginInstalled(cwd: string, name: string, agentDir: string = getAgentDir()): boolean {\n\treturn listInstalledPlugins(cwd, agentDir).some((p) => p.id === name);\n}\n\nfunction execGit(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(\"git\", args);\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout?.on(\"data\", (d) => {\n\t\t\tstdout += d.toString();\n\t\t});\n\t\tchild.stderr?.on(\"data\", (d) => {\n\t\t\tstderr += d.toString();\n\t\t});\n\t\tchild.on(\"error\", (e) => resolve({ code: 1, stdout, stderr: stderr || String(e) }));\n\t\tchild.on(\"close\", (code) => resolve({ code: code ?? 1, stdout, stderr }));\n\t});\n}\n\n/**\n * Clone a git repository into `dest`.\n * If `ref` is provided, does a shallow clone of that branch/tag.\n * If only `sha` is provided, does a full clone and checks out the commit.\n * Returns the clone result; on failure `dest` may be partially created.\n */\nasync function cloneGitRepo(\n\turl: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ code: number; stdout: string; stderr: string }> {\n\tif (ref) {\n\t\treturn execGit([\"clone\", \"--branch\", ref, \"--depth\", \"1\", \"--\", url, dest]);\n\t}\n\tif (sha) {\n\t\tconst cloneRes = await execGit([\"clone\", \"--\", url, dest]);\n\t\tif (cloneRes.code !== 0) return cloneRes;\n\t\treturn execGit([\"-C\", dest, \"checkout\", \"--quiet\", sha]);\n\t}\n\treturn execGit([\"clone\", \"--depth\", \"1\", \"--\", url, dest]);\n}\n\n/**\n * Resolve a git-subdir source: clone the repo, checkout the requested ref/sha,\n * and copy the subdirectory to `dest`. Cleans up the temporary clone on failure.\n */\nasync function installGitSubdir(\n\turl: string,\n\tsubdir: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ ok: true } | { ok: false; message: string }> {\n\tconst tmpDir = mkdtempSync(path.join(os.tmpdir(), \"hoo-plugin-clone-\"));\n\ttry {\n\t\tconst cloneRes = await cloneGitRepo(url, tmpDir, ref, sha);\n\t\tif (cloneRes.code !== 0) {\n\t\t\treturn { ok: false, message: `git clone failed: ${cloneRes.stderr || cloneRes.stdout}`.trim() };\n\t\t}\n\t\tconst src = path.resolve(tmpDir, subdir);\n\t\tif (!existsSync(src)) {\n\t\t\treturn { ok: false, message: `Plugin subdirectory not found in cloned repo: ${subdir}` };\n\t\t}\n\t\tcpSync(src, dest, { recursive: true });\n\t\treturn { ok: true };\n\t} finally {\n\t\trmSync(tmpDir, { recursive: true, force: true });\n\t}\n}\n\nexport interface InstallOutcome {\n\tinstalled: boolean;\n\t/** Install destination directory (when installed). */\n\tdest?: string;\n\t/** Platforms the installed plugin supports (read back from disk). */\n\tsupportPlatform?: MarketplacePlatform[];\n\t/** Human-readable summary suitable for a tool result or notification. */\n\tmessage: string;\n}\n\n/**\n * Install an available plugin by name into `.agents/plugins`. Copies local\n * sources; clones git sources. Transparent + reversible by construction — the\n * plugin lands in a named directory and {@link uninstallPlugin} removes it.\n * Callers activate the result (live activation via AgentSession.activatePlugin,\n * or a reload).\n */\nexport async function installAvailablePlugin(cwd: string, name: string): Promise<InstallOutcome> {\n\tconst found = findAvailablePlugin(cwd, name);\n\tif (!found) return { installed: false, message: `Plugin \"${name}\" not found in any registered marketplace.` };\n\n\tconst resolved = resolvePluginSource(found.source, found.marketplaceRoot);\n\tconst dest = path.join(installedPluginsDir(cwd), sanitizeForDir(name));\n\trmSync(dest, { recursive: true, force: true });\n\tmkdirSync(installedPluginsDir(cwd), { recursive: true });\n\n\tif (resolved.kind === \"local\") {\n\t\tif (!existsSync(resolved.path)) {\n\t\t\treturn { installed: false, message: `Plugin source path not found: ${resolved.path}` };\n\t\t}\n\t\tcpSync(resolved.path, dest, { recursive: true });\n\t} else if (resolved.kind === \"git\") {\n\t\tconst res = await cloneGitRepo(resolved.url, dest, resolved.ref, resolved.sha);\n\t\tif (res.code !== 0) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: `git clone failed: ${res.stderr || res.stdout}`.trim() };\n\t\t}\n\t} else if (resolved.kind === \"git-subdir\") {\n\t\tconst res = await installGitSubdir(resolved.url, resolved.path, dest, resolved.ref, resolved.sha);\n\t\tif (!res.ok) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: res.message };\n\t\t}\n\t} else {\n\t\treturn { installed: false, message: `npm plugin sources are not supported yet (${resolved.spec}).` };\n\t}\n\n\tlet parsed = parsePluginDir(dest);\n\tif (!parsed) {\n\t\t// Manifest-less plugin dir (some marketplaces index bare capability trees,\n\t\t// e.g. a plugin that is just a `skills/` directory): synthesize a native\n\t\t// manifest from the marketplace entry so the standard loader can carry it.\n\t\tconst manifestDir = path.join(dest, \".agents-plugin\");\n\t\tmkdirSync(manifestDir, { recursive: true });\n\t\twriteFileSync(\n\t\t\tpath.join(manifestDir, \"plugin.json\"),\n\t\t\t`${JSON.stringify(\n\t\t\t\t{ name: found.name, ...(found.description ? { description: found.description } : {}) },\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t)}\\n`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tparsed = parsePluginDir(dest);\n\t}\n\tif (!parsed) {\n\t\trmSync(dest, { recursive: true, force: true });\n\t\treturn { installed: false, message: `Installed source for \"${name}\" has no recognizable plugin manifest.` };\n\t}\n\treturn {\n\t\tinstalled: true,\n\t\tdest,\n\t\tsupportPlatform: parsed.supportPlatform,\n\t\tmessage:\n\t\t\t`Installed \"${name}\" from marketplace \"${found.marketplaceName}\" ` +\n\t\t\t`(${parsed.supportPlatform.join(\", \")}) to ${path.relative(cwd, dest) || dest}. ` +\n\t\t\t`Remove it with UninstallPlugin.`,\n\t};\n}\n\nexport interface UninstallOutcome {\n\tremoved: boolean;\n\tmessage: string;\n}\n\n/** Remove an installed plugin from `.agents/plugins` (and the legacy `.hoocode/plugins`). */\nexport function uninstallPlugin(cwd: string, name: string): UninstallOutcome {\n\tconst candidates = [\n\t\tpath.join(installedPluginsDir(cwd), sanitizeForDir(name)),\n\t\tpath.join(cwd, \".hoocode\", \"plugins\", sanitizeForDir(name)),\n\t];\n\tconst present = candidates.filter((p) => existsSync(p));\n\tif (present.length === 0) return { removed: false, message: `Plugin \"${name}\" is not installed.` };\n\tfor (const p of present) rmSync(p, { recursive: true, force: true });\n\treturn { removed: true, message: `Removed \"${name}\".` };\n}\n"]}
@@ -35,7 +35,7 @@ export function sanitizeForDir(s) {
35
35
  return s.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 80);
36
36
  }
37
37
  /** Absolute path to the curated default marketplace bundled with hoocode. */
38
- export function defaultMarketplaceDir() {
38
+ function defaultMarketplaceDir() {
39
39
  return fileURLToPath(new URL("./default-marketplace/", import.meta.url));
40
40
  }
41
41
  /** The default marketplace record — always present so source-level trust is meaningful out of the box. */
@@ -1 +1 @@
1
- {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/install.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Plugin install/uninstall/discovery — the shared engine behind both the\n * `/plugin` slash command (human-driven) and the model-facing lifecycle tools\n * (`SearchPlugins`, `InstallPlugin`, ...). Keeping it in one place means the two\n * surfaces can never drift on where plugins live or how sources resolve.\n *\n * Trust model (see docs/plugin-system-spec.md): *adding* a marketplace is the\n * human trust boundary; *installing* from an already-trusted marketplace is the\n * model's discretion (package-manager model), and must stay transparent and\n * reversible. This module performs the mechanical install/remove; the gating\n * (announce, injection carve-out) lives with the callers.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../../config.js\";\nimport { defaultPluginDirs } from \"../loader.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\nimport { discoverPlugins } from \"./index.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\nimport { parsePluginDir } from \"./manifest.js\";\nimport {\n\ttype MarketplacePluginSource,\n\ttype MarketplaceRecord,\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n} from \"./marketplace.js\";\n\n/** `.agents/` is the primary, cross-vendor home for installed plugins and the added-marketplace registry. */\nexport function installedPluginsDir(cwd: string): string {\n\treturn path.join(cwd, \".agents\", \"plugins\");\n}\n\nfunction marketplaceStorePath(cwd: string): string {\n\treturn path.join(cwd, \".agents\", \"marketplaces.json\");\n}\n\nfunction legacyStorePath(cwd: string): string {\n\treturn path.join(cwd, \".hoocode\", \"marketplaces.json\");\n}\n\n/** Filesystem-safe directory name derived from a plugin name (matches the `/plugin` command). */\nexport function sanitizeForDir(s: string): string {\n\treturn s.replace(/[^a-zA-Z0-9._-]+/g, \"_\").slice(0, 80);\n}\n\n/** Absolute path to the curated default marketplace bundled with hoocode. */\nexport function defaultMarketplaceDir(): string {\n\treturn fileURLToPath(new URL(\"./default-marketplace/\", import.meta.url));\n}\n\n/** The default marketplace record — always present so source-level trust is meaningful out of the box. */\nexport function defaultMarketplaceRecord(): MarketplaceRecord {\n\treturn { location: \"hoocode-default\", dir: defaultMarketplaceDir() };\n}\n\n/**\n * Curated well-known marketplaces, trusted out of the box. Shipping an entry\n * here is a maintainer-level trust decision (the human half of the \"adding a\n * marketplace is the human trust boundary\" rule) — keep this list short and\n * high-trust. Indices are cloned lazily into the marketplace cache on first\n * search and never auto-updated afterwards (plugin sources inside the official\n * index are sha-pinned).\n */\nexport const WELL_KNOWN_MARKETPLACES: ReadonlyArray<{ name: string; url: string }> = [\n\t{ name: \"claude-plugins-official\", url: \"https://github.com/anthropics/claude-plugins-official\" },\n\t// The Copilot plugin directory ships both a `.github/plugin/marketplace.json`\n\t// and a `.claude-plugin/marketplace.json`, so it parses with\n\t// supportPlatform [\"claude\", \"github\"] — the default `github` source.\n\t{ name: \"copilot-plugins\", url: \"https://github.com/github/copilot-plugins\" },\n];\n\n/** Local cache directory for a marketplace fetched from `url` (same convention as `/plugin marketplace add`). */\nexport function marketplaceCacheDir(cwd: string, url: string): string {\n\treturn path.join(cwd, \".agents\", \"marketplace-cache\", sanitizeForDir(url));\n}\n\n/**\n * Clone any well-known marketplace index that is not in the local cache yet.\n * No-op (and no network) when every index is already cached. Returns one error\n * string per marketplace that could not be fetched (offline is non-fatal —\n * search degrades to the marketplaces already available).\n */\nexport async function ensureWellKnownMarketplaces(cwd: string): Promise<string[]> {\n\tconst errors: string[] = [];\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(cwd, wk.url);\n\t\tif (existsSync(dir)) continue;\n\t\tmkdirSync(path.dirname(dir), { recursive: true });\n\t\tconst res = await cloneGitRepo(wk.url, dir);\n\t\tif (res.code !== 0) {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\terrors.push(`${wk.name}: ${(res.stderr || res.stdout).trim()}`);\n\t\t}\n\t}\n\treturn errors;\n}\n\n/**\n * All marketplace records in effect: the bundled default first (curated,\n * trusted), then user-added ones from `.agents/` (falling back to legacy\n * `.hoocode/`). Deduplicated by directory.\n */\nexport function readMarketplaceRecords(cwd: string): MarketplaceRecord[] {\n\tconst records: MarketplaceRecord[] = [];\n\tconst def = defaultMarketplaceRecord();\n\tif (existsSync(def.dir)) records.push(def);\n\n\t// Well-known marketplaces participate once their index is cached locally\n\t// (see ensureWellKnownMarketplaces; SearchPlugins fetches lazily).\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(cwd, wk.url);\n\t\tif (existsSync(dir) && !records.some((x) => x.dir === dir)) {\n\t\t\trecords.push({ location: wk.url, dir });\n\t\t}\n\t}\n\n\tconst primary = readMarketplaceStore(marketplaceStorePath(cwd));\n\tconst user =\n\t\tprimary.length > 0 || existsSync(marketplaceStorePath(cwd))\n\t\t\t? primary\n\t\t\t: readMarketplaceStore(legacyStorePath(cwd));\n\tfor (const r of user) {\n\t\tif (!records.some((x) => x.dir === r.dir)) records.push(r);\n\t}\n\treturn records;\n}\n\n/** A plugin offered by some registered marketplace (not necessarily installed). */\nexport interface AvailablePlugin {\n\tname: string;\n\tdescription?: string;\n\t/** Relative path, git URL, `npm:<spec>`, or structured source object. */\n\tsource: MarketplacePluginSource;\n\t/** Resolved source kind, for display and gating. */\n\tsourceKind: \"local\" | \"git\" | \"git-subdir\" | \"npm\";\n\tmarketplaceName: string;\n\tmarketplaceRoot: string;\n\t/** Platforms this entry targets (per-entry hint, else the marketplace's). */\n\tsupportPlatform: MarketplacePlatform[];\n}\n\n/** Every plugin offered across all registered marketplaces (first marketplace wins on name clash). */\nexport function listAvailablePlugins(cwd: string): AvailablePlugin[] {\n\tconst out: AvailablePlugin[] = [];\n\tconst seen = new Set<string>();\n\tfor (const rec of readMarketplaceRecords(cwd)) {\n\t\tconst market = parseMarketplaceDir(rec.dir);\n\t\tif (!market) continue;\n\t\tfor (const entry of market.plugins) {\n\t\t\tif (seen.has(entry.name)) continue;\n\t\t\tseen.add(entry.name);\n\t\t\tout.push({\n\t\t\t\tname: entry.name,\n\t\t\t\tdescription: entry.description,\n\t\t\t\tsource: entry.source,\n\t\t\t\tsourceKind: resolvePluginSource(entry.source, market.root).kind,\n\t\t\t\tmarketplaceName: market.name,\n\t\t\t\tmarketplaceRoot: market.root,\n\t\t\t\tsupportPlatform: entry.supportPlatform ?? market.supportPlatform,\n\t\t\t});\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Find a single available plugin by exact name. */\nexport function findAvailablePlugin(cwd: string, name: string): AvailablePlugin | undefined {\n\treturn listAvailablePlugins(cwd).find((p) => p.name === name);\n}\n\n/** All currently installed plugins (project + global plugin dirs). */\nexport function listInstalledPlugins(cwd: string, agentDir: string = getAgentDir()): NormalizedPlugin[] {\n\treturn discoverPlugins(defaultPluginDirs(cwd, agentDir));\n}\n\n/** Whether a plugin with `name` (by id) is already installed. */\nexport function isPluginInstalled(cwd: string, name: string, agentDir: string = getAgentDir()): boolean {\n\treturn listInstalledPlugins(cwd, agentDir).some((p) => p.id === name);\n}\n\nfunction execGit(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(\"git\", args);\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout?.on(\"data\", (d) => {\n\t\t\tstdout += d.toString();\n\t\t});\n\t\tchild.stderr?.on(\"data\", (d) => {\n\t\t\tstderr += d.toString();\n\t\t});\n\t\tchild.on(\"error\", (e) => resolve({ code: 1, stdout, stderr: stderr || String(e) }));\n\t\tchild.on(\"close\", (code) => resolve({ code: code ?? 1, stdout, stderr }));\n\t});\n}\n\n/**\n * Clone a git repository into `dest`.\n * If `ref` is provided, does a shallow clone of that branch/tag.\n * If only `sha` is provided, does a full clone and checks out the commit.\n * Returns the clone result; on failure `dest` may be partially created.\n */\nasync function cloneGitRepo(\n\turl: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ code: number; stdout: string; stderr: string }> {\n\tif (ref) {\n\t\treturn execGit([\"clone\", \"--branch\", ref, \"--depth\", \"1\", \"--\", url, dest]);\n\t}\n\tif (sha) {\n\t\tconst cloneRes = await execGit([\"clone\", \"--\", url, dest]);\n\t\tif (cloneRes.code !== 0) return cloneRes;\n\t\treturn execGit([\"-C\", dest, \"checkout\", \"--quiet\", sha]);\n\t}\n\treturn execGit([\"clone\", \"--depth\", \"1\", \"--\", url, dest]);\n}\n\n/**\n * Resolve a git-subdir source: clone the repo, checkout the requested ref/sha,\n * and copy the subdirectory to `dest`. Cleans up the temporary clone on failure.\n */\nasync function installGitSubdir(\n\turl: string,\n\tsubdir: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ ok: true } | { ok: false; message: string }> {\n\tconst tmpDir = mkdtempSync(path.join(os.tmpdir(), \"hoo-plugin-clone-\"));\n\ttry {\n\t\tconst cloneRes = await cloneGitRepo(url, tmpDir, ref, sha);\n\t\tif (cloneRes.code !== 0) {\n\t\t\treturn { ok: false, message: `git clone failed: ${cloneRes.stderr || cloneRes.stdout}`.trim() };\n\t\t}\n\t\tconst src = path.resolve(tmpDir, subdir);\n\t\tif (!existsSync(src)) {\n\t\t\treturn { ok: false, message: `Plugin subdirectory not found in cloned repo: ${subdir}` };\n\t\t}\n\t\tcpSync(src, dest, { recursive: true });\n\t\treturn { ok: true };\n\t} finally {\n\t\trmSync(tmpDir, { recursive: true, force: true });\n\t}\n}\n\nexport interface InstallOutcome {\n\tinstalled: boolean;\n\t/** Install destination directory (when installed). */\n\tdest?: string;\n\t/** Platforms the installed plugin supports (read back from disk). */\n\tsupportPlatform?: MarketplacePlatform[];\n\t/** Human-readable summary suitable for a tool result or notification. */\n\tmessage: string;\n}\n\n/**\n * Install an available plugin by name into `.agents/plugins`. Copies local\n * sources; clones git sources. Transparent + reversible by construction — the\n * plugin lands in a named directory and {@link uninstallPlugin} removes it.\n * Callers activate the result (live activation via AgentSession.activatePlugin,\n * or a reload).\n */\nexport async function installAvailablePlugin(cwd: string, name: string): Promise<InstallOutcome> {\n\tconst found = findAvailablePlugin(cwd, name);\n\tif (!found) return { installed: false, message: `Plugin \"${name}\" not found in any registered marketplace.` };\n\n\tconst resolved = resolvePluginSource(found.source, found.marketplaceRoot);\n\tconst dest = path.join(installedPluginsDir(cwd), sanitizeForDir(name));\n\trmSync(dest, { recursive: true, force: true });\n\tmkdirSync(installedPluginsDir(cwd), { recursive: true });\n\n\tif (resolved.kind === \"local\") {\n\t\tif (!existsSync(resolved.path)) {\n\t\t\treturn { installed: false, message: `Plugin source path not found: ${resolved.path}` };\n\t\t}\n\t\tcpSync(resolved.path, dest, { recursive: true });\n\t} else if (resolved.kind === \"git\") {\n\t\tconst res = await cloneGitRepo(resolved.url, dest, resolved.ref, resolved.sha);\n\t\tif (res.code !== 0) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: `git clone failed: ${res.stderr || res.stdout}`.trim() };\n\t\t}\n\t} else if (resolved.kind === \"git-subdir\") {\n\t\tconst res = await installGitSubdir(resolved.url, resolved.path, dest, resolved.ref, resolved.sha);\n\t\tif (!res.ok) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: res.message };\n\t\t}\n\t} else {\n\t\treturn { installed: false, message: `npm plugin sources are not supported yet (${resolved.spec}).` };\n\t}\n\n\tlet parsed = parsePluginDir(dest);\n\tif (!parsed) {\n\t\t// Manifest-less plugin dir (some marketplaces index bare capability trees,\n\t\t// e.g. a plugin that is just a `skills/` directory): synthesize a native\n\t\t// manifest from the marketplace entry so the standard loader can carry it.\n\t\tconst manifestDir = path.join(dest, \".agents-plugin\");\n\t\tmkdirSync(manifestDir, { recursive: true });\n\t\twriteFileSync(\n\t\t\tpath.join(manifestDir, \"plugin.json\"),\n\t\t\t`${JSON.stringify(\n\t\t\t\t{ name: found.name, ...(found.description ? { description: found.description } : {}) },\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t)}\\n`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tparsed = parsePluginDir(dest);\n\t}\n\tif (!parsed) {\n\t\trmSync(dest, { recursive: true, force: true });\n\t\treturn { installed: false, message: `Installed source for \"${name}\" has no recognizable plugin manifest.` };\n\t}\n\treturn {\n\t\tinstalled: true,\n\t\tdest,\n\t\tsupportPlatform: parsed.supportPlatform,\n\t\tmessage:\n\t\t\t`Installed \"${name}\" from marketplace \"${found.marketplaceName}\" ` +\n\t\t\t`(${parsed.supportPlatform.join(\", \")}) to ${path.relative(cwd, dest) || dest}. ` +\n\t\t\t`Remove it with UninstallPlugin.`,\n\t};\n}\n\nexport interface UninstallOutcome {\n\tremoved: boolean;\n\tmessage: string;\n}\n\n/** Remove an installed plugin from `.agents/plugins` (and the legacy `.hoocode/plugins`). */\nexport function uninstallPlugin(cwd: string, name: string): UninstallOutcome {\n\tconst candidates = [\n\t\tpath.join(installedPluginsDir(cwd), sanitizeForDir(name)),\n\t\tpath.join(cwd, \".hoocode\", \"plugins\", sanitizeForDir(name)),\n\t];\n\tconst present = candidates.filter((p) => existsSync(p));\n\tif (present.length === 0) return { removed: false, message: `Plugin \"${name}\" is not installed.` };\n\tfor (const p of present) rmSync(p, { recursive: true, force: true });\n\treturn { removed: true, message: `Removed \"${name}\".` };\n}\n"]}
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/install.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Plugin install/uninstall/discovery — the shared engine behind both the\n * `/plugin` slash command (human-driven) and the model-facing lifecycle tools\n * (`SearchPlugins`, `InstallPlugin`, ...). Keeping it in one place means the two\n * surfaces can never drift on where plugins live or how sources resolve.\n *\n * Trust model (see docs/plugin-system-spec.md): *adding* a marketplace is the\n * human trust boundary; *installing* from an already-trusted marketplace is the\n * model's discretion (package-manager model), and must stay transparent and\n * reversible. This module performs the mechanical install/remove; the gating\n * (announce, injection carve-out) lives with the callers.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../../config.js\";\nimport { defaultPluginDirs } from \"../loader.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\nimport { discoverPlugins } from \"./index.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\nimport { parsePluginDir } from \"./manifest.js\";\nimport {\n\ttype MarketplacePluginSource,\n\ttype MarketplaceRecord,\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n} from \"./marketplace.js\";\n\n/** `.agents/` is the primary, cross-vendor home for installed plugins and the added-marketplace registry. */\nexport function installedPluginsDir(cwd: string): string {\n\treturn path.join(cwd, \".agents\", \"plugins\");\n}\n\nfunction marketplaceStorePath(cwd: string): string {\n\treturn path.join(cwd, \".agents\", \"marketplaces.json\");\n}\n\nfunction legacyStorePath(cwd: string): string {\n\treturn path.join(cwd, \".hoocode\", \"marketplaces.json\");\n}\n\n/** Filesystem-safe directory name derived from a plugin name (matches the `/plugin` command). */\nexport function sanitizeForDir(s: string): string {\n\treturn s.replace(/[^a-zA-Z0-9._-]+/g, \"_\").slice(0, 80);\n}\n\n/** Absolute path to the curated default marketplace bundled with hoocode. */\nfunction defaultMarketplaceDir(): string {\n\treturn fileURLToPath(new URL(\"./default-marketplace/\", import.meta.url));\n}\n\n/** The default marketplace record — always present so source-level trust is meaningful out of the box. */\nexport function defaultMarketplaceRecord(): MarketplaceRecord {\n\treturn { location: \"hoocode-default\", dir: defaultMarketplaceDir() };\n}\n\n/**\n * Curated well-known marketplaces, trusted out of the box. Shipping an entry\n * here is a maintainer-level trust decision (the human half of the \"adding a\n * marketplace is the human trust boundary\" rule) — keep this list short and\n * high-trust. Indices are cloned lazily into the marketplace cache on first\n * search and never auto-updated afterwards (plugin sources inside the official\n * index are sha-pinned).\n */\nexport const WELL_KNOWN_MARKETPLACES: ReadonlyArray<{ name: string; url: string }> = [\n\t{ name: \"claude-plugins-official\", url: \"https://github.com/anthropics/claude-plugins-official\" },\n\t// The Copilot plugin directory ships both a `.github/plugin/marketplace.json`\n\t// and a `.claude-plugin/marketplace.json`, so it parses with\n\t// supportPlatform [\"claude\", \"github\"] — the default `github` source.\n\t{ name: \"copilot-plugins\", url: \"https://github.com/github/copilot-plugins\" },\n];\n\n/** Local cache directory for a marketplace fetched from `url` (same convention as `/plugin marketplace add`). */\nexport function marketplaceCacheDir(cwd: string, url: string): string {\n\treturn path.join(cwd, \".agents\", \"marketplace-cache\", sanitizeForDir(url));\n}\n\n/**\n * Clone any well-known marketplace index that is not in the local cache yet.\n * No-op (and no network) when every index is already cached. Returns one error\n * string per marketplace that could not be fetched (offline is non-fatal —\n * search degrades to the marketplaces already available).\n */\nexport async function ensureWellKnownMarketplaces(cwd: string): Promise<string[]> {\n\tconst errors: string[] = [];\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(cwd, wk.url);\n\t\tif (existsSync(dir)) continue;\n\t\tmkdirSync(path.dirname(dir), { recursive: true });\n\t\tconst res = await cloneGitRepo(wk.url, dir);\n\t\tif (res.code !== 0) {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\terrors.push(`${wk.name}: ${(res.stderr || res.stdout).trim()}`);\n\t\t}\n\t}\n\treturn errors;\n}\n\n/**\n * All marketplace records in effect: the bundled default first (curated,\n * trusted), then user-added ones from `.agents/` (falling back to legacy\n * `.hoocode/`). Deduplicated by directory.\n */\nexport function readMarketplaceRecords(cwd: string): MarketplaceRecord[] {\n\tconst records: MarketplaceRecord[] = [];\n\tconst def = defaultMarketplaceRecord();\n\tif (existsSync(def.dir)) records.push(def);\n\n\t// Well-known marketplaces participate once their index is cached locally\n\t// (see ensureWellKnownMarketplaces; SearchPlugins fetches lazily).\n\tfor (const wk of WELL_KNOWN_MARKETPLACES) {\n\t\tconst dir = marketplaceCacheDir(cwd, wk.url);\n\t\tif (existsSync(dir) && !records.some((x) => x.dir === dir)) {\n\t\t\trecords.push({ location: wk.url, dir });\n\t\t}\n\t}\n\n\tconst primary = readMarketplaceStore(marketplaceStorePath(cwd));\n\tconst user =\n\t\tprimary.length > 0 || existsSync(marketplaceStorePath(cwd))\n\t\t\t? primary\n\t\t\t: readMarketplaceStore(legacyStorePath(cwd));\n\tfor (const r of user) {\n\t\tif (!records.some((x) => x.dir === r.dir)) records.push(r);\n\t}\n\treturn records;\n}\n\n/** A plugin offered by some registered marketplace (not necessarily installed). */\nexport interface AvailablePlugin {\n\tname: string;\n\tdescription?: string;\n\t/** Relative path, git URL, `npm:<spec>`, or structured source object. */\n\tsource: MarketplacePluginSource;\n\t/** Resolved source kind, for display and gating. */\n\tsourceKind: \"local\" | \"git\" | \"git-subdir\" | \"npm\";\n\tmarketplaceName: string;\n\tmarketplaceRoot: string;\n\t/** Platforms this entry targets (per-entry hint, else the marketplace's). */\n\tsupportPlatform: MarketplacePlatform[];\n}\n\n/** Every plugin offered across all registered marketplaces (first marketplace wins on name clash). */\nexport function listAvailablePlugins(cwd: string): AvailablePlugin[] {\n\tconst out: AvailablePlugin[] = [];\n\tconst seen = new Set<string>();\n\tfor (const rec of readMarketplaceRecords(cwd)) {\n\t\tconst market = parseMarketplaceDir(rec.dir);\n\t\tif (!market) continue;\n\t\tfor (const entry of market.plugins) {\n\t\t\tif (seen.has(entry.name)) continue;\n\t\t\tseen.add(entry.name);\n\t\t\tout.push({\n\t\t\t\tname: entry.name,\n\t\t\t\tdescription: entry.description,\n\t\t\t\tsource: entry.source,\n\t\t\t\tsourceKind: resolvePluginSource(entry.source, market.root).kind,\n\t\t\t\tmarketplaceName: market.name,\n\t\t\t\tmarketplaceRoot: market.root,\n\t\t\t\tsupportPlatform: entry.supportPlatform ?? market.supportPlatform,\n\t\t\t});\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Find a single available plugin by exact name. */\nexport function findAvailablePlugin(cwd: string, name: string): AvailablePlugin | undefined {\n\treturn listAvailablePlugins(cwd).find((p) => p.name === name);\n}\n\n/** All currently installed plugins (project + global plugin dirs). */\nexport function listInstalledPlugins(cwd: string, agentDir: string = getAgentDir()): NormalizedPlugin[] {\n\treturn discoverPlugins(defaultPluginDirs(cwd, agentDir));\n}\n\n/** Whether a plugin with `name` (by id) is already installed. */\nexport function isPluginInstalled(cwd: string, name: string, agentDir: string = getAgentDir()): boolean {\n\treturn listInstalledPlugins(cwd, agentDir).some((p) => p.id === name);\n}\n\nfunction execGit(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(\"git\", args);\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout?.on(\"data\", (d) => {\n\t\t\tstdout += d.toString();\n\t\t});\n\t\tchild.stderr?.on(\"data\", (d) => {\n\t\t\tstderr += d.toString();\n\t\t});\n\t\tchild.on(\"error\", (e) => resolve({ code: 1, stdout, stderr: stderr || String(e) }));\n\t\tchild.on(\"close\", (code) => resolve({ code: code ?? 1, stdout, stderr }));\n\t});\n}\n\n/**\n * Clone a git repository into `dest`.\n * If `ref` is provided, does a shallow clone of that branch/tag.\n * If only `sha` is provided, does a full clone and checks out the commit.\n * Returns the clone result; on failure `dest` may be partially created.\n */\nasync function cloneGitRepo(\n\turl: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ code: number; stdout: string; stderr: string }> {\n\tif (ref) {\n\t\treturn execGit([\"clone\", \"--branch\", ref, \"--depth\", \"1\", \"--\", url, dest]);\n\t}\n\tif (sha) {\n\t\tconst cloneRes = await execGit([\"clone\", \"--\", url, dest]);\n\t\tif (cloneRes.code !== 0) return cloneRes;\n\t\treturn execGit([\"-C\", dest, \"checkout\", \"--quiet\", sha]);\n\t}\n\treturn execGit([\"clone\", \"--depth\", \"1\", \"--\", url, dest]);\n}\n\n/**\n * Resolve a git-subdir source: clone the repo, checkout the requested ref/sha,\n * and copy the subdirectory to `dest`. Cleans up the temporary clone on failure.\n */\nasync function installGitSubdir(\n\turl: string,\n\tsubdir: string,\n\tdest: string,\n\tref?: string,\n\tsha?: string,\n): Promise<{ ok: true } | { ok: false; message: string }> {\n\tconst tmpDir = mkdtempSync(path.join(os.tmpdir(), \"hoo-plugin-clone-\"));\n\ttry {\n\t\tconst cloneRes = await cloneGitRepo(url, tmpDir, ref, sha);\n\t\tif (cloneRes.code !== 0) {\n\t\t\treturn { ok: false, message: `git clone failed: ${cloneRes.stderr || cloneRes.stdout}`.trim() };\n\t\t}\n\t\tconst src = path.resolve(tmpDir, subdir);\n\t\tif (!existsSync(src)) {\n\t\t\treturn { ok: false, message: `Plugin subdirectory not found in cloned repo: ${subdir}` };\n\t\t}\n\t\tcpSync(src, dest, { recursive: true });\n\t\treturn { ok: true };\n\t} finally {\n\t\trmSync(tmpDir, { recursive: true, force: true });\n\t}\n}\n\nexport interface InstallOutcome {\n\tinstalled: boolean;\n\t/** Install destination directory (when installed). */\n\tdest?: string;\n\t/** Platforms the installed plugin supports (read back from disk). */\n\tsupportPlatform?: MarketplacePlatform[];\n\t/** Human-readable summary suitable for a tool result or notification. */\n\tmessage: string;\n}\n\n/**\n * Install an available plugin by name into `.agents/plugins`. Copies local\n * sources; clones git sources. Transparent + reversible by construction — the\n * plugin lands in a named directory and {@link uninstallPlugin} removes it.\n * Callers activate the result (live activation via AgentSession.activatePlugin,\n * or a reload).\n */\nexport async function installAvailablePlugin(cwd: string, name: string): Promise<InstallOutcome> {\n\tconst found = findAvailablePlugin(cwd, name);\n\tif (!found) return { installed: false, message: `Plugin \"${name}\" not found in any registered marketplace.` };\n\n\tconst resolved = resolvePluginSource(found.source, found.marketplaceRoot);\n\tconst dest = path.join(installedPluginsDir(cwd), sanitizeForDir(name));\n\trmSync(dest, { recursive: true, force: true });\n\tmkdirSync(installedPluginsDir(cwd), { recursive: true });\n\n\tif (resolved.kind === \"local\") {\n\t\tif (!existsSync(resolved.path)) {\n\t\t\treturn { installed: false, message: `Plugin source path not found: ${resolved.path}` };\n\t\t}\n\t\tcpSync(resolved.path, dest, { recursive: true });\n\t} else if (resolved.kind === \"git\") {\n\t\tconst res = await cloneGitRepo(resolved.url, dest, resolved.ref, resolved.sha);\n\t\tif (res.code !== 0) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: `git clone failed: ${res.stderr || res.stdout}`.trim() };\n\t\t}\n\t} else if (resolved.kind === \"git-subdir\") {\n\t\tconst res = await installGitSubdir(resolved.url, resolved.path, dest, resolved.ref, resolved.sha);\n\t\tif (!res.ok) {\n\t\t\trmSync(dest, { recursive: true, force: true });\n\t\t\treturn { installed: false, message: res.message };\n\t\t}\n\t} else {\n\t\treturn { installed: false, message: `npm plugin sources are not supported yet (${resolved.spec}).` };\n\t}\n\n\tlet parsed = parsePluginDir(dest);\n\tif (!parsed) {\n\t\t// Manifest-less plugin dir (some marketplaces index bare capability trees,\n\t\t// e.g. a plugin that is just a `skills/` directory): synthesize a native\n\t\t// manifest from the marketplace entry so the standard loader can carry it.\n\t\tconst manifestDir = path.join(dest, \".agents-plugin\");\n\t\tmkdirSync(manifestDir, { recursive: true });\n\t\twriteFileSync(\n\t\t\tpath.join(manifestDir, \"plugin.json\"),\n\t\t\t`${JSON.stringify(\n\t\t\t\t{ name: found.name, ...(found.description ? { description: found.description } : {}) },\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t)}\\n`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tparsed = parsePluginDir(dest);\n\t}\n\tif (!parsed) {\n\t\trmSync(dest, { recursive: true, force: true });\n\t\treturn { installed: false, message: `Installed source for \"${name}\" has no recognizable plugin manifest.` };\n\t}\n\treturn {\n\t\tinstalled: true,\n\t\tdest,\n\t\tsupportPlatform: parsed.supportPlatform,\n\t\tmessage:\n\t\t\t`Installed \"${name}\" from marketplace \"${found.marketplaceName}\" ` +\n\t\t\t`(${parsed.supportPlatform.join(\", \")}) to ${path.relative(cwd, dest) || dest}. ` +\n\t\t\t`Remove it with UninstallPlugin.`,\n\t};\n}\n\nexport interface UninstallOutcome {\n\tremoved: boolean;\n\tmessage: string;\n}\n\n/** Remove an installed plugin from `.agents/plugins` (and the legacy `.hoocode/plugins`). */\nexport function uninstallPlugin(cwd: string, name: string): UninstallOutcome {\n\tconst candidates = [\n\t\tpath.join(installedPluginsDir(cwd), sanitizeForDir(name)),\n\t\tpath.join(cwd, \".hoocode\", \"plugins\", sanitizeForDir(name)),\n\t];\n\tconst present = candidates.filter((p) => existsSync(p));\n\tif (present.length === 0) return { removed: false, message: `Plugin \"${name}\" is not installed.` };\n\tfor (const p of present) rmSync(p, { recursive: true, force: true });\n\treturn { removed: true, message: `Removed \"${name}\".` };\n}\n"]}
@@ -17,12 +17,6 @@
17
17
  */
18
18
  import type { ProviderConfig } from "../types.js";
19
19
  import type { MarketplacePlatform } from "./formats/types.js";
20
- /** Marker subdirectory + manifest filename for each supported format. */
21
- export declare const NATIVE_MANIFEST_DIR = ".agents-plugin";
22
- export declare const CLAUDE_MANIFEST_DIR = ".claude-plugin";
23
- export declare const COPILOT_MANIFEST_DIR = ".github";
24
- export declare const MANIFEST_FILE = "plugin.json";
25
- export declare const COPILOT_MANIFEST_FILE = "copilot-plugin.json";
26
20
  /** A single shell command invoked by a hook. */
27
21
  export interface PluginHookCommand {
28
22
  type?: "command";
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,yEAAyE;AACzE,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AACpD,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AACpD,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAC9C,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAC3C,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAE3D,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACtC,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,EAAE,CAAC,CAAC;AAEzE,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;CACvB;AAED,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAChC,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC;;;;;;OAMG;IACH,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,oGAAoG;IACpG,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAEpE","sourcesContent":["/**\n * Plugin manifest types and the format-dispatching parse entry point.\n *\n * A \"plugin\" is a directory that bundles agent capabilities (skills, commands,\n * agents/modes, themes, providers, hooks, MCP servers) behind a single manifest.\n * Several vendor formats are supported and parsed into one {@link NormalizedPlugin}:\n *\n * - `.agents-plugin/plugin.json` — native format (a strict superset)\n * - `.claude-plugin/plugin.json` — Claude Code compatible format\n * - `.github/copilot-plugin.json` — GitHub Copilot format (`.github/` layout)\n *\n * The per-format detect/parse/emit logic lives in {@link ./formats}, one adapter\n * per vendor, behind the {@link PluginFormatAdapter} contract — so this module\n * carries only the shared types and delegates directory parsing to the registry.\n * When a directory carries more than one format, the highest-precedence one wins\n * (native > Claude > Copilot; no merge).\n */\n\nimport type { ProviderConfig } from \"../types.js\";\nimport { parsePluginWithFormats } from \"./formats/index.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** Marker subdirectory + manifest filename for each supported format. */\nexport const NATIVE_MANIFEST_DIR = \".agents-plugin\";\nexport const CLAUDE_MANIFEST_DIR = \".claude-plugin\";\nexport const COPILOT_MANIFEST_DIR = \".github\";\nexport const MANIFEST_FILE = \"plugin.json\";\nexport const COPILOT_MANIFEST_FILE = \"copilot-plugin.json\";\n\n/** A single shell command invoked by a hook. */\nexport interface PluginHookCommand {\n\ttype?: \"command\";\n\tcommand: string;\n\t/** Timeout in seconds (Claude Code convention). */\n\ttimeout?: number;\n}\n\n/** A matcher group: run these commands when `matcher` matches. */\nexport interface PluginHookMatcherGroup {\n\t/** Regex string matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\thooks: PluginHookCommand[];\n}\n\n/**\n * Hook event map keyed by Claude Code event name\n * (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ...).\n */\nexport type PluginHooksConfig = Record<string, PluginHookMatcherGroup[]>;\n\n/** A provider contributed by a native plugin (config-only; uses a built-in API handler). */\nexport interface PluginProvider {\n\tname: string;\n\tconfig: ProviderConfig;\n}\n\n/** Normalized, format-agnostic representation of a plugin. */\nexport interface NormalizedPlugin {\n\t/** Stable identifier (manifest `name`). */\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/** Absolute path to the plugin root directory. */\n\troot: string;\n\t/** Absolute path to the parsed manifest file. */\n\tmanifestPath: string;\n\t/** Which manifest format produced this plugin (the precedence winner). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this plugin directory offers. When a directory carries more\n\t * than one format manifest (e.g. both `.claude-plugin/` and `.github/`), all\n\t * are recorded here — the precedence winner's platform first — rather than\n\t * hidden behind the single `format`. Always non-empty (at least `format`'s\n\t * platform), mirroring the marketplace's `supportPlatform`.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Resolved capability directories (only set when they exist on disk). */\n\tskillsDir?: string;\n\tcommandsDir?: string;\n\tagentsDir?: string;\n\tthemesDir?: string;\n\t/** Parsed hooks (from `hooks/hooks.json` or inline `hooks`). */\n\thooks?: PluginHooksConfig;\n\t/** Parsed MCP servers (from `.mcp.json` or inline `mcpServers`); connected via the MCP registry. */\n\tmcpServers?: Record<string, unknown>;\n\t/** Native-only: providers to register. */\n\tproviders?: PluginProvider[];\n}\n\n/**\n * Parse and normalize a single plugin directory.\n * Returns null if no recognized manifest is present or it is invalid.\n *\n * Delegates to the format registry; see {@link ./formats/index.ts}.\n */\nexport function parsePluginDir(root: string): NormalizedPlugin | null {\n\treturn parsePluginWithFormats(root);\n}\n"]}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kEAAkE;AAClE,MAAM,WAAW,sBAAsB;IACtC,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,EAAE,CAAC,CAAC;AAEzE,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;CACvB;AAED,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAChC,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC;;;;;;OAMG;IACH,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,oGAAoG;IACpG,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAEpE","sourcesContent":["/**\n * Plugin manifest types and the format-dispatching parse entry point.\n *\n * A \"plugin\" is a directory that bundles agent capabilities (skills, commands,\n * agents/modes, themes, providers, hooks, MCP servers) behind a single manifest.\n * Several vendor formats are supported and parsed into one {@link NormalizedPlugin}:\n *\n * - `.agents-plugin/plugin.json` — native format (a strict superset)\n * - `.claude-plugin/plugin.json` — Claude Code compatible format\n * - `.github/copilot-plugin.json` — GitHub Copilot format (`.github/` layout)\n *\n * The per-format detect/parse/emit logic lives in {@link ./formats}, one adapter\n * per vendor, behind the {@link PluginFormatAdapter} contract — so this module\n * carries only the shared types and delegates directory parsing to the registry.\n * When a directory carries more than one format, the highest-precedence one wins\n * (native > Claude > Copilot; no merge).\n */\n\nimport type { ProviderConfig } from \"../types.js\";\nimport { parsePluginWithFormats } from \"./formats/index.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** A single shell command invoked by a hook. */\nexport interface PluginHookCommand {\n\ttype?: \"command\";\n\tcommand: string;\n\t/** Timeout in seconds (Claude Code convention). */\n\ttimeout?: number;\n}\n\n/** A matcher group: run these commands when `matcher` matches. */\nexport interface PluginHookMatcherGroup {\n\t/** Regex string matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\thooks: PluginHookCommand[];\n}\n\n/**\n * Hook event map keyed by Claude Code event name\n * (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ...).\n */\nexport type PluginHooksConfig = Record<string, PluginHookMatcherGroup[]>;\n\n/** A provider contributed by a native plugin (config-only; uses a built-in API handler). */\nexport interface PluginProvider {\n\tname: string;\n\tconfig: ProviderConfig;\n}\n\n/** Normalized, format-agnostic representation of a plugin. */\nexport interface NormalizedPlugin {\n\t/** Stable identifier (manifest `name`). */\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/** Absolute path to the plugin root directory. */\n\troot: string;\n\t/** Absolute path to the parsed manifest file. */\n\tmanifestPath: string;\n\t/** Which manifest format produced this plugin (the precedence winner). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this plugin directory offers. When a directory carries more\n\t * than one format manifest (e.g. both `.claude-plugin/` and `.github/`), all\n\t * are recorded here — the precedence winner's platform first — rather than\n\t * hidden behind the single `format`. Always non-empty (at least `format`'s\n\t * platform), mirroring the marketplace's `supportPlatform`.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Resolved capability directories (only set when they exist on disk). */\n\tskillsDir?: string;\n\tcommandsDir?: string;\n\tagentsDir?: string;\n\tthemesDir?: string;\n\t/** Parsed hooks (from `hooks/hooks.json` or inline `hooks`). */\n\thooks?: PluginHooksConfig;\n\t/** Parsed MCP servers (from `.mcp.json` or inline `mcpServers`); connected via the MCP registry. */\n\tmcpServers?: Record<string, unknown>;\n\t/** Native-only: providers to register. */\n\tproviders?: PluginProvider[];\n}\n\n/**\n * Parse and normalize a single plugin directory.\n * Returns null if no recognized manifest is present or it is invalid.\n *\n * Delegates to the format registry; see {@link ./formats/index.ts}.\n */\nexport function parsePluginDir(root: string): NormalizedPlugin | null {\n\treturn parsePluginWithFormats(root);\n}\n"]}
@@ -16,12 +16,6 @@
16
16
  * (native > Claude > Copilot; no merge).
17
17
  */
18
18
  import { parsePluginWithFormats } from "./formats/index.js";
19
- /** Marker subdirectory + manifest filename for each supported format. */
20
- export const NATIVE_MANIFEST_DIR = ".agents-plugin";
21
- export const CLAUDE_MANIFEST_DIR = ".claude-plugin";
22
- export const COPILOT_MANIFEST_DIR = ".github";
23
- export const MANIFEST_FILE = "plugin.json";
24
- export const COPILOT_MANIFEST_FILE = "copilot-plugin.json";
25
19
  /**
26
20
  * Parse and normalize a single plugin directory.
27
21
  * Returns null if no recognized manifest is present or it is invalid.
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAG5D,yEAAyE;AACzE,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AACpD,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AACpD,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAC;AAC9C,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;AAC3C,MAAM,CAAC,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AA+D3D;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAA2B;IACrE,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;AAAA,CACpC","sourcesContent":["/**\n * Plugin manifest types and the format-dispatching parse entry point.\n *\n * A \"plugin\" is a directory that bundles agent capabilities (skills, commands,\n * agents/modes, themes, providers, hooks, MCP servers) behind a single manifest.\n * Several vendor formats are supported and parsed into one {@link NormalizedPlugin}:\n *\n * - `.agents-plugin/plugin.json` — native format (a strict superset)\n * - `.claude-plugin/plugin.json` — Claude Code compatible format\n * - `.github/copilot-plugin.json` — GitHub Copilot format (`.github/` layout)\n *\n * The per-format detect/parse/emit logic lives in {@link ./formats}, one adapter\n * per vendor, behind the {@link PluginFormatAdapter} contract — so this module\n * carries only the shared types and delegates directory parsing to the registry.\n * When a directory carries more than one format, the highest-precedence one wins\n * (native > Claude > Copilot; no merge).\n */\n\nimport type { ProviderConfig } from \"../types.js\";\nimport { parsePluginWithFormats } from \"./formats/index.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** Marker subdirectory + manifest filename for each supported format. */\nexport const NATIVE_MANIFEST_DIR = \".agents-plugin\";\nexport const CLAUDE_MANIFEST_DIR = \".claude-plugin\";\nexport const COPILOT_MANIFEST_DIR = \".github\";\nexport const MANIFEST_FILE = \"plugin.json\";\nexport const COPILOT_MANIFEST_FILE = \"copilot-plugin.json\";\n\n/** A single shell command invoked by a hook. */\nexport interface PluginHookCommand {\n\ttype?: \"command\";\n\tcommand: string;\n\t/** Timeout in seconds (Claude Code convention). */\n\ttimeout?: number;\n}\n\n/** A matcher group: run these commands when `matcher` matches. */\nexport interface PluginHookMatcherGroup {\n\t/** Regex string matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\thooks: PluginHookCommand[];\n}\n\n/**\n * Hook event map keyed by Claude Code event name\n * (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ...).\n */\nexport type PluginHooksConfig = Record<string, PluginHookMatcherGroup[]>;\n\n/** A provider contributed by a native plugin (config-only; uses a built-in API handler). */\nexport interface PluginProvider {\n\tname: string;\n\tconfig: ProviderConfig;\n}\n\n/** Normalized, format-agnostic representation of a plugin. */\nexport interface NormalizedPlugin {\n\t/** Stable identifier (manifest `name`). */\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/** Absolute path to the plugin root directory. */\n\troot: string;\n\t/** Absolute path to the parsed manifest file. */\n\tmanifestPath: string;\n\t/** Which manifest format produced this plugin (the precedence winner). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this plugin directory offers. When a directory carries more\n\t * than one format manifest (e.g. both `.claude-plugin/` and `.github/`), all\n\t * are recorded here — the precedence winner's platform first — rather than\n\t * hidden behind the single `format`. Always non-empty (at least `format`'s\n\t * platform), mirroring the marketplace's `supportPlatform`.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Resolved capability directories (only set when they exist on disk). */\n\tskillsDir?: string;\n\tcommandsDir?: string;\n\tagentsDir?: string;\n\tthemesDir?: string;\n\t/** Parsed hooks (from `hooks/hooks.json` or inline `hooks`). */\n\thooks?: PluginHooksConfig;\n\t/** Parsed MCP servers (from `.mcp.json` or inline `mcpServers`); connected via the MCP registry. */\n\tmcpServers?: Record<string, unknown>;\n\t/** Native-only: providers to register. */\n\tproviders?: PluginProvider[];\n}\n\n/**\n * Parse and normalize a single plugin directory.\n * Returns null if no recognized manifest is present or it is invalid.\n *\n * Delegates to the format registry; see {@link ./formats/index.ts}.\n */\nexport function parsePluginDir(root: string): NormalizedPlugin | null {\n\treturn parsePluginWithFormats(root);\n}\n"]}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAgE5D;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAA2B;IACrE,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;AAAA,CACpC","sourcesContent":["/**\n * Plugin manifest types and the format-dispatching parse entry point.\n *\n * A \"plugin\" is a directory that bundles agent capabilities (skills, commands,\n * agents/modes, themes, providers, hooks, MCP servers) behind a single manifest.\n * Several vendor formats are supported and parsed into one {@link NormalizedPlugin}:\n *\n * - `.agents-plugin/plugin.json` — native format (a strict superset)\n * - `.claude-plugin/plugin.json` — Claude Code compatible format\n * - `.github/copilot-plugin.json` — GitHub Copilot format (`.github/` layout)\n *\n * The per-format detect/parse/emit logic lives in {@link ./formats}, one adapter\n * per vendor, behind the {@link PluginFormatAdapter} contract — so this module\n * carries only the shared types and delegates directory parsing to the registry.\n * When a directory carries more than one format, the highest-precedence one wins\n * (native > Claude > Copilot; no merge).\n */\n\nimport type { ProviderConfig } from \"../types.js\";\nimport { parsePluginWithFormats } from \"./formats/index.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** A single shell command invoked by a hook. */\nexport interface PluginHookCommand {\n\ttype?: \"command\";\n\tcommand: string;\n\t/** Timeout in seconds (Claude Code convention). */\n\ttimeout?: number;\n}\n\n/** A matcher group: run these commands when `matcher` matches. */\nexport interface PluginHookMatcherGroup {\n\t/** Regex string matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\thooks: PluginHookCommand[];\n}\n\n/**\n * Hook event map keyed by Claude Code event name\n * (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ...).\n */\nexport type PluginHooksConfig = Record<string, PluginHookMatcherGroup[]>;\n\n/** A provider contributed by a native plugin (config-only; uses a built-in API handler). */\nexport interface PluginProvider {\n\tname: string;\n\tconfig: ProviderConfig;\n}\n\n/** Normalized, format-agnostic representation of a plugin. */\nexport interface NormalizedPlugin {\n\t/** Stable identifier (manifest `name`). */\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/** Absolute path to the plugin root directory. */\n\troot: string;\n\t/** Absolute path to the parsed manifest file. */\n\tmanifestPath: string;\n\t/** Which manifest format produced this plugin (the precedence winner). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this plugin directory offers. When a directory carries more\n\t * than one format manifest (e.g. both `.claude-plugin/` and `.github/`), all\n\t * are recorded here — the precedence winner's platform first — rather than\n\t * hidden behind the single `format`. Always non-empty (at least `format`'s\n\t * platform), mirroring the marketplace's `supportPlatform`.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Resolved capability directories (only set when they exist on disk). */\n\tskillsDir?: string;\n\tcommandsDir?: string;\n\tagentsDir?: string;\n\tthemesDir?: string;\n\t/** Parsed hooks (from `hooks/hooks.json` or inline `hooks`). */\n\thooks?: PluginHooksConfig;\n\t/** Parsed MCP servers (from `.mcp.json` or inline `mcpServers`); connected via the MCP registry. */\n\tmcpServers?: Record<string, unknown>;\n\t/** Native-only: providers to register. */\n\tproviders?: PluginProvider[];\n}\n\n/**\n * Parse and normalize a single plugin directory.\n * Returns null if no recognized manifest is present or it is invalid.\n *\n * Delegates to the format registry; see {@link ./formats/index.ts}.\n */\nexport function parsePluginDir(root: string): NormalizedPlugin | null {\n\treturn parsePluginWithFormats(root);\n}\n"]}
@@ -29,14 +29,14 @@ export type { MarketplacePlatform } from "./formats/types.js";
29
29
  /** Normalize an authored `supportPlatform` (string | string[]) to canonical tokens. */
30
30
  export declare function normalizePlatforms(value: unknown): MarketplacePlatform[];
31
31
  /** Structured source pointing at an entire git repository. */
32
- export interface MarketplacePluginSourceUrl {
32
+ interface MarketplacePluginSourceUrl {
33
33
  source: "url";
34
34
  url: string;
35
35
  ref?: string;
36
36
  sha?: string;
37
37
  }
38
38
  /** Structured source pointing at a subdirectory within a git repository. */
39
- export interface MarketplacePluginSourceGitSubdir {
39
+ interface MarketplacePluginSourceGitSubdir {
40
40
  source: "git-subdir";
41
41
  url: string;
42
42
  /** Subdirectory path inside the repository. */
@@ -46,7 +46,7 @@ export interface MarketplacePluginSourceGitSubdir {
46
46
  }
47
47
  /** Source value authored in a marketplace manifest. */
48
48
  export type MarketplacePluginSource = string | MarketplacePluginSourceUrl | MarketplacePluginSourceGitSubdir;
49
- export interface MarketplacePluginEntry {
49
+ interface MarketplacePluginEntry {
50
50
  name: string;
51
51
  /** Relative path, git URL, `npm:<spec>`, or structured source object. */
52
52
  source: MarketplacePluginSource;
@@ -1 +1 @@
1
- {"version":3,"file":"marketplace.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/marketplace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;;;;;GAQG;AACH,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAO9D,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,EAAE,CASxE;AAED,8DAA8D;AAC9D,MAAM,WAAW,0BAA0B;IAC1C,MAAM,EAAE,KAAK,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,4EAA4E;AAC5E,MAAM,WAAW,gCAAgC;IAChD,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,uDAAuD;AACvD,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,0BAA0B,GAAG,gCAAgC,CAAC;AAE7G,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,MAAM,EAAE,uBAAuB,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC;;;;;;OAMG;IACH,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,sBAAsB,EAAE,CAAC;CAClC;AAiED,6CAA6C;AAC7C,MAAM,MAAM,oBAAoB,GAC7B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7E;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAUjC;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAwD7E;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,GAAG,oBAAoB,CAYlH;AAMD,MAAM,WAAW,iBAAiB;IACjC,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,GAAG,EAAE,MAAM,CAAC;CACZ;AAMD,8DAA8D;AAC9D,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAG3E;AAED,mDAAmD;AACnD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAG3F","sourcesContent":["/**\n * Plugin marketplaces — install plugins from a curated index hosted in a git repo\n * (or a local directory).\n *\n * A marketplace is a directory containing an index manifest in one of three formats:\n * - Native: `.agents-plugin/marketplace.json` (preferred; the `.agents` surface)\n * - Claude: `.claude-plugin/marketplace.json`\n * - Copilot: `.github/marketplace.json` (Copilot-style git index)\n *\n * All three use the same shape: `{ name?, owner?, plugins: [{ name, source, description? }] }`.\n * A plugin `source` is either a path relative to the marketplace root (a plugin\n * directory inside the repo), a git URL, or an `npm:<spec>` reference.\n *\n * When more than one is present the native `.agents-plugin` format wins, then\n * Claude, then Copilot (no merge) — matching the \"`.agents/` first\" policy used\n * across hoocode's resource surfaces.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { PLUGIN_FORMATS } from \"./formats/index.js\";\nimport { normalizePlatformToken } from \"./formats/platform-targets.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/**\n * Canonical platform token used by the optional `supportPlatform` field. The\n * `.github/marketplace.json` (Copilot-style) format is surfaced as `\"github\"` —\n * friendlier than the internal `format: \"copilot\"` and matching where the file\n * lives. Authored aliases (`copilot`, `gh`) normalize to `github`.\n *\n * Re-exported from the format registry so the platform vocabulary has a single\n * source of truth.\n */\nexport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** Map an internal marketplace `format` to its public platform token. */\nfunction formatToPlatform(format: NormalizedMarketplace[\"format\"]): MarketplacePlatform {\n\treturn format === \"copilot\" ? \"github\" : format;\n}\n\n/** Normalize an authored `supportPlatform` (string | string[]) to canonical tokens. */\nexport function normalizePlatforms(value: unknown): MarketplacePlatform[] {\n\tconst raw = Array.isArray(value) ? value : value == null ? [] : [value];\n\tconst out: MarketplacePlatform[] = [];\n\tfor (const v of raw) {\n\t\tif (typeof v !== \"string\") continue;\n\t\tconst canonical = normalizePlatformToken(v);\n\t\tif (canonical && !out.includes(canonical)) out.push(canonical);\n\t}\n\treturn out;\n}\n\n/** Structured source pointing at an entire git repository. */\nexport interface MarketplacePluginSourceUrl {\n\tsource: \"url\";\n\turl: string;\n\tref?: string;\n\tsha?: string;\n}\n\n/** Structured source pointing at a subdirectory within a git repository. */\nexport interface MarketplacePluginSourceGitSubdir {\n\tsource: \"git-subdir\";\n\turl: string;\n\t/** Subdirectory path inside the repository. */\n\tpath: string;\n\tref?: string;\n\tsha?: string;\n}\n\n/** Source value authored in a marketplace manifest. */\nexport type MarketplacePluginSource = string | MarketplacePluginSourceUrl | MarketplacePluginSourceGitSubdir;\n\nexport interface MarketplacePluginEntry {\n\tname: string;\n\t/** Relative path, git URL, `npm:<spec>`, or structured source object. */\n\tsource: MarketplacePluginSource;\n\tdescription?: string;\n\t/**\n\t * Optional platform(s) this entry targets. Only set when authored on the\n\t * entry; absent means \"no per-entry restriction\" (the marketplace's\n\t * platforms apply). Purely informational today — nothing filters on it.\n\t */\n\tsupportPlatform?: MarketplacePlatform[];\n}\n\nexport interface NormalizedMarketplace {\n\tname: string;\n\t/** Precedence winner when several index files conflict (agents > claude > copilot). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this marketplace directory offers, so a conflict between\n\t * multiple index files (e.g. both `.github/` and `.claude-plugin/`) is\n\t * recorded rather than hidden. Always includes the resolved `format`'s\n\t * platform; also folds in any authored top-level `supportPlatform`. Never\n\t * empty.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Absolute path to the marketplace directory. */\n\troot: string;\n\tmanifestPath: string;\n\tplugins: MarketplacePluginEntry[];\n}\n\ninterface RawMarketplace {\n\tname?: string;\n\t/** Both Claude Code and Copilot CLI define `owner` as `{ name, email? }`; a bare string is tolerated. */\n\towner?: string | { name?: string; email?: string };\n\t/** Shared optional metadata: `pluginRoot` is a base dir prepended to relative plugin source paths. */\n\tmetadata?: { description?: string; version?: string; pluginRoot?: string };\n\t/** Optional authored platform hint (string | string[]); folded into supportPlatform. */\n\tsupportPlatform?: unknown;\n\tplugins?: Array<{ name?: string; source?: unknown; description?: string; supportPlatform?: unknown }>;\n}\n\n/** True when a string source is a path (not npm:/git/URL) that `pluginRoot` should prefix. */\nfunction isRelativePathSource(source: string): boolean {\n\tconst s = source.trim();\n\treturn (\n\t\t!s.startsWith(\"npm:\") &&\n\t\t!/^https?:\\/\\//.test(s) &&\n\t\t!s.startsWith(\"git@\") &&\n\t\t!s.endsWith(\".git\") &&\n\t\t!path.isAbsolute(s)\n\t);\n}\n\n/**\n * Apply `metadata.pluginRoot` (Claude Code and Copilot CLI share the field):\n * a base directory prepended to relative plugin source paths, so\n * `\"source\": \"formatter\"` with `\"pluginRoot\": \"./plugins\"` resolves like\n * `\"./plugins/formatter\"`.\n */\nfunction applyPluginRoot(source: MarketplacePluginSource, pluginRoot: string | undefined): MarketplacePluginSource {\n\tif (!pluginRoot || typeof source !== \"string\" || !isRelativePathSource(source)) return source;\n\treturn `${pluginRoot.replace(/\\/+$/, \"\")}/${source.trim().replace(/^\\.\\//, \"\")}`;\n}\n\n/** Validate and normalize an authored source value. Returns null for unsupported/invalid shapes. */\nfunction normalizeSource(source: unknown): MarketplacePluginSource | null {\n\tif (typeof source === \"string\") return source;\n\tif (!source || typeof source !== \"object\") return null;\n\tconst obj = source as Record<string, unknown>;\n\tconst src = obj.source;\n\tconst url = obj.url;\n\tconst pin = {\n\t\t...(typeof obj.ref === \"string\" ? { ref: obj.ref } : {}),\n\t\t...(typeof obj.sha === \"string\" ? { sha: obj.sha } : {}),\n\t};\n\tif (src === \"url\" && typeof url === \"string\") {\n\t\treturn { source: \"url\", url, ...pin };\n\t}\n\tif (src === \"git-subdir\" && typeof url === \"string\" && typeof obj.path === \"string\") {\n\t\treturn { source: \"git-subdir\", url, path: obj.path, ...pin };\n\t}\n\t// Copilot marketplace shorthand: { source: \"github\", repo: \"owner/name\", path? }\n\t// (used by github/copilot-plugins). Normalize to the equivalent git source.\n\tif (src === \"github\" && typeof obj.repo === \"string\" && /^[\\w.-]+\\/[\\w.-]+$/.test(obj.repo)) {\n\t\tconst repoUrl = `https://github.com/${obj.repo}.git`;\n\t\tif (typeof obj.path === \"string\" && obj.path.length > 0) {\n\t\t\treturn { source: \"git-subdir\", url: repoUrl, path: obj.path, ...pin };\n\t\t}\n\t\treturn { source: \"url\", url: repoUrl, ...pin };\n\t}\n\treturn null;\n}\n\n/** A resolved, installable plugin source. */\nexport type ResolvedPluginSource =\n\t| { kind: \"local\"; path: string }\n\t| { kind: \"git\"; url: string; ref?: string; sha?: string }\n\t| { kind: \"git-subdir\"; url: string; path: string; ref?: string; sha?: string }\n\t| { kind: \"npm\"; spec: string };\n\nfunction readJson<T>(file: string): T | null {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(file, \"utf8\")) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Parse a marketplace directory. Native `.agents-plugin` wins, then Claude, then\n * Copilot when more than one file is present. Returns null if no manifest exists\n * or it is invalid.\n */\nexport function parseMarketplaceDir(dir: string): NormalizedMarketplace | null {\n\t// Record every index format present so a conflict (more than one file) is\n\t// visible via supportPlatform, not silently dropped. PLUGIN_FORMATS is\n\t// precedence-ordered (native > Claude > Copilot), so `present` inherits it.\n\tconst present: Array<{ format: NormalizedMarketplace[\"format\"]; path: string }> = [];\n\tfor (const fmt of PLUGIN_FORMATS) {\n\t\tfor (const candidate of fmt.marketplaceFiles) {\n\t\t\tconst p = path.join(dir, candidate);\n\t\t\tif (fs.existsSync(p)) {\n\t\t\t\tpresent.push({ format: fmt.id, path: p });\n\t\t\t\tbreak; // first candidate wins within a format\n\t\t\t}\n\t\t}\n\t}\n\tif (present.length === 0) return null;\n\n\t// Precedence winner (present is built in agents > claude > copilot order).\n\tconst { format, path: manifestPath } = present[0];\n\n\tconst raw = readJson<RawMarketplace>(manifestPath);\n\tif (!raw) return null;\n\n\tconst pluginRoot =\n\t\ttypeof raw.metadata?.pluginRoot === \"string\" && raw.metadata.pluginRoot.trim()\n\t\t\t? raw.metadata.pluginRoot.trim()\n\t\t\t: undefined;\n\n\tconst plugins: MarketplacePluginEntry[] = [];\n\tfor (const entry of raw.plugins ?? []) {\n\t\tif (!entry?.name || !entry.source) continue;\n\t\tconst normalized = normalizeSource(entry.source);\n\t\tif (!normalized) continue;\n\t\tconst entryPlatforms = normalizePlatforms(entry.supportPlatform);\n\t\tplugins.push({\n\t\t\tname: entry.name,\n\t\t\tsource: applyPluginRoot(normalized, pluginRoot),\n\t\t\tdescription: entry.description,\n\t\t\t...(entryPlatforms.length > 0 ? { supportPlatform: entryPlatforms } : {}),\n\t\t});\n\t}\n\n\t// supportPlatform = platforms of all present index files, plus any authored\n\t// top-level hint, deduped. Always non-empty (at least the resolved format).\n\tconst supportPlatform: MarketplacePlatform[] = [];\n\tfor (const p of [...present.map((e) => formatToPlatform(e.format)), ...normalizePlatforms(raw.supportPlatform)]) {\n\t\tif (!supportPlatform.includes(p)) supportPlatform.push(p);\n\t}\n\n\treturn {\n\t\tname: (raw.name ?? path.basename(dir)).trim() || path.basename(dir),\n\t\tformat,\n\t\tsupportPlatform,\n\t\troot: dir,\n\t\tmanifestPath,\n\t\tplugins,\n\t};\n}\n\n/** Classify and resolve a plugin `source` against its marketplace root. */\nexport function resolvePluginSource(source: MarketplacePluginSource, marketplaceRoot: string): ResolvedPluginSource {\n\tif (typeof source === \"string\") {\n\t\tconst s = source.trim();\n\t\tif (s.startsWith(\"npm:\")) return { kind: \"npm\", spec: s.slice(4) };\n\t\tif (/^https?:\\/\\//.test(s) || s.startsWith(\"git@\") || s.endsWith(\".git\")) return { kind: \"git\", url: s };\n\t\t// Otherwise a path relative to (or absolute within) the marketplace repo.\n\t\treturn { kind: \"local\", path: path.isAbsolute(s) ? s : path.resolve(marketplaceRoot, s) };\n\t}\n\tif (source.source === \"url\") {\n\t\treturn { kind: \"git\", url: source.url, ref: source.ref, sha: source.sha };\n\t}\n\treturn { kind: \"git-subdir\", url: source.url, path: source.path, ref: source.ref, sha: source.sha };\n}\n\n// ============================================================================\n// Marketplace registry (persisted list of added marketplaces)\n// ============================================================================\n\nexport interface MarketplaceRecord {\n\t/** Original location the user added (git URL or local path). */\n\tlocation: string;\n\t/** Local directory to read the manifest from (clone dir for git, else location). */\n\tdir: string;\n}\n\ninterface MarketplaceStoreFile {\n\tmarketplaces?: MarketplaceRecord[];\n}\n\n/** Read the persisted marketplace list from `<storePath>`. */\nexport function readMarketplaceStore(storePath: string): MarketplaceRecord[] {\n\tconst parsed = readJson<MarketplaceStoreFile>(storePath);\n\treturn Array.isArray(parsed?.marketplaces) ? parsed.marketplaces : [];\n}\n\n/** Write the marketplace list to `<storePath>`. */\nexport function writeMarketplaceStore(storePath: string, records: MarketplaceRecord[]): void {\n\tfs.mkdirSync(path.dirname(storePath), { recursive: true });\n\tfs.writeFileSync(storePath, `${JSON.stringify({ marketplaces: records }, null, 2)}\\n`, \"utf8\");\n}\n"]}
1
+ {"version":3,"file":"marketplace.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/marketplace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;;;;;GAQG;AACH,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAO9D,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,EAAE,CASxE;AAED,8DAA8D;AAC9D,UAAU,0BAA0B;IACnC,MAAM,EAAE,KAAK,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,4EAA4E;AAC5E,UAAU,gCAAgC;IACzC,MAAM,EAAE,YAAY,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,uDAAuD;AACvD,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,0BAA0B,GAAG,gCAAgC,CAAC;AAE7G,UAAU,sBAAsB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,MAAM,EAAE,uBAAuB,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC;;;;;;OAMG;IACH,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,sBAAsB,EAAE,CAAC;CAClC;AAiED,6CAA6C;AAC7C,MAAM,MAAM,oBAAoB,GAC7B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7E;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAUjC;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAwD7E;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,GAAG,oBAAoB,CAYlH;AAMD,MAAM,WAAW,iBAAiB;IACjC,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,GAAG,EAAE,MAAM,CAAC;CACZ;AAMD,8DAA8D;AAC9D,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAG3E;AAED,mDAAmD;AACnD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAG3F","sourcesContent":["/**\n * Plugin marketplaces — install plugins from a curated index hosted in a git repo\n * (or a local directory).\n *\n * A marketplace is a directory containing an index manifest in one of three formats:\n * - Native: `.agents-plugin/marketplace.json` (preferred; the `.agents` surface)\n * - Claude: `.claude-plugin/marketplace.json`\n * - Copilot: `.github/marketplace.json` (Copilot-style git index)\n *\n * All three use the same shape: `{ name?, owner?, plugins: [{ name, source, description? }] }`.\n * A plugin `source` is either a path relative to the marketplace root (a plugin\n * directory inside the repo), a git URL, or an `npm:<spec>` reference.\n *\n * When more than one is present the native `.agents-plugin` format wins, then\n * Claude, then Copilot (no merge) — matching the \"`.agents/` first\" policy used\n * across hoocode's resource surfaces.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { PLUGIN_FORMATS } from \"./formats/index.js\";\nimport { normalizePlatformToken } from \"./formats/platform-targets.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/**\n * Canonical platform token used by the optional `supportPlatform` field. The\n * `.github/marketplace.json` (Copilot-style) format is surfaced as `\"github\"` —\n * friendlier than the internal `format: \"copilot\"` and matching where the file\n * lives. Authored aliases (`copilot`, `gh`) normalize to `github`.\n *\n * Re-exported from the format registry so the platform vocabulary has a single\n * source of truth.\n */\nexport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** Map an internal marketplace `format` to its public platform token. */\nfunction formatToPlatform(format: NormalizedMarketplace[\"format\"]): MarketplacePlatform {\n\treturn format === \"copilot\" ? \"github\" : format;\n}\n\n/** Normalize an authored `supportPlatform` (string | string[]) to canonical tokens. */\nexport function normalizePlatforms(value: unknown): MarketplacePlatform[] {\n\tconst raw = Array.isArray(value) ? value : value == null ? [] : [value];\n\tconst out: MarketplacePlatform[] = [];\n\tfor (const v of raw) {\n\t\tif (typeof v !== \"string\") continue;\n\t\tconst canonical = normalizePlatformToken(v);\n\t\tif (canonical && !out.includes(canonical)) out.push(canonical);\n\t}\n\treturn out;\n}\n\n/** Structured source pointing at an entire git repository. */\ninterface MarketplacePluginSourceUrl {\n\tsource: \"url\";\n\turl: string;\n\tref?: string;\n\tsha?: string;\n}\n\n/** Structured source pointing at a subdirectory within a git repository. */\ninterface MarketplacePluginSourceGitSubdir {\n\tsource: \"git-subdir\";\n\turl: string;\n\t/** Subdirectory path inside the repository. */\n\tpath: string;\n\tref?: string;\n\tsha?: string;\n}\n\n/** Source value authored in a marketplace manifest. */\nexport type MarketplacePluginSource = string | MarketplacePluginSourceUrl | MarketplacePluginSourceGitSubdir;\n\ninterface MarketplacePluginEntry {\n\tname: string;\n\t/** Relative path, git URL, `npm:<spec>`, or structured source object. */\n\tsource: MarketplacePluginSource;\n\tdescription?: string;\n\t/**\n\t * Optional platform(s) this entry targets. Only set when authored on the\n\t * entry; absent means \"no per-entry restriction\" (the marketplace's\n\t * platforms apply). Purely informational today — nothing filters on it.\n\t */\n\tsupportPlatform?: MarketplacePlatform[];\n}\n\nexport interface NormalizedMarketplace {\n\tname: string;\n\t/** Precedence winner when several index files conflict (agents > claude > copilot). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this marketplace directory offers, so a conflict between\n\t * multiple index files (e.g. both `.github/` and `.claude-plugin/`) is\n\t * recorded rather than hidden. Always includes the resolved `format`'s\n\t * platform; also folds in any authored top-level `supportPlatform`. Never\n\t * empty.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Absolute path to the marketplace directory. */\n\troot: string;\n\tmanifestPath: string;\n\tplugins: MarketplacePluginEntry[];\n}\n\ninterface RawMarketplace {\n\tname?: string;\n\t/** Both Claude Code and Copilot CLI define `owner` as `{ name, email? }`; a bare string is tolerated. */\n\towner?: string | { name?: string; email?: string };\n\t/** Shared optional metadata: `pluginRoot` is a base dir prepended to relative plugin source paths. */\n\tmetadata?: { description?: string; version?: string; pluginRoot?: string };\n\t/** Optional authored platform hint (string | string[]); folded into supportPlatform. */\n\tsupportPlatform?: unknown;\n\tplugins?: Array<{ name?: string; source?: unknown; description?: string; supportPlatform?: unknown }>;\n}\n\n/** True when a string source is a path (not npm:/git/URL) that `pluginRoot` should prefix. */\nfunction isRelativePathSource(source: string): boolean {\n\tconst s = source.trim();\n\treturn (\n\t\t!s.startsWith(\"npm:\") &&\n\t\t!/^https?:\\/\\//.test(s) &&\n\t\t!s.startsWith(\"git@\") &&\n\t\t!s.endsWith(\".git\") &&\n\t\t!path.isAbsolute(s)\n\t);\n}\n\n/**\n * Apply `metadata.pluginRoot` (Claude Code and Copilot CLI share the field):\n * a base directory prepended to relative plugin source paths, so\n * `\"source\": \"formatter\"` with `\"pluginRoot\": \"./plugins\"` resolves like\n * `\"./plugins/formatter\"`.\n */\nfunction applyPluginRoot(source: MarketplacePluginSource, pluginRoot: string | undefined): MarketplacePluginSource {\n\tif (!pluginRoot || typeof source !== \"string\" || !isRelativePathSource(source)) return source;\n\treturn `${pluginRoot.replace(/\\/+$/, \"\")}/${source.trim().replace(/^\\.\\//, \"\")}`;\n}\n\n/** Validate and normalize an authored source value. Returns null for unsupported/invalid shapes. */\nfunction normalizeSource(source: unknown): MarketplacePluginSource | null {\n\tif (typeof source === \"string\") return source;\n\tif (!source || typeof source !== \"object\") return null;\n\tconst obj = source as Record<string, unknown>;\n\tconst src = obj.source;\n\tconst url = obj.url;\n\tconst pin = {\n\t\t...(typeof obj.ref === \"string\" ? { ref: obj.ref } : {}),\n\t\t...(typeof obj.sha === \"string\" ? { sha: obj.sha } : {}),\n\t};\n\tif (src === \"url\" && typeof url === \"string\") {\n\t\treturn { source: \"url\", url, ...pin };\n\t}\n\tif (src === \"git-subdir\" && typeof url === \"string\" && typeof obj.path === \"string\") {\n\t\treturn { source: \"git-subdir\", url, path: obj.path, ...pin };\n\t}\n\t// Copilot marketplace shorthand: { source: \"github\", repo: \"owner/name\", path? }\n\t// (used by github/copilot-plugins). Normalize to the equivalent git source.\n\tif (src === \"github\" && typeof obj.repo === \"string\" && /^[\\w.-]+\\/[\\w.-]+$/.test(obj.repo)) {\n\t\tconst repoUrl = `https://github.com/${obj.repo}.git`;\n\t\tif (typeof obj.path === \"string\" && obj.path.length > 0) {\n\t\t\treturn { source: \"git-subdir\", url: repoUrl, path: obj.path, ...pin };\n\t\t}\n\t\treturn { source: \"url\", url: repoUrl, ...pin };\n\t}\n\treturn null;\n}\n\n/** A resolved, installable plugin source. */\nexport type ResolvedPluginSource =\n\t| { kind: \"local\"; path: string }\n\t| { kind: \"git\"; url: string; ref?: string; sha?: string }\n\t| { kind: \"git-subdir\"; url: string; path: string; ref?: string; sha?: string }\n\t| { kind: \"npm\"; spec: string };\n\nfunction readJson<T>(file: string): T | null {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(file, \"utf8\")) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Parse a marketplace directory. Native `.agents-plugin` wins, then Claude, then\n * Copilot when more than one file is present. Returns null if no manifest exists\n * or it is invalid.\n */\nexport function parseMarketplaceDir(dir: string): NormalizedMarketplace | null {\n\t// Record every index format present so a conflict (more than one file) is\n\t// visible via supportPlatform, not silently dropped. PLUGIN_FORMATS is\n\t// precedence-ordered (native > Claude > Copilot), so `present` inherits it.\n\tconst present: Array<{ format: NormalizedMarketplace[\"format\"]; path: string }> = [];\n\tfor (const fmt of PLUGIN_FORMATS) {\n\t\tfor (const candidate of fmt.marketplaceFiles) {\n\t\t\tconst p = path.join(dir, candidate);\n\t\t\tif (fs.existsSync(p)) {\n\t\t\t\tpresent.push({ format: fmt.id, path: p });\n\t\t\t\tbreak; // first candidate wins within a format\n\t\t\t}\n\t\t}\n\t}\n\tif (present.length === 0) return null;\n\n\t// Precedence winner (present is built in agents > claude > copilot order).\n\tconst { format, path: manifestPath } = present[0];\n\n\tconst raw = readJson<RawMarketplace>(manifestPath);\n\tif (!raw) return null;\n\n\tconst pluginRoot =\n\t\ttypeof raw.metadata?.pluginRoot === \"string\" && raw.metadata.pluginRoot.trim()\n\t\t\t? raw.metadata.pluginRoot.trim()\n\t\t\t: undefined;\n\n\tconst plugins: MarketplacePluginEntry[] = [];\n\tfor (const entry of raw.plugins ?? []) {\n\t\tif (!entry?.name || !entry.source) continue;\n\t\tconst normalized = normalizeSource(entry.source);\n\t\tif (!normalized) continue;\n\t\tconst entryPlatforms = normalizePlatforms(entry.supportPlatform);\n\t\tplugins.push({\n\t\t\tname: entry.name,\n\t\t\tsource: applyPluginRoot(normalized, pluginRoot),\n\t\t\tdescription: entry.description,\n\t\t\t...(entryPlatforms.length > 0 ? { supportPlatform: entryPlatforms } : {}),\n\t\t});\n\t}\n\n\t// supportPlatform = platforms of all present index files, plus any authored\n\t// top-level hint, deduped. Always non-empty (at least the resolved format).\n\tconst supportPlatform: MarketplacePlatform[] = [];\n\tfor (const p of [...present.map((e) => formatToPlatform(e.format)), ...normalizePlatforms(raw.supportPlatform)]) {\n\t\tif (!supportPlatform.includes(p)) supportPlatform.push(p);\n\t}\n\n\treturn {\n\t\tname: (raw.name ?? path.basename(dir)).trim() || path.basename(dir),\n\t\tformat,\n\t\tsupportPlatform,\n\t\troot: dir,\n\t\tmanifestPath,\n\t\tplugins,\n\t};\n}\n\n/** Classify and resolve a plugin `source` against its marketplace root. */\nexport function resolvePluginSource(source: MarketplacePluginSource, marketplaceRoot: string): ResolvedPluginSource {\n\tif (typeof source === \"string\") {\n\t\tconst s = source.trim();\n\t\tif (s.startsWith(\"npm:\")) return { kind: \"npm\", spec: s.slice(4) };\n\t\tif (/^https?:\\/\\//.test(s) || s.startsWith(\"git@\") || s.endsWith(\".git\")) return { kind: \"git\", url: s };\n\t\t// Otherwise a path relative to (or absolute within) the marketplace repo.\n\t\treturn { kind: \"local\", path: path.isAbsolute(s) ? s : path.resolve(marketplaceRoot, s) };\n\t}\n\tif (source.source === \"url\") {\n\t\treturn { kind: \"git\", url: source.url, ref: source.ref, sha: source.sha };\n\t}\n\treturn { kind: \"git-subdir\", url: source.url, path: source.path, ref: source.ref, sha: source.sha };\n}\n\n// ============================================================================\n// Marketplace registry (persisted list of added marketplaces)\n// ============================================================================\n\nexport interface MarketplaceRecord {\n\t/** Original location the user added (git URL or local path). */\n\tlocation: string;\n\t/** Local directory to read the manifest from (clone dir for git, else location). */\n\tdir: string;\n}\n\ninterface MarketplaceStoreFile {\n\tmarketplaces?: MarketplaceRecord[];\n}\n\n/** Read the persisted marketplace list from `<storePath>`. */\nexport function readMarketplaceStore(storePath: string): MarketplaceRecord[] {\n\tconst parsed = readJson<MarketplaceStoreFile>(storePath);\n\treturn Array.isArray(parsed?.marketplaces) ? parsed.marketplaces : [];\n}\n\n/** Write the marketplace list to `<storePath>`. */\nexport function writeMarketplaceStore(storePath: string, records: MarketplaceRecord[]): void {\n\tfs.mkdirSync(path.dirname(storePath), { recursive: true });\n\tfs.writeFileSync(storePath, `${JSON.stringify({ marketplaces: records }, null, 2)}\\n`, \"utf8\");\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"marketplace.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/marketplace.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Plugin marketplaces — install plugins from a curated index hosted in a git repo\n * (or a local directory).\n *\n * A marketplace is a directory containing an index manifest in one of three formats:\n * - Native: `.agents-plugin/marketplace.json` (preferred; the `.agents` surface)\n * - Claude: `.claude-plugin/marketplace.json`\n * - Copilot: `.github/marketplace.json` (Copilot-style git index)\n *\n * All three use the same shape: `{ name?, owner?, plugins: [{ name, source, description? }] }`.\n * A plugin `source` is either a path relative to the marketplace root (a plugin\n * directory inside the repo), a git URL, or an `npm:<spec>` reference.\n *\n * When more than one is present the native `.agents-plugin` format wins, then\n * Claude, then Copilot (no merge) — matching the \"`.agents/` first\" policy used\n * across hoocode's resource surfaces.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { PLUGIN_FORMATS } from \"./formats/index.js\";\nimport { normalizePlatformToken } from \"./formats/platform-targets.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/**\n * Canonical platform token used by the optional `supportPlatform` field. The\n * `.github/marketplace.json` (Copilot-style) format is surfaced as `\"github\"` —\n * friendlier than the internal `format: \"copilot\"` and matching where the file\n * lives. Authored aliases (`copilot`, `gh`) normalize to `github`.\n *\n * Re-exported from the format registry so the platform vocabulary has a single\n * source of truth.\n */\nexport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** Map an internal marketplace `format` to its public platform token. */\nfunction formatToPlatform(format: NormalizedMarketplace[\"format\"]): MarketplacePlatform {\n\treturn format === \"copilot\" ? \"github\" : format;\n}\n\n/** Normalize an authored `supportPlatform` (string | string[]) to canonical tokens. */\nexport function normalizePlatforms(value: unknown): MarketplacePlatform[] {\n\tconst raw = Array.isArray(value) ? value : value == null ? [] : [value];\n\tconst out: MarketplacePlatform[] = [];\n\tfor (const v of raw) {\n\t\tif (typeof v !== \"string\") continue;\n\t\tconst canonical = normalizePlatformToken(v);\n\t\tif (canonical && !out.includes(canonical)) out.push(canonical);\n\t}\n\treturn out;\n}\n\n/** Structured source pointing at an entire git repository. */\nexport interface MarketplacePluginSourceUrl {\n\tsource: \"url\";\n\turl: string;\n\tref?: string;\n\tsha?: string;\n}\n\n/** Structured source pointing at a subdirectory within a git repository. */\nexport interface MarketplacePluginSourceGitSubdir {\n\tsource: \"git-subdir\";\n\turl: string;\n\t/** Subdirectory path inside the repository. */\n\tpath: string;\n\tref?: string;\n\tsha?: string;\n}\n\n/** Source value authored in a marketplace manifest. */\nexport type MarketplacePluginSource = string | MarketplacePluginSourceUrl | MarketplacePluginSourceGitSubdir;\n\nexport interface MarketplacePluginEntry {\n\tname: string;\n\t/** Relative path, git URL, `npm:<spec>`, or structured source object. */\n\tsource: MarketplacePluginSource;\n\tdescription?: string;\n\t/**\n\t * Optional platform(s) this entry targets. Only set when authored on the\n\t * entry; absent means \"no per-entry restriction\" (the marketplace's\n\t * platforms apply). Purely informational today — nothing filters on it.\n\t */\n\tsupportPlatform?: MarketplacePlatform[];\n}\n\nexport interface NormalizedMarketplace {\n\tname: string;\n\t/** Precedence winner when several index files conflict (agents > claude > copilot). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this marketplace directory offers, so a conflict between\n\t * multiple index files (e.g. both `.github/` and `.claude-plugin/`) is\n\t * recorded rather than hidden. Always includes the resolved `format`'s\n\t * platform; also folds in any authored top-level `supportPlatform`. Never\n\t * empty.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Absolute path to the marketplace directory. */\n\troot: string;\n\tmanifestPath: string;\n\tplugins: MarketplacePluginEntry[];\n}\n\ninterface RawMarketplace {\n\tname?: string;\n\t/** Both Claude Code and Copilot CLI define `owner` as `{ name, email? }`; a bare string is tolerated. */\n\towner?: string | { name?: string; email?: string };\n\t/** Shared optional metadata: `pluginRoot` is a base dir prepended to relative plugin source paths. */\n\tmetadata?: { description?: string; version?: string; pluginRoot?: string };\n\t/** Optional authored platform hint (string | string[]); folded into supportPlatform. */\n\tsupportPlatform?: unknown;\n\tplugins?: Array<{ name?: string; source?: unknown; description?: string; supportPlatform?: unknown }>;\n}\n\n/** True when a string source is a path (not npm:/git/URL) that `pluginRoot` should prefix. */\nfunction isRelativePathSource(source: string): boolean {\n\tconst s = source.trim();\n\treturn (\n\t\t!s.startsWith(\"npm:\") &&\n\t\t!/^https?:\\/\\//.test(s) &&\n\t\t!s.startsWith(\"git@\") &&\n\t\t!s.endsWith(\".git\") &&\n\t\t!path.isAbsolute(s)\n\t);\n}\n\n/**\n * Apply `metadata.pluginRoot` (Claude Code and Copilot CLI share the field):\n * a base directory prepended to relative plugin source paths, so\n * `\"source\": \"formatter\"` with `\"pluginRoot\": \"./plugins\"` resolves like\n * `\"./plugins/formatter\"`.\n */\nfunction applyPluginRoot(source: MarketplacePluginSource, pluginRoot: string | undefined): MarketplacePluginSource {\n\tif (!pluginRoot || typeof source !== \"string\" || !isRelativePathSource(source)) return source;\n\treturn `${pluginRoot.replace(/\\/+$/, \"\")}/${source.trim().replace(/^\\.\\//, \"\")}`;\n}\n\n/** Validate and normalize an authored source value. Returns null for unsupported/invalid shapes. */\nfunction normalizeSource(source: unknown): MarketplacePluginSource | null {\n\tif (typeof source === \"string\") return source;\n\tif (!source || typeof source !== \"object\") return null;\n\tconst obj = source as Record<string, unknown>;\n\tconst src = obj.source;\n\tconst url = obj.url;\n\tconst pin = {\n\t\t...(typeof obj.ref === \"string\" ? { ref: obj.ref } : {}),\n\t\t...(typeof obj.sha === \"string\" ? { sha: obj.sha } : {}),\n\t};\n\tif (src === \"url\" && typeof url === \"string\") {\n\t\treturn { source: \"url\", url, ...pin };\n\t}\n\tif (src === \"git-subdir\" && typeof url === \"string\" && typeof obj.path === \"string\") {\n\t\treturn { source: \"git-subdir\", url, path: obj.path, ...pin };\n\t}\n\t// Copilot marketplace shorthand: { source: \"github\", repo: \"owner/name\", path? }\n\t// (used by github/copilot-plugins). Normalize to the equivalent git source.\n\tif (src === \"github\" && typeof obj.repo === \"string\" && /^[\\w.-]+\\/[\\w.-]+$/.test(obj.repo)) {\n\t\tconst repoUrl = `https://github.com/${obj.repo}.git`;\n\t\tif (typeof obj.path === \"string\" && obj.path.length > 0) {\n\t\t\treturn { source: \"git-subdir\", url: repoUrl, path: obj.path, ...pin };\n\t\t}\n\t\treturn { source: \"url\", url: repoUrl, ...pin };\n\t}\n\treturn null;\n}\n\n/** A resolved, installable plugin source. */\nexport type ResolvedPluginSource =\n\t| { kind: \"local\"; path: string }\n\t| { kind: \"git\"; url: string; ref?: string; sha?: string }\n\t| { kind: \"git-subdir\"; url: string; path: string; ref?: string; sha?: string }\n\t| { kind: \"npm\"; spec: string };\n\nfunction readJson<T>(file: string): T | null {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(file, \"utf8\")) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Parse a marketplace directory. Native `.agents-plugin` wins, then Claude, then\n * Copilot when more than one file is present. Returns null if no manifest exists\n * or it is invalid.\n */\nexport function parseMarketplaceDir(dir: string): NormalizedMarketplace | null {\n\t// Record every index format present so a conflict (more than one file) is\n\t// visible via supportPlatform, not silently dropped. PLUGIN_FORMATS is\n\t// precedence-ordered (native > Claude > Copilot), so `present` inherits it.\n\tconst present: Array<{ format: NormalizedMarketplace[\"format\"]; path: string }> = [];\n\tfor (const fmt of PLUGIN_FORMATS) {\n\t\tfor (const candidate of fmt.marketplaceFiles) {\n\t\t\tconst p = path.join(dir, candidate);\n\t\t\tif (fs.existsSync(p)) {\n\t\t\t\tpresent.push({ format: fmt.id, path: p });\n\t\t\t\tbreak; // first candidate wins within a format\n\t\t\t}\n\t\t}\n\t}\n\tif (present.length === 0) return null;\n\n\t// Precedence winner (present is built in agents > claude > copilot order).\n\tconst { format, path: manifestPath } = present[0];\n\n\tconst raw = readJson<RawMarketplace>(manifestPath);\n\tif (!raw) return null;\n\n\tconst pluginRoot =\n\t\ttypeof raw.metadata?.pluginRoot === \"string\" && raw.metadata.pluginRoot.trim()\n\t\t\t? raw.metadata.pluginRoot.trim()\n\t\t\t: undefined;\n\n\tconst plugins: MarketplacePluginEntry[] = [];\n\tfor (const entry of raw.plugins ?? []) {\n\t\tif (!entry?.name || !entry.source) continue;\n\t\tconst normalized = normalizeSource(entry.source);\n\t\tif (!normalized) continue;\n\t\tconst entryPlatforms = normalizePlatforms(entry.supportPlatform);\n\t\tplugins.push({\n\t\t\tname: entry.name,\n\t\t\tsource: applyPluginRoot(normalized, pluginRoot),\n\t\t\tdescription: entry.description,\n\t\t\t...(entryPlatforms.length > 0 ? { supportPlatform: entryPlatforms } : {}),\n\t\t});\n\t}\n\n\t// supportPlatform = platforms of all present index files, plus any authored\n\t// top-level hint, deduped. Always non-empty (at least the resolved format).\n\tconst supportPlatform: MarketplacePlatform[] = [];\n\tfor (const p of [...present.map((e) => formatToPlatform(e.format)), ...normalizePlatforms(raw.supportPlatform)]) {\n\t\tif (!supportPlatform.includes(p)) supportPlatform.push(p);\n\t}\n\n\treturn {\n\t\tname: (raw.name ?? path.basename(dir)).trim() || path.basename(dir),\n\t\tformat,\n\t\tsupportPlatform,\n\t\troot: dir,\n\t\tmanifestPath,\n\t\tplugins,\n\t};\n}\n\n/** Classify and resolve a plugin `source` against its marketplace root. */\nexport function resolvePluginSource(source: MarketplacePluginSource, marketplaceRoot: string): ResolvedPluginSource {\n\tif (typeof source === \"string\") {\n\t\tconst s = source.trim();\n\t\tif (s.startsWith(\"npm:\")) return { kind: \"npm\", spec: s.slice(4) };\n\t\tif (/^https?:\\/\\//.test(s) || s.startsWith(\"git@\") || s.endsWith(\".git\")) return { kind: \"git\", url: s };\n\t\t// Otherwise a path relative to (or absolute within) the marketplace repo.\n\t\treturn { kind: \"local\", path: path.isAbsolute(s) ? s : path.resolve(marketplaceRoot, s) };\n\t}\n\tif (source.source === \"url\") {\n\t\treturn { kind: \"git\", url: source.url, ref: source.ref, sha: source.sha };\n\t}\n\treturn { kind: \"git-subdir\", url: source.url, path: source.path, ref: source.ref, sha: source.sha };\n}\n\n// ============================================================================\n// Marketplace registry (persisted list of added marketplaces)\n// ============================================================================\n\nexport interface MarketplaceRecord {\n\t/** Original location the user added (git URL or local path). */\n\tlocation: string;\n\t/** Local directory to read the manifest from (clone dir for git, else location). */\n\tdir: string;\n}\n\ninterface MarketplaceStoreFile {\n\tmarketplaces?: MarketplaceRecord[];\n}\n\n/** Read the persisted marketplace list from `<storePath>`. */\nexport function readMarketplaceStore(storePath: string): MarketplaceRecord[] {\n\tconst parsed = readJson<MarketplaceStoreFile>(storePath);\n\treturn Array.isArray(parsed?.marketplaces) ? parsed.marketplaces : [];\n}\n\n/** Write the marketplace list to `<storePath>`. */\nexport function writeMarketplaceStore(storePath: string, records: MarketplaceRecord[]): void {\n\tfs.mkdirSync(path.dirname(storePath), { recursive: true });\n\tfs.writeFileSync(storePath, `${JSON.stringify({ marketplaces: records }, null, 2)}\\n`, \"utf8\");\n}\n"]}
1
+ {"version":3,"file":"marketplace.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/marketplace.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Plugin marketplaces — install plugins from a curated index hosted in a git repo\n * (or a local directory).\n *\n * A marketplace is a directory containing an index manifest in one of three formats:\n * - Native: `.agents-plugin/marketplace.json` (preferred; the `.agents` surface)\n * - Claude: `.claude-plugin/marketplace.json`\n * - Copilot: `.github/marketplace.json` (Copilot-style git index)\n *\n * All three use the same shape: `{ name?, owner?, plugins: [{ name, source, description? }] }`.\n * A plugin `source` is either a path relative to the marketplace root (a plugin\n * directory inside the repo), a git URL, or an `npm:<spec>` reference.\n *\n * When more than one is present the native `.agents-plugin` format wins, then\n * Claude, then Copilot (no merge) — matching the \"`.agents/` first\" policy used\n * across hoocode's resource surfaces.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { PLUGIN_FORMATS } from \"./formats/index.js\";\nimport { normalizePlatformToken } from \"./formats/platform-targets.js\";\nimport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/**\n * Canonical platform token used by the optional `supportPlatform` field. The\n * `.github/marketplace.json` (Copilot-style) format is surfaced as `\"github\"` —\n * friendlier than the internal `format: \"copilot\"` and matching where the file\n * lives. Authored aliases (`copilot`, `gh`) normalize to `github`.\n *\n * Re-exported from the format registry so the platform vocabulary has a single\n * source of truth.\n */\nexport type { MarketplacePlatform } from \"./formats/types.js\";\n\n/** Map an internal marketplace `format` to its public platform token. */\nfunction formatToPlatform(format: NormalizedMarketplace[\"format\"]): MarketplacePlatform {\n\treturn format === \"copilot\" ? \"github\" : format;\n}\n\n/** Normalize an authored `supportPlatform` (string | string[]) to canonical tokens. */\nexport function normalizePlatforms(value: unknown): MarketplacePlatform[] {\n\tconst raw = Array.isArray(value) ? value : value == null ? [] : [value];\n\tconst out: MarketplacePlatform[] = [];\n\tfor (const v of raw) {\n\t\tif (typeof v !== \"string\") continue;\n\t\tconst canonical = normalizePlatformToken(v);\n\t\tif (canonical && !out.includes(canonical)) out.push(canonical);\n\t}\n\treturn out;\n}\n\n/** Structured source pointing at an entire git repository. */\ninterface MarketplacePluginSourceUrl {\n\tsource: \"url\";\n\turl: string;\n\tref?: string;\n\tsha?: string;\n}\n\n/** Structured source pointing at a subdirectory within a git repository. */\ninterface MarketplacePluginSourceGitSubdir {\n\tsource: \"git-subdir\";\n\turl: string;\n\t/** Subdirectory path inside the repository. */\n\tpath: string;\n\tref?: string;\n\tsha?: string;\n}\n\n/** Source value authored in a marketplace manifest. */\nexport type MarketplacePluginSource = string | MarketplacePluginSourceUrl | MarketplacePluginSourceGitSubdir;\n\ninterface MarketplacePluginEntry {\n\tname: string;\n\t/** Relative path, git URL, `npm:<spec>`, or structured source object. */\n\tsource: MarketplacePluginSource;\n\tdescription?: string;\n\t/**\n\t * Optional platform(s) this entry targets. Only set when authored on the\n\t * entry; absent means \"no per-entry restriction\" (the marketplace's\n\t * platforms apply). Purely informational today — nothing filters on it.\n\t */\n\tsupportPlatform?: MarketplacePlatform[];\n}\n\nexport interface NormalizedMarketplace {\n\tname: string;\n\t/** Precedence winner when several index files conflict (agents > claude > copilot). */\n\tformat: \"agents\" | \"claude\" | \"copilot\";\n\t/**\n\t * Every platform this marketplace directory offers, so a conflict between\n\t * multiple index files (e.g. both `.github/` and `.claude-plugin/`) is\n\t * recorded rather than hidden. Always includes the resolved `format`'s\n\t * platform; also folds in any authored top-level `supportPlatform`. Never\n\t * empty.\n\t */\n\tsupportPlatform: MarketplacePlatform[];\n\t/** Absolute path to the marketplace directory. */\n\troot: string;\n\tmanifestPath: string;\n\tplugins: MarketplacePluginEntry[];\n}\n\ninterface RawMarketplace {\n\tname?: string;\n\t/** Both Claude Code and Copilot CLI define `owner` as `{ name, email? }`; a bare string is tolerated. */\n\towner?: string | { name?: string; email?: string };\n\t/** Shared optional metadata: `pluginRoot` is a base dir prepended to relative plugin source paths. */\n\tmetadata?: { description?: string; version?: string; pluginRoot?: string };\n\t/** Optional authored platform hint (string | string[]); folded into supportPlatform. */\n\tsupportPlatform?: unknown;\n\tplugins?: Array<{ name?: string; source?: unknown; description?: string; supportPlatform?: unknown }>;\n}\n\n/** True when a string source is a path (not npm:/git/URL) that `pluginRoot` should prefix. */\nfunction isRelativePathSource(source: string): boolean {\n\tconst s = source.trim();\n\treturn (\n\t\t!s.startsWith(\"npm:\") &&\n\t\t!/^https?:\\/\\//.test(s) &&\n\t\t!s.startsWith(\"git@\") &&\n\t\t!s.endsWith(\".git\") &&\n\t\t!path.isAbsolute(s)\n\t);\n}\n\n/**\n * Apply `metadata.pluginRoot` (Claude Code and Copilot CLI share the field):\n * a base directory prepended to relative plugin source paths, so\n * `\"source\": \"formatter\"` with `\"pluginRoot\": \"./plugins\"` resolves like\n * `\"./plugins/formatter\"`.\n */\nfunction applyPluginRoot(source: MarketplacePluginSource, pluginRoot: string | undefined): MarketplacePluginSource {\n\tif (!pluginRoot || typeof source !== \"string\" || !isRelativePathSource(source)) return source;\n\treturn `${pluginRoot.replace(/\\/+$/, \"\")}/${source.trim().replace(/^\\.\\//, \"\")}`;\n}\n\n/** Validate and normalize an authored source value. Returns null for unsupported/invalid shapes. */\nfunction normalizeSource(source: unknown): MarketplacePluginSource | null {\n\tif (typeof source === \"string\") return source;\n\tif (!source || typeof source !== \"object\") return null;\n\tconst obj = source as Record<string, unknown>;\n\tconst src = obj.source;\n\tconst url = obj.url;\n\tconst pin = {\n\t\t...(typeof obj.ref === \"string\" ? { ref: obj.ref } : {}),\n\t\t...(typeof obj.sha === \"string\" ? { sha: obj.sha } : {}),\n\t};\n\tif (src === \"url\" && typeof url === \"string\") {\n\t\treturn { source: \"url\", url, ...pin };\n\t}\n\tif (src === \"git-subdir\" && typeof url === \"string\" && typeof obj.path === \"string\") {\n\t\treturn { source: \"git-subdir\", url, path: obj.path, ...pin };\n\t}\n\t// Copilot marketplace shorthand: { source: \"github\", repo: \"owner/name\", path? }\n\t// (used by github/copilot-plugins). Normalize to the equivalent git source.\n\tif (src === \"github\" && typeof obj.repo === \"string\" && /^[\\w.-]+\\/[\\w.-]+$/.test(obj.repo)) {\n\t\tconst repoUrl = `https://github.com/${obj.repo}.git`;\n\t\tif (typeof obj.path === \"string\" && obj.path.length > 0) {\n\t\t\treturn { source: \"git-subdir\", url: repoUrl, path: obj.path, ...pin };\n\t\t}\n\t\treturn { source: \"url\", url: repoUrl, ...pin };\n\t}\n\treturn null;\n}\n\n/** A resolved, installable plugin source. */\nexport type ResolvedPluginSource =\n\t| { kind: \"local\"; path: string }\n\t| { kind: \"git\"; url: string; ref?: string; sha?: string }\n\t| { kind: \"git-subdir\"; url: string; path: string; ref?: string; sha?: string }\n\t| { kind: \"npm\"; spec: string };\n\nfunction readJson<T>(file: string): T | null {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(file, \"utf8\")) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Parse a marketplace directory. Native `.agents-plugin` wins, then Claude, then\n * Copilot when more than one file is present. Returns null if no manifest exists\n * or it is invalid.\n */\nexport function parseMarketplaceDir(dir: string): NormalizedMarketplace | null {\n\t// Record every index format present so a conflict (more than one file) is\n\t// visible via supportPlatform, not silently dropped. PLUGIN_FORMATS is\n\t// precedence-ordered (native > Claude > Copilot), so `present` inherits it.\n\tconst present: Array<{ format: NormalizedMarketplace[\"format\"]; path: string }> = [];\n\tfor (const fmt of PLUGIN_FORMATS) {\n\t\tfor (const candidate of fmt.marketplaceFiles) {\n\t\t\tconst p = path.join(dir, candidate);\n\t\t\tif (fs.existsSync(p)) {\n\t\t\t\tpresent.push({ format: fmt.id, path: p });\n\t\t\t\tbreak; // first candidate wins within a format\n\t\t\t}\n\t\t}\n\t}\n\tif (present.length === 0) return null;\n\n\t// Precedence winner (present is built in agents > claude > copilot order).\n\tconst { format, path: manifestPath } = present[0];\n\n\tconst raw = readJson<RawMarketplace>(manifestPath);\n\tif (!raw) return null;\n\n\tconst pluginRoot =\n\t\ttypeof raw.metadata?.pluginRoot === \"string\" && raw.metadata.pluginRoot.trim()\n\t\t\t? raw.metadata.pluginRoot.trim()\n\t\t\t: undefined;\n\n\tconst plugins: MarketplacePluginEntry[] = [];\n\tfor (const entry of raw.plugins ?? []) {\n\t\tif (!entry?.name || !entry.source) continue;\n\t\tconst normalized = normalizeSource(entry.source);\n\t\tif (!normalized) continue;\n\t\tconst entryPlatforms = normalizePlatforms(entry.supportPlatform);\n\t\tplugins.push({\n\t\t\tname: entry.name,\n\t\t\tsource: applyPluginRoot(normalized, pluginRoot),\n\t\t\tdescription: entry.description,\n\t\t\t...(entryPlatforms.length > 0 ? { supportPlatform: entryPlatforms } : {}),\n\t\t});\n\t}\n\n\t// supportPlatform = platforms of all present index files, plus any authored\n\t// top-level hint, deduped. Always non-empty (at least the resolved format).\n\tconst supportPlatform: MarketplacePlatform[] = [];\n\tfor (const p of [...present.map((e) => formatToPlatform(e.format)), ...normalizePlatforms(raw.supportPlatform)]) {\n\t\tif (!supportPlatform.includes(p)) supportPlatform.push(p);\n\t}\n\n\treturn {\n\t\tname: (raw.name ?? path.basename(dir)).trim() || path.basename(dir),\n\t\tformat,\n\t\tsupportPlatform,\n\t\troot: dir,\n\t\tmanifestPath,\n\t\tplugins,\n\t};\n}\n\n/** Classify and resolve a plugin `source` against its marketplace root. */\nexport function resolvePluginSource(source: MarketplacePluginSource, marketplaceRoot: string): ResolvedPluginSource {\n\tif (typeof source === \"string\") {\n\t\tconst s = source.trim();\n\t\tif (s.startsWith(\"npm:\")) return { kind: \"npm\", spec: s.slice(4) };\n\t\tif (/^https?:\\/\\//.test(s) || s.startsWith(\"git@\") || s.endsWith(\".git\")) return { kind: \"git\", url: s };\n\t\t// Otherwise a path relative to (or absolute within) the marketplace repo.\n\t\treturn { kind: \"local\", path: path.isAbsolute(s) ? s : path.resolve(marketplaceRoot, s) };\n\t}\n\tif (source.source === \"url\") {\n\t\treturn { kind: \"git\", url: source.url, ref: source.ref, sha: source.sha };\n\t}\n\treturn { kind: \"git-subdir\", url: source.url, path: source.path, ref: source.ref, sha: source.sha };\n}\n\n// ============================================================================\n// Marketplace registry (persisted list of added marketplaces)\n// ============================================================================\n\nexport interface MarketplaceRecord {\n\t/** Original location the user added (git URL or local path). */\n\tlocation: string;\n\t/** Local directory to read the manifest from (clone dir for git, else location). */\n\tdir: string;\n}\n\ninterface MarketplaceStoreFile {\n\tmarketplaces?: MarketplaceRecord[];\n}\n\n/** Read the persisted marketplace list from `<storePath>`. */\nexport function readMarketplaceStore(storePath: string): MarketplaceRecord[] {\n\tconst parsed = readJson<MarketplaceStoreFile>(storePath);\n\treturn Array.isArray(parsed?.marketplaces) ? parsed.marketplaces : [];\n}\n\n/** Write the marketplace list to `<storePath>`. */\nexport function writeMarketplaceStore(storePath: string, records: MarketplaceRecord[]): void {\n\tfs.mkdirSync(path.dirname(storePath), { recursive: true });\n\tfs.writeFileSync(storePath, `${JSON.stringify({ marketplaces: records }, null, 2)}\\n`, \"utf8\");\n}\n"]}