@capybara-hq/kernel 0.0.0-stage → 0.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (631) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +20 -2
  4. package/bootstrap.json +22 -0
  5. package/dist/admin/admin.controller.d.ts +139 -0
  6. package/dist/admin/admin.controller.js +238 -0
  7. package/dist/admin/extensions.controller.d.ts +100 -0
  8. package/dist/admin/extensions.controller.js +226 -0
  9. package/dist/admin/setup.controller.d.ts +36 -0
  10. package/dist/admin/setup.controller.js +106 -0
  11. package/dist/admin/setup.service.d.ts +47 -0
  12. package/dist/admin/setup.service.js +90 -0
  13. package/dist/agents/agents.controller.d.ts +205 -0
  14. package/dist/agents/agents.controller.js +144 -0
  15. package/dist/agents/agents.service.d.ts +177 -0
  16. package/dist/agents/agents.service.js +677 -0
  17. package/dist/agents/definition.d.ts +198 -0
  18. package/dist/agents/definition.js +152 -0
  19. package/dist/agents/models.controller.d.ts +13 -0
  20. package/dist/agents/models.controller.js +31 -0
  21. package/dist/agents/templates.controller.d.ts +15 -0
  22. package/dist/agents/templates.controller.js +37 -0
  23. package/dist/agents/templates.d.ts +83 -0
  24. package/dist/agents/templates.js +173 -0
  25. package/dist/catalog/admin-catalog.controller.d.ts +143 -0
  26. package/dist/catalog/admin-catalog.controller.js +245 -0
  27. package/dist/catalog/catalog.service.d.ts +430 -0
  28. package/dist/catalog/catalog.service.js +882 -0
  29. package/dist/catalog/claude/agents.d.ts +27 -0
  30. package/dist/catalog/claude/agents.js +270 -0
  31. package/dist/catalog/claude/commands.d.ts +50 -0
  32. package/dist/catalog/claude/commands.js +179 -0
  33. package/dist/catalog/claude/manifest.d.ts +69 -0
  34. package/dist/catalog/claude/manifest.js +134 -0
  35. package/dist/catalog/claude/marketplace.d.ts +35 -0
  36. package/dist/catalog/claude/marketplace.js +194 -0
  37. package/dist/catalog/claude/mcp.d.ts +33 -0
  38. package/dist/catalog/claude/mcp.js +226 -0
  39. package/dist/catalog/claude/report.d.ts +86 -0
  40. package/dist/catalog/claude/report.js +121 -0
  41. package/dist/catalog/claude/translate.d.ts +56 -0
  42. package/dist/catalog/claude/translate.js +857 -0
  43. package/dist/catalog/cli.d.ts +46 -0
  44. package/dist/catalog/cli.js +211 -0
  45. package/dist/catalog/git-source.d.ts +127 -0
  46. package/dist/catalog/git-source.js +520 -0
  47. package/dist/catalog/marketplace-fetch.d.ts +99 -0
  48. package/dist/catalog/marketplace-fetch.js +203 -0
  49. package/dist/catalog/marketplaces.controller.d.ts +97 -0
  50. package/dist/catalog/marketplaces.controller.js +197 -0
  51. package/dist/catalog/marketplaces.service.d.ts +340 -0
  52. package/dist/catalog/marketplaces.service.js +1217 -0
  53. package/dist/catalog/org-catalog.controller.d.ts +128 -0
  54. package/dist/catalog/org-catalog.controller.js +277 -0
  55. package/dist/catalog/org-catalog.service.d.ts +246 -0
  56. package/dist/catalog/org-catalog.service.js +625 -0
  57. package/dist/catalog/plugin-import.d.ts +436 -0
  58. package/dist/catalog/plugin-import.js +1069 -0
  59. package/dist/catalog/plugin-install.d.ts +447 -0
  60. package/dist/catalog/plugin-install.js +1333 -0
  61. package/dist/catalog/plugin-package.d.ts +143 -0
  62. package/dist/catalog/plugin-package.js +382 -0
  63. package/dist/catalog/plugin-upgrade.d.ts +222 -0
  64. package/dist/catalog/plugin-upgrade.js +1219 -0
  65. package/dist/catalog/requests.service.d.ts +162 -0
  66. package/dist/catalog/requests.service.js +504 -0
  67. package/dist/catalog/schema-path.d.ts +7 -0
  68. package/dist/catalog/schema-path.js +63 -0
  69. package/dist/catalog/upgrade-targets.d.ts +59 -0
  70. package/dist/catalog/upgrade-targets.js +90 -0
  71. package/dist/catalog/versions.d.ts +31 -0
  72. package/dist/catalog/versions.js +44 -0
  73. package/dist/channels/actions.d.ts +37 -0
  74. package/dist/channels/actions.js +121 -0
  75. package/dist/channels/cards.d.ts +44 -0
  76. package/dist/channels/cards.js +156 -0
  77. package/dist/channels/connections.controller.d.ts +59 -0
  78. package/dist/channels/connections.controller.js +115 -0
  79. package/dist/channels/connections.service.d.ts +187 -0
  80. package/dist/channels/connections.service.js +534 -0
  81. package/dist/channels/features.d.ts +12 -0
  82. package/dist/channels/features.js +22 -0
  83. package/dist/channels/inbound.d.ts +120 -0
  84. package/dist/channels/inbound.js +332 -0
  85. package/dist/channels/outbound.d.ts +140 -0
  86. package/dist/channels/outbound.js +1125 -0
  87. package/dist/channels/phrases.d.ts +100 -0
  88. package/dist/channels/phrases.js +81 -0
  89. package/dist/channels/supervisor.d.ts +182 -0
  90. package/dist/channels/supervisor.js +625 -0
  91. package/dist/channels/threads.d.ts +29 -0
  92. package/dist/channels/threads.js +104 -0
  93. package/dist/channels/webhook-url.d.ts +16 -0
  94. package/dist/channels/webhook-url.js +34 -0
  95. package/dist/channels/webhook.controller.d.ts +19 -0
  96. package/dist/channels/webhook.controller.js +72 -0
  97. package/dist/channels/webhook.d.ts +73 -0
  98. package/dist/channels/webhook.js +274 -0
  99. package/dist/config/docs.d.ts +9 -0
  100. package/dist/config/docs.js +85 -0
  101. package/dist/config/env.d.ts +6 -0
  102. package/dist/config/env.js +73 -0
  103. package/dist/config/fields.d.ts +42 -0
  104. package/dist/config/fields.js +214 -0
  105. package/dist/config/file-source.d.ts +3 -0
  106. package/dist/config/file-source.js +31 -0
  107. package/dist/config/load.d.ts +20 -0
  108. package/dist/config/load.js +98 -0
  109. package/dist/config/nacos-source.d.ts +31 -0
  110. package/dist/config/nacos-source.js +229 -0
  111. package/dist/config/schema.d.ts +223 -0
  112. package/dist/config/schema.js +1151 -0
  113. package/dist/config/sources.d.ts +18 -0
  114. package/dist/config/sources.js +59 -0
  115. package/dist/config/types.d.ts +9 -0
  116. package/dist/config/types.js +7 -0
  117. package/dist/conversations/artifacts.d.ts +52 -0
  118. package/dist/conversations/artifacts.js +137 -0
  119. package/dist/conversations/attachments.controller.d.ts +28 -0
  120. package/dist/conversations/attachments.controller.js +96 -0
  121. package/dist/conversations/attachments.service.d.ts +180 -0
  122. package/dist/conversations/attachments.service.js +578 -0
  123. package/dist/conversations/conversations.controller.d.ts +288 -0
  124. package/dist/conversations/conversations.controller.js +607 -0
  125. package/dist/conversations/conversations.service.d.ts +321 -0
  126. package/dist/conversations/conversations.service.js +754 -0
  127. package/dist/conversations/event-log.d.ts +122 -0
  128. package/dist/conversations/event-log.js +259 -0
  129. package/dist/conversations/events.d.ts +856 -0
  130. package/dist/conversations/events.js +639 -0
  131. package/dist/conversations/files.d.ts +92 -0
  132. package/dist/conversations/files.js +213 -0
  133. package/dist/conversations/outputs-read.d.ts +34 -0
  134. package/dist/conversations/outputs-read.js +37 -0
  135. package/dist/conversations/queue.d.ts +169 -0
  136. package/dist/conversations/queue.js +527 -0
  137. package/dist/conversations/sandbox-status.d.ts +21 -0
  138. package/dist/conversations/sandbox-status.js +24 -0
  139. package/dist/conversations/selection.d.ts +46 -0
  140. package/dist/conversations/selection.js +75 -0
  141. package/dist/conversations/titles.d.ts +93 -0
  142. package/dist/conversations/titles.js +422 -0
  143. package/dist/ctx/bus.d.ts +16 -0
  144. package/dist/ctx/bus.js +101 -0
  145. package/dist/ctx/create-ctx.d.ts +109 -0
  146. package/dist/ctx/create-ctx.js +592 -0
  147. package/dist/ctx/kv.d.ts +4 -0
  148. package/dist/ctx/kv.js +31 -0
  149. package/dist/ctx/lock.d.ts +4 -0
  150. package/dist/ctx/lock.js +39 -0
  151. package/dist/ctx/namespace.d.ts +6 -0
  152. package/dist/ctx/namespace.js +12 -0
  153. package/dist/ctx/queue.d.ts +25 -0
  154. package/dist/ctx/queue.js +150 -0
  155. package/dist/ctx/secrets.d.ts +45 -0
  156. package/dist/ctx/secrets.js +175 -0
  157. package/dist/db/kernel-schema.d.ts +1122 -0
  158. package/dist/db/kernel-schema.js +5 -0
  159. package/dist/db/migrator.d.ts +28 -0
  160. package/dist/db/migrator.js +140 -0
  161. package/dist/db/pool.d.ts +21 -0
  162. package/dist/db/pool.js +28 -0
  163. package/dist/db/sql-statements.d.ts +34 -0
  164. package/dist/db/sql-statements.js +168 -0
  165. package/dist/errors.d.ts +210 -0
  166. package/dist/errors.js +212 -0
  167. package/dist/evals/budget.d.ts +61 -0
  168. package/dist/evals/budget.js +181 -0
  169. package/dist/evals/diff.d.ts +55 -0
  170. package/dist/evals/diff.js +133 -0
  171. package/dist/evals/judge.d.ts +53 -0
  172. package/dist/evals/judge.js +149 -0
  173. package/dist/evals/plan.d.ts +47 -0
  174. package/dist/evals/plan.js +77 -0
  175. package/dist/evals/reconstruct.d.ts +137 -0
  176. package/dist/evals/reconstruct.js +322 -0
  177. package/dist/evals/recorded-tools.d.ts +54 -0
  178. package/dist/evals/recorded-tools.js +167 -0
  179. package/dist/evals/run-replay.d.ts +86 -0
  180. package/dist/evals/run-replay.js +502 -0
  181. package/dist/evals/runs.controller.d.ts +100 -0
  182. package/dist/evals/runs.controller.js +199 -0
  183. package/dist/evals/runs.service.d.ts +275 -0
  184. package/dist/evals/runs.service.js +836 -0
  185. package/dist/evals/sets.controller.d.ts +61 -0
  186. package/dist/evals/sets.controller.js +183 -0
  187. package/dist/evals/sets.service.d.ts +128 -0
  188. package/dist/evals/sets.service.js +558 -0
  189. package/dist/evals/snapshot.d.ts +76 -0
  190. package/dist/evals/snapshot.js +207 -0
  191. package/dist/evals/step-replay.d.ts +113 -0
  192. package/dist/evals/step-replay.js +181 -0
  193. package/dist/evals/trajectory.controller.d.ts +41 -0
  194. package/dist/evals/trajectory.controller.js +88 -0
  195. package/dist/evals/trajectory.d.ts +144 -0
  196. package/dist/evals/trajectory.js +338 -0
  197. package/dist/evals/trajectory.service.d.ts +108 -0
  198. package/dist/evals/trajectory.service.js +323 -0
  199. package/dist/evals/worker.d.ts +101 -0
  200. package/dist/evals/worker.js +789 -0
  201. package/dist/extensions/admin-ops.d.ts +105 -0
  202. package/dist/extensions/admin-ops.js +353 -0
  203. package/dist/extensions/admin-view.d.ts +79 -0
  204. package/dist/extensions/admin-view.js +327 -0
  205. package/dist/extensions/approvals.d.ts +119 -0
  206. package/dist/extensions/approvals.js +205 -0
  207. package/dist/extensions/blobs.d.ts +45 -0
  208. package/dist/extensions/blobs.js +160 -0
  209. package/dist/extensions/bootstrap.d.ts +149 -0
  210. package/dist/extensions/bootstrap.js +441 -0
  211. package/dist/extensions/cli.d.ts +61 -0
  212. package/dist/extensions/cli.js +165 -0
  213. package/dist/extensions/config-json-schema.d.ts +9 -0
  214. package/dist/extensions/config-json-schema.js +73 -0
  215. package/dist/extensions/deployment-config.d.ts +138 -0
  216. package/dist/extensions/deployment-config.js +394 -0
  217. package/dist/extensions/enabled.d.ts +31 -0
  218. package/dist/extensions/enabled.js +55 -0
  219. package/dist/extensions/hooks.d.ts +161 -0
  220. package/dist/extensions/hooks.js +425 -0
  221. package/dist/extensions/host.d.ts +577 -0
  222. package/dist/extensions/host.js +2071 -0
  223. package/dist/extensions/inflight.d.ts +69 -0
  224. package/dist/extensions/inflight.js +277 -0
  225. package/dist/extensions/installs.d.ts +166 -0
  226. package/dist/extensions/installs.js +253 -0
  227. package/dist/extensions/instance.d.ts +167 -0
  228. package/dist/extensions/instance.js +329 -0
  229. package/dist/extensions/ledger.d.ts +30 -0
  230. package/dist/extensions/ledger.js +50 -0
  231. package/dist/extensions/lifecycle.d.ts +80 -0
  232. package/dist/extensions/lifecycle.js +152 -0
  233. package/dist/extensions/migrate-lock.d.ts +51 -0
  234. package/dist/extensions/migrate-lock.js +100 -0
  235. package/dist/extensions/org-config.controller.d.ts +24 -0
  236. package/dist/extensions/org-config.controller.js +59 -0
  237. package/dist/extensions/org-config.d.ts +169 -0
  238. package/dist/extensions/org-config.js +520 -0
  239. package/dist/extensions/package.d.ts +115 -0
  240. package/dist/extensions/package.js +246 -0
  241. package/dist/extensions/permission-set.d.ts +117 -0
  242. package/dist/extensions/permission-set.js +184 -0
  243. package/dist/extensions/permission-view.d.ts +194 -0
  244. package/dist/extensions/permission-view.js +284 -0
  245. package/dist/extensions/registry.d.ts +24 -0
  246. package/dist/extensions/registry.js +56 -0
  247. package/dist/extensions/resolve.d.ts +12 -0
  248. package/dist/extensions/resolve.js +71 -0
  249. package/dist/extensions/sources.d.ts +69 -0
  250. package/dist/extensions/sources.js +120 -0
  251. package/dist/extensions/sync.d.ts +40 -0
  252. package/dist/extensions/sync.js +242 -0
  253. package/dist/files/previewers.controller.d.ts +46 -0
  254. package/dist/files/previewers.controller.js +105 -0
  255. package/dist/files/previewers.d.ts +100 -0
  256. package/dist/files/previewers.js +250 -0
  257. package/dist/http/api.module.d.ts +114 -0
  258. package/dist/http/api.module.js +448 -0
  259. package/dist/http/app.d.ts +77 -0
  260. package/dist/http/app.js +170 -0
  261. package/dist/http/error.filter.d.ts +21 -0
  262. package/dist/http/error.filter.js +85 -0
  263. package/dist/http/gateway.module.d.ts +15 -0
  264. package/dist/http/gateway.module.js +13 -0
  265. package/dist/http/health.controller.d.ts +26 -0
  266. package/dist/http/health.controller.js +79 -0
  267. package/dist/http/nest-logger.d.ts +20 -0
  268. package/dist/http/nest-logger.js +28 -0
  269. package/dist/http/plugin-dispatch.d.ts +68 -0
  270. package/dist/http/plugin-dispatch.js +200 -0
  271. package/dist/http/tokens.d.ts +131 -0
  272. package/dist/http/tokens.js +131 -0
  273. package/dist/http/zod.pipe.d.ts +8 -0
  274. package/dist/http/zod.pipe.js +14 -0
  275. package/dist/identity/accounts.d.ts +93 -0
  276. package/dist/identity/accounts.js +367 -0
  277. package/dist/identity/api-tokens.d.ts +73 -0
  278. package/dist/identity/api-tokens.js +197 -0
  279. package/dist/identity/audit.d.ts +23 -0
  280. package/dist/identity/audit.js +41 -0
  281. package/dist/identity/auth.controller.d.ts +40 -0
  282. package/dist/identity/auth.controller.js +130 -0
  283. package/dist/identity/auth.guard.d.ts +41 -0
  284. package/dist/identity/auth.guard.js +162 -0
  285. package/dist/identity/binding.d.ts +47 -0
  286. package/dist/identity/binding.js +84 -0
  287. package/dist/identity/cookies.d.ts +16 -0
  288. package/dist/identity/cookies.js +32 -0
  289. package/dist/identity/credential-check.d.ts +17 -0
  290. package/dist/identity/credential-check.js +24 -0
  291. package/dist/identity/enterprise-admin.d.ts +14 -0
  292. package/dist/identity/enterprise-admin.js +27 -0
  293. package/dist/identity/external.d.ts +106 -0
  294. package/dist/identity/external.js +337 -0
  295. package/dist/identity/hash.d.ts +2 -0
  296. package/dist/identity/hash.js +3 -0
  297. package/dist/identity/login.d.ts +6 -0
  298. package/dist/identity/login.js +20 -0
  299. package/dist/identity/me.controller.d.ts +61 -0
  300. package/dist/identity/me.controller.js +159 -0
  301. package/dist/identity/passwords.d.ts +6 -0
  302. package/dist/identity/passwords.js +37 -0
  303. package/dist/identity/principal.d.ts +56 -0
  304. package/dist/identity/principal.js +38 -0
  305. package/dist/identity/rate-limit.d.ts +55 -0
  306. package/dist/identity/rate-limit.js +121 -0
  307. package/dist/identity/sessions.d.ts +84 -0
  308. package/dist/identity/sessions.js +306 -0
  309. package/dist/identity/settings.d.ts +121 -0
  310. package/dist/identity/settings.js +248 -0
  311. package/dist/identity/sso.controller.d.ts +59 -0
  312. package/dist/identity/sso.controller.js +192 -0
  313. package/dist/identity/sso.d.ts +83 -0
  314. package/dist/identity/sso.js +202 -0
  315. package/dist/inbox/inbox.service.d.ts +43 -0
  316. package/dist/inbox/inbox.service.js +57 -0
  317. package/dist/index.d.ts +67 -0
  318. package/dist/index.js +65 -0
  319. package/dist/kernel.d.ts +256 -0
  320. package/dist/kernel.js +1326 -0
  321. package/dist/log.d.ts +48 -0
  322. package/dist/log.js +229 -0
  323. package/dist/models/prices.d.ts +53 -0
  324. package/dist/models/prices.js +121 -0
  325. package/dist/models/purposes.controller.d.ts +19 -0
  326. package/dist/models/purposes.controller.js +56 -0
  327. package/dist/models/purposes.d.ts +140 -0
  328. package/dist/models/purposes.js +267 -0
  329. package/dist/net/proxy.d.ts +44 -0
  330. package/dist/net/proxy.js +162 -0
  331. package/dist/net/public-address.d.ts +60 -0
  332. package/dist/net/public-address.js +271 -0
  333. package/dist/net/safe-fetch.d.ts +38 -0
  334. package/dist/net/safe-fetch.js +140 -0
  335. package/dist/orgs/directory-sync.controller.d.ts +14 -0
  336. package/dist/orgs/directory-sync.controller.js +40 -0
  337. package/dist/orgs/directory-sync.d.ts +166 -0
  338. package/dist/orgs/directory-sync.js +558 -0
  339. package/dist/orgs/invitations.controller.d.ts +58 -0
  340. package/dist/orgs/invitations.controller.js +137 -0
  341. package/dist/orgs/invitations.service.d.ts +80 -0
  342. package/dist/orgs/invitations.service.js +217 -0
  343. package/dist/orgs/members.service.d.ts +63 -0
  344. package/dist/orgs/members.service.js +234 -0
  345. package/dist/orgs/offboarding.d.ts +141 -0
  346. package/dist/orgs/offboarding.js +398 -0
  347. package/dist/orgs/org-lock.d.ts +32 -0
  348. package/dist/orgs/org-lock.js +46 -0
  349. package/dist/orgs/org.guard.d.ts +35 -0
  350. package/dist/orgs/org.guard.js +95 -0
  351. package/dist/orgs/orgs.controller.d.ts +78 -0
  352. package/dist/orgs/orgs.controller.js +192 -0
  353. package/dist/orgs/orgs.service.d.ts +61 -0
  354. package/dist/orgs/orgs.service.js +209 -0
  355. package/dist/orgs/roles.d.ts +13 -0
  356. package/dist/orgs/roles.js +58 -0
  357. package/dist/orgs/settings.controller.d.ts +20 -0
  358. package/dist/orgs/settings.controller.js +53 -0
  359. package/dist/orgs/settings.service.d.ts +120 -0
  360. package/dist/orgs/settings.service.js +178 -0
  361. package/dist/packages/npm.d.ts +50 -0
  362. package/dist/packages/npm.js +218 -0
  363. package/dist/packages/provenance.d.ts +47 -0
  364. package/dist/packages/provenance.js +147 -0
  365. package/dist/packages/tgz.d.ts +76 -0
  366. package/dist/packages/tgz.js +225 -0
  367. package/dist/runtime/abort.d.ts +32 -0
  368. package/dist/runtime/abort.js +71 -0
  369. package/dist/runtime/checkpointer.d.ts +32 -0
  370. package/dist/runtime/checkpointer.js +243 -0
  371. package/dist/runtime/context/build.d.ts +254 -0
  372. package/dist/runtime/context/build.js +693 -0
  373. package/dist/runtime/context/claude-tools.d.ts +11 -0
  374. package/dist/runtime/context/claude-tools.js +24 -0
  375. package/dist/runtime/context/compaction.d.ts +26 -0
  376. package/dist/runtime/context/compaction.js +195 -0
  377. package/dist/runtime/context/pairing.d.ts +11 -0
  378. package/dist/runtime/context/pairing.js +82 -0
  379. package/dist/runtime/context/reminders.d.ts +5 -0
  380. package/dist/runtime/context/reminders.js +11 -0
  381. package/dist/runtime/context/skills.d.ts +105 -0
  382. package/dist/runtime/context/skills.js +283 -0
  383. package/dist/runtime/context/tokens.d.ts +19 -0
  384. package/dist/runtime/context/tokens.js +104 -0
  385. package/dist/runtime/conversation-lock.d.ts +40 -0
  386. package/dist/runtime/conversation-lock.js +97 -0
  387. package/dist/runtime/hook-calls.d.ts +84 -0
  388. package/dist/runtime/hook-calls.js +136 -0
  389. package/dist/runtime/langgraph-loop.d.ts +52 -0
  390. package/dist/runtime/langgraph-loop.js +247 -0
  391. package/dist/runtime/loop.d.ts +93 -0
  392. package/dist/runtime/loop.js +7 -0
  393. package/dist/runtime/maintenance.d.ts +80 -0
  394. package/dist/runtime/maintenance.js +147 -0
  395. package/dist/runtime/media-cache.d.ts +21 -0
  396. package/dist/runtime/media-cache.js +51 -0
  397. package/dist/runtime/model-call.d.ts +176 -0
  398. package/dist/runtime/model-call.js +1049 -0
  399. package/dist/runtime/providers.d.ts +86 -0
  400. package/dist/runtime/providers.js +224 -0
  401. package/dist/runtime/quota.d.ts +29 -0
  402. package/dist/runtime/quota.js +70 -0
  403. package/dist/runtime/run-finish.d.ts +45 -0
  404. package/dist/runtime/run-finish.js +69 -0
  405. package/dist/runtime/run-queue.d.ts +121 -0
  406. package/dist/runtime/run-queue.js +493 -0
  407. package/dist/runtime/run-worker.d.ts +138 -0
  408. package/dist/runtime/run-worker.js +805 -0
  409. package/dist/runtime/stream.controller.d.ts +34 -0
  410. package/dist/runtime/stream.controller.js +122 -0
  411. package/dist/runtime/stream.d.ts +63 -0
  412. package/dist/runtime/stream.js +262 -0
  413. package/dist/runtime/subagents/child.d.ts +80 -0
  414. package/dist/runtime/subagents/child.js +225 -0
  415. package/dist/runtime/subagents/definition.d.ts +103 -0
  416. package/dist/runtime/subagents/definition.js +392 -0
  417. package/dist/runtime/subagents/dispatch.d.ts +69 -0
  418. package/dist/runtime/subagents/dispatch.js +502 -0
  419. package/dist/runtime/subagents/limits.d.ts +23 -0
  420. package/dist/runtime/subagents/limits.js +34 -0
  421. package/dist/runtime/subagents/org-agents.d.ts +50 -0
  422. package/dist/runtime/subagents/org-agents.js +70 -0
  423. package/dist/runtime/subagents/preamble.d.ts +7 -0
  424. package/dist/runtime/subagents/preamble.js +11 -0
  425. package/dist/runtime/subagents/progress.d.ts +26 -0
  426. package/dist/runtime/subagents/progress.js +36 -0
  427. package/dist/runtime/subagents/roster.d.ts +55 -0
  428. package/dist/runtime/subagents/roster.js +111 -0
  429. package/dist/runtime/subagents/skill-fork.d.ts +123 -0
  430. package/dist/runtime/subagents/skill-fork.js +259 -0
  431. package/dist/runtime/usage-report.d.ts +163 -0
  432. package/dist/runtime/usage-report.js +550 -0
  433. package/dist/runtime/waits.d.ts +233 -0
  434. package/dist/runtime/waits.js +883 -0
  435. package/dist/sandbox/leases.d.ts +169 -0
  436. package/dist/sandbox/leases.js +515 -0
  437. package/dist/sandbox/lifecycle.d.ts +21 -0
  438. package/dist/sandbox/lifecycle.js +68 -0
  439. package/dist/sandbox/sandbox.service.d.ts +249 -0
  440. package/dist/sandbox/sandbox.service.js +908 -0
  441. package/dist/skills/catalog.d.ts +149 -0
  442. package/dist/skills/catalog.js +405 -0
  443. package/dist/skills/changes.controller.d.ts +56 -0
  444. package/dist/skills/changes.controller.js +136 -0
  445. package/dist/skills/changes.service.d.ts +175 -0
  446. package/dist/skills/changes.service.js +805 -0
  447. package/dist/skills/invocable.controller.d.ts +23 -0
  448. package/dist/skills/invocable.controller.js +50 -0
  449. package/dist/skills/invocation-keys.d.ts +23 -0
  450. package/dist/skills/invocation-keys.js +44 -0
  451. package/dist/skills/invoke.d.ts +135 -0
  452. package/dist/skills/invoke.js +286 -0
  453. package/dist/skills/package.d.ts +87 -0
  454. package/dist/skills/package.js +227 -0
  455. package/dist/skills/skills.controller.d.ts +40 -0
  456. package/dist/skills/skills.controller.js +110 -0
  457. package/dist/skills/skills.service.d.ts +192 -0
  458. package/dist/skills/skills.service.js +567 -0
  459. package/dist/storage/prefix.d.ts +36 -0
  460. package/dist/storage/prefix.js +104 -0
  461. package/dist/storage/purge.d.ts +27 -0
  462. package/dist/storage/purge.js +75 -0
  463. package/dist/storage/storage.service.d.ts +44 -0
  464. package/dist/storage/storage.service.js +145 -0
  465. package/dist/text.d.ts +13 -0
  466. package/dist/text.js +13 -0
  467. package/dist/tools/approvals.controller.d.ts +53 -0
  468. package/dist/tools/approvals.controller.js +128 -0
  469. package/dist/tools/approvals.service.d.ts +172 -0
  470. package/dist/tools/approvals.service.js +568 -0
  471. package/dist/tools/breaker.d.ts +28 -0
  472. package/dist/tools/breaker.js +72 -0
  473. package/dist/tools/builtins/agent.d.ts +81 -0
  474. package/dist/tools/builtins/agent.js +223 -0
  475. package/dist/tools/builtins/ask-user.d.ts +28 -0
  476. package/dist/tools/builtins/ask-user.js +139 -0
  477. package/dist/tools/builtins/read-tool-output.d.ts +18 -0
  478. package/dist/tools/builtins/read-tool-output.js +108 -0
  479. package/dist/tools/builtins/sandbox/bash.d.ts +16 -0
  480. package/dist/tools/builtins/sandbox/bash.js +137 -0
  481. package/dist/tools/builtins/sandbox/common.d.ts +114 -0
  482. package/dist/tools/builtins/sandbox/common.js +304 -0
  483. package/dist/tools/builtins/sandbox/deliver.d.ts +5 -0
  484. package/dist/tools/builtins/sandbox/deliver.js +180 -0
  485. package/dist/tools/builtins/sandbox/edit.d.ts +4 -0
  486. package/dist/tools/builtins/sandbox/edit.js +98 -0
  487. package/dist/tools/builtins/sandbox/glob-match.d.ts +25 -0
  488. package/dist/tools/builtins/sandbox/glob-match.js +240 -0
  489. package/dist/tools/builtins/sandbox/glob.d.ts +4 -0
  490. package/dist/tools/builtins/sandbox/glob.js +104 -0
  491. package/dist/tools/builtins/sandbox/grep.d.ts +9 -0
  492. package/dist/tools/builtins/sandbox/grep.js +133 -0
  493. package/dist/tools/builtins/sandbox/index.d.ts +12 -0
  494. package/dist/tools/builtins/sandbox/index.js +27 -0
  495. package/dist/tools/builtins/sandbox/names.d.ts +7 -0
  496. package/dist/tools/builtins/sandbox/names.js +20 -0
  497. package/dist/tools/builtins/sandbox/read.d.ts +8 -0
  498. package/dist/tools/builtins/sandbox/read.js +121 -0
  499. package/dist/tools/builtins/sandbox/write.d.ts +4 -0
  500. package/dist/tools/builtins/sandbox/write.js +88 -0
  501. package/dist/tools/builtins/skill-load.d.ts +53 -0
  502. package/dist/tools/builtins/skill-load.js +446 -0
  503. package/dist/tools/builtins/skill-write.d.ts +59 -0
  504. package/dist/tools/builtins/skill-write.js +244 -0
  505. package/dist/tools/builtins/tool-search.d.ts +31 -0
  506. package/dist/tools/builtins/tool-search.js +171 -0
  507. package/dist/tools/connector-content.controller.d.ts +34 -0
  508. package/dist/tools/connector-content.controller.js +78 -0
  509. package/dist/tools/connector-content.d.ts +104 -0
  510. package/dist/tools/connector-content.js +267 -0
  511. package/dist/tools/connectors.controller.d.ts +210 -0
  512. package/dist/tools/connectors.controller.js +234 -0
  513. package/dist/tools/connectors.service.d.ts +303 -0
  514. package/dist/tools/connectors.service.js +1102 -0
  515. package/dist/tools/credentials.d.ts +139 -0
  516. package/dist/tools/credentials.js +351 -0
  517. package/dist/tools/drift.d.ts +141 -0
  518. package/dist/tools/drift.js +539 -0
  519. package/dist/tools/elicitation.d.ts +81 -0
  520. package/dist/tools/elicitation.js +266 -0
  521. package/dist/tools/endpoint.d.ts +18 -0
  522. package/dist/tools/endpoint.js +115 -0
  523. package/dist/tools/info.d.ts +67 -0
  524. package/dist/tools/info.js +113 -0
  525. package/dist/tools/naming.d.ts +27 -0
  526. package/dist/tools/naming.js +47 -0
  527. package/dist/tools/oauth/discovery.d.ts +65 -0
  528. package/dist/tools/oauth/discovery.js +307 -0
  529. package/dist/tools/oauth/flow.d.ts +63 -0
  530. package/dist/tools/oauth/flow.js +237 -0
  531. package/dist/tools/oauth/oauth.controller.d.ts +31 -0
  532. package/dist/tools/oauth/oauth.controller.js +95 -0
  533. package/dist/tools/oauth/tokens.d.ts +111 -0
  534. package/dist/tools/oauth/tokens.js +258 -0
  535. package/dist/tools/outputs.d.ts +61 -0
  536. package/dist/tools/outputs.js +156 -0
  537. package/dist/tools/pipeline.d.ts +322 -0
  538. package/dist/tools/pipeline.js +1236 -0
  539. package/dist/tools/policy.d.ts +60 -0
  540. package/dist/tools/policy.js +236 -0
  541. package/dist/tools/questions.controller.d.ts +24 -0
  542. package/dist/tools/questions.controller.js +58 -0
  543. package/dist/tools/questions.service.d.ts +103 -0
  544. package/dist/tools/questions.service.js +295 -0
  545. package/dist/tools/redact.d.ts +20 -0
  546. package/dist/tools/redact.js +60 -0
  547. package/dist/tools/registry.d.ts +246 -0
  548. package/dist/tools/registry.js +481 -0
  549. package/dist/tools/resources.d.ts +16 -0
  550. package/dist/tools/resources.js +76 -0
  551. package/dist/tools/specs.d.ts +14 -0
  552. package/dist/tools/specs.js +31 -0
  553. package/dist/tools/validate.d.ts +20 -0
  554. package/dist/tools/validate.js +183 -0
  555. package/dist/tools/view.d.ts +43 -0
  556. package/dist/tools/view.js +225 -0
  557. package/dist/ui/embed-token.d.ts +31 -0
  558. package/dist/ui/embed-token.js +132 -0
  559. package/dist/ui/registry.d.ts +58 -0
  560. package/dist/ui/registry.js +90 -0
  561. package/dist/ui/ui-stream.d.ts +37 -0
  562. package/dist/ui/ui-stream.js +162 -0
  563. package/dist/ui/views.controller.d.ts +81 -0
  564. package/dist/ui/views.controller.js +261 -0
  565. package/dist/ui/views.d.ts +155 -0
  566. package/dist/ui/views.js +306 -0
  567. package/dist/version.d.ts +5 -0
  568. package/dist/version.js +6 -0
  569. package/package.json +67 -3
  570. package/src/migrations/0001_init.sql +8 -0
  571. package/src/migrations/0002_secrets.sql +8 -0
  572. package/src/migrations/0003_identity.sql +117 -0
  573. package/src/migrations/0004_plugin_org_config_nullable.sql +3 -0
  574. package/src/migrations/0005_agents.sql +27 -0
  575. package/src/migrations/0006_conversations.sql +37 -0
  576. package/src/migrations/0007_checkpoints.sql +26 -0
  577. package/src/migrations/0008_runs_trigger_seq.sql +3 -0
  578. package/src/migrations/0009_runs_active_unique.sql +4 -0
  579. package/src/migrations/0010_tools.sql +125 -0
  580. package/src/migrations/0011_approvals_spec.sql +10 -0
  581. package/src/migrations/0012_run_waits.sql +24 -0
  582. package/src/migrations/0013_app_tools_source.sql +4 -0
  583. package/src/migrations/0014_tool_specs.sql +8 -0
  584. package/src/migrations/0015_tool_drift.sql +20 -0
  585. package/src/migrations/0016_plugin_permissions.sql +11 -0
  586. package/src/migrations/0017_identity_sources.sql +6 -0
  587. package/src/migrations/0018_channels.sql +74 -0
  588. package/src/migrations/0019_identity_trust.sql +17 -0
  589. package/src/migrations/0020_channel_card_lookup.sql +10 -0
  590. package/src/migrations/0021_org_storage_purge.sql +9 -0
  591. package/src/migrations/0022_attachments.sql +29 -0
  592. package/src/migrations/0023_sandboxes.sql +37 -0
  593. package/src/migrations/0024_skills.sql +46 -0
  594. package/src/migrations/0025_skills_catalog_idx.sql +6 -0
  595. package/src/migrations/0026_skill_changes.sql +34 -0
  596. package/src/migrations/0027_model_prices.sql +18 -0
  597. package/src/migrations/0028_evals.sql +105 -0
  598. package/src/migrations/0029_model_purposes.sql +18 -0
  599. package/src/migrations/0030_plugin_installs.sql +73 -0
  600. package/src/migrations/0031_plugin_snapshot_source.sql +6 -0
  601. package/src/migrations/0032_plugin_deployment_configs.sql +10 -0
  602. package/src/migrations/0033_rename_apps_to_connectors.sql +25 -0
  603. package/src/migrations/0034_directory.sql +49 -0
  604. package/src/migrations/0035_catalogs.sql +40 -0
  605. package/src/migrations/0036_app_import_staging.sql +17 -0
  606. package/src/migrations/0037_directory_installs.sql +92 -0
  607. package/src/migrations/0038_channel_inbound_mode.sql +6 -0
  608. package/src/migrations/0039_channel_card_pending.sql +8 -0
  609. package/src/migrations/0040_usage_report_sweep.sql +10 -0
  610. package/src/migrations/0041_rename_plugins_to_extensions.sql +38 -0
  611. package/src/migrations/0042_rename_directory_to_catalog.sql +80 -0
  612. package/src/migrations/0043_rename_app_installs_to_plugin_installs.sql +20 -0
  613. package/src/migrations/0044_catalog_one_kind.sql +90 -0
  614. package/src/migrations/0045_plugin_dependencies.sql +9 -0
  615. package/src/migrations/0046_git_sources.sql +25 -0
  616. package/src/migrations/0047_marketplace_entry_heads.sql +7 -0
  617. package/src/migrations/0048_marketplace_entry_trees.sql +8 -0
  618. package/src/migrations/0049_plugin_upgrades.sql +37 -0
  619. package/src/migrations/0050_bootstrap.sql +53 -0
  620. package/src/migrations/0051_claude_compat.sql +26 -0
  621. package/src/migrations/0052_conversations_account_agent_idx.sql +5 -0
  622. package/src/migrations/0053_subagents.sql +73 -0
  623. package/src/migrations/0054_subagent_wait_expiry.sql +7 -0
  624. package/src/migrations/0055_subagent_parent_indexes.sql +10 -0
  625. package/src/migrations/0056_approval_reason.sql +3 -0
  626. package/src/migrations/0057_conversation_titles.sql +11 -0
  627. package/src/migrations/0058_conversation_selection.sql +4 -0
  628. package/src/migrations/0059_conversation_queue.sql +22 -0
  629. package/src/migrations/0060_file_previewers.sql +10 -0
  630. package/src/migrations/0061_queue_selection.sql +5 -0
  631. package/src/migrations/0062_queued_skill.sql +8 -0
