crewx 0.8.7-rc.21 → 0.8.7-rc.22

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 (222) hide show
  1. package/dist/assets/{MarketPage-aY16UW1_.js → MarketPage-lLBtAENs.js} +10 -15
  2. package/dist/assets/{PromptTab-C3aTJS6u.js → PromptTab-Daq54dMi.js} +1 -1
  3. package/dist/assets/_baseUniq-BJDqmZBO.js +1 -0
  4. package/dist/assets/{arc-BCQAokB1.js → arc-ByjRIa5p.js} +1 -1
  5. package/dist/assets/{architectureDiagram-UYN6MBPD-Bc87ujv2.js → architectureDiagram-UYN6MBPD-DcKpqSFb.js} +1 -1
  6. package/dist/assets/{blockDiagram-ZHA2E4KO-wC9uH8Lb.js → blockDiagram-ZHA2E4KO-YvUeULT5.js} +1 -1
  7. package/dist/assets/{c4Diagram-6F5ED5ID-CqB7lqni.js → c4Diagram-6F5ED5ID-vggzH2N5.js} +1 -1
  8. package/dist/assets/channel-1Y6YooBb.js +1 -0
  9. package/dist/assets/{chunk-5HRBRIJM-Cc1L17Eh.js → chunk-5HRBRIJM-BbaQwJkn.js} +1 -1
  10. package/dist/assets/{chunk-7U56Z5CX-Df3nu_7G.js → chunk-7U56Z5CX-DVYVpFaa.js} +1 -1
  11. package/dist/assets/{chunk-ASOPGD6M-DIXslLG-.js → chunk-ASOPGD6M-CXEvWb-8.js} +1 -1
  12. package/dist/assets/{chunk-KFBOBJHC-FytVljyP.js → chunk-KFBOBJHC-CadVXFV3.js} +1 -1
  13. package/dist/assets/{chunk-T2TOU4HS-CU5z-cXq.js → chunk-T2TOU4HS-lDZr1sVF.js} +1 -1
  14. package/dist/assets/{chunk-TMUBEWPD-CWo1AFyn.js → chunk-TMUBEWPD-D5A0MrFD.js} +1 -1
  15. package/dist/assets/classDiagram-LNE6IOMH-CDxkV2JV.js +1 -0
  16. package/dist/assets/classDiagram-v2-MQ7JQ4JX-CDxkV2JV.js +1 -0
  17. package/dist/assets/clone-6xznz_9G.js +1 -0
  18. package/dist/assets/{dagre-4EVJKHTY-CgZW5tg4.js → dagre-4EVJKHTY-Bncx7Ijq.js} +1 -1
  19. package/dist/assets/{diagram-QW4FP2JN-CJAng8RC.js → diagram-QW4FP2JN-RkxVNs99.js} +1 -1
  20. package/dist/assets/{erDiagram-6RL3IURR-C1uHztHX.js → erDiagram-6RL3IURR-DIiKqQdx.js} +1 -1
  21. package/dist/assets/{flowDiagram-7ASYPVHJ-Yiu2odO-.js → flowDiagram-7ASYPVHJ-DQ5PN7v6.js} +1 -1
  22. package/dist/assets/{ganttDiagram-NTVNEXSI-tutc0dJP.js → ganttDiagram-NTVNEXSI-BeNWGpB6.js} +1 -1
  23. package/dist/assets/{gitGraph-YCYPL57B-C3jM6T4G.js → gitGraph-YCYPL57B-CdMe059q.js} +1 -1
  24. package/dist/assets/{gitGraphDiagram-NRZ2UAAF-0aqHEKzz.js → gitGraphDiagram-NRZ2UAAF-BEke5TyK.js} +1 -1
  25. package/dist/assets/{graph-DC224ILV.js → graph-DyQIwWJd.js} +1 -1
  26. package/dist/assets/{infoDiagram-A4XQUW5V-Do7Mpzkx.js → infoDiagram-A4XQUW5V-CUcC8I_5.js} +1 -1
  27. package/dist/assets/{journeyDiagram-G5WM74LC-DAruEJc7.js → journeyDiagram-G5WM74LC-CQ5DqAH5.js} +1 -1
  28. package/dist/assets/{kanban-definition-QRCXZQQD-DB63vLR0.js → kanban-definition-QRCXZQQD-BBsgOii7.js} +1 -1
  29. package/dist/assets/{layout-BiYH5x6Q.js → layout-D7vBs_Wy.js} +1 -1
  30. package/dist/assets/{linear-CLyGW2s0.js → linear-CEOF9oJz.js} +1 -1
  31. package/dist/assets/{main-CEKkUqZb.js → main-Bx1nZ7_p.js} +278 -273
  32. package/dist/assets/main-C4ebvOIJ.css +10 -0
  33. package/dist/assets/{min-ChJMFU2N.js → min-RK9J5Utn.js} +1 -1
  34. package/dist/assets/{mindmap-definition-GWI6TPTV-CtADqQgd.js → mindmap-definition-GWI6TPTV-BXvEU7qC.js} +1 -1
  35. package/dist/assets/{pieDiagram-YF2LJOPJ-BSOfzo3O.js → pieDiagram-YF2LJOPJ-TrXqcpni.js} +1 -1
  36. package/dist/assets/{quadrantDiagram-OS5C2QUG-BLBQNFVb.js → quadrantDiagram-OS5C2QUG-B-chTEU4.js} +1 -1
  37. package/dist/assets/{requirementDiagram-MIRIMTAZ-DqorvuS6.js → requirementDiagram-MIRIMTAZ-BgJhNLp_.js} +1 -1
  38. package/dist/assets/{sankeyDiagram-Y46BX6SQ-4NDcrLkB.js → sankeyDiagram-Y46BX6SQ-BVsgYKK7.js} +1 -1
  39. package/dist/assets/{sequenceDiagram-G6AWOVSC-yha6BxK2.js → sequenceDiagram-G6AWOVSC-ByJOpkq3.js} +1 -1
  40. package/dist/assets/{stateDiagram-MAYHULR4-XJ_I41ez.js → stateDiagram-MAYHULR4-Dw7lUL1B.js} +1 -1
  41. package/dist/assets/stateDiagram-v2-4JROLMXI-DYJiM3bF.js +1 -0
  42. package/dist/assets/{timeline-definition-U7ZMHBDA-DCNrmvE7.js → timeline-definition-U7ZMHBDA-CSoEYs60.js} +1 -1
  43. package/dist/assets/{xychartDiagram-6QU3TZC5-9N-Ohhdp.js → xychartDiagram-6QU3TZC5-0_rG7p3L.js} +1 -1
  44. package/dist/index.html +2 -2
  45. package/package.json +8 -8
  46. package/packages/cli/package.json +1 -1
  47. package/dist/assets/_baseUniq-DxEFlwNt.js +0 -1
  48. package/dist/assets/channel-DNLHkiNp.js +0 -1
  49. package/dist/assets/classDiagram-LNE6IOMH-B430Onym.js +0 -1
  50. package/dist/assets/classDiagram-v2-MQ7JQ4JX-B430Onym.js +0 -1
  51. package/dist/assets/clone-B9O3E3P1.js +0 -1
  52. package/dist/assets/main-D3YeRndF.css +0 -10
  53. package/dist/assets/stateDiagram-v2-4JROLMXI-CxcyPJO2.js +0 -1
  54. package/dist-server/adapters/adapter.module.js +0 -35
  55. package/dist-server/adapters/adapter.service.js +0 -79
  56. package/dist-server/adapters/http-router.service.js +0 -91
  57. package/dist-server/app.module.js +0 -111
  58. package/dist-server/bootstrap/crewx-server.js +0 -80
  59. package/dist-server/bootstrap/tls.js +0 -138
  60. package/dist-server/common/analytics.client.js +0 -180
  61. package/dist-server/common/analytics.module.js +0 -21
  62. package/dist-server/common/cors.js +0 -15
  63. package/dist-server/common/decorators/project.decorator.js +0 -21
  64. package/dist-server/common/decorators/workspace.decorator.js +0 -24
  65. package/dist-server/common/device-id.js +0 -33
  66. package/dist-server/common/interceptor/api-id-header.interceptor.js +0 -53
  67. package/dist-server/common/interceptor/http-logging.interceptor.js +0 -124
  68. package/dist-server/common/limits/defaults.js +0 -13
  69. package/dist-server/common/limits/limits-provider.js +0 -6
  70. package/dist-server/common/limits/limits.controller.js +0 -38
  71. package/dist-server/common/limits/limits.module.js +0 -24
  72. package/dist-server/common/limits/local-limits.provider.js +0 -27
  73. package/dist-server/common/middleware/workspace.middleware.js +0 -210
  74. package/dist-server/common/repository.exception-filter.js +0 -48
  75. package/dist-server/common/workspace-context.store.js +0 -25
  76. package/dist-server/domain/agent/agent.controller.js +0 -96
  77. package/dist-server/domain/agent/agent.module.js +0 -27
  78. package/dist-server/domain/agent/agent.service.js +0 -335
  79. package/dist-server/domain/agent/agent.types.js +0 -3
  80. package/dist-server/domain/agent/dto/create-agent.dto.js +0 -80
  81. package/dist-server/domain/agent/dto/preview-prompt.dto.js +0 -77
  82. package/dist-server/domain/agent/dto/provider-meta.dto.js +0 -2
  83. package/dist-server/domain/agent/dto/update-agent.dto.js +0 -92
  84. package/dist-server/domain/agent/provider.controller.js +0 -38
  85. package/dist-server/domain/agent/template-processor.service.js +0 -71
  86. package/dist-server/domain/auth/auth.constants.js +0 -4
  87. package/dist-server/domain/auth/auth.controller.js +0 -123
  88. package/dist-server/domain/auth/auth.module.js +0 -47
  89. package/dist-server/domain/auth/decorators/public.decorator.js +0 -7
  90. package/dist-server/domain/auth/guards/base-auth.guard.js +0 -66
  91. package/dist-server/domain/auth/session-store.js +0 -26
  92. package/dist-server/domain/auth/utils/ip.utils.js +0 -31
  93. package/dist-server/domain/box/box.controller.js +0 -77
  94. package/dist-server/domain/box/box.module.js +0 -21
  95. package/dist-server/domain/box/box.service.js +0 -97
  96. package/dist-server/domain/box/dto/create-box.dto.js +0 -74
  97. package/dist-server/domain/cli/cli.controller.js +0 -136
  98. package/dist-server/domain/cli/cli.module.js +0 -21
  99. package/dist-server/domain/cli/cli.service.js +0 -598
  100. package/dist-server/domain/cli/dto/login-cli.dto.js +0 -25
  101. package/dist-server/domain/cli/dto/update-cli.dto.js +0 -31
  102. package/dist-server/domain/doc/doc.controller.js +0 -95
  103. package/dist-server/domain/doc/doc.module.js +0 -21
  104. package/dist-server/domain/doc/doc.service.js +0 -368
  105. package/dist-server/domain/doc/dto/doc.dto.js +0 -64
  106. package/dist-server/domain/document/document.controller.js +0 -57
  107. package/dist-server/domain/document/document.module.js +0 -22
  108. package/dist-server/domain/document/document.service.js +0 -274
  109. package/dist-server/domain/document/dto/register-document.dto.js +0 -24
  110. package/dist-server/domain/fs/dto/mkdir-request.dto.js +0 -47
  111. package/dist-server/domain/fs/dto/mkdir-response.dto.js +0 -2
  112. package/dist-server/domain/fs/dto/quick-access.dto.js +0 -2
  113. package/dist-server/domain/fs/dto/tree-node.dto.js +0 -2
  114. package/dist-server/domain/fs/dto/tree-query.dto.js +0 -35
  115. package/dist-server/domain/fs/fs.controller.js +0 -63
  116. package/dist-server/domain/fs/fs.module.js +0 -22
  117. package/dist-server/domain/fs/fs.service.js +0 -303
  118. package/dist-server/domain/git/git.module.js +0 -20
  119. package/dist-server/domain/git/git.service.js +0 -222
  120. package/dist-server/domain/goal/dto/goal.dto.js +0 -49
  121. package/dist-server/domain/goal/goal.controller.js +0 -86
  122. package/dist-server/domain/goal/goal.module.js +0 -22
  123. package/dist-server/domain/goal/goal.service.js +0 -197
  124. package/dist-server/domain/goal/parser/goal-md-parser.js +0 -166
  125. package/dist-server/domain/goal/parser/goal-md-serializer.js +0 -110
  126. package/dist-server/domain/health/health.controller.js +0 -57
  127. package/dist-server/domain/health/health.module.js +0 -19
  128. package/dist-server/domain/knowledge/dto/graph-knowledge.dto.js +0 -27
  129. package/dist-server/domain/knowledge/dto/knowledge.dto.js +0 -71
  130. package/dist-server/domain/knowledge/knowledge.controller.js +0 -122
  131. package/dist-server/domain/knowledge/knowledge.module.js +0 -21
  132. package/dist-server/domain/knowledge/knowledge.service.js +0 -538
  133. package/dist-server/domain/market/dto/add-registry.dto.js +0 -32
  134. package/dist-server/domain/market/dto/install-market.dto.js +0 -30
  135. package/dist-server/domain/market/market.controller.js +0 -163
  136. package/dist-server/domain/market/market.module.js +0 -22
  137. package/dist-server/domain/market/market.service.js +0 -1447
  138. package/dist-server/domain/mcp/browser-session.store.js +0 -247
  139. package/dist-server/domain/mcp/crewx-tool.factory.js +0 -119
  140. package/dist-server/domain/mcp/mcp-auth.guard.js +0 -61
  141. package/dist-server/domain/mcp/mcp.constants.js +0 -4
  142. package/dist-server/domain/mcp/mcp.controller.js +0 -125
  143. package/dist-server/domain/mcp/mcp.dto.js +0 -12
  144. package/dist-server/domain/mcp/mcp.module.js +0 -49
  145. package/dist-server/domain/mcp/mcp.service.js +0 -684
  146. package/dist-server/domain/mcp/tool-router.service.js +0 -120
  147. package/dist-server/domain/message/dto/list-messages.dto.js +0 -57
  148. package/dist-server/domain/message/dto/send-message.dto.js +0 -45
  149. package/dist-server/domain/message/message.controller.js +0 -73
  150. package/dist-server/domain/message/message.module.js +0 -25
  151. package/dist-server/domain/message/message.service.js +0 -160
  152. package/dist-server/domain/onboarding/onboarding.controller.js +0 -97
  153. package/dist-server/domain/onboarding/onboarding.module.js +0 -23
  154. package/dist-server/domain/onboarding/onboarding.service.js +0 -106
  155. package/dist-server/domain/onboarding/onboarding.types.js +0 -2
  156. package/dist-server/domain/onboarding/presets/custom.js +0 -10
  157. package/dist-server/domain/onboarding/presets/dev-team.js +0 -41
  158. package/dist-server/domain/onboarding/presets/index.js +0 -15
  159. package/dist-server/domain/onboarding/presets/marketing-team.js +0 -29
  160. package/dist-server/domain/onboarding/presets/planning-team.js +0 -29
  161. package/dist-server/domain/onboarding/presets/solo.js +0 -17
  162. package/dist-server/domain/planner/dto/planner.dto.js +0 -58
  163. package/dist-server/domain/planner/planner.controller.js +0 -74
  164. package/dist-server/domain/planner/planner.module.js +0 -22
  165. package/dist-server/domain/planner/planner.service.js +0 -269
  166. package/dist-server/domain/project/dto/create-project.dto.js +0 -46
  167. package/dist-server/domain/project/dto/update-project.dto.js +0 -60
  168. package/dist-server/domain/project/project.controller.js +0 -129
  169. package/dist-server/domain/project/project.module.js +0 -22
  170. package/dist-server/domain/project/project.service.js +0 -173
  171. package/dist-server/domain/settings/settings.controller.js +0 -52
  172. package/dist-server/domain/settings/settings.module.js +0 -22
  173. package/dist-server/domain/settings/settings.service.js +0 -105
  174. package/dist-server/domain/task/dto/all-tasks.dto.js +0 -55
  175. package/dist-server/domain/task/dto/list-tasks.dto.js +0 -69
  176. package/dist-server/domain/task/dto/search-tasks.dto.js +0 -66
  177. package/dist-server/domain/task/dto/send-message.dto.js +0 -31
  178. package/dist-server/domain/task/dto/task-status.dto.js +0 -34
  179. package/dist-server/domain/task/dto/workspace-usage.dto.js +0 -38
  180. package/dist-server/domain/task/task.constants.js +0 -5
  181. package/dist-server/domain/task/task.controller.js +0 -169
  182. package/dist-server/domain/task/task.module.js +0 -23
  183. package/dist-server/domain/task/task.service.js +0 -775
  184. package/dist-server/domain/thread/dto/build-context.dto.js +0 -42
  185. package/dist-server/domain/thread/dto/list-messages.dto.js +0 -49
  186. package/dist-server/domain/thread/dto/list-threads.dto.js +0 -70
  187. package/dist-server/domain/thread/dto/search-threads.dto.js +0 -13
  188. package/dist-server/domain/thread/dto/update-thread.dto.js +0 -23
  189. package/dist-server/domain/thread/thread.controller.js +0 -160
  190. package/dist-server/domain/thread/thread.module.js +0 -23
  191. package/dist-server/domain/thread/thread.service.js +0 -419
  192. package/dist-server/domain/usage/usage.controller.js +0 -118
  193. package/dist-server/domain/usage/usage.module.js +0 -21
  194. package/dist-server/domain/usage/usage.service.js +0 -358
  195. package/dist-server/domain/usage/usage.types.js +0 -4
  196. package/dist-server/domain/wbs/wbs.controller.js +0 -51
  197. package/dist-server/domain/wbs/wbs.module.js +0 -21
  198. package/dist-server/domain/wbs/wbs.service.js +0 -94
  199. package/dist-server/domain/workflow/workflow.controller.js +0 -53
  200. package/dist-server/domain/workflow/workflow.module.js +0 -21
  201. package/dist-server/domain/workflow/workflow.service.js +0 -209
  202. package/dist-server/domain/workspace/dto/create-workspace.dto.js +0 -45
  203. package/dist-server/domain/workspace/dto/switch-workspace.dto.js +0 -32
  204. package/dist-server/domain/workspace/dto/workspace-info.dto.js +0 -2
  205. package/dist-server/domain/workspace/workspace.controller.js +0 -98
  206. package/dist-server/domain/workspace/workspace.module.js +0 -22
  207. package/dist-server/domain/workspace/workspace.service.js +0 -198
  208. package/dist-server/main.js +0 -204
  209. package/dist-server/modules/crewx-pool.service.js +0 -75
  210. package/dist-server/modules/crewx.module.js +0 -47
  211. package/dist-server/repository/base-sqlite.repository.js +0 -18
  212. package/dist-server/repository/box.repository.js +0 -99
  213. package/dist-server/repository/message.repository.js +0 -22
  214. package/dist-server/repository/project.repository.js +0 -51
  215. package/dist-server/repository/repository.module.js +0 -34
  216. package/dist-server/repository/request-log.repository.js +0 -96
  217. package/dist-server/repository/task.repository.js +0 -105
  218. package/dist-server/repository/thread.repository.js +0 -165
  219. package/dist-server/shared/crewx-home.js +0 -58
  220. package/dist-server/shared/crewx-state.js +0 -66
  221. package/dist-server/test-utils/compat-database.js +0 -30
  222. package/dist-server/utils/tokenizer.js +0 -8
