crewx 0.8.7-rc.2 → 0.8.7-rc.21
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.
- package/dist/assets/MarketPage-aY16UW1_.js +51 -0
- package/dist/assets/{PromptTab-DPxKgALd.js → PromptTab-C3aTJS6u.js} +7 -7
- package/dist/assets/_baseUniq-DxEFlwNt.js +1 -0
- package/dist/assets/{arc-Wd7DG8CX.js → arc-BCQAokB1.js} +1 -1
- package/dist/assets/architectureDiagram-UYN6MBPD-Bc87ujv2.js +36 -0
- package/dist/assets/blockDiagram-ZHA2E4KO-wC9uH8Lb.js +121 -0
- package/dist/assets/{c4Diagram-6F5ED5ID-CcfPHYky.js → c4Diagram-6F5ED5ID-CqB7lqni.js} +1 -1
- package/dist/assets/channel-DNLHkiNp.js +1 -0
- package/dist/assets/{chunk-5HRBRIJM-TGnRph8o.js → chunk-5HRBRIJM-Cc1L17Eh.js} +1 -1
- package/dist/assets/{chunk-7U56Z5CX-D24Jb5U0.js → chunk-7U56Z5CX-Df3nu_7G.js} +1 -1
- package/dist/assets/{chunk-ASOPGD6M-zBk7x36Z.js → chunk-ASOPGD6M-DIXslLG-.js} +1 -1
- package/dist/assets/{chunk-KFBOBJHC-U1tpWuJd.js → chunk-KFBOBJHC-FytVljyP.js} +1 -1
- package/dist/assets/{chunk-T2TOU4HS-P78s0i18.js → chunk-T2TOU4HS-CU5z-cXq.js} +1 -1
- package/dist/assets/{chunk-TMUBEWPD-_9HBqU-T.js → chunk-TMUBEWPD-CWo1AFyn.js} +1 -1
- package/dist/assets/classDiagram-LNE6IOMH-B430Onym.js +1 -0
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-B430Onym.js +1 -0
- package/dist/assets/clone-B9O3E3P1.js +1 -0
- package/dist/assets/dagre-4EVJKHTY-CgZW5tg4.js +4 -0
- package/dist/assets/diagram-QW4FP2JN-CJAng8RC.js +24 -0
- package/dist/assets/{erDiagram-6RL3IURR-CR3kiDSu.js → erDiagram-6RL3IURR-C1uHztHX.js} +2 -2
- package/dist/assets/{flowDiagram-7ASYPVHJ-B0gVEfBk.js → flowDiagram-7ASYPVHJ-Yiu2odO-.js} +1 -1
- package/dist/assets/{ganttDiagram-NTVNEXSI-CIEk1X3_.js → ganttDiagram-NTVNEXSI-tutc0dJP.js} +3 -3
- package/dist/assets/gitGraph-YCYPL57B-C3jM6T4G.js +133 -0
- package/dist/assets/gitGraphDiagram-NRZ2UAAF-0aqHEKzz.js +65 -0
- package/dist/assets/graph-DC224ILV.js +1 -0
- package/dist/assets/infoDiagram-A4XQUW5V-Do7Mpzkx.js +2 -0
- package/dist/assets/{journeyDiagram-G5WM74LC-U9w5k3my.js → journeyDiagram-G5WM74LC-DAruEJc7.js} +1 -1
- package/dist/assets/{kanban-definition-QRCXZQQD-CO8Lwd1_.js → kanban-definition-QRCXZQQD-DB63vLR0.js} +1 -1
- package/dist/assets/layout-BiYH5x6Q.js +1 -0
- package/dist/assets/{linear-CHGWcXFV.js → linear-CLyGW2s0.js} +1 -1
- package/dist/assets/main-CEKkUqZb.js +1094 -0
- package/dist/assets/main-D3YeRndF.css +10 -0
- package/dist/assets/min-ChJMFU2N.js +1 -0
- package/dist/assets/{mindmap-definition-GWI6TPTV-FRHfidFi.js → mindmap-definition-GWI6TPTV-CtADqQgd.js} +1 -1
- package/dist/assets/pieDiagram-YF2LJOPJ-BSOfzo3O.js +30 -0
- package/dist/assets/{quadrantDiagram-OS5C2QUG-N0MJL1hU.js → quadrantDiagram-OS5C2QUG-BLBQNFVb.js} +1 -1
- package/dist/assets/{requirementDiagram-MIRIMTAZ-n0yYIEdg.js → requirementDiagram-MIRIMTAZ-DqorvuS6.js} +2 -2
- package/dist/assets/{sankeyDiagram-Y46BX6SQ-DzRt1UUn.js → sankeyDiagram-Y46BX6SQ-4NDcrLkB.js} +1 -1
- package/dist/assets/{sequenceDiagram-G6AWOVSC-Bw73EL9L.js → sequenceDiagram-G6AWOVSC-yha6BxK2.js} +1 -1
- package/dist/assets/stateDiagram-MAYHULR4-XJ_I41ez.js +1 -0
- package/dist/assets/stateDiagram-v2-4JROLMXI-CxcyPJO2.js +1 -0
- package/dist/assets/{timeline-definition-U7ZMHBDA-BEgtmEeC.js → timeline-definition-U7ZMHBDA-DCNrmvE7.js} +1 -1
- package/dist/assets/{xychartDiagram-6QU3TZC5-bWuCFYmh.js → xychartDiagram-6QU3TZC5-9N-Ohhdp.js} +2 -2
- package/dist/index.html +2 -2
- package/dist-server/app.module.js +6 -0
- package/dist-server/common/analytics.client.js +180 -0
- package/dist-server/common/analytics.module.js +21 -0
- package/dist-server/common/device-id.js +33 -0
- package/dist-server/domain/cli/cli.service.js +66 -4
- package/dist-server/domain/document/document.service.js +6 -3
- package/dist-server/domain/goal/dto/goal.dto.js +49 -0
- package/dist-server/domain/goal/goal.controller.js +86 -0
- package/dist-server/domain/goal/goal.module.js +22 -0
- package/dist-server/domain/goal/goal.service.js +197 -0
- package/dist-server/domain/goal/parser/goal-md-parser.js +166 -0
- package/dist-server/domain/goal/parser/goal-md-serializer.js +110 -0
- package/dist-server/domain/market/market.controller.js +25 -1
- package/dist-server/domain/market/market.service.js +414 -18
- package/dist-server/domain/planner/dto/planner.dto.js +7 -44
- package/dist-server/domain/planner/planner.controller.js +2 -2
- package/dist-server/domain/planner/planner.service.js +24 -0
- package/dist-server/domain/settings/settings.controller.js +52 -0
- package/dist-server/domain/settings/settings.module.js +22 -0
- package/dist-server/domain/settings/settings.service.js +105 -0
- package/dist-server/domain/task/task.service.js +20 -0
- package/dist-server/domain/thread/thread.controller.js +28 -0
- package/dist-server/domain/thread/thread.service.js +20 -0
- package/dist-server/main.js +1 -0
- package/dist-server/repository/task.repository.js +4 -0
- package/dist-server/repository/thread.repository.js +22 -0
- package/package.json +13 -8
- package/packages/cli/dist/builtin.js +1 -0
- package/packages/cli/dist/commands/init.js +251 -8
- package/packages/cli/package.json +2 -1
- package/server.js +6 -3
- package/dist/assets/MarketPage-CjJYSssq.js +0 -56
- package/dist/assets/architectureDiagram-UYN6MBPD-CMavUkL9.js +0 -36
- package/dist/assets/blockDiagram-ZHA2E4KO-D1rNF19s.js +0 -121
- package/dist/assets/channel-CyMi7jVG.js +0 -1
- package/dist/assets/classDiagram-LNE6IOMH-DVWuJ-Df.js +0 -1
- package/dist/assets/classDiagram-v2-MQ7JQ4JX-DVWuJ-Df.js +0 -1
- package/dist/assets/dagre-4EVJKHTY-CcSwRLTq.js +0 -4
- package/dist/assets/diagram-QW4FP2JN-N887K_Ef.js +0 -24
- package/dist/assets/gitGraph-YCYPL57B-Zl60oDrh.js +0 -133
- package/dist/assets/gitGraphDiagram-NRZ2UAAF-CmIpkGbx.js +0 -65
- package/dist/assets/graph-BlwPajkw.js +0 -1
- package/dist/assets/infoDiagram-A4XQUW5V-D3UxyUCV.js +0 -2
- package/dist/assets/layout-Bvwu4dCi.js +0 -1
- package/dist/assets/main-Dnmo7KzM.js +0 -1064
- package/dist/assets/main-Fz5tpoGZ.css +0 -10
- package/dist/assets/pieDiagram-YF2LJOPJ-Eo_N9Dkd.js +0 -30
- package/dist/assets/stateDiagram-MAYHULR4-hqIdsHFt.js +0 -1
- package/dist/assets/stateDiagram-v2-4JROLMXI-BoqygJ0s.js +0 -1
- /package/dist/assets/{gemini-logo.svg → google-logo.svg} +0 -0
|
@@ -38,6 +38,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
return result;
|
|
39
39
|
};
|
|
40
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
|
+
};
|
|
41
44
|
var MarketService_1;
|
|
42
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
46
|
exports.MarketService = void 0;
|
|
@@ -55,6 +58,7 @@ const yaml = __importStar(require("js-yaml"));
|
|
|
55
58
|
const zod_1 = require("zod");
|
|
56
59
|
const skill_1 = require("@crewx/skill");
|
|
57
60
|
const workspace_context_store_js_1 = require("../../common/workspace-context.store.js");
|
|
61
|
+
const analytics_client_js_1 = require("../../common/analytics.client.js");
|
|
58
62
|
// --- Zod schemas ---
|
|
59
63
|
const MarketPackageTypeSchema = zod_1.z.enum(['skill', 'template', 'workflow', 'plugin']);
|
|
60
64
|
const TemplateEntrySchema = zod_1.z.object({
|
|
@@ -69,6 +73,24 @@ const TemplateEntrySchema = zod_1.z.object({
|
|
|
69
73
|
features: zod_1.z.array(zod_1.z.string()).optional(),
|
|
70
74
|
crewxVersion: zod_1.z.string().optional(),
|
|
71
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
|
+
});
|
|
72
94
|
const VALID_REGISTRY_SOURCE_TYPES = new Set(['crewx', 'codex', 'claude']);
|
|
73
95
|
const TRUST_RANK = { trusted: 2, verified: 1, unverified: 0 };
|
|
74
96
|
function boolToTrustLevel(isVerified, isDefault, source) {
|
|
@@ -87,12 +109,31 @@ function trustLevelToIsVerified(level) {
|
|
|
87
109
|
// --- Constants ---
|
|
88
110
|
const DEFAULT_REGISTRY_URL = 'https://github.com/sowonlabs/crewx-templates';
|
|
89
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
|
+
}
|
|
90
125
|
// Cache TTL: 15 minutes (§4.3 rate limit)
|
|
91
126
|
const CACHE_TTL_MS = 15 * 60 * 1000;
|
|
92
127
|
let MarketService = MarketService_1 = class MarketService {
|
|
93
128
|
logger = new common_1.Logger(MarketService_1.name);
|
|
129
|
+
analytics;
|
|
130
|
+
constructor(analytics) {
|
|
131
|
+
this.analytics = analytics;
|
|
132
|
+
}
|
|
94
133
|
// In-memory cache for templates.json fetches
|
|
95
134
|
templateCache = new Map();
|
|
135
|
+
marketplaceMemoryCache = null;
|
|
136
|
+
marketplaceRefreshPending = false;
|
|
96
137
|
get skillsDir() {
|
|
97
138
|
return (0, path_1.join)((0, workspace_context_store_js_1.getWorkspacePath)(), 'skills');
|
|
98
139
|
}
|
|
@@ -108,6 +149,9 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
108
149
|
get marketplacePath() {
|
|
109
150
|
return (0, path_1.join)((0, os_1.homedir)(), '.crewx', 'marketplace.json');
|
|
110
151
|
}
|
|
152
|
+
get marketplaceCachePath() {
|
|
153
|
+
return (0, path_1.join)((0, os_1.homedir)(), '.crewx', 'marketplace.cache.json');
|
|
154
|
+
}
|
|
111
155
|
get workspaceMarketplacePath() {
|
|
112
156
|
return (0, path_1.join)((0, workspace_context_store_js_1.getWorkspacePath)(), '.crewx', 'marketplace.json');
|
|
113
157
|
}
|
|
@@ -142,6 +186,23 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
142
186
|
if (tokenEnv !== undefined) {
|
|
143
187
|
this.validateTokenEnv(tokenEnv);
|
|
144
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
|
+
}
|
|
145
206
|
const entries = this.loadRegistries();
|
|
146
207
|
// Check duplicate URL
|
|
147
208
|
const normalized = this.normalizeUrl(url);
|
|
@@ -157,6 +218,7 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
157
218
|
trustLevel: 'unverified',
|
|
158
219
|
isDefault: false,
|
|
159
220
|
addedAt: new Date().toISOString(),
|
|
221
|
+
source: 'workspace',
|
|
160
222
|
...(tokenEnv ? { tokenEnv } : {}),
|
|
161
223
|
};
|
|
162
224
|
entries.push(entry);
|
|
@@ -172,6 +234,9 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
172
234
|
if (entries[idx].isDefault) {
|
|
173
235
|
throw new common_1.BadRequestException('Cannot delete the default registry');
|
|
174
236
|
}
|
|
237
|
+
if (entries[idx].source === 'global') {
|
|
238
|
+
throw new common_1.BadRequestException('Cannot delete official registry from marketplace policy');
|
|
239
|
+
}
|
|
175
240
|
entries.splice(idx, 1);
|
|
176
241
|
this.saveRegistries(entries);
|
|
177
242
|
this.templateCache.delete(id);
|
|
@@ -184,7 +249,11 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
184
249
|
throw new common_1.NotFoundException(`Registry '${registryId}' not found`);
|
|
185
250
|
}
|
|
186
251
|
const templates = await this.fetchTemplates(registry);
|
|
187
|
-
const installedNames = await
|
|
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, '-')));
|
|
188
257
|
const items = templates.map((t) => ({
|
|
189
258
|
name: t.name,
|
|
190
259
|
displayName: t.displayName || t.name,
|
|
@@ -195,12 +264,48 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
195
264
|
type: t.type || 'template',
|
|
196
265
|
trustLevel: registry.trustLevel,
|
|
197
266
|
isVerified: trustLevelToIsVerified(registry.trustLevel),
|
|
198
|
-
isInstalled: installedNames.has(t.name)
|
|
267
|
+
isInstalled: installedNames.has(t.name) ||
|
|
268
|
+
installedSlugs.has(t.name.toLowerCase().replace(/[\s_]+/g, '-')),
|
|
199
269
|
registryId: registry.id,
|
|
200
270
|
registryName: registry.name,
|
|
271
|
+
installCount: installStats[t.name]?.unique ?? 0,
|
|
201
272
|
}));
|
|
202
273
|
return { items };
|
|
203
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
|
+
}
|
|
204
309
|
// --- Install / Uninstall ---
|
|
205
310
|
async installMarketItem(registryId, marketName) {
|
|
206
311
|
this.validateItemName(marketName);
|
|
@@ -209,6 +314,14 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
209
314
|
if (!registry) {
|
|
210
315
|
throw new common_1.NotFoundException(`Registry '${registryId}' not found`);
|
|
211
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
|
+
}
|
|
212
325
|
const templates = await this.fetchTemplates(registry);
|
|
213
326
|
const template = templates.find((t) => t.name === marketName);
|
|
214
327
|
if (!template) {
|
|
@@ -271,7 +384,7 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
271
384
|
const relPath = file.path.slice(skillPrefix.length);
|
|
272
385
|
// Path traversal guard
|
|
273
386
|
const destPath = (0, path_1.resolve)((0, path_1.join)(tempDir, relPath));
|
|
274
|
-
if (!destPath.startsWith(resolvedTempDir +
|
|
387
|
+
if (!destPath.startsWith(resolvedTempDir + path_1.sep) && destPath !== resolvedTempDir) {
|
|
275
388
|
throw new Error(`Path traversal detected: ${relPath}`);
|
|
276
389
|
}
|
|
277
390
|
const destFileDir = (0, path_1.dirname)(destPath);
|
|
@@ -371,6 +484,12 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
371
484
|
this.logger.warn(`Failed to cache GitHub metadata for '${marketName}': ${err}`);
|
|
372
485
|
});
|
|
373
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
|
+
});
|
|
374
493
|
return { installed: true, path: targetDir };
|
|
375
494
|
}
|
|
376
495
|
catch (err) {
|
|
@@ -450,23 +569,26 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
450
569
|
uninstallMarketItem(name) {
|
|
451
570
|
this.validateItemName(name);
|
|
452
571
|
const targetDir = (0, path_1.join)(this.skillsDir, name);
|
|
453
|
-
if (
|
|
454
|
-
|
|
572
|
+
if ((0, fs_1.existsSync)(targetDir)) {
|
|
573
|
+
(0, fs_1.rmSync)(targetDir, { recursive: true, force: true });
|
|
455
574
|
}
|
|
456
|
-
(0, fs_1.rmSync)(targetDir, { recursive: true, force: true });
|
|
457
575
|
const lock = this.loadLockFile();
|
|
458
576
|
delete lock.skills[name];
|
|
459
577
|
this.saveLockFile(lock);
|
|
460
578
|
this.logger.log(`Uninstalled skill '${name}'`);
|
|
579
|
+
this.analytics.track('market.uninstall', {
|
|
580
|
+
item_name: name,
|
|
581
|
+
item_type: 'skill',
|
|
582
|
+
});
|
|
461
583
|
}
|
|
462
584
|
// --- Installed items ---
|
|
463
585
|
resolveItemSource(dir, projectRoot) {
|
|
464
|
-
const nmDir = (0, path_1.join)(projectRoot, 'node_modules') +
|
|
586
|
+
const nmDir = (0, path_1.join)(projectRoot, 'node_modules') + path_1.sep;
|
|
465
587
|
if (!dir.startsWith(nmDir) && dir !== (0, path_1.join)(projectRoot, 'node_modules')) {
|
|
466
588
|
return 'local';
|
|
467
589
|
}
|
|
468
590
|
const relFromNm = dir.slice(nmDir.length);
|
|
469
|
-
return relFromNm.startsWith(
|
|
591
|
+
return relFromNm.startsWith(`@crewx${path_1.sep}`) ? 'builtin' : 'node_modules';
|
|
470
592
|
}
|
|
471
593
|
async listInstalledMarkets() {
|
|
472
594
|
const projectRoot = (0, path_1.dirname)(this.skillsDir);
|
|
@@ -658,6 +780,37 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
658
780
|
}
|
|
659
781
|
return { frontmatter: result, body: match[2] || '' };
|
|
660
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
|
+
}
|
|
661
814
|
detectInstallSource(itemName) {
|
|
662
815
|
const builtinDir = (0, path_1.join)(process.cwd(), 'packages', 'built-in', 'skill', 'skills', itemName);
|
|
663
816
|
if ((0, fs_1.existsSync)(builtinDir)) {
|
|
@@ -835,21 +988,133 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
835
988
|
};
|
|
836
989
|
}
|
|
837
990
|
loadMarketplaceJson() {
|
|
838
|
-
|
|
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))
|
|
839
1031
|
return null;
|
|
840
1032
|
try {
|
|
841
|
-
|
|
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
|
+
};
|
|
842
1045
|
}
|
|
843
1046
|
catch {
|
|
844
|
-
this.logger.warn('Failed to parse marketplace.json');
|
|
845
1047
|
return null;
|
|
846
1048
|
}
|
|
847
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
|
+
}
|
|
848
1107
|
loadWorkspaceMarketplaceJson() {
|
|
849
1108
|
if (!(0, fs_1.existsSync)(this.workspaceMarketplacePath))
|
|
850
1109
|
return null;
|
|
851
1110
|
try {
|
|
852
|
-
|
|
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;
|
|
853
1118
|
}
|
|
854
1119
|
catch {
|
|
855
1120
|
this.logger.warn('Failed to parse workspace marketplace.json');
|
|
@@ -869,18 +1134,25 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
869
1134
|
...(item.tokenEnv ? { tokenEnv: item.tokenEnv } : {}),
|
|
870
1135
|
};
|
|
871
1136
|
}
|
|
1137
|
+
isBuiltinExempt(reg) {
|
|
1138
|
+
return (reg.name.startsWith('@crewx/') ||
|
|
1139
|
+
this.normalizeUrl(reg.url) === this.normalizeUrl(DEFAULT_REGISTRY_URL));
|
|
1140
|
+
}
|
|
872
1141
|
mergeRegistries(global, workspace, forceDisabled) {
|
|
873
1142
|
const disabledSet = new Set(forceDisabled);
|
|
874
1143
|
const merged = new Map();
|
|
875
1144
|
for (const reg of global) {
|
|
876
|
-
if (!disabledSet.has(reg.id)) {
|
|
877
|
-
merged.set(reg.
|
|
1145
|
+
if (!disabledSet.has(reg.id) || this.isBuiltinExempt(reg)) {
|
|
1146
|
+
merged.set(this.normalizeUrl(reg.url), { ...reg, source: 'global' });
|
|
878
1147
|
}
|
|
879
1148
|
}
|
|
880
1149
|
for (const reg of workspace) {
|
|
881
|
-
if (disabledSet.has(reg.id))
|
|
1150
|
+
if (disabledSet.has(reg.id) && !this.isBuiltinExempt(reg))
|
|
1151
|
+
continue;
|
|
1152
|
+
const key = this.normalizeUrl(reg.url);
|
|
1153
|
+
if (merged.has(key))
|
|
882
1154
|
continue;
|
|
883
|
-
merged.set(
|
|
1155
|
+
merged.set(key, { ...reg, source: 'workspace', trustLevel: 'unverified' });
|
|
884
1156
|
}
|
|
885
1157
|
return Array.from(merged.values());
|
|
886
1158
|
}
|
|
@@ -890,6 +1162,11 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
890
1162
|
const config = this.loadConfig();
|
|
891
1163
|
const registriesYaml = config?.skills?.registries;
|
|
892
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
|
+
}
|
|
893
1170
|
if (!registriesYaml || registriesYaml.length === 0) {
|
|
894
1171
|
const defaults = this.getDefaultRegistries();
|
|
895
1172
|
this.saveRegistries(defaults);
|
|
@@ -930,7 +1207,8 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
930
1207
|
if (!this.isPlainObject(config.skills)) {
|
|
931
1208
|
config.skills = {};
|
|
932
1209
|
}
|
|
933
|
-
|
|
1210
|
+
const persistable = entries.filter((e) => e.source !== 'global');
|
|
1211
|
+
config.skills.registries = persistable.map((e) => this.registryEntryToYaml(e));
|
|
934
1212
|
this.saveConfig(config);
|
|
935
1213
|
}
|
|
936
1214
|
getDefaultRegistries() {
|
|
@@ -965,6 +1243,9 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
965
1243
|
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
966
1244
|
return cached.data;
|
|
967
1245
|
}
|
|
1246
|
+
if (registry.type === 'claude') {
|
|
1247
|
+
return this.fetchClaudeTemplates(registry);
|
|
1248
|
+
}
|
|
968
1249
|
const parsed = this.parseGitHubUrl(registry.url);
|
|
969
1250
|
if (!parsed) {
|
|
970
1251
|
this.logger.warn(`Invalid registry URL: ${registry.url}`);
|
|
@@ -996,6 +1277,120 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
996
1277
|
return [];
|
|
997
1278
|
}
|
|
998
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
|
+
}
|
|
999
1394
|
async fetchUrl(url, headers) {
|
|
1000
1395
|
const response = await fetch(url, headers ? { headers } : undefined);
|
|
1001
1396
|
if (!response.ok) {
|
|
@@ -1047,5 +1442,6 @@ let MarketService = MarketService_1 = class MarketService {
|
|
|
1047
1442
|
};
|
|
1048
1443
|
exports.MarketService = MarketService;
|
|
1049
1444
|
exports.MarketService = MarketService = MarketService_1 = __decorate([
|
|
1050
|
-
(0, common_1.Injectable)()
|
|
1445
|
+
(0, common_1.Injectable)(),
|
|
1446
|
+
__metadata("design:paramtypes", [analytics_client_js_1.AnalyticsService])
|
|
1051
1447
|
], MarketService);
|