@@ -0,0 +1,1217 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { CATALOG_ITEM_NAME_RE, marketplaceEntryVersion, PLUGIN_MANIFEST_FILE, parseMarketplaceManifest, parsePluginManifest, SDK_VERSION, SEMVER_VERSION_RE, } from "@capybara-hq/plugin-sdk";
3
+ import { sql } from "kysely";
4
+ import semver from "semver";
5
+ import { MARKETPLACE_MAX_MIRRORS } from "../config/schema.js";
6
+ import { ConfigError } from "../config/types.js";
7
+ import { createSecrets } from "../ctx/secrets.js";
8
+ import { ApiError } from "../errors.js";
9
+ import { DEPLOYMENT_SECRETS_ORG } from "../extensions/deployment-config.js";
10
+ import { audit } from "../identity/audit.js";
11
+ import { lockEnterpriseAdminsAndAuthorize } from "../identity/enterprise-admin.js";
12
+ import { KERNEL_VERSION } from "../version.js";
13
+ import { CLAUDE_PLUGIN_FILE } from "./claude/manifest.js";
14
+ import { CLAUDE_MARKETPLACE_FILE, parseClaudeMarketplace, } from "./claude/marketplace.js";
15
+ import { expandGitUrl, GitSourceError, gitErrorDetails, gitLocationIdentity, gitUrlIdentity, normalGitPath, readGitTreeAndOids, resolveGitCommit, } from "./git-source.js";
16
+ import { fetchMarketplaceManifest, MarketplaceFetchError, MarketplaceInvalidError, marketplaceNetPolicy, marketplaceUrlProblem, } from "./marketplace-fetch.js";
17
+ import { compareVersions, newestFirst, sameRelease } from "./versions.js";
18
+ /** 官方清单来源的 id。 */
19
+ export const OFFICIAL_MARKETPLACE_ID = "official";
20
+ const OFFICIAL_MARKETPLACE_NAME = "Capybara official marketplace";
21
+ /** 企业清单的可选令牌:secrets 的 namespace `kernel`、org `_deployment`、键 `catalog-token:<id>`(preflight #65)。 */
22
+ const SECRETS_NAMESPACE = "kernel";
23
+ const tokenKey = (id) => `catalog-token:${id}`;
24
+ /** 拉取一份清单的超时。 */
25
+ export const MARKETPLACE_FETCH_TIMEOUT_MS = 30_000;
26
+ /**
27
+ * Overall deadline of the periodic refresh of every marketplace (each URL also has its own
28
+ * {@link MARKETPLACE_FETCH_TIMEOUT_MS}).
29
+ */
30
+ export const MARKETPLACES_REFRESH_DEADLINE_MS = 600_000;
31
+ /** The longest entry summary (SDK `MARKETPLACE_LIMITS.summaryChars`). */
32
+ const SUMMARY_MAX = 280;
33
+ /** `entries()` 一次最多返回的条目数。 */
34
+ export const MARKETPLACE_ENTRIES_MAX = 5000;
35
+ const INSERT_BATCH = 500;
36
+ const MAX_ERROR_CHARS = 1000;
37
+ const MAX_TOKEN_CHARS = 4096;
38
+ /**
39
+ * At most this many branch heads are resolved per refresh (entries that follow a git branch and
40
+ * are already imported; spec §5.3 "content changed at the same version").
41
+ */
42
+ export const MARKETPLACE_HEADS_MAX = 50;
43
+ const HEADS_CONCURRENCY = 4;
44
+ const notFound = () => new ApiError("marketplace.not_found", "Marketplace not found");
45
+ /** A plugin name from a repo URL or path segment (lower case, invalid characters as `-`). */
46
+ export function pluginNameFrom(segment) {
47
+ const name = segment
48
+ .replace(/\.git$/, "")
49
+ .toLowerCase()
50
+ .replace(/[^a-z0-9-]+/g, "-")
51
+ .replace(/^-+/, "")
52
+ .slice(0, 64)
53
+ .replace(/-+$/, "");
54
+ return name === "" ? "plugin" : name;
55
+ }
56
+ const builtinErr = (what) => new ApiError("marketplace.builtin", `The official marketplace is configured in marketplaces.official.*; it cannot be ${what} here (only enabled or disabled)`);
57
+ /** Entry versions newest first; `''` (no version) last. */
58
+ const bySemverDescOrNone = (a, b) => a === b ? 0 : a === "" ? 1 : b === "" ? -1 : compareVersions(b, a);
59
+ const gitDetailOf = (v) => {
60
+ const d = v.source_detail;
61
+ return typeof d?.host === "string" &&
62
+ typeof d.repo === "string" &&
63
+ typeof d.path === "string" &&
64
+ typeof d.commit === "string"
65
+ ? {
66
+ host: d.host,
67
+ repo: d.repo,
68
+ path: d.path,
69
+ commit: d.commit,
70
+ ...(typeof d.tree === "string" && { tree: d.tree }),
71
+ }
72
+ : null;
73
+ };
74
+ const gitCommitOf = (detail) => {
75
+ const c = detail?.commit;
76
+ return typeof c === "string" ? c : null;
77
+ };
78
+ /**
79
+ * Whether the content of an entry changed at a version already imported from the same source (see
80
+ * {@link MarketplaceEntryView.contentChanged}). Only versions imported from what the entry points
81
+ * at count: a git / path entry -> the same git location (host, repo, path); an archive -> an
82
+ * archive from the same URL or listed by this marketplace; npm -> the same package version, from this marketplace or straight from npm, with an
83
+ * integrity. Of those, the entry's release (all of them for an entry without a version). Changed
84
+ * when none of them matches what the entry points at now: npm / archive by integrity; git / path
85
+ * by tree oid when both sides know it (a monorepo commit elsewhere changes nothing), else by
86
+ * commit. Unknown (no head or last commit) -> false.
87
+ */
88
+ export function entryContentChanged(e, at, versions) {
89
+ const src = e.source;
90
+ const version = marketplaceEntryVersion(e);
91
+ let fromSource;
92
+ let same;
93
+ if ("npm" in src) {
94
+ const ref = `${src.npm.package}@${src.npm.version}`;
95
+ fromSource = (v) => v.integrity !== null &&
96
+ ((v.source === "marketplace" && v.source_ref === `${at.marketplaceId}:${ref}`) ||
97
+ (v.source === "npm" && v.source_ref === ref));
98
+ same = (v) => v.integrity === src.npm.integrity;
99
+ }
100
+ else if ("archive" in src) {
101
+ const u = new URL(src.archive.url);
102
+ const url = `${u.origin}${u.pathname}`;
103
+ // the same URL, or an archive this marketplace listed before (archive URLs often carry the
104
+ // version, so a re-hosted archive of the same release is still this entry's)
105
+ fromSource = (v) => v.integrity !== null &&
106
+ v.source === "marketplace" &&
107
+ (v.source_detail?.url === url ||
108
+ (v.source_ref.startsWith(`${at.marketplaceId}:`) &&
109
+ typeof v.source_detail?.url === "string"));
110
+ same = (v) => v.integrity === src.archive.integrity;
111
+ }
112
+ else {
113
+ const where = "git" in src ? gitUrlIdentity(src.git.url, src.git.path) : gitUrlIdentity(at.url, src.path);
114
+ const commit = "git" in src ? (src.git.commit?.toLowerCase() ?? at.headCommit) : at.lastCommit;
115
+ if (commit === null)
116
+ return false;
117
+ fromSource = (v) => {
118
+ const d = gitDetailOf(v);
119
+ return d !== null && gitLocationIdentity(d) === where;
120
+ };
121
+ same = (v) => {
122
+ const d = gitDetailOf(v);
123
+ return (d.commit === commit ||
124
+ (at.treeOid !== null && d.tree !== undefined && d.tree === at.treeOid));
125
+ };
126
+ }
127
+ const candidates = versions.filter((v) => fromSource(v) && (version === null || sameRelease(v.version, version)));
128
+ return candidates.length > 0 && !candidates.some(same);
129
+ }
130
+ /**
131
+ * The directories of the path entries of a git marketplace's manifest (read leniently: the
132
+ * manifest is validated afterwards), or the root when there is none (a single-plugin repo).
133
+ */
134
+ function pathEntryDirs(raw, maxBytes) {
135
+ if (raw === undefined)
136
+ return ["."];
137
+ if (raw.byteLength > maxBytes)
138
+ return [];
139
+ try {
140
+ const plugins = JSON.parse(new TextDecoder().decode(raw)).plugins;
141
+ if (!Array.isArray(plugins))
142
+ return [];
143
+ const dirs = new Set();
144
+ for (const p of plugins) {
145
+ const path = p?.source?.path;
146
+ if (typeof path === "string")
147
+ dirs.add(normalGitPath(path));
148
+ }
149
+ return [...dirs];
150
+ }
151
+ catch {
152
+ return [];
153
+ }
154
+ }
155
+ /** {@link pathEntryDirs} of a Claude Code marketplace (read leniently, like the native one). */
156
+ function claudePathEntryDirs(raw, maxBytes, allowInsecureLoopback) {
157
+ if (raw.byteLength > maxBytes)
158
+ return [];
159
+ try {
160
+ const parsed = parseClaudeMarketplace(JSON.parse(new TextDecoder().decode(raw)), {
161
+ allowInsecureLoopback,
162
+ });
163
+ if ("errors" in parsed)
164
+ return [];
165
+ const dirs = new Set();
166
+ for (const e of parsed.manifest.plugins) {
167
+ if ("path" in e.source)
168
+ dirs.add(normalGitPath(e.source.path));
169
+ }
170
+ return [...dirs];
171
+ }
172
+ catch {
173
+ return [];
174
+ }
175
+ }
176
+ /**
177
+ * The one entry of a repository that is a single Claude Code plugin (`.claude-plugin/plugin.json`
178
+ * at the root): named by its `name`, with its description and semver version when it has them.
179
+ */
180
+ function claudeSinglePluginEntry(text) {
181
+ let m;
182
+ try {
183
+ m = JSON.parse(text.startsWith("\uFEFF") ? text.slice(1) : text);
184
+ }
185
+ catch {
186
+ throw new MarketplaceInvalidError(`the repository is a single plugin, but its ${CLAUDE_PLUGIN_FILE} is not valid JSON`, []);
187
+ }
188
+ const o = (typeof m === "object" && m !== null ? m : {});
189
+ if (typeof o.name !== "string" || !CATALOG_ITEM_NAME_RE.test(o.name)) {
190
+ throw new MarketplaceInvalidError(`the repository is a single plugin, but its ${CLAUDE_PLUGIN_FILE} name must match ^[a-z0-9][a-z0-9-]{0,63}$`, []);
191
+ }
192
+ const desc = typeof o.description === "string" ? o.description.trim().slice(0, SUMMARY_MAX) : "";
193
+ return {
194
+ name: o.name,
195
+ summary: { en: desc !== "" ? desc : `Claude Code plugin ${o.name}` },
196
+ ...(typeof o.version === "string" &&
197
+ SEMVER_VERSION_RE.test(o.version) && { version: o.version }),
198
+ source: { path: "." },
199
+ };
200
+ }
201
+ /**
202
+ * Which entries a refresh resolves the branch head of: git entries without a pinned commit, on a
203
+ * host `marketplaces.allowedHosts` lets through, with a version imported from the same git
204
+ * location (of the entry's release; any, for an entry without a version), at most `max`, by name.
205
+ */
206
+ export function headsToResolve(entries, imported, config, max) {
207
+ return entries
208
+ .filter((e) => "git" in e.source && e.source.git.commit === undefined)
209
+ .filter((e) => {
210
+ const host = entrySourceHost(e);
211
+ if (host === undefined || !hostAllowed(host, config))
212
+ return false;
213
+ const where = gitUrlIdentity(e.source.git.url, e.source.git.path);
214
+ const version = marketplaceEntryVersion(e);
215
+ return imported.some((v) => {
216
+ const d = gitDetailOf(v);
217
+ return (v.name === e.name &&
218
+ d !== null &&
219
+ gitLocationIdentity(d) === where &&
220
+ (version === null || sameRelease(v.version, version)));
221
+ });
222
+ })
223
+ .sort((a, b) => a.name.localeCompare(b.name))
224
+ .slice(0, max);
225
+ }
226
+ /** The host versions (an entry's `minKernel` is judged against them). */
227
+ export const HOST_VERSIONS = { kernel: KERNEL_VERSION, sdk: SDK_VERSION };
228
+ /** Whether this deployment satisfies the entry's `minKernel`. */
229
+ export const entryCompatible = (e) => e.minKernel === undefined || semver.gte(HOST_VERSIONS.kernel, e.minKernel);
230
+ /**
231
+ * The form of a marketplace address (spec §5.1): `owner/repo` (expanded to GitHub), an https URL
232
+ * whose path ends in `.git`, or any URL with `form: "git"` is a git repository; anything else
233
+ * (a `.json` URL included) is an http marketplace.json URL. `form` given wins.
234
+ */
235
+ export function marketplaceForm(url, form) {
236
+ if (form === "http")
237
+ return { form, url };
238
+ const expanded = expandGitUrl(url);
239
+ if (form === "git" || expanded !== url)
240
+ return { form: "git", url: expanded };
241
+ try {
242
+ if (/\.git\/?$/.test(new URL(url).pathname))
243
+ return { form: "git", url };
244
+ }
245
+ catch {
246
+ // not a URL: checked (and refused) by the caller
247
+ }
248
+ return { form: "http", url };
249
+ }
250
+ /** The host a git / archive entry downloads from (lower case), undefined for npm / path. */
251
+ export function entrySourceHost(e) {
252
+ const url = "git" in e.source ? e.source.git.url : "archive" in e.source ? e.source.archive.url : undefined;
253
+ if (url === undefined)
254
+ return undefined;
255
+ try {
256
+ // IPv6 hostnames come bracketed from URL; allowedHosts entries are written without brackets
257
+ return new URL(url).hostname.toLowerCase().replace(/^\[(.*)\]$/, "$1");
258
+ }
259
+ catch {
260
+ return undefined;
261
+ }
262
+ }
263
+ /** Whether `marketplaces.allowedHosts` lets an entry download from `host` (empty: any host). */
264
+ export function hostAllowed(host, config) {
265
+ const allowed = config.marketplaces.allowedHosts;
266
+ return allowed.length === 0 || allowed.includes(host.toLowerCase());
267
+ }
268
+ /** Why an entry is not importable from a marketplace of `form`, or undefined when it is. */
269
+ function unimportable(e, form, config) {
270
+ if ("path" in e.source && form !== "git")
271
+ return "path_needs_git_marketplace";
272
+ const host = entrySourceHost(e);
273
+ if (host !== undefined && !hostAllowed(host, config))
274
+ return "host_not_allowed";
275
+ return undefined;
276
+ }
277
+ /** 来源是否生效:本身启用,official 还要配置没关。 */
278
+ export const sourceEffective = (row, config) => row.enabled && (!row.builtin || config.marketplaces.official.enabled);
279
+ /**
280
+ * Reads a marketplace entry for import: `version` given -> the entry listing that version; omitted
281
+ * -> the newest compatible versioned entry, else the entry without a version, else the newest
282
+ * versioned one (refused later as incompatible). Marketplace missing -> `marketplace.not_found`;
283
+ * marketplace disabled (official offline included), entry or version missing ->
284
+ * `marketplace.entry_not_found`.
285
+ */
286
+ export async function loadMarketplaceEntry(db, config, sourceId, name, version) {
287
+ const src = await db
288
+ .selectFrom("marketplaces")
289
+ .select(["name", "builtin", "enabled", "url", "form", "ref", "last_commit", "owner_name"])
290
+ .where("id", "=", sourceId)
291
+ .executeTakeFirst();
292
+ if (!src)
293
+ throw notFound();
294
+ const rows = sourceEffective(src, config)
295
+ ? await db
296
+ .selectFrom("marketplace_entries")
297
+ .select(["version", "entry", "claude"])
298
+ .where("source_id", "=", sourceId)
299
+ .where("name", "=", name)
300
+ .execute()
301
+ : [];
302
+ const entries = rows.map((r) => ({
303
+ version: r.version,
304
+ entry: r.entry,
305
+ claude: r.claude ?? undefined,
306
+ }));
307
+ let pickedRow;
308
+ if (version !== undefined) {
309
+ pickedRow = entries.find((e) => e.version === version);
310
+ }
311
+ else {
312
+ const versioned = entries
313
+ .filter((e) => e.version !== "")
314
+ .sort((a, b) => newestFirst(a.version, b.version));
315
+ pickedRow =
316
+ versioned.find((e) => entryCompatible(e.entry)) ??
317
+ entries.find((e) => e.version === "") ??
318
+ versioned[0];
319
+ }
320
+ const picked = pickedRow?.entry;
321
+ if (!picked) {
322
+ throw new ApiError("marketplace.entry_not_found", version !== undefined
323
+ ? `Version ${version} of marketplace entry ${name} not found`
324
+ : `Marketplace entry ${name} not found`);
325
+ }
326
+ return {
327
+ entry: picked,
328
+ ...(pickedRow?.claude !== undefined && { claude: pickedRow.claude }),
329
+ marketplace: {
330
+ url: src.url,
331
+ form: src.form,
332
+ ref: src.ref,
333
+ lastCommit: src.last_commit,
334
+ ownerName: src.owner_name,
335
+ builtin: src.builtin,
336
+ },
337
+ };
338
+ }
339
+ /**
340
+ * Marketplaces (D16 / C25 / C26, `/api/v1/admin/marketplaces/*`, enterprise admins only): the official
341
+ * marketplace (config `marketplaces.official.*`, synced at startup into the marketplace with id
342
+ * `official`) and enterprise marketplaces (PG, optional token in secrets). A refresh (maintenance
343
+ * job `catalogs-refresh` every `marketplaces.refreshIntervalMinutes` minutes, or manual) tries the main
344
+ * URL and then the mirrors with If-None-Match; on success it replaces that marketplace's cached
345
+ * entries as a whole, when all fail (an invalid manifest included) it keeps the previous ones with
346
+ * status `error`. A status change audits `marketplace.refreshed` and logs once. Stateless, shared
347
+ * by every replica.
348
+ */
349
+ export class MarketplacesService {
350
+ d;
351
+ constructor(d) {
352
+ this.d = d;
353
+ }
354
+ /**
355
+ * Creates / updates the official marketplace from config (name, URL, its form per
356
+ * {@link marketplaceForm}, mirrors; the admin's enable switch is left alone). Called by every
357
+ * replica at start; idempotent. A changed URL or form drops the ETag and the last git commit, so
358
+ * the next refresh reads everything (the entries stay until then). A git URL with mirrors is a
359
+ * config error (the config schema refuses it first; checked again here for configs built
360
+ * without the schema).
361
+ */
362
+ async syncOfficialFromConfig() {
363
+ const o = this.d.config().marketplaces.official;
364
+ const { form, url } = marketplaceForm(o.url);
365
+ if (form === "git" && o.mirrors.length > 0) {
366
+ throw new ConfigError("invalid configuration:\n - marketplaces.official.mirrors: a git marketplace has no mirrors (point marketplaces.official.url at a mirror repository instead)");
367
+ }
368
+ const mirrors = JSON.stringify(o.mirrors);
369
+ const same = sql `marketplaces.url = excluded.url AND marketplaces.form = excluded.form`;
370
+ await this.d.db
371
+ .insertInto("marketplaces")
372
+ .values({
373
+ id: OFFICIAL_MARKETPLACE_ID,
374
+ name: OFFICIAL_MARKETPLACE_NAME,
375
+ url,
376
+ form,
377
+ mirrors,
378
+ builtin: true,
379
+ enabled: true,
380
+ })
381
+ .onConflict((oc) => oc.column("id").doUpdateSet({
382
+ name: OFFICIAL_MARKETPLACE_NAME,
383
+ url,
384
+ form,
385
+ // the official marketplace follows the remote HEAD
386
+ ref: null,
387
+ mirrors,
388
+ builtin: true,
389
+ etag: sql `CASE WHEN ${same} THEN marketplaces.etag ELSE NULL END`,
390
+ last_commit: sql `CASE WHEN ${same} THEN marketplaces.last_commit ELSE NULL END`,
391
+ }))
392
+ .execute();
393
+ }
394
+ /** 全部来源(official 在前,其余按创建时间)。 */
395
+ async list() {
396
+ const rows = (await this.d.db
397
+ .selectFrom("marketplaces")
398
+ .selectAll()
399
+ .orderBy("builtin", "desc")
400
+ .orderBy("created_at")
401
+ .orderBy("id")
402
+ .execute());
403
+ return this.views(rows);
404
+ }
405
+ async get(id) {
406
+ const row = (await this.d.db
407
+ .selectFrom("marketplaces")
408
+ .selectAll()
409
+ .where("id", "=", id)
410
+ .executeTakeFirst());
411
+ if (!row)
412
+ throw notFound();
413
+ return (await this.views([row]))[0];
414
+ }
415
+ /**
416
+ * Adds an enterprise marketplace (audit `marketplace.added`). URL and mirrors must be https,
417
+ * without user info. The form follows {@link marketplaceForm}; a git marketplace may name a `ref`
418
+ * (branch or tag, default the remote HEAD) and has no mirrors.
419
+ */
420
+ async add(actor, input, opts = {}) {
421
+ const name = this.checkName(input.name);
422
+ const { form, url } = marketplaceForm(input.url, input.form);
423
+ this.checkUrls(url, input.mirrors ?? []);
424
+ this.checkGitFields(form, input.ref ?? null, input.mirrors ?? []);
425
+ if (input.token !== undefined)
426
+ this.checkToken(input.token);
427
+ const id = randomUUID();
428
+ await this.d.db.transaction().execute(async (trx) => {
429
+ await lockEnterpriseAdminsAndAuthorize(trx, actor);
430
+ await trx
431
+ .insertInto("marketplaces")
432
+ .values({
433
+ id,
434
+ name,
435
+ url,
436
+ form,
437
+ ref: input.ref ?? null,
438
+ mirrors: JSON.stringify(input.mirrors ?? []),
439
+ builtin: false,
440
+ enabled: true,
441
+ created_by: actor,
442
+ })
443
+ .execute();
444
+ if (input.token !== undefined) {
445
+ await this.secrets(trx).set(DEPLOYMENT_SECRETS_ORG, tokenKey(id), input.token);
446
+ }
447
+ await audit(trx, {
448
+ actorAccountId: actor,
449
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
450
+ action: "marketplace.added",
451
+ target: id,
452
+ detail: {
453
+ name,
454
+ url,
455
+ form,
456
+ ...(input.ref !== undefined && { ref: input.ref }),
457
+ mirrors: input.mirrors ?? [],
458
+ hasToken: input.token !== undefined,
459
+ },
460
+ });
461
+ });
462
+ return this.get(id);
463
+ }
464
+ /**
465
+ * Updates an enterprise marketplace (audit `marketplace.updated`, listing the changed fields, the
466
+ * token's value never recorded; not written when unchanged). `token: null` clears the token.
467
+ * Official only takes `enabled`, any other field -> `marketplace.builtin`. Changing the URL, form
468
+ * or ref clears the ETag and the last commit (the next refresh reads everything again); a new URL
469
+ * gets its form from {@link marketplaceForm} unless `form` is given. `ref: null` follows the
470
+ * remote HEAD. Switching the form clears the other form's field (the ref when becoming http,
471
+ * the mirrors when becoming git) unless the patch sets it.
472
+ */
473
+ async update(actor, id, patch, opts = {}) {
474
+ const name = patch.name !== undefined ? this.checkName(patch.name) : undefined;
475
+ await this.d.db.transaction().execute(async (trx) => {
476
+ await lockEnterpriseAdminsAndAuthorize(trx, actor);
477
+ const row = (await trx
478
+ .selectFrom("marketplaces")
479
+ .selectAll()
480
+ .where("id", "=", id)
481
+ .forUpdate()
482
+ .executeTakeFirst());
483
+ if (!row)
484
+ throw notFound();
485
+ if (row.builtin &&
486
+ (name !== undefined ||
487
+ patch.url !== undefined ||
488
+ patch.form !== undefined ||
489
+ patch.ref !== undefined ||
490
+ patch.mirrors !== undefined ||
491
+ patch.token !== undefined)) {
492
+ throw builtinErr("changed");
493
+ }
494
+ const target = patch.url !== undefined || patch.form !== undefined
495
+ ? marketplaceForm(patch.url ?? row.url, patch.form)
496
+ : { form: row.form, url: row.url };
497
+ // Switching form clears the other form's fields unless the patch sets them: the ref when
498
+ // becoming http, the mirrors when becoming git
499
+ const switched = target.form !== row.form;
500
+ const ref = patch.ref !== undefined ? patch.ref : switched && target.form === "http" ? null : row.ref;
501
+ const mirrors = patch.mirrors ?? (switched && target.form === "git" ? [] : row.mirrors);
502
+ this.checkUrls(target.url, mirrors);
503
+ this.checkGitFields(target.form, ref, mirrors);
504
+ if (typeof patch.token === "string")
505
+ this.checkToken(patch.token);
506
+ const changed = [];
507
+ const set = {};
508
+ if (name !== undefined && name !== row.name) {
509
+ set.name = name;
510
+ changed.push("name");
511
+ }
512
+ if (target.url !== row.url) {
513
+ set.url = target.url;
514
+ changed.push("url");
515
+ }
516
+ if (target.form !== row.form) {
517
+ set.form = target.form;
518
+ changed.push("form");
519
+ }
520
+ if (ref !== row.ref) {
521
+ set.ref = ref;
522
+ changed.push("ref");
523
+ }
524
+ if (set.url !== undefined || set.form !== undefined || set.ref !== undefined) {
525
+ set.etag = null;
526
+ set.last_commit = null;
527
+ }
528
+ if (JSON.stringify(mirrors) !== JSON.stringify(row.mirrors)) {
529
+ set.mirrors = JSON.stringify(mirrors);
530
+ changed.push("mirrors");
531
+ }
532
+ if (patch.enabled !== undefined && patch.enabled !== row.enabled) {
533
+ set.enabled = patch.enabled;
534
+ changed.push("enabled");
535
+ }
536
+ if (Object.keys(set).length > 0) {
537
+ await trx.updateTable("marketplaces").set(set).where("id", "=", id).execute();
538
+ }
539
+ if (patch.token !== undefined) {
540
+ const secrets = this.secrets(trx);
541
+ const had = (await secrets.get(DEPLOYMENT_SECRETS_ORG, tokenKey(id))) !== null;
542
+ if (patch.token === null) {
543
+ if (had) {
544
+ await secrets.delete(DEPLOYMENT_SECRETS_ORG, tokenKey(id));
545
+ changed.push("token");
546
+ }
547
+ }
548
+ else {
549
+ await secrets.set(DEPLOYMENT_SECRETS_ORG, tokenKey(id), patch.token);
550
+ changed.push("token");
551
+ }
552
+ }
553
+ if (changed.length === 0)
554
+ return;
555
+ await audit(trx, {
556
+ actorAccountId: actor,
557
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
558
+ action: "marketplace.updated",
559
+ target: id,
560
+ detail: {
561
+ changed,
562
+ ...(set.enabled !== undefined && { enabled: set.enabled }),
563
+ ...(set.url !== undefined && { url: set.url }),
564
+ ...(set.form !== undefined && { form: set.form }),
565
+ ...(set.ref !== undefined && { ref: set.ref }),
566
+ },
567
+ });
568
+ });
569
+ return this.get(id);
570
+ }
571
+ /**
572
+ * Deletes an enterprise marketplace (audit `marketplace.removed`): its cached entries and token
573
+ * are deleted; imported catalog items are kept (their marketplace becomes null). Official can
574
+ * only be disabled -> `marketplace.builtin`.
575
+ */
576
+ async remove(actor, id, opts = {}) {
577
+ await this.d.db.transaction().execute(async (trx) => {
578
+ await lockEnterpriseAdminsAndAuthorize(trx, actor);
579
+ const row = await trx
580
+ .selectFrom("marketplaces")
581
+ .select(["builtin", "name", "url"])
582
+ .where("id", "=", id)
583
+ .forUpdate()
584
+ .executeTakeFirst();
585
+ if (!row)
586
+ throw notFound();
587
+ if (row.builtin)
588
+ throw builtinErr("deleted");
589
+ await trx.deleteFrom("marketplaces").where("id", "=", id).execute();
590
+ await this.secrets(trx).delete(DEPLOYMENT_SECRETS_ORG, tokenKey(id));
591
+ await audit(trx, {
592
+ actorAccountId: actor,
593
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
594
+ action: "marketplace.removed",
595
+ target: id,
596
+ detail: { name: row.name, url: row.url },
597
+ });
598
+ });
599
+ }
600
+ /**
601
+ * Refreshes one marketplace: the main URL, then the mirrors (with If-None-Match, at most
602
+ * `marketplaces.maxIndexBytes`); 304 keeps the entries, a valid 200 replaces them as a whole; when all
603
+ * fail the old entries are kept with status `error`. A disabled marketplace -> 409 `conflict`.
604
+ * A status change audits `marketplace.refreshed {from, to}` (a periodic refresh has no actor) and
605
+ * logs once. If the URL / mirrors changed during the fetch: the result is discarded and nothing is
606
+ * written (M-1).
607
+ * `throwOnError` (manual refresh): after recording the status, a failure throws 502 —
608
+ * `marketplace.invalid` when some URL returned an invalid manifest, else `marketplace.fetch_failed`
609
+ * (the message gives each URL's failure).
610
+ */
611
+ async refresh(id, signal, actor, opts = {}) {
612
+ const config = this.d.config();
613
+ const row = (await this.d.db
614
+ .selectFrom("marketplaces")
615
+ .selectAll()
616
+ .where("id", "=", id)
617
+ .executeTakeFirst());
618
+ if (!row)
619
+ throw notFound();
620
+ if (!sourceEffective(row, config)) {
621
+ throw new ApiError("conflict", row.builtin && !config.marketplaces.official.enabled
622
+ ? "The official marketplace is disabled by marketplaces.official.enabled (offline mode)"
623
+ : "This marketplace is disabled; enable it before refreshing");
624
+ }
625
+ const token = row.builtin
626
+ ? undefined
627
+ : ((await this.secrets(this.d.db).get(DEPLOYMENT_SECRETS_ORG, tokenKey(id))) ?? undefined);
628
+ const tokenOrigin = new URL(row.url).origin;
629
+ const errors = [];
630
+ // the API code of each failed URL (undefined: an ordinary fetch failure)
631
+ const netCodes = new Set();
632
+ let sawInvalid = false;
633
+ // what the last refused URL names (host, repo, ref), for the error's details
634
+ let lastDetail;
635
+ let outcome;
636
+ if (row.form === "git") {
637
+ try {
638
+ outcome = await this.fetchGit(row, token, signal);
639
+ }
640
+ catch (e) {
641
+ if (!(e instanceof GitSourceError || e instanceof MarketplaceInvalidError))
642
+ throw e;
643
+ if (e instanceof MarketplaceInvalidError)
644
+ sawInvalid = true;
645
+ else {
646
+ netCodes.add(e.netCode ?? e.code);
647
+ lastDetail = gitErrorDetails(e, {
648
+ url: row.url,
649
+ ...(row.ref !== null && { ref: row.ref }),
650
+ });
651
+ }
652
+ errors.push(`${row.url}: ${e.message}`);
653
+ }
654
+ }
655
+ for (const url of row.form === "git" ? [] : [row.url, ...row.mirrors]) {
656
+ if (signal.aborted)
657
+ break;
658
+ try {
659
+ const r = await fetchMarketplaceManifest(url, {
660
+ etag: row.etag,
661
+ ...(token !== undefined && { token, tokenOrigin }),
662
+ maxBytes: config.marketplaces.maxIndexBytes,
663
+ timeoutMs: this.d.fetchTimeoutMs ?? MARKETPLACE_FETCH_TIMEOUT_MS,
664
+ net: marketplaceNetPolicy(config),
665
+ signal,
666
+ });
667
+ outcome = r.status === "not_modified" ? "not_modified" : r;
668
+ break;
669
+ }
670
+ catch (e) {
671
+ if (!(e instanceof MarketplaceFetchError || e instanceof MarketplaceInvalidError))
672
+ throw e;
673
+ if (e instanceof MarketplaceInvalidError)
674
+ sawInvalid = true;
675
+ else {
676
+ netCodes.add(e.code);
677
+ if (e.host !== undefined)
678
+ lastDetail = { host: e.host };
679
+ }
680
+ errors.push(`${url}: ${e.message}`);
681
+ }
682
+ }
683
+ if (outcome === undefined && errors.length === 0)
684
+ errors.push("refresh aborted");
685
+ const status = outcome === undefined ? "error" : "ok";
686
+ // the branch heads of imported branch-following git entries (outside the transaction)
687
+ const heads = outcome === undefined
688
+ ? new Map()
689
+ : await this.resolveHeads(id, outcome === "not_modified" ? undefined : outcome.manifest.plugins, { url: row.url, token }, signal);
690
+ const lastError = status === "error" ? errors.join("\n").slice(0, MAX_ERROR_CHARS) : null;
691
+ const from = await this.d.db.transaction().execute(async (trx) => {
692
+ await sql `SELECT pg_advisory_xact_lock(hashtext(${`capybara.catalog-source:${id}`}))`.execute(trx);
693
+ const cur = await trx
694
+ .selectFrom("marketplaces")
695
+ .select(["last_status", "url", "mirrors", "form", "ref", "last_commit"])
696
+ .where("id", "=", id)
697
+ .forUpdate()
698
+ .executeTakeFirst();
699
+ if (!cur)
700
+ throw notFound();
701
+ // The admin changed the URL / mirrors during the fetch: what we fetched is the old address's
702
+ // content, so write no entries, ETag or status (the next refresh uses the new address)
703
+ if (cur.url !== row.url ||
704
+ cur.form !== row.form ||
705
+ cur.ref !== row.ref ||
706
+ // another refresh wrote a commit meanwhile: ours may be older, never move it back
707
+ cur.last_commit !== row.last_commit ||
708
+ JSON.stringify(cur.mirrors) !== JSON.stringify(row.mirrors)) {
709
+ return "stale";
710
+ }
711
+ if (outcome !== undefined && outcome !== "not_modified") {
712
+ await this.replaceEntries(trx, id, outcome.manifest.plugins, outcome.trees, outcome.overlays);
713
+ }
714
+ if (outcome !== undefined) {
715
+ // heads not resolved this time are unknown again (never a stale head)
716
+ await trx
717
+ .updateTable("marketplace_entries")
718
+ .set({ head_commit: null, tree_oid: null })
719
+ .where("source_id", "=", id)
720
+ .where("head_commit", "is not", null)
721
+ .execute();
722
+ }
723
+ for (const [key, head] of heads) {
724
+ const [name, version] = key.split("\0");
725
+ await trx
726
+ .updateTable("marketplace_entries")
727
+ .set({ head_commit: head.commit, tree_oid: head.tree })
728
+ .where("source_id", "=", id)
729
+ .where("name", "=", name)
730
+ .where("version", "=", version)
731
+ .execute();
732
+ }
733
+ await trx
734
+ .updateTable("marketplaces")
735
+ .set({
736
+ last_status: status,
737
+ last_error: lastError,
738
+ last_refreshed_at: sql `now()`,
739
+ ...(outcome !== undefined &&
740
+ outcome !== "not_modified" && {
741
+ etag: outcome.etag,
742
+ last_commit: outcome.commit ?? null,
743
+ owner_name: outcome.manifest.owner?.name ?? null,
744
+ skipped: JSON.stringify(outcome.skipped ?? []),
745
+ }),
746
+ })
747
+ .where("id", "=", id)
748
+ .execute();
749
+ if (cur.last_status !== status) {
750
+ await audit(trx, {
751
+ ...(actor !== undefined && { actorAccountId: actor }),
752
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
753
+ action: "marketplace.refreshed",
754
+ target: id,
755
+ detail: {
756
+ from: cur.last_status,
757
+ to: status,
758
+ ...(lastError !== null && { error: lastError }),
759
+ },
760
+ });
761
+ }
762
+ return cur.last_status;
763
+ });
764
+ if (from === "stale") {
765
+ this.d.log.info("marketplace changed during refresh; result discarded", { marketplace: id });
766
+ return this.get(id);
767
+ }
768
+ if (from !== status) {
769
+ if (status === "error") {
770
+ this.d.log.warn("marketplace refresh failed", { marketplace: id, error: lastError });
771
+ }
772
+ else {
773
+ this.d.log.info("marketplace refreshed", { marketplace: id });
774
+ }
775
+ }
776
+ if (status === "error" && opts.throwOnError) {
777
+ // every URL refused by the same network rule (or the git reader's own code) -> that code,
778
+ // else 502
779
+ const [only] = netCodes;
780
+ const netCode = !sawInvalid && netCodes.size === 1 ? only : undefined;
781
+ throw new ApiError(sawInvalid ? "marketplace.invalid" : (netCode ?? "marketplace.fetch_failed"), `Marketplace refresh failed; the previous entries are kept. ${lastError ?? ""}`.trim(), netCode !== undefined && lastDetail !== undefined ? [lastDetail] : undefined);
782
+ }
783
+ return this.get(id);
784
+ }
785
+ /**
786
+ * Reads a git marketplace (spec §5.1): resolves `ref` (default the remote HEAD); the same commit
787
+ * as the last refresh -> `not_modified` (nothing else is fetched). Otherwise only what the root
788
+ * needs is read, in this order: `.capybara-plugin/marketplace.json` (at most
789
+ * `marketplaces.maxIndexBytes`, validated like an http manifest); `.claude-plugin/marketplace.json`
790
+ * (a Claude Code marketplace: each entry's component fields and `strict` become its overlay, and
791
+ * entries Capybara cannot store are `skipped`); else a single-plugin repo (the root has
792
+ * `.capybara-plugin/plugin.json`, `.claude-plugin/plugin.json` or `skills/`) becomes one entry
793
+ * with source path `.`, named by plugin.json or, for a skills-only repo, by the repo name
794
+ * (Decision 3). Throws {@link GitSourceError} or {@link MarketplaceInvalidError}.
795
+ */
796
+ async fetchGit(row, token, signal) {
797
+ const config = this.d.config();
798
+ const options = {
799
+ net: marketplaceNetPolicy(config),
800
+ maxBytes: config.packages.maxBytes,
801
+ signal,
802
+ ...(token !== undefined && { token, tokenOrigin: new URL(row.url).origin }),
803
+ };
804
+ const loc = { url: row.url, ...(row.ref !== null && { ref: row.ref }) };
805
+ const commit = await resolveGitCommit(loc, options);
806
+ if (commit === row.last_commit)
807
+ return "not_modified";
808
+ const MANIFEST = ".capybara-plugin/marketplace.json";
809
+ let hasSkills = false;
810
+ const dec = (b) => new TextDecoder().decode(b);
811
+ const { files, trees } = await readGitTreeAndOids({ url: row.url, commit }, {
812
+ ...options,
813
+ include: (path, type) => {
814
+ if (path === "skills" && type === "tree")
815
+ hasSkills = true;
816
+ return (path === ".capybara-plugin" ||
817
+ path === MANIFEST ||
818
+ path === PLUGIN_MANIFEST_FILE ||
819
+ path === ".claude-plugin" ||
820
+ path === CLAUDE_MARKETPLACE_FILE ||
821
+ path === CLAUDE_PLUGIN_FILE);
822
+ },
823
+ // the directory of every path entry (the root for a single-plugin repo)
824
+ trees: (read) => read.has(MANIFEST) || !read.has(CLAUDE_MARKETPLACE_FILE)
825
+ ? pathEntryDirs(read.get(MANIFEST), config.marketplaces.maxIndexBytes)
826
+ : claudePathEntryDirs(read.get(CLAUDE_MARKETPLACE_FILE), config.marketplaces.maxIndexBytes, config.marketplaces.allowInsecureLoopback),
827
+ });
828
+ const raw = files.get(MANIFEST);
829
+ const claudeRaw = files.get(CLAUDE_MARKETPLACE_FILE);
830
+ let manifest;
831
+ if (raw === undefined && claudeRaw !== undefined) {
832
+ if (claudeRaw.byteLength > config.marketplaces.maxIndexBytes) {
833
+ throw new MarketplaceInvalidError(`${CLAUDE_MARKETPLACE_FILE} is larger than ${config.marketplaces.maxIndexBytes} bytes`, []);
834
+ }
835
+ let json;
836
+ try {
837
+ json = JSON.parse(dec(claudeRaw));
838
+ }
839
+ catch {
840
+ throw new MarketplaceInvalidError(`${CLAUDE_MARKETPLACE_FILE} is not valid JSON`, []);
841
+ }
842
+ const parsed = parseClaudeMarketplace(json, {
843
+ allowInsecureLoopback: config.marketplaces.allowInsecureLoopback,
844
+ });
845
+ if ("errors" in parsed) {
846
+ throw new MarketplaceInvalidError(`${CLAUDE_MARKETPLACE_FILE} is not a valid Claude Code marketplace: ${parsed.errors.join("; ")}`, parsed.errors);
847
+ }
848
+ return {
849
+ manifest: parsed.manifest,
850
+ etag: null,
851
+ commit,
852
+ trees,
853
+ overlays: parsed.overlays,
854
+ skipped: parsed.skipped,
855
+ };
856
+ }
857
+ if (raw !== undefined) {
858
+ if (raw.byteLength > config.marketplaces.maxIndexBytes) {
859
+ throw new MarketplaceInvalidError(`${MANIFEST} is larger than ${config.marketplaces.maxIndexBytes} bytes`, []);
860
+ }
861
+ let json;
862
+ try {
863
+ json = JSON.parse(dec(raw));
864
+ }
865
+ catch {
866
+ throw new MarketplaceInvalidError(`${MANIFEST} is not valid JSON`, []);
867
+ }
868
+ const parsed = parseMarketplaceManifest(json, {
869
+ allowInsecureLoopback: config.marketplaces.allowInsecureLoopback,
870
+ });
871
+ if ("errors" in parsed) {
872
+ throw new MarketplaceInvalidError(`${MANIFEST} is not a valid marketplace.json: ${parsed.errors.slice(0, 5).join("; ")}`, parsed.errors);
873
+ }
874
+ manifest = parsed;
875
+ }
876
+ else {
877
+ const pj = files.get(PLUGIN_MANIFEST_FILE);
878
+ let entry;
879
+ if (pj !== undefined) {
880
+ const m = parsePluginManifest(dec(pj));
881
+ if ("errors" in m) {
882
+ throw new MarketplaceInvalidError(`the repository is a single plugin, but its ${PLUGIN_MANIFEST_FILE} is invalid: ${m.errors.slice(0, 5).join("; ")}`, m.errors);
883
+ }
884
+ entry = {
885
+ name: m.name,
886
+ summary: { en: m.description.slice(0, SUMMARY_MAX) },
887
+ version: m.version,
888
+ source: { path: "." },
889
+ };
890
+ }
891
+ else if (files.has(CLAUDE_PLUGIN_FILE)) {
892
+ entry = claudeSinglePluginEntry(dec(files.get(CLAUDE_PLUGIN_FILE)));
893
+ }
894
+ else if (hasSkills) {
895
+ const name = pluginNameFrom(new URL(row.url).pathname
896
+ .split("/")
897
+ .filter((p) => p !== "")
898
+ .pop() ?? "plugin");
899
+ entry = { name, summary: { en: `Skills of ${name}` }, source: { path: "." } };
900
+ }
901
+ else {
902
+ throw new MarketplaceInvalidError(`the repository has no ${MANIFEST} or ${CLAUDE_MARKETPLACE_FILE} and is not a plugin (no ${PLUGIN_MANIFEST_FILE}, ${CLAUDE_PLUGIN_FILE} or skills/ at the root)`, []);
903
+ }
904
+ manifest = { schemaVersion: 1, name: row.name, plugins: [entry] };
905
+ }
906
+ return { manifest, etag: null, commit, trees };
907
+ }
908
+ /**
909
+ * Resolves the branch heads of the entries {@link headsToResolve} picks (at most
910
+ * {@link MARKETPLACE_HEADS_MAX} per refresh), each through the network rules with the fetch
911
+ * timeout; the token goes only to the marketplace URL's origin. For an entry with a path whose
912
+ * head is not a commit already imported from it, the head is fetched too, for the tree oid of
913
+ * that directory (equal oids: unchanged although the repo moved). A failed lookup leaves the
914
+ * entry unknown (never fails the refresh). Returns `name\0version` -> head.
915
+ */
916
+ async resolveHeads(id, fresh, m, signal) {
917
+ const config = this.d.config();
918
+ const listed = fresh ??
919
+ (await this.d.db
920
+ .selectFrom("marketplace_entries")
921
+ .select("entry")
922
+ .where("source_id", "=", id)
923
+ .execute()).map((r) => r.entry);
924
+ const out = new Map();
925
+ const names = [...new Set(listed.filter((e) => "git" in e.source).map((e) => e.name))];
926
+ if (names.length === 0)
927
+ return out;
928
+ const imported = await this.d.db
929
+ .selectFrom("catalog_items as i")
930
+ .innerJoin("catalog_item_versions as v", "v.item_id", "i.id")
931
+ .select(["i.name", "v.version", "v.source", "v.source_ref", "v.integrity", "v.source_detail"])
932
+ .where("i.deleted_at", "is", null)
933
+ .where("i.name", "in", names)
934
+ .execute();
935
+ const todo = headsToResolve(listed, imported, config, MARKETPLACE_HEADS_MAX);
936
+ const timeoutMs = this.d.fetchTimeoutMs ?? MARKETPLACE_FETCH_TIMEOUT_MS;
937
+ const queue = [...todo];
938
+ await Promise.all(Array.from({ length: Math.min(HEADS_CONCURRENCY, queue.length) }, async () => {
939
+ for (let e = queue.shift(); e !== undefined; e = queue.shift()) {
940
+ if (signal.aborted)
941
+ return;
942
+ const git = e.source.git;
943
+ const options = {
944
+ net: marketplaceNetPolicy(config),
945
+ maxBytes: config.packages.maxBytes,
946
+ signal: AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]),
947
+ ...(m.token !== undefined && { token: m.token, tokenOrigin: new URL(m.url).origin }),
948
+ };
949
+ try {
950
+ const commit = await resolveGitCommit({ url: git.url, ...(git.ref !== undefined && { ref: git.ref }) }, options);
951
+ const dir = normalGitPath(git.path);
952
+ const known = imported.some((v) => v.name === e.name && gitCommitOf(v.source_detail) === commit);
953
+ let tree = null;
954
+ if (dir !== "." && !known) {
955
+ const read = await readGitTreeAndOids({ url: git.url, commit }, { ...options, include: () => false, trees: () => [dir] });
956
+ tree = read.trees.get(dir) ?? null;
957
+ }
958
+ out.set(`${e.name}\0${marketplaceEntryVersion(e) ?? ""}`, { commit, tree });
959
+ }
960
+ catch (err) {
961
+ // a head lookup never fails the refresh: that entry stays unknown
962
+ this.d.log.warn("cannot resolve the branch head of a marketplace entry", {
963
+ marketplace: id,
964
+ entry: e.name,
965
+ error: err instanceof Error ? err.message : String(err),
966
+ });
967
+ }
968
+ }
969
+ }));
970
+ return out;
971
+ }
972
+ /** The token of an enterprise marketplace (undefined without one; official never has one). */
973
+ async token(id) {
974
+ return (await this.secrets(this.d.db).get(DEPLOYMENT_SECRETS_ORG, tokenKey(id))) ?? undefined;
975
+ }
976
+ /** Refreshes every effective marketplace (maintenance job `catalogs-refresh`), one failing not stopping the rest. */
977
+ async refreshAll(signal) {
978
+ const config = this.d.config();
979
+ const rows = await this.d.db
980
+ .selectFrom("marketplaces")
981
+ .select(["id", "builtin", "enabled"])
982
+ .orderBy("id")
983
+ .execute();
984
+ for (const r of rows) {
985
+ if (signal.aborted)
986
+ return;
987
+ if (!sourceEffective(r, config))
988
+ continue;
989
+ try {
990
+ await this.refresh(r.id, signal);
991
+ }
992
+ catch (err) {
993
+ this.d.log.error("marketplace refresh crashed", { marketplace: r.id, err });
994
+ }
995
+ }
996
+ }
997
+ /**
998
+ * Browses entries (enabled marketplaces only): filtered by marketplace, `q` matches a substring of
999
+ * the name / npm package name / summary (case-insensitive). Ordered by name, version (newest
1000
+ * first), marketplace, at most {@link MARKETPLACE_ENTRIES_MAX}; `truncated` means there are more
1001
+ * (the UI suggests narrowing the filter).
1002
+ */
1003
+ async entries(q = {}) {
1004
+ const config = this.d.config();
1005
+ let query = this.d.db
1006
+ .selectFrom("marketplace_entries as e")
1007
+ .innerJoin("marketplaces as s", "s.id", "e.source_id")
1008
+ .select([
1009
+ "e.source_id",
1010
+ "e.name",
1011
+ "e.version",
1012
+ "e.entry",
1013
+ "e.head_commit",
1014
+ "e.tree_oid",
1015
+ "e.claude",
1016
+ "s.name as source_name",
1017
+ "s.form",
1018
+ "s.url",
1019
+ "s.last_commit",
1020
+ ])
1021
+ .where("s.enabled", "=", true)
1022
+ .orderBy("e.name")
1023
+ .orderBy("e.version", "desc")
1024
+ .orderBy("e.source_id")
1025
+ .limit(MARKETPLACE_ENTRIES_MAX + 1);
1026
+ if (!config.marketplaces.official.enabled)
1027
+ query = query.where("s.builtin", "=", false);
1028
+ if (q.marketplaceId !== undefined)
1029
+ query = query.where("e.source_id", "=", q.marketplaceId);
1030
+ if (q.q !== undefined && q.q !== "") {
1031
+ const like = `%${q.q.replace(/[\\%_]/g, (c) => `\\${c}`)}%`;
1032
+ query = query.where((eb) => eb.or([
1033
+ eb("e.name", "ilike", like),
1034
+ eb(sql `coalesce(e.npm, '')`, "ilike", like),
1035
+ eb(sql `e.entry->'summary'->>'en'`, "ilike", like),
1036
+ eb(sql `coalesce(e.entry->'summary'->>'zh', '')`, "ilike", like),
1037
+ ]));
1038
+ }
1039
+ const fetched = await query.execute();
1040
+ const truncated = fetched.length > MARKETPLACE_ENTRIES_MAX;
1041
+ const rows = (truncated ? fetched.slice(0, MARKETPLACE_ENTRIES_MAX) : fetched).sort((a, b) => a.name.localeCompare(b.name) ||
1042
+ bySemverDescOrNone(a.version, b.version) ||
1043
+ a.source_id.localeCompare(b.source_id));
1044
+ if (rows.length === 0)
1045
+ return { items: [], truncated: false };
1046
+ const items = await this.d.db
1047
+ .selectFrom("catalog_items as i")
1048
+ .leftJoin("catalog_item_versions as v", "v.item_id", "i.id")
1049
+ .select([
1050
+ "i.id",
1051
+ "i.name",
1052
+ "v.version",
1053
+ "v.source",
1054
+ "v.source_ref",
1055
+ "v.integrity",
1056
+ "v.source_detail",
1057
+ "v.imported_at",
1058
+ ])
1059
+ .where("i.deleted_at", "is", null)
1060
+ .where("i.name", "in", [...new Set(rows.map((r) => r.name))])
1061
+ .execute();
1062
+ const adoptedBy = new Map();
1063
+ for (const i of items) {
1064
+ const a = adoptedBy.get(i.name) ?? { itemId: i.id, imported: [], versions: [] };
1065
+ if (i.version !== null) {
1066
+ a.imported.push({
1067
+ version: i.version,
1068
+ source: i.source,
1069
+ source_ref: i.source_ref,
1070
+ integrity: i.integrity,
1071
+ source_detail: i.source_detail,
1072
+ importedAt: i.imported_at,
1073
+ });
1074
+ }
1075
+ adoptedBy.set(i.name, a);
1076
+ }
1077
+ for (const a of adoptedBy.values()) {
1078
+ a.imported.sort(newestFirst);
1079
+ a.versions = a.imported.map((v) => v.version);
1080
+ }
1081
+ const views = rows.map((r) => {
1082
+ const e = r.entry;
1083
+ const version = marketplaceEntryVersion(e);
1084
+ const compatible = entryCompatible(e);
1085
+ const reason = unimportable(e, r.form, config);
1086
+ const importable = reason === undefined;
1087
+ const adopted = adoptedBy.get(r.name) ?? null;
1088
+ const top = adopted?.versions[0];
1089
+ return {
1090
+ sourceId: r.source_id,
1091
+ sourceName: r.source_name,
1092
+ name: r.name,
1093
+ version,
1094
+ summary: e.summary,
1095
+ minKernel: e.minKernel ?? null,
1096
+ homepage: e.homepage ?? null,
1097
+ permissions: e.permissions ?? null,
1098
+ source: e.source,
1099
+ format: r.claude === null ? "capybara" : "claude-code",
1100
+ compatible,
1101
+ importable,
1102
+ ...(reason !== undefined && { reason }),
1103
+ adopted: adopted ? { itemId: adopted.itemId, versions: [...adopted.versions] } : null,
1104
+ newerAvailable: compatible && version !== null && top !== undefined && compareVersions(version, top) > 0,
1105
+ contentChanged: adopted !== null &&
1106
+ entryContentChanged(e, {
1107
+ marketplaceId: r.source_id,
1108
+ headCommit: r.head_commit,
1109
+ treeOid: r.tree_oid,
1110
+ url: r.url,
1111
+ lastCommit: r.last_commit,
1112
+ }, adopted.imported),
1113
+ };
1114
+ });
1115
+ return { items: views, truncated };
1116
+ }
1117
+ /**
1118
+ * Replaces the entries of a marketplace; `trees` (a git marketplace) gives each path entry the
1119
+ * tree oid of its directory.
1120
+ */
1121
+ async replaceEntries(trx, id, entries, trees, overlays) {
1122
+ await trx.deleteFrom("marketplace_entries").where("source_id", "=", id).execute();
1123
+ for (let i = 0; i < entries.length; i += INSERT_BATCH) {
1124
+ await trx
1125
+ .insertInto("marketplace_entries")
1126
+ .values(entries.slice(i, i + INSERT_BATCH).map((e) => ({
1127
+ source_id: id,
1128
+ name: e.name,
1129
+ version: marketplaceEntryVersion(e) ?? "",
1130
+ npm: "npm" in e.source ? e.source.npm.package : null,
1131
+ entry: JSON.stringify(e),
1132
+ tree_oid: "path" in e.source ? (trees?.get(normalGitPath(e.source.path)) ?? null) : null,
1133
+ claude: overlays?.has(e.name) ? JSON.stringify(overlays.get(e.name)) : null,
1134
+ })))
1135
+ .execute();
1136
+ }
1137
+ }
1138
+ async views(rows) {
1139
+ if (rows.length === 0)
1140
+ return [];
1141
+ const config = this.d.config();
1142
+ const ids = rows.map((r) => r.id);
1143
+ const counts = await this.d.db
1144
+ .selectFrom("marketplace_entries")
1145
+ .select(["source_id", (eb) => eb.fn.countAll().as("n")])
1146
+ .where("source_id", "in", ids)
1147
+ .groupBy("source_id")
1148
+ .execute();
1149
+ const tokens = await this.d.db
1150
+ .selectFrom("secrets")
1151
+ .select("key")
1152
+ .where("namespace", "=", SECRETS_NAMESPACE)
1153
+ .where("org_id", "=", DEPLOYMENT_SECRETS_ORG)
1154
+ .where("key", "in", ids.map((id) => tokenKey(id)))
1155
+ .execute();
1156
+ const hasToken = new Set(tokens.map((t) => t.key));
1157
+ const count = new Map(counts.map((c) => [c.source_id, Number(c.n)]));
1158
+ return rows.map((r) => ({
1159
+ id: r.id,
1160
+ name: r.name,
1161
+ url: r.url,
1162
+ form: r.form,
1163
+ ref: r.ref,
1164
+ lastCommit: r.last_commit,
1165
+ mirrors: r.mirrors,
1166
+ builtin: r.builtin,
1167
+ enabled: sourceEffective(r, config),
1168
+ disabledByConfig: r.builtin && !config.marketplaces.official.enabled,
1169
+ hasToken: hasToken.has(tokenKey(r.id)),
1170
+ etag: r.etag,
1171
+ lastRefreshedAt: r.last_refreshed_at?.toISOString() ?? null,
1172
+ lastStatus: r.last_status,
1173
+ lastError: r.last_error,
1174
+ entryCount: count.get(r.id) ?? 0,
1175
+ skipped: r.skipped ?? [],
1176
+ createdAt: r.created_at.toISOString(),
1177
+ }));
1178
+ }
1179
+ secrets(db) {
1180
+ return createSecrets(db, this.d.keyring, SECRETS_NAMESPACE);
1181
+ }
1182
+ checkName(name) {
1183
+ const n = name.trim();
1184
+ if (n.length < 1 || [...n].length > 100) {
1185
+ throw new ApiError("validation_failed", "name must be 1–100 characters");
1186
+ }
1187
+ return n;
1188
+ }
1189
+ checkUrls(url, mirrors) {
1190
+ const allow = this.d.config().marketplaces.allowInsecureLoopback;
1191
+ if (mirrors.length > MARKETPLACE_MAX_MIRRORS) {
1192
+ throw new ApiError("validation_failed", `at most ${MARKETPLACE_MAX_MIRRORS} mirrors`);
1193
+ }
1194
+ for (const [label, u] of [["url", url], ...mirrors.map((m, i) => [`mirrors.${i}`, m])]) {
1195
+ const p = marketplaceUrlProblem(u, allow);
1196
+ if (p !== null)
1197
+ throw new ApiError("validation_failed", `${label}: ${p}`);
1198
+ }
1199
+ }
1200
+ /** A git marketplace has no mirrors; only a git marketplace has a ref. */
1201
+ checkGitFields(form, ref, mirrors) {
1202
+ if (form === "git" && mirrors.length > 0) {
1203
+ throw new ApiError("validation_failed", "mirrors: a git marketplace has no mirrors (point it at a mirror repository instead)");
1204
+ }
1205
+ if (form === "http" && ref !== null) {
1206
+ throw new ApiError("validation_failed", "ref: only a git marketplace follows a ref");
1207
+ }
1208
+ if (ref !== null && (ref.length < 1 || ref.length > 256 || /[\s\0]/.test(ref))) {
1209
+ throw new ApiError("validation_failed", "ref must be 1–256 characters without spaces");
1210
+ }
1211
+ }
1212
+ checkToken(token) {
1213
+ if (token.length < 1 || token.length > MAX_TOKEN_CHARS || /[\r\n]/.test(token)) {
1214
+ throw new ApiError("validation_failed", `token must be 1–${MAX_TOKEN_CHARS} characters without line breaks`);
1215
+ }
1216
+ }
1217
+ }