@@ -1,1447 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
- return c > 3 && r && Object.defineProperty(target, key, r), r;
23
- };
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
- var __metadata = (this && this.__metadata) || function (k, v) {
42
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
- };
44
- var MarketService_1;
45
- Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.MarketService = void 0;
47
- exports.boolToTrustLevel = boolToTrustLevel;
48
- exports.maxTrustLevel = maxTrustLevel;
49
- exports.trustLevelToIsVerified = trustLevelToIsVerified;
50
- const common_1 = require("@nestjs/common");
51
- const fs_1 = require("fs");
52
- const promises_1 = require("fs/promises");
53
- const path_1 = require("path");
54
- const os_1 = require("os");
55
- const crypto_1 = require("crypto");
56
- const child_process_1 = require("child_process");
57
- const yaml = __importStar(require("js-yaml"));
58
- const zod_1 = require("zod");
59
- const skill_1 = require("@crewx/skill");
60
- const workspace_context_store_js_1 = require("../../common/workspace-context.store.js");
61
- const analytics_client_js_1 = require("../../common/analytics.client.js");
62
- // --- Zod schemas ---
63
- const MarketPackageTypeSchema = zod_1.z.enum(['skill', 'template', 'workflow', 'plugin']);
64
- const TemplateEntrySchema = zod_1.z.object({
65
- name: zod_1.z.string(),
66
- displayName: zod_1.z.string().optional(),
67
- description: zod_1.z.string().optional(),
68
- author: zod_1.z.string().optional(),
69
- version: zod_1.z.string().optional(),
70
- tags: zod_1.z.array(zod_1.z.string()).optional(),
71
- type: MarketPackageTypeSchema.optional().default('skill'),
72
- path: zod_1.z.string().optional(),
73
- features: zod_1.z.array(zod_1.z.string()).optional(),
74
- crewxVersion: zod_1.z.string().optional(),
75
- });
76
- const MarketplaceRegistryEntrySchema = zod_1.z.object({
77
- id: zod_1.z.string(),
78
- name: zod_1.z.string().optional(),
79
- url: zod_1.z.string(),
80
- type: zod_1.z.string().optional(),
81
- trust: zod_1.z.enum(['trusted', 'verified', 'unverified']).optional(),
82
- verified: zod_1.z.boolean().optional(),
83
- default: zod_1.z.boolean().optional(),
84
- tokenEnv: zod_1.z.string().optional(),
85
- allow: zod_1.z.array(zod_1.z.string()).optional(),
86
- });
87
- const MarketplaceConfigSchema = zod_1.z.object({
88
- version: zod_1.z.string().optional(),
89
- forceDisabled: zod_1.z.array(zod_1.z.string()).optional(),
90
- forceEnabled: zod_1.z.array(zod_1.z.string()).optional(),
91
- deny: zod_1.z.array(zod_1.z.string()).optional(),
92
- registries: zod_1.z.array(MarketplaceRegistryEntrySchema),
93
- });
94
- const VALID_REGISTRY_SOURCE_TYPES = new Set(['crewx', 'codex', 'claude']);
95
- const TRUST_RANK = { trusted: 2, verified: 1, unverified: 0 };
96
- function boolToTrustLevel(isVerified, isDefault, source) {
97
- if (source === 'builtin')
98
- return 'trusted';
99
- if (isDefault || isVerified)
100
- return 'verified';
101
- return 'unverified';
102
- }
103
- function maxTrustLevel(a, b) {
104
- return TRUST_RANK[a] >= TRUST_RANK[b] ? a : b;
105
- }
106
- function trustLevelToIsVerified(level) {
107
- return level !== 'unverified';
108
- }
109
- // --- Constants ---
110
- const DEFAULT_REGISTRY_URL = 'https://github.com/sowonlabs/crewx-templates';
111
- const DEFAULT_REGISTRY_NAME = 'crewx-templates';
112
- const MARKETPLACE_REMOTE_URL = 'https://raw.githubusercontent.com/sowonlabs/crewx-templates/main/marketplace.json';
113
- const MARKETPLACE_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24h
114
- function loadBundledMarketplace() {
115
- try {
116
- const bundlePath = (0, path_1.join)(__dirname, 'default-marketplace.json');
117
- const raw = JSON.parse((0, fs_1.readFileSync)(bundlePath, 'utf-8'));
118
- const result = MarketplaceConfigSchema.safeParse(raw);
119
- return result.success ? result.data : null;
120
- }
121
- catch {
122
- return null;
123
- }
124
- }
125
- // Cache TTL: 15 minutes (§4.3 rate limit)
126
- const CACHE_TTL_MS = 15 * 60 * 1000;
127
- let MarketService = MarketService_1 = class MarketService {
128
- logger = new common_1.Logger(MarketService_1.name);
129
- analytics;
130
- constructor(analytics) {
131
- this.analytics = analytics;
132
- }
133
- // In-memory cache for templates.json fetches
134
- templateCache = new Map();
135
- marketplaceMemoryCache = null;
136
- marketplaceRefreshPending = false;
137
- get skillsDir() {
138
- return (0, path_1.join)((0, workspace_context_store_js_1.getWorkspacePath)(), 'skills');
139
- }
140
- get configPath() {
141
- return process.env.CREWX_CONFIG || (0, path_1.join)((0, workspace_context_store_js_1.getWorkspacePath)(), 'crewx.yaml');
142
- }
143
- get registryCachePath() {
144
- return (0, path_1.join)((0, workspace_context_store_js_1.getWorkspacePath)(), '.crewx', 'registry.json');
145
- }
146
- get lockFilePath() {
147
- return (0, path_1.join)((0, workspace_context_store_js_1.getWorkspacePath)(), '.crewx', 'skills.lock.json');
148
- }
149
- get marketplacePath() {
150
- return (0, path_1.join)((0, os_1.homedir)(), '.crewx', 'marketplace.json');
151
- }
152
- get marketplaceCachePath() {
153
- return (0, path_1.join)((0, os_1.homedir)(), '.crewx', 'marketplace.cache.json');
154
- }
155
- get workspaceMarketplacePath() {
156
- return (0, path_1.join)((0, workspace_context_store_js_1.getWorkspacePath)(), '.crewx', 'marketplace.json');
157
- }
158
- // --- Path traversal guard ---
159
- validateItemName(name) {
160
- if (name.includes('..') || name.includes('/') || name.includes('\\')) {
161
- throw new common_1.BadRequestException('Invalid skill name');
162
- }
163
- const resolved = (0, path_1.resolve)(this.skillsDir, name);
164
- if (!resolved.startsWith((0, path_1.resolve)(this.skillsDir))) {
165
- throw new common_1.BadRequestException('Path traversal detected');
166
- }
167
- }
168
- // --- Registry CRUD ---
169
- listRegistries() {
170
- const entries = this.loadRegistries();
171
- const registries = entries.map((r) => ({
172
- ...r,
173
- isVerified: trustLevelToIsVerified(r.trustLevel),
174
- itemCount: this.getCachedItemCount(r.id),
175
- }));
176
- return { registries };
177
- }
178
- addRegistry(url, tokenEnv) {
179
- if (!url || typeof url !== 'string') {
180
- throw new common_1.BadRequestException('url is required');
181
- }
182
- const parsed = this.parseGitHubUrl(url);
183
- if (!parsed) {
184
- throw new common_1.BadRequestException('Invalid GitHub repository URL. Expected format: https://github.com/{owner}/{repo}');
185
- }
186
- if (tokenEnv !== undefined) {
187
- this.validateTokenEnv(tokenEnv);
188
- }
189
- const mpConfig = this.loadMarketplaceJson();
190
- if (mpConfig?.forceDisabled?.length) {
191
- const disabledSet = new Set(mpConfig.forceDisabled);
192
- const parsedInput = this.parseGitHubUrl(url);
193
- const blockedByPolicy = mpConfig.registries.some((r) => {
194
- if (!disabledSet.has(r.id))
195
- return false;
196
- const parsedBlocked = this.parseGitHubUrl(r.url);
197
- if (!parsedInput || !parsedBlocked)
198
- return false;
199
- return (parsedInput.owner.toLowerCase() === parsedBlocked.owner.toLowerCase() &&
200
- parsedInput.repo.toLowerCase() === parsedBlocked.repo.toLowerCase());
201
- });
202
- if (blockedByPolicy) {
203
- throw new common_1.BadRequestException('This registry is blocked by marketplace policy (forceDisabled)');
204
- }
205
- }
206
- const entries = this.loadRegistries();
207
- // Check duplicate URL
208
- const normalized = this.normalizeUrl(url);
209
- const exists = entries.some((r) => this.normalizeUrl(r.url) === normalized);
210
- if (exists) {
211
- throw new common_1.BadRequestException('Registry with this URL already exists');
212
- }
213
- const entry = {
214
- id: (0, crypto_1.randomUUID)(),
215
- name: parsed.repo,
216
- url: normalized,
217
- type: 'crewx',
218
- trustLevel: 'unverified',
219
- isDefault: false,
220
- addedAt: new Date().toISOString(),
221
- source: 'workspace',
222
- ...(tokenEnv ? { tokenEnv } : {}),
223
- };
224
- entries.push(entry);
225
- this.saveRegistries(entries);
226
- return { ...entry, isVerified: trustLevelToIsVerified(entry.trustLevel), itemCount: 0 };
227
- }
228
- deleteRegistry(id) {
229
- const entries = this.loadRegistries();
230
- const idx = entries.findIndex((r) => r.id === id);
231
- if (idx === -1) {
232
- throw new common_1.NotFoundException(`Registry '${id}' not found`);
233
- }
234
- if (entries[idx].isDefault) {
235
- throw new common_1.BadRequestException('Cannot delete the default registry');
236
- }
237
- if (entries[idx].source === 'global') {
238
- throw new common_1.BadRequestException('Cannot delete official registry from marketplace policy');
239
- }
240
- entries.splice(idx, 1);
241
- this.saveRegistries(entries);
242
- this.templateCache.delete(id);
243
- }
244
- // --- Market item listing ---
245
- async listMarketItems(registryId) {
246
- const entries = this.loadRegistries();
247
- const registry = entries.find((r) => r.id === registryId);
248
- if (!registry) {
249
- throw new common_1.NotFoundException(`Registry '${registryId}' not found`);
250
- }
251
- const templates = await this.fetchTemplates(registry);
252
- const [installedNames, installStats] = await Promise.all([
253
- this.getInstalledItemNames(),
254
- this.analytics.fetchInstallStats(templates.map((t) => t.name)),
255
- ]);
256
- const installedSlugs = new Set([...installedNames].map((n) => n.toLowerCase().replace(/[\s_]+/g, '-')));
257
- const items = templates.map((t) => ({
258
- name: t.name,
259
- displayName: t.displayName || t.name,
260
- description: t.description || '',
261
- author: t.author || '',
262
- version: t.version || '0.0.0',
263
- tags: t.tags || [],
264
- type: t.type || 'template',
265
- trustLevel: registry.trustLevel,
266
- isVerified: trustLevelToIsVerified(registry.trustLevel),
267
- isInstalled: installedNames.has(t.name) ||
268
- installedSlugs.has(t.name.toLowerCase().replace(/[\s_]+/g, '-')),
269
- registryId: registry.id,
270
- registryName: registry.name,
271
- installCount: installStats[t.name]?.unique ?? 0,
272
- }));
273
- return { items };
274
- }
275
- async listAllMarketItems() {
276
- const registries = this.loadRegistries();
277
- const [installedNames, ...templateResults] = await Promise.all([
278
- this.getInstalledItemNames(),
279
- ...registries.map((registry) => this.fetchTemplates(registry)
280
- .then((templates) => ({ registry, templates }))
281
- .catch(() => ({ registry, templates: [] }))),
282
- ]);
283
- const installedSlugs = new Set([...installedNames].map((n) => n.toLowerCase().replace(/[\s_]+/g, '-')));
284
- const allNames = templateResults.flatMap(({ templates }) => templates.map((t) => t.name));
285
- const installStats = await this.analytics.fetchInstallStats(allNames);
286
- const items = [];
287
- for (const { registry, templates } of templateResults) {
288
- for (const t of templates) {
289
- items.push({
290
- name: t.name,
291
- displayName: t.displayName || t.name,
292
- description: t.description || '',
293
- author: t.author || '',
294
- version: t.version || '0.0.0',
295
- tags: t.tags || [],
296
- type: t.type || 'template',
297
- trustLevel: registry.trustLevel,
298
- isVerified: trustLevelToIsVerified(registry.trustLevel),
299
- isInstalled: installedNames.has(t.name) ||
300
- installedSlugs.has(t.name.toLowerCase().replace(/[\s_]+/g, '-')),
301
- registryId: registry.id,
302
- registryName: registry.name,
303
- installCount: installStats[t.name]?.unique ?? 0,
304
- });
305
- }
306
- }
307
- return { items };
308
- }
309
- // --- Install / Uninstall ---
310
- async installMarketItem(registryId, marketName) {
311
- this.validateItemName(marketName);
312
- const entries = this.loadRegistries();
313
- const registry = entries.find((r) => r.id === registryId);
314
- if (!registry) {
315
- throw new common_1.NotFoundException(`Registry '${registryId}' not found`);
316
- }
317
- // Deny list: block packages listed in marketplace.json deny[]
318
- const mpConfig = this.loadMarketplaceJson();
319
- if (mpConfig?.deny?.length) {
320
- const denySet = new Set(mpConfig.deny.map((d) => d.toLowerCase()));
321
- if (denySet.has(marketName.toLowerCase())) {
322
- throw new common_1.BadRequestException(`Package '${marketName}' is blocked by marketplace policy (deny list).`);
323
- }
324
- }
325
- const templates = await this.fetchTemplates(registry);
326
- const template = templates.find((t) => t.name === marketName);
327
- if (!template) {
328
- throw new common_1.NotFoundException(`Skill '${marketName}' not found in registry '${registry.name}'`);
329
- }
330
- // Block exec/plugin from unverified registries (security policy — 마켓-설계서 6.1)
331
- if (registry.trustLevel === 'unverified') {
332
- if (template.type === 'exec') {
333
- throw new common_1.BadRequestException('Unverified registry exec skill installation is blocked for security.\n' +
334
- 'Use a verified registry or manually copy the skill files.');
335
- }
336
- if (template.type === 'plugin') {
337
- throw new common_1.BadRequestException('Unverified registry plugin installation is blocked for security.\n' +
338
- 'Use a verified registry or manually copy the plugin files.');
339
- }
340
- }
341
- const parsed = this.parseGitHubUrl(registry.url);
342
- if (!parsed) {
343
- throw new common_1.BadRequestException('Invalid registry URL');
344
- }
345
- const skillPath = template.path || marketName;
346
- const targetDir = (0, path_1.join)(this.skillsDir, marketName);
347
- const tempDir = (0, path_1.join)(this.skillsDir, `.tmp-${marketName}-${Date.now()}`);
348
- try {
349
- // 1. Fetch GitHub Trees API to get full file list
350
- const treesUrl = `https://api.github.com/repos/${parsed.owner}/${parsed.repo}/git/trees/main?recursive=1`;
351
- const treesRes = await fetch(treesUrl, { headers: this.getGitHubHeaders(registry) });
352
- if (!treesRes.ok) {
353
- throw new Error(`Trees API: HTTP ${treesRes.status} ${treesRes.statusText}`);
354
- }
355
- const treesData = await treesRes.json();
356
- // 2. Filter: blobs under skillPath/ with safety exclusions
357
- const BLOCKED_PREFIXES = ['node_modules/', '.git/'];
358
- const skillPrefix = `${skillPath}/`;
359
- const files = treesData.tree.filter((item) => {
360
- if (item.type !== 'blob')
361
- return false;
362
- if (!item.path.startsWith(skillPrefix))
363
- return false;
364
- const relPath = item.path.slice(skillPrefix.length);
365
- if (!relPath)
366
- return false;
367
- // Safety filter: block node_modules/, .git/, and .env files
368
- if (BLOCKED_PREFIXES.some((p) => relPath.startsWith(p)))
369
- return false;
370
- if (relPath === '.env' || relPath.startsWith('.env/'))
371
- return false;
372
- return true;
373
- });
374
- if (files.length === 0) {
375
- throw new Error(`No files found in skill directory '${skillPath}'`);
376
- }
377
- // 3. Download all files to tempDir
378
- if (!(0, fs_1.existsSync)(this.skillsDir)) {
379
- (0, fs_1.mkdirSync)(this.skillsDir, { recursive: true });
380
- }
381
- (0, fs_1.mkdirSync)(tempDir, { recursive: true });
382
- const resolvedTempDir = (0, path_1.resolve)(tempDir);
383
- for (const file of files) {
384
- const relPath = file.path.slice(skillPrefix.length);
385
- // Path traversal guard
386
- const destPath = (0, path_1.resolve)((0, path_1.join)(tempDir, relPath));
387
- if (!destPath.startsWith(resolvedTempDir + path_1.sep) && destPath !== resolvedTempDir) {
388
- throw new Error(`Path traversal detected: ${relPath}`);
389
- }
390
- const destFileDir = (0, path_1.dirname)(destPath);
391
- if (!(0, fs_1.existsSync)(destFileDir)) {
392
- (0, fs_1.mkdirSync)(destFileDir, { recursive: true });
393
- }
394
- const rawUrl = `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/main/${file.path}`;
395
- const fileContent = await this.fetchUrlBytes(rawUrl, this.getGitHubHeaders(registry));
396
- (0, fs_1.writeFileSync)(destPath, fileContent);
397
- }
398
- // 3.5. Compute treeSha early (needed for Step 5.5 and Step 8)
399
- const treeSha = treesData.tree.find((item) => item.type === 'tree' && item.path === skillPath)?.sha ?? '';
400
- // 4. Trust check: exec skill from unverified registry → block
401
- if (registry.trustLevel === 'unverified' && this.isExecItem(template, tempDir)) {
402
- throw new common_1.BadRequestException('Unverified registry exec skill installation is blocked for security. ' +
403
- 'Use a verified registry or manually copy the skill files.');
404
- }
405
- // 5. Atomic rename: backup-rename pattern for safe rollback
406
- const backupDir = (0, fs_1.existsSync)(targetDir)
407
- ? (0, path_1.join)(this.skillsDir, `.backup-${marketName}-${Date.now()}`)
408
- : null;
409
- if (backupDir) {
410
- (0, fs_1.renameSync)(targetDir, backupDir);
411
- }
412
- try {
413
- (0, fs_1.renameSync)(tempDir, targetDir);
414
- if (backupDir && (0, fs_1.existsSync)(backupDir)) {
415
- (0, fs_1.rmSync)(backupDir, { recursive: true, force: true });
416
- }
417
- }
418
- catch (renameErr) {
419
- if (backupDir && (0, fs_1.existsSync)(backupDir)) {
420
- (0, fs_1.renameSync)(backupDir, targetDir);
421
- }
422
- throw renameErr;
423
- }
424
- // 5.5. Ensure SKILL.md exists and inject metadata.source (§3.3)
425
- const skillMdPath = (0, path_1.join)(targetDir, 'SKILL.md');
426
- if (!(0, fs_1.existsSync)(skillMdPath)) {
427
- const defaultContent = [
428
- '---',
429
- `name: ${template.displayName || marketName}`,
430
- `description: "${template.description || ''}"`,
431
- `version: ${template.version || '0.0.0'}`,
432
- '---',
433
- '',
434
- `# ${template.displayName || marketName}`,
435
- '',
436
- template.description || '',
437
- '',
438
- ].join('\n');
439
- (0, fs_1.writeFileSync)(skillMdPath, defaultContent, 'utf-8');
440
- }
441
- const installedAt = new Date().toISOString();
442
- this.injectSourceMetadata(skillMdPath, {
443
- registry: `${parsed.owner}/${parsed.repo}`,
444
- type: registry.type,
445
- path: template.path ?? marketName,
446
- version: template.version ?? '0.0.0',
447
- treeSha,
448
- installedAt,
449
- });
450
- // 6. Install npm dependencies if package.json exists
451
- if ((0, fs_1.existsSync)((0, path_1.join)(targetDir, 'package.json'))) {
452
- this.logger.log(`Installing npm dependencies for '${marketName}'`);
453
- (0, child_process_1.execSync)('npm install --production --ignore-scripts', { cwd: targetDir });
454
- }
455
- // 7. Record install tracking in registry.json
456
- this.updateRegistryInstallRecord(marketName, {
457
- version: template.version,
458
- registryId: registry.id,
459
- installedAt,
460
- });
461
- // 8. Write lockfile entry (treeSha reused from Step 3.5)
462
- const fileShas = {};
463
- for (const item of treesData.tree) {
464
- if (item.type === 'blob' && item.path.startsWith(skillPrefix)) {
465
- const relPath = item.path.slice(skillPrefix.length);
466
- if (relPath)
467
- fileShas[relPath] = item.sha;
468
- }
469
- }
470
- const lockEntry = {
471
- name: marketName,
472
- version: template.version ?? '0.0.0',
473
- registryId: registry.id,
474
- registryUrl: registry.url,
475
- treeSha,
476
- fileShas,
477
- installedAt,
478
- };
479
- const lock = this.loadLockFile();
480
- lock.skills[marketName] = lockEntry;
481
- this.saveLockFile(lock);
482
- // Cache GitHub metadata (non-blocking — best-effort)
483
- this.cacheGitHubMetadata(marketName, registry).catch((err) => {
484
- this.logger.warn(`Failed to cache GitHub metadata for '${marketName}': ${err}`);
485
- });
486
- this.logger.log(`Installed skill '${marketName}' to ${targetDir}`);
487
- this.analytics.track('market.install', {
488
- item_name: marketName,
489
- item_type: template.type || 'template',
490
- item_version: template.version || '0.0.0',
491
- registry_id: registryId,
492
- });
493
- return { installed: true, path: targetDir };
494
- }
495
- catch (err) {
496
- // Cleanup temp dir on any failure
497
- if ((0, fs_1.existsSync)(tempDir)) {
498
- (0, fs_1.rmSync)(tempDir, { recursive: true, force: true });
499
- }
500
- if (err instanceof common_1.BadRequestException || err instanceof common_1.NotFoundException) {
501
- throw err;
502
- }
503
- this.logger.error(`Failed to install skill '${marketName}': ${err}`);
504
- throw new common_1.BadRequestException(`Failed to install skill '${marketName}': ${err instanceof Error ? err.message : String(err)}`);
505
- }
506
- }
507
- isExecItem(template, tempDir) {
508
- if (template.type === 'exec')
509
- return true;
510
- const pkgPath = (0, path_1.join)(tempDir, 'package.json');
511
- if ((0, fs_1.existsSync)(pkgPath)) {
512
- try {
513
- const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf-8'));
514
- if (pkg.bin || pkg.main)
515
- return true;
516
- }
517
- catch {
518
- // ignore malformed package.json
519
- }
520
- }
521
- return false;
522
- }
523
- injectSourceMetadata(skillMdPath, source) {
524
- const raw = (0, fs_1.readFileSync)(skillMdPath, 'utf-8');
525
- const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)/);
526
- let frontmatter;
527
- let body;
528
- if (fmMatch) {
529
- frontmatter = yaml.load(fmMatch[1]) ?? {};
530
- body = fmMatch[2] ?? '';
531
- }
532
- else {
533
- frontmatter = {};
534
- body = raw;
535
- }
536
- const existingMetadata = frontmatter.metadata != null && typeof frontmatter.metadata === 'object'
537
- ? frontmatter.metadata
538
- : {};
539
- frontmatter.metadata = {
540
- ...existingMetadata,
541
- version: frontmatter.version ?? source.version,
542
- source,
543
- };
544
- const yamlStr = yaml.dump(frontmatter, { lineWidth: -1 });
545
- (0, fs_1.writeFileSync)(skillMdPath, `---\n${yamlStr}---\n${body}`, 'utf-8');
546
- }
547
- updateRegistryInstallRecord(itemName, info) {
548
- const cache = this.loadRegistryCache();
549
- const existing = cache[itemName];
550
- cache[itemName] = {
551
- authors: existing?.authors ?? [],
552
- stars: existing?.stars ?? 0,
553
- license: existing?.license ?? '',
554
- updatedAt: existing?.updatedAt ?? '',
555
- fetchedAt: existing?.fetchedAt ?? '',
556
- version: info.version,
557
- registryId: info.registryId,
558
- installedAt: info.installedAt,
559
- };
560
- this.saveRegistryCache(cache);
561
- }
562
- async fetchUrlBytes(url, headers) {
563
- const response = await fetch(url, headers ? { headers } : undefined);
564
- if (!response.ok) {
565
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
566
- }
567
- return new Uint8Array(await response.arrayBuffer());
568
- }
569
- uninstallMarketItem(name) {
570
- this.validateItemName(name);
571
- const targetDir = (0, path_1.join)(this.skillsDir, name);
572
- if ((0, fs_1.existsSync)(targetDir)) {
573
- (0, fs_1.rmSync)(targetDir, { recursive: true, force: true });
574
- }
575
- const lock = this.loadLockFile();
576
- delete lock.skills[name];
577
- this.saveLockFile(lock);
578
- this.logger.log(`Uninstalled skill '${name}'`);
579
- this.analytics.track('market.uninstall', {
580
- item_name: name,
581
- item_type: 'skill',
582
- });
583
- }
584
- // --- Installed items ---
585
- resolveItemSource(dir, projectRoot) {
586
- const nmDir = (0, path_1.join)(projectRoot, 'node_modules') + path_1.sep;
587
- if (!dir.startsWith(nmDir) && dir !== (0, path_1.join)(projectRoot, 'node_modules')) {
588
- return 'local';
589
- }
590
- const relFromNm = dir.slice(nmDir.length);
591
- return relFromNm.startsWith(`@crewx${path_1.sep}`) ? 'builtin' : 'node_modules';
592
- }
593
- async listInstalledMarkets() {
594
- const projectRoot = (0, path_1.dirname)(this.skillsDir);
595
- const nmDir = (0, path_1.join)(projectRoot, 'node_modules');
596
- const engine = new skill_1.SkillEngine(projectRoot, (0, fs_1.existsSync)(nmDir) ? [nmDir] : []);
597
- const discovered = engine.discover();
598
- const items = [];
599
- for (const item of discovered) {
600
- const source = this.resolveItemSource(item.dir, projectRoot);
601
- if (source === 'local') {
602
- const skillMdPath = item.skillMdPath ?? (0, path_1.join)(item.dir, 'SKILL.md');
603
- const { frontmatter } = (0, fs_1.existsSync)(skillMdPath)
604
- ? await this.parseSkillMdAsync(skillMdPath)
605
- : { frontmatter: {} };
606
- const installSource = this.detectInstallSource(item.name);
607
- const cached = this.getGitHubCacheEntry(item.name);
608
- let updatedAt = cached?.updatedAt;
609
- if (!updatedAt) {
610
- try {
611
- updatedAt = (await (0, promises_1.stat)(skillMdPath)).mtime.toISOString();
612
- }
613
- catch {
614
- // ignore stat errors
615
- }
616
- }
617
- const localTrust = installSource === 'builtin' ? 'trusted' : 'unverified';
618
- items.push({
619
- name: item.name,
620
- displayName: item.name,
621
- description: item.description || frontmatter.description || '',
622
- author: frontmatter.author || '',
623
- version: item.version || frontmatter.version || '0.0.0',
624
- tags: frontmatter.tags ? String(frontmatter.tags).split(',').map((t) => t.trim()) : [],
625
- type: item.type,
626
- trustLevel: localTrust,
627
- isVerified: trustLevelToIsVerified(localTrust),
628
- isInstalled: true,
629
- source,
630
- installSource,
631
- registryId: cached?.registryId,
632
- authors: cached?.authors,
633
- updatedAt,
634
- stars: cached?.stars,
635
- license: cached?.license,
636
- });
637
- }
638
- else {
639
- const nmTrust = source === 'builtin' ? 'trusted' : 'unverified';
640
- items.push({
641
- name: item.name,
642
- displayName: item.name,
643
- description: item.description || '',
644
- author: '',
645
- version: item.version || '0.0.0',
646
- tags: [],
647
- type: item.type,
648
- trustLevel: nmTrust,
649
- isVerified: trustLevelToIsVerified(nmTrust),
650
- isInstalled: false,
651
- source,
652
- });
653
- }
654
- }
655
- return { items };
656
- }
657
- async checkUpdates() {
658
- const registries = this.loadRegistries();
659
- const installed = await this.listInstalledMarkets();
660
- const cache = this.loadRegistryCache();
661
- const updates = [];
662
- const registryTemplates = new Map();
663
- for (const item of installed.items) {
664
- if (item.installSource !== 'registry')
665
- continue;
666
- const cached = cache[item.name];
667
- if (!cached?.registryId)
668
- continue;
669
- const registry = registries.find((r) => r.id === cached.registryId);
670
- if (!registry)
671
- continue;
672
- let templates = registryTemplates.get(registry.id);
673
- if (!templates) {
674
- try {
675
- templates = await this.fetchTemplates(registry);
676
- registryTemplates.set(registry.id, templates);
677
- }
678
- catch {
679
- continue;
680
- }
681
- }
682
- const remote = templates.find((t) => t.name === item.name);
683
- if (!remote?.version || !cached.version)
684
- continue;
685
- const hasUpdate = remote.version !== cached.version;
686
- const isCustomized = await this.checkIsCustomized(item.name);
687
- updates.push({
688
- name: item.name,
689
- hasUpdate,
690
- installedVersion: cached.version,
691
- remoteVersion: remote.version,
692
- isCustomized,
693
- });
694
- }
695
- return { updates };
696
- }
697
- async checkIsCustomized(_skillName) {
698
- // Disabled: GitHub tree SHA vs local SHA-256 mismatch (SQA #9)
699
- return false;
700
- }
701
- async getInstalledMarketDetail(name) {
702
- this.validateItemName(name);
703
- const entryPath = (0, path_1.join)(this.skillsDir, name);
704
- if (!(0, fs_1.existsSync)(entryPath) || !(0, fs_1.statSync)(entryPath).isDirectory()) {
705
- throw new common_1.NotFoundException(`Installed skill '${name}' not found`);
706
- }
707
- const skillMdPath = (0, path_1.join)(entryPath, 'SKILL.md');
708
- if (!(0, fs_1.existsSync)(skillMdPath)) {
709
- throw new common_1.NotFoundException(`Installed skill '${name}' not found`);
710
- }
711
- const { frontmatter, body } = this.parseSkillMd(skillMdPath);
712
- const installSource = this.detectInstallSource(name);
713
- const cached = this.getGitHubCacheEntry(name);
714
- let updatedAt = cached?.updatedAt;
715
- if (!updatedAt) {
716
- try {
717
- updatedAt = (0, fs_1.statSync)(skillMdPath).mtime.toISOString();
718
- }
719
- catch {
720
- // ignore stat errors
721
- }
722
- }
723
- const detailTrust = installSource === 'builtin' ? 'trusted' : 'unverified';
724
- return {
725
- name: frontmatter.name || name,
726
- displayName: frontmatter.name || name,
727
- description: frontmatter.description || '',
728
- author: frontmatter.author || '',
729
- version: frontmatter.version || '0.0.0',
730
- tags: frontmatter.tags ? frontmatter.tags.split(',').map((t) => t.trim()) : [],
731
- type: 'skill',
732
- trustLevel: detailTrust,
733
- isVerified: trustLevelToIsVerified(detailTrust),
734
- isInstalled: true,
735
- installSource,
736
- authors: cached?.authors,
737
- updatedAt,
738
- stars: cached?.stars,
739
- license: cached?.license,
740
- body,
741
- };
742
- }
743
- parseSkillMd(filePath) {
744
- try {
745
- const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
746
- return this.parseFrontmatter(content);
747
- }
748
- catch {
749
- return { frontmatter: {}, body: '' };
750
- }
751
- }
752
- async parseSkillMdAsync(filePath) {
753
- try {
754
- const content = await (0, promises_1.readFile)(filePath, 'utf-8');
755
- return this.parseFrontmatter(content);
756
- }
757
- catch {
758
- return { frontmatter: {}, body: '' };
759
- }
760
- }
761
- parseFrontmatter(content) {
762
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)/);
763
- if (!match)
764
- return { frontmatter: {}, body: content };
765
- const result = {};
766
- const lines = match[1].split('\n');
767
- for (const line of lines) {
768
- const colonIdx = line.indexOf(':');
769
- if (colonIdx === -1)
770
- continue;
771
- const key = line.slice(0, colonIdx).trim();
772
- let value = line.slice(colonIdx + 1).trim();
773
- // Strip surrounding quotes
774
- if ((value.startsWith('"') && value.endsWith('"')) ||
775
- (value.startsWith("'") && value.endsWith("'"))) {
776
- value = value.slice(1, -1);
777
- }
778
- if (key)
779
- result[key] = value;
780
- }
781
- return { frontmatter: result, body: match[2] || '' };
782
- }
783
- async getRemoteSkillDetail(registryId, skillName) {
784
- this.validateItemName(skillName);
785
- const entries = this.loadRegistries();
786
- const registry = entries.find((r) => r.id === registryId);
787
- if (!registry) {
788
- throw new common_1.NotFoundException(`Registry '${registryId}' not found`);
789
- }
790
- const parsed = this.parseGitHubUrl(registry.url);
791
- if (!parsed) {
792
- return { body: '' };
793
- }
794
- const templates = await this.fetchTemplates(registry);
795
- const template = templates.find((t) => t.name === skillName);
796
- const skillPath = template?.path || skillName;
797
- const candidates = registry.type === 'claude'
798
- ? [`${skillPath}/SKILL.md`, `${skillPath}/README.md`]
799
- : [`skills/${skillPath}/SKILL.md`, `${skillPath}/SKILL.md`, `skills/${skillPath}/README.md`, `${skillPath}/README.md`];
800
- const headers = this.getGitHubHeaders(registry);
801
- for (const candidate of candidates) {
802
- const url = `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/main/${candidate}`;
803
- try {
804
- const content = await this.fetchUrl(url, headers);
805
- const { body } = this.parseFrontmatter(content);
806
- return { body: body.trim() ? body : content };
807
- }
808
- catch {
809
- continue;
810
- }
811
- }
812
- return { body: '' };
813
- }
814
- detectInstallSource(itemName) {
815
- const builtinDir = (0, path_1.join)(process.cwd(), 'packages', 'built-in', 'skill', 'skills', itemName);
816
- if ((0, fs_1.existsSync)(builtinDir)) {
817
- return 'builtin';
818
- }
819
- const cache = this.loadRegistryCache();
820
- if (cache[itemName]?.installedAt || cache[itemName]?.fetchedAt) {
821
- return 'registry';
822
- }
823
- return 'custom';
824
- }
825
- // --- Lockfile helpers ---
826
- loadLockFile() {
827
- if (!(0, fs_1.existsSync)(this.lockFilePath)) {
828
- return { lockVersion: 1, generatedAt: new Date().toISOString(), skills: {} };
829
- }
830
- const data = JSON.parse((0, fs_1.readFileSync)(this.lockFilePath, 'utf-8'));
831
- if (data.lockVersion !== 1) {
832
- throw new Error(`Unsupported lock version: ${data.lockVersion}`);
833
- }
834
- return data;
835
- }
836
- saveLockFile(lock) {
837
- lock.generatedAt = new Date().toISOString();
838
- const dir = (0, path_1.dirname)(this.lockFilePath);
839
- if (!(0, fs_1.existsSync)(dir)) {
840
- (0, fs_1.mkdirSync)(dir, { recursive: true });
841
- }
842
- (0, fs_1.writeFileSync)(this.lockFilePath, JSON.stringify(lock, null, 2) + '\n', 'utf-8');
843
- }
844
- // --- GitHub cache helpers ---
845
- loadRegistryCache() {
846
- if (!(0, fs_1.existsSync)(this.registryCachePath))
847
- return {};
848
- try {
849
- return JSON.parse((0, fs_1.readFileSync)(this.registryCachePath, 'utf-8'));
850
- }
851
- catch {
852
- return {};
853
- }
854
- }
855
- saveRegistryCache(cache) {
856
- const dir = (0, path_1.join)(this.registryCachePath, '..');
857
- if (!(0, fs_1.existsSync)(dir)) {
858
- (0, fs_1.mkdirSync)(dir, { recursive: true });
859
- }
860
- (0, fs_1.writeFileSync)(this.registryCachePath, JSON.stringify(cache, null, 2), 'utf-8');
861
- }
862
- getGitHubCacheEntry(itemName) {
863
- const cache = this.loadRegistryCache();
864
- return cache[itemName];
865
- }
866
- async fetchGitHubAuthors(registry) {
867
- const parsed = this.parseGitHubUrl(registry.url);
868
- if (!parsed)
869
- return [];
870
- try {
871
- const res = await fetch(`https://api.github.com/repos/${parsed.owner}/${parsed.repo}/contributors?per_page=5`, { headers: this.getGitHubHeaders(registry) });
872
- if (!res.ok)
873
- return [];
874
- const contributors = await res.json();
875
- return contributors.map((c) => ({
876
- login: c.login,
877
- avatarUrl: c.avatar_url,
878
- profileUrl: c.html_url,
879
- contributions: c.contributions,
880
- }));
881
- }
882
- catch {
883
- return [];
884
- }
885
- }
886
- async fetchGitHubRepoMeta(registry) {
887
- const parsed = this.parseGitHubUrl(registry.url);
888
- if (!parsed)
889
- return null;
890
- try {
891
- const res = await fetch(`https://api.github.com/repos/${parsed.owner}/${parsed.repo}`, { headers: this.getGitHubHeaders(registry) });
892
- if (!res.ok)
893
- return null;
894
- const data = await res.json();
895
- return {
896
- stars: data.stargazers_count ?? 0,
897
- license: data.license?.spdx_id ?? '',
898
- updatedAt: data.pushed_at ?? '',
899
- };
900
- }
901
- catch {
902
- return null;
903
- }
904
- }
905
- async cacheGitHubMetadata(itemName, registry) {
906
- const [authors, meta] = await Promise.all([
907
- this.fetchGitHubAuthors(registry),
908
- this.fetchGitHubRepoMeta(registry),
909
- ]);
910
- const cache = this.loadRegistryCache();
911
- cache[itemName] = {
912
- ...(cache[itemName] ?? {}),
913
- authors,
914
- stars: meta?.stars ?? 0,
915
- license: meta?.license ?? '',
916
- updatedAt: meta?.updatedAt ?? '',
917
- fetchedAt: new Date().toISOString(),
918
- };
919
- this.saveRegistryCache(cache);
920
- }
921
- getGitHubHeaders(registry) {
922
- const headers = {
923
- Accept: 'application/vnd.github.v3+json',
924
- 'User-Agent': 'crewx-ui',
925
- };
926
- const tokenEnvName = registry?.tokenEnv;
927
- const token = tokenEnvName ? process.env[tokenEnvName] : process.env.GITHUB_TOKEN;
928
- if (token) {
929
- headers['Authorization'] = `token ${token}`;
930
- }
931
- return headers;
932
- }
933
- validateTokenEnv(tokenEnv) {
934
- if (!/^[A-Z_][A-Z0-9_]*$/.test(tokenEnv)) {
935
- throw new common_1.BadRequestException('tokenEnv must match pattern ^[A-Z_][A-Z0-9_]*$ (uppercase letters, digits, underscores only)');
936
- }
937
- }
938
- // --- Internal helpers ---
939
- isPlainObject(val) {
940
- return Object.prototype.toString.call(val) === '[object Object]';
941
- }
942
- loadConfig() {
943
- if (!(0, fs_1.existsSync)(this.configPath)) {
944
- return {};
945
- }
946
- try {
947
- const content = (0, fs_1.readFileSync)(this.configPath, 'utf-8');
948
- const parsed = yaml.load(content);
949
- if (!this.isPlainObject(parsed)) {
950
- return {};
951
- }
952
- return parsed;
953
- }
954
- catch {
955
- return {};
956
- }
957
- }
958
- saveConfig(config) {
959
- (0, fs_1.writeFileSync)(this.configPath, yaml.dump(config, { lineWidth: -1 }), 'utf-8');
960
- }
961
- parseRegistrySourceType(raw) {
962
- return typeof raw === 'string' && VALID_REGISTRY_SOURCE_TYPES.has(raw)
963
- ? raw
964
- : 'crewx';
965
- }
966
- yamlToRegistryEntry(item) {
967
- return {
968
- id: item.id || `reg-${(0, crypto_1.randomUUID)()}`,
969
- name: item.name || '',
970
- url: item.url || '',
971
- type: this.parseRegistrySourceType(item.type),
972
- trustLevel: boolToTrustLevel(!!item.verified, !!item.default),
973
- isDefault: !!item.default,
974
- addedAt: item.addedAt || new Date().toISOString(),
975
- ...(item.tokenEnv ? { tokenEnv: item.tokenEnv } : {}),
976
- };
977
- }
978
- registryEntryToYaml(entry) {
979
- return {
980
- id: entry.id,
981
- name: entry.name,
982
- url: entry.url,
983
- type: entry.type,
984
- verified: trustLevelToIsVerified(entry.trustLevel),
985
- default: entry.isDefault,
986
- addedAt: entry.addedAt,
987
- ...(entry.tokenEnv ? { tokenEnv: entry.tokenEnv } : {}),
988
- };
989
- }
990
- loadMarketplaceJson() {
991
- // 1. Memory cache (process lifetime)
992
- if (this.marketplaceMemoryCache) {
993
- const age = Date.now() - this.marketplaceMemoryCache.fetchedAt;
994
- if (age >= MARKETPLACE_CACHE_TTL_MS && !this.marketplaceRefreshPending) {
995
- this.marketplaceRefreshPending = true;
996
- this.refreshMarketplaceRemote();
997
- }
998
- return this.marketplaceMemoryCache.config;
999
- }
1000
- // 2. User-managed override (~/.crewx/marketplace.json)
1001
- if ((0, fs_1.existsSync)(this.marketplacePath)) {
1002
- try {
1003
- const raw = JSON.parse((0, fs_1.readFileSync)(this.marketplacePath, 'utf-8'));
1004
- const result = MarketplaceConfigSchema.safeParse(raw);
1005
- if (result.success) {
1006
- const config = result.data;
1007
- this.marketplaceMemoryCache = { config, fetchedAt: Date.now() };
1008
- return config;
1009
- }
1010
- this.logger.warn(`Invalid marketplace.json schema: ${result.error.message}`);
1011
- }
1012
- catch {
1013
- this.logger.warn('Failed to parse marketplace.json');
1014
- }
1015
- }
1016
- // 3. Disk cache (~/.crewx/marketplace.cache.json)
1017
- const diskCache = this.loadMarketplaceDiskCache();
1018
- if (diskCache) {
1019
- this.marketplaceMemoryCache = diskCache;
1020
- if (Date.now() - diskCache.fetchedAt >= MARKETPLACE_CACHE_TTL_MS && !this.marketplaceRefreshPending) {
1021
- this.marketplaceRefreshPending = true;
1022
- this.refreshMarketplaceRemote();
1023
- }
1024
- return diskCache.config;
1025
- }
1026
- // 4. No local data — return null, let loadRegistries() handle fallback.
1027
- return null;
1028
- }
1029
- loadMarketplaceDiskCache() {
1030
- if (!(0, fs_1.existsSync)(this.marketplaceCachePath))
1031
- return null;
1032
- try {
1033
- const raw = JSON.parse((0, fs_1.readFileSync)(this.marketplaceCachePath, 'utf-8'));
1034
- if (!raw || typeof raw.fetchedAt !== 'number' || !raw.config)
1035
- return null;
1036
- const result = MarketplaceConfigSchema.safeParse(raw.config);
1037
- if (!result.success)
1038
- return null;
1039
- return {
1040
- config: result.data,
1041
- etag: raw.etag,
1042
- lastModified: raw.lastModified,
1043
- fetchedAt: raw.fetchedAt,
1044
- };
1045
- }
1046
- catch {
1047
- return null;
1048
- }
1049
- }
1050
- saveMarketplaceDiskCache(cache) {
1051
- try {
1052
- const dir = (0, path_1.dirname)(this.marketplaceCachePath);
1053
- if (!(0, fs_1.existsSync)(dir))
1054
- (0, fs_1.mkdirSync)(dir, { recursive: true });
1055
- (0, fs_1.writeFileSync)(this.marketplaceCachePath, JSON.stringify(cache, null, 2), 'utf-8');
1056
- }
1057
- catch {
1058
- this.logger.warn('Failed to save marketplace disk cache');
1059
- }
1060
- }
1061
- refreshMarketplaceRemote() {
1062
- const existing = this.marketplaceMemoryCache;
1063
- const headers = {};
1064
- if (existing?.etag)
1065
- headers['If-None-Match'] = existing.etag;
1066
- if (existing?.lastModified)
1067
- headers['If-Modified-Since'] = existing.lastModified;
1068
- fetch(MARKETPLACE_REMOTE_URL, { headers, signal: AbortSignal.timeout(10_000) })
1069
- .then(async (res) => {
1070
- if (res.status === 304) {
1071
- if (existing) {
1072
- existing.fetchedAt = Date.now();
1073
- this.saveMarketplaceDiskCache(existing);
1074
- }
1075
- return;
1076
- }
1077
- if (!res.ok)
1078
- return;
1079
- const text = await res.text();
1080
- const parsed = MarketplaceConfigSchema.safeParse(JSON.parse(text));
1081
- if (!parsed.success)
1082
- return;
1083
- const cache = {
1084
- config: parsed.data,
1085
- etag: res.headers.get('etag') ?? undefined,
1086
- lastModified: res.headers.get('last-modified') ?? undefined,
1087
- fetchedAt: Date.now(),
1088
- };
1089
- this.marketplaceMemoryCache = cache;
1090
- this.saveMarketplaceDiskCache(cache);
1091
- })
1092
- .catch(() => {
1093
- // Network failure — seed disk cache from bundled default if nothing cached yet
1094
- if (!this.marketplaceMemoryCache && !(0, fs_1.existsSync)(this.marketplaceCachePath)) {
1095
- const bundled = loadBundledMarketplace();
1096
- if (bundled) {
1097
- const cache = { config: bundled, fetchedAt: Date.now() };
1098
- this.marketplaceMemoryCache = cache;
1099
- this.saveMarketplaceDiskCache(cache);
1100
- }
1101
- }
1102
- })
1103
- .finally(() => {
1104
- this.marketplaceRefreshPending = false;
1105
- });
1106
- }
1107
- loadWorkspaceMarketplaceJson() {
1108
- if (!(0, fs_1.existsSync)(this.workspaceMarketplacePath))
1109
- return null;
1110
- try {
1111
- const raw = JSON.parse((0, fs_1.readFileSync)(this.workspaceMarketplacePath, 'utf-8'));
1112
- const result = MarketplaceConfigSchema.safeParse(raw);
1113
- if (!result.success) {
1114
- this.logger.warn(`Invalid workspace marketplace.json schema: ${result.error.message}`);
1115
- return null;
1116
- }
1117
- return result.data;
1118
- }
1119
- catch {
1120
- this.logger.warn('Failed to parse workspace marketplace.json');
1121
- return null;
1122
- }
1123
- }
1124
- marketplaceEntryToRegistry(item) {
1125
- const trust = item.trust ?? boolToTrustLevel(item.verified ?? true, !!item.default);
1126
- return {
1127
- id: item.id,
1128
- name: item.name || '',
1129
- url: item.url,
1130
- type: this.parseRegistrySourceType(item.type),
1131
- trustLevel: trust,
1132
- isDefault: !!item.default,
1133
- addedAt: new Date().toISOString(),
1134
- ...(item.tokenEnv ? { tokenEnv: item.tokenEnv } : {}),
1135
- };
1136
- }
1137
- isBuiltinExempt(reg) {
1138
- return (reg.name.startsWith('@crewx/') ||
1139
- this.normalizeUrl(reg.url) === this.normalizeUrl(DEFAULT_REGISTRY_URL));
1140
- }
1141
- mergeRegistries(global, workspace, forceDisabled) {
1142
- const disabledSet = new Set(forceDisabled);
1143
- const merged = new Map();
1144
- for (const reg of global) {
1145
- if (!disabledSet.has(reg.id) || this.isBuiltinExempt(reg)) {
1146
- merged.set(this.normalizeUrl(reg.url), { ...reg, source: 'global' });
1147
- }
1148
- }
1149
- for (const reg of workspace) {
1150
- if (disabledSet.has(reg.id) && !this.isBuiltinExempt(reg))
1151
- continue;
1152
- const key = this.normalizeUrl(reg.url);
1153
- if (merged.has(key))
1154
- continue;
1155
- merged.set(key, { ...reg, source: 'workspace', trustLevel: 'unverified' });
1156
- }
1157
- return Array.from(merged.values());
1158
- }
1159
- loadRegistries() {
1160
- const mpConfig = this.loadMarketplaceJson();
1161
- const wsMpConfig = this.loadWorkspaceMarketplaceJson();
1162
- const config = this.loadConfig();
1163
- const registriesYaml = config?.skills?.registries;
1164
- if (!mpConfig && !wsMpConfig) {
1165
- // No marketplace.json anywhere — seed disk cache from remote for next restart
1166
- if (!this.marketplaceRefreshPending) {
1167
- this.marketplaceRefreshPending = true;
1168
- this.refreshMarketplaceRemote();
1169
- }
1170
- if (!registriesYaml || registriesYaml.length === 0) {
1171
- const defaults = this.getDefaultRegistries();
1172
- this.saveRegistries(defaults);
1173
- return defaults;
1174
- }
1175
- try {
1176
- const entries = registriesYaml.map((item) => this.yamlToRegistryEntry(item));
1177
- return this.ensureDefaultRegistry(entries);
1178
- }
1179
- catch {
1180
- const defaults = this.getDefaultRegistries();
1181
- this.saveRegistries(defaults);
1182
- return defaults;
1183
- }
1184
- }
1185
- const globalRegistries = mpConfig
1186
- ? mpConfig.registries.map((r) => this.marketplaceEntryToRegistry(r))
1187
- : [];
1188
- const forceDisabled = mpConfig?.forceDisabled ?? [];
1189
- let workspaceRegistries = [];
1190
- if (wsMpConfig) {
1191
- workspaceRegistries = wsMpConfig.registries.map((r) => this.marketplaceEntryToRegistry(r));
1192
- }
1193
- if (registriesYaml && registriesYaml.length > 0) {
1194
- try {
1195
- const yamlEntries = registriesYaml.map((item) => this.yamlToRegistryEntry(item));
1196
- workspaceRegistries = [...workspaceRegistries, ...yamlEntries];
1197
- }
1198
- catch {
1199
- // ignore workspace parse errors
1200
- }
1201
- }
1202
- const merged = this.mergeRegistries(globalRegistries, workspaceRegistries, forceDisabled);
1203
- return this.ensureDefaultRegistry(merged);
1204
- }
1205
- saveRegistries(entries) {
1206
- const config = this.loadConfig();
1207
- if (!this.isPlainObject(config.skills)) {
1208
- config.skills = {};
1209
- }
1210
- const persistable = entries.filter((e) => e.source !== 'global');
1211
- config.skills.registries = persistable.map((e) => this.registryEntryToYaml(e));
1212
- this.saveConfig(config);
1213
- }
1214
- getDefaultRegistries() {
1215
- return [
1216
- {
1217
- id: 'default-crewx-templates',
1218
- name: DEFAULT_REGISTRY_NAME,
1219
- url: DEFAULT_REGISTRY_URL,
1220
- type: 'crewx',
1221
- trustLevel: 'verified',
1222
- isDefault: true,
1223
- addedAt: new Date().toISOString(),
1224
- },
1225
- ];
1226
- }
1227
- ensureDefaultRegistry(entries) {
1228
- const defaultUrl = this.normalizeUrl(DEFAULT_REGISTRY_URL);
1229
- const officialEntry = entries.find((r) => this.normalizeUrl(r.url) === defaultUrl);
1230
- if (!officialEntry) {
1231
- entries.unshift(this.getDefaultRegistries()[0]);
1232
- this.saveRegistries(entries);
1233
- }
1234
- else if (!officialEntry.isDefault || officialEntry.trustLevel === 'unverified') {
1235
- officialEntry.isDefault = true;
1236
- officialEntry.trustLevel = 'verified';
1237
- this.saveRegistries(entries);
1238
- }
1239
- return entries;
1240
- }
1241
- async fetchTemplates(registry) {
1242
- const cached = this.templateCache.get(registry.id);
1243
- if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
1244
- return cached.data;
1245
- }
1246
- if (registry.type === 'claude') {
1247
- return this.fetchClaudeTemplates(registry);
1248
- }
1249
- const parsed = this.parseGitHubUrl(registry.url);
1250
- if (!parsed) {
1251
- this.logger.warn(`Invalid registry URL: ${registry.url}`);
1252
- return [];
1253
- }
1254
- const url = `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/main/templates.json`;
1255
- try {
1256
- const content = await this.fetchUrl(url, this.getGitHubHeaders(registry));
1257
- const json = JSON.parse(content);
1258
- const rawTemplates = json.templates || [];
1259
- const templates = [];
1260
- for (const raw of rawTemplates) {
1261
- const result = TemplateEntrySchema.safeParse(raw);
1262
- if (result.success) {
1263
- templates.push(result.data);
1264
- }
1265
- else {
1266
- this.logger.warn(`Skipping invalid template entry: ${JSON.stringify(result.error.flatten().fieldErrors)}`);
1267
- }
1268
- }
1269
- this.templateCache.set(registry.id, {
1270
- data: templates,
1271
- fetchedAt: Date.now(),
1272
- });
1273
- return templates;
1274
- }
1275
- catch (err) {
1276
- this.logger.warn(`Failed to fetch templates.json from ${registry.url}: ${err}`);
1277
- return [];
1278
- }
1279
- }
1280
- async fetchClaudeTemplates(registry) {
1281
- const parsed = this.parseGitHubUrl(registry.url);
1282
- if (!parsed) {
1283
- this.logger.warn(`Invalid registry URL: ${registry.url}`);
1284
- return [];
1285
- }
1286
- const url = `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/main/.claude-plugin/marketplace.json`;
1287
- try {
1288
- const content = await this.fetchUrl(url, this.getGitHubHeaders(registry));
1289
- const json = JSON.parse(content);
1290
- const author = json.owner?.name ?? parsed.owner;
1291
- const version = json.metadata?.version ?? '1.0.0';
1292
- const templates = [];
1293
- for (const plugin of json.plugins ?? []) {
1294
- if (plugin.skills && plugin.skills.length > 0) {
1295
- for (const skillRef of plugin.skills) {
1296
- const skillPath = skillRef.replace(/^\.\//, '');
1297
- const skillName = skillPath.split('/').pop() ?? skillPath;
1298
- const displayName = skillName
1299
- .split('-')
1300
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
1301
- .join(' ');
1302
- templates.push({
1303
- name: skillName,
1304
- displayName,
1305
- description: plugin.description,
1306
- path: skillPath,
1307
- type: 'skill',
1308
- author,
1309
- version,
1310
- });
1311
- }
1312
- }
1313
- else if (plugin.source != null) {
1314
- const discovered = await this.discoverSkillsViaSource(plugin.source, parsed, registry);
1315
- for (const skillName of discovered) {
1316
- const displayName = skillName
1317
- .split('-')
1318
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
1319
- .join(' ');
1320
- templates.push({
1321
- name: skillName,
1322
- displayName,
1323
- description: plugin.description,
1324
- path: `skills/${skillName}`,
1325
- type: 'skill',
1326
- author,
1327
- version,
1328
- });
1329
- }
1330
- }
1331
- }
1332
- this.templateCache.set(registry.id, {
1333
- data: templates,
1334
- fetchedAt: Date.now(),
1335
- });
1336
- return templates;
1337
- }
1338
- catch (err) {
1339
- this.logger.warn(`Failed to fetch .claude-plugin/marketplace.json from ${registry.url}: ${err}`);
1340
- return [];
1341
- }
1342
- }
1343
- async discoverSkillsViaSource(source, registryParsed, registry) {
1344
- let owner;
1345
- let repo;
1346
- let ref = 'main';
1347
- let basePath = '';
1348
- if (typeof source === 'string') {
1349
- owner = registryParsed.owner;
1350
- repo = registryParsed.repo;
1351
- basePath = source.replace(/^\.\//, '').replace(/\/+$/, '');
1352
- }
1353
- else {
1354
- if (source.url) {
1355
- const srcParsed = this.parseGitHubUrl(source.url);
1356
- if (!srcParsed)
1357
- return [];
1358
- owner = srcParsed.owner;
1359
- repo = srcParsed.repo;
1360
- }
1361
- else {
1362
- owner = registryParsed.owner;
1363
- repo = registryParsed.repo;
1364
- }
1365
- if (source.ref)
1366
- ref = source.ref;
1367
- basePath = (source.path ?? '').replace(/^\.\//, '').replace(/\/+$/, '');
1368
- }
1369
- try {
1370
- const treesUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`;
1371
- const treesRes = await fetch(treesUrl, { headers: this.getGitHubHeaders(registry) });
1372
- if (!treesRes.ok)
1373
- return [];
1374
- const treesData = await treesRes.json();
1375
- const skillsPrefix = basePath ? `${basePath}/skills/` : 'skills/';
1376
- const skillNames = new Set();
1377
- for (const item of treesData.tree) {
1378
- if (item.type !== 'blob')
1379
- continue;
1380
- if (!item.path.startsWith(skillsPrefix))
1381
- continue;
1382
- const rel = item.path.slice(skillsPrefix.length);
1383
- const parts = rel.split('/');
1384
- if (parts.length === 2 && parts[1] === 'SKILL.md') {
1385
- skillNames.add(parts[0]);
1386
- }
1387
- }
1388
- return [...skillNames];
1389
- }
1390
- catch {
1391
- return [];
1392
- }
1393
- }
1394
- async fetchUrl(url, headers) {
1395
- const response = await fetch(url, headers ? { headers } : undefined);
1396
- if (!response.ok) {
1397
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1398
- }
1399
- return response.text();
1400
- }
1401
- parseGitHubUrl(url) {
1402
- const match = url.match(/(?:https?:\/\/)?github\.com\/([^/]+)\/([^/.]+)/);
1403
- if (!match)
1404
- return null;
1405
- return { owner: match[1], repo: match[2] };
1406
- }
1407
- normalizeUrl(url) {
1408
- return url
1409
- .replace(/\.git$/, '')
1410
- .replace(/\/+$/, '')
1411
- .toLowerCase();
1412
- }
1413
- async getInstalledItemNames() {
1414
- if (!(0, fs_1.existsSync)(this.skillsDir))
1415
- return new Set();
1416
- try {
1417
- const entries = await (0, promises_1.readdir)(this.skillsDir);
1418
- const names = new Set();
1419
- for (const entry of entries) {
1420
- const entryPath = (0, path_1.join)(this.skillsDir, entry);
1421
- try {
1422
- if ((await (0, promises_1.stat)(entryPath)).isDirectory()) {
1423
- if ((0, fs_1.existsSync)((0, path_1.join)(entryPath, 'SKILL.md'))) {
1424
- names.add(entry);
1425
- }
1426
- }
1427
- }
1428
- catch {
1429
- // skip inaccessible entries
1430
- }
1431
- }
1432
- return names;
1433
- }
1434
- catch {
1435
- return new Set();
1436
- }
1437
- }
1438
- getCachedItemCount(registryId) {
1439
- const cached = this.templateCache.get(registryId);
1440
- return cached ? cached.data.length : 0;
1441
- }
1442
- };
1443
- exports.MarketService = MarketService;
1444
- exports.MarketService = MarketService = MarketService_1 = __decorate([
1445
- (0, common_1.Injectable)(),
1446
- __metadata("design:paramtypes", [analytics_client_js_1.AnalyticsService])
1447
- ], MarketService);