itismyskillmarket 1.3.0 → 1.3.2
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/.github/workflows/publish-npm.yml +59 -0
- package/.github/workflows/publish-skill.yml +72 -0
- package/5e51cb7aa8b8e60d49d86f4689f5d4d1.png +0 -0
- package/CHANGELOG.md +410 -0
- package/DEVELOPMENT.md +376 -0
- package/README.md +75 -6
- package/SKILLMARKET-GUIDE.md +288 -0
- package/dist/index.js +733 -212
- package/docs/WEEKLY-UPDATE-2026-04-23.md +43 -0
- package/docs/plans/2026-04-01-skillmarket-design.md +267 -0
- package/docs/plans/2026-04-01-skillmarket-implementation.md +1031 -0
- package/docs/plans/2026-04-15-cross-platform-adapter-design.md +416 -0
- package/docs/plans/2026-04-15-cross-platform-adapter-plan.md +833 -0
- package/docs/plans/2026-04-16-keyword-search-design.md +143 -0
- package/docs/plans/2026-04-29-weekly-update.md +57 -0
- package/package.json +1 -6
- package/skills/README.md +54 -0
- package/skills/test-skill/SKILL.md +25 -0
- package/skills/test-skill/index.js +66 -0
- package/skills/test-skill/metadata.json +9 -0
- package/skills/test-skill/package.json +19 -0
- package/skills/test-skill-1/SKILL.md +24 -0
- package/skills/test-skill-1/index.js +13 -0
- package/skills/test-skill-1/metadata.json +9 -0
- package/skills/test-skill-1/package.json +16 -0
- package/skills/test-skill-2/SKILL.md +25 -0
- package/skills/test-skill-2/index.js +13 -0
- package/skills/test-skill-2/metadata.json +9 -0
- package/skills/test-skill-2/package.json +16 -0
- package/src/adapters/base.ts +87 -0
- package/src/adapters/claude.ts +31 -0
- package/src/adapters/index.ts +9 -0
- package/src/adapters/opencode.ts +40 -0
- package/src/adapters/registry.ts +77 -0
- package/src/adapters/vscode.ts +62 -0
- package/src/cli.ts +189 -75
- package/src/commands/info.ts +4 -15
- package/src/commands/install.ts +93 -54
- package/src/commands/ls.ts +182 -17
- package/src/commands/npm.ts +118 -16
- package/src/commands/search.ts +12 -7
- package/src/commands/sync.ts +6 -27
- package/src/commands/uninstall.ts +313 -15
- package/src/commands/update.ts +2 -2
- package/src/index.ts +27 -0
- package/src/types.ts +35 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +22 -0
- package/wanxuchen-skillmarket-1.0.1.tgz +0 -0
package/dist/index.js
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { dirname, resolve } from "path";
|
|
8
|
+
|
|
9
|
+
// src/commands/registry.ts
|
|
10
|
+
import fs2 from "fs-extra";
|
|
11
|
+
|
|
12
|
+
// src/utils/dirs.ts
|
|
13
|
+
import os from "os";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import fs from "fs-extra";
|
|
5
16
|
|
|
6
17
|
// src/constants.ts
|
|
7
18
|
var MARKET_DIR = "skillmarket";
|
|
@@ -30,13 +41,7 @@ var PLATFORMS = [
|
|
|
30
41
|
var REGISTRY_FILE = "registry.json";
|
|
31
42
|
var LATEST_LINK = "latest";
|
|
32
43
|
|
|
33
|
-
// src/commands/registry.ts
|
|
34
|
-
import fs2 from "fs-extra";
|
|
35
|
-
|
|
36
44
|
// src/utils/dirs.ts
|
|
37
|
-
import os from "os";
|
|
38
|
-
import path from "path";
|
|
39
|
-
import fs from "fs-extra";
|
|
40
45
|
function getMarketHome() {
|
|
41
46
|
return path.join(os.homedir(), MARKET_DIR);
|
|
42
47
|
}
|
|
@@ -95,7 +100,7 @@ async function isSkillInstalled(skillId) {
|
|
|
95
100
|
import https from "https";
|
|
96
101
|
import { URL } from "url";
|
|
97
102
|
async function fetchNpmPackage(packageName) {
|
|
98
|
-
return new Promise((
|
|
103
|
+
return new Promise((resolve2, reject) => {
|
|
99
104
|
const isScoped = packageName.startsWith("@");
|
|
100
105
|
let encodedName;
|
|
101
106
|
if (isScoped) {
|
|
@@ -121,12 +126,12 @@ async function fetchNpmPackage(packageName) {
|
|
|
121
126
|
try {
|
|
122
127
|
const parsed = JSON.parse(data);
|
|
123
128
|
if (parsed.error) {
|
|
124
|
-
|
|
129
|
+
resolve2(null);
|
|
125
130
|
return;
|
|
126
131
|
}
|
|
127
|
-
|
|
132
|
+
resolve2(parsed);
|
|
128
133
|
} catch {
|
|
129
|
-
|
|
134
|
+
resolve2(null);
|
|
130
135
|
}
|
|
131
136
|
});
|
|
132
137
|
});
|
|
@@ -137,12 +142,46 @@ async function fetchNpmPackage(packageName) {
|
|
|
137
142
|
});
|
|
138
143
|
});
|
|
139
144
|
}
|
|
140
|
-
|
|
145
|
+
var SKILL_SCOPES = [
|
|
146
|
+
"@wanxuchen",
|
|
147
|
+
// 原作者 scope
|
|
148
|
+
"@itismyskillmarket",
|
|
149
|
+
// 当前包名 scope
|
|
150
|
+
"@thisisskillmarket",
|
|
151
|
+
// 曾用 scope
|
|
152
|
+
"@this-is-skillmarket",
|
|
153
|
+
// 曾用 scope (带横线)
|
|
154
|
+
"@skillmarket"
|
|
155
|
+
// 通用 scope
|
|
156
|
+
];
|
|
157
|
+
function getPossiblePackageNames(skillId) {
|
|
158
|
+
if (skillId.startsWith("@")) {
|
|
159
|
+
return [skillId];
|
|
160
|
+
}
|
|
161
|
+
return SKILL_SCOPES.map((scope) => `${scope}/${skillId}`);
|
|
162
|
+
}
|
|
163
|
+
async function fetchSkillPackage(skillId) {
|
|
164
|
+
const packageNames = getPossiblePackageNames(skillId);
|
|
165
|
+
for (const packageName of packageNames) {
|
|
166
|
+
try {
|
|
167
|
+
const info = await fetchNpmPackage(packageName);
|
|
168
|
+
if (info) {
|
|
169
|
+
return info;
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
async function searchSkillmarketPackages(options = {}) {
|
|
177
|
+
const { from = 0, size = 100, keyword } = options;
|
|
141
178
|
const packages = [];
|
|
142
|
-
|
|
179
|
+
let total = 0;
|
|
180
|
+
return new Promise((resolve2, reject) => {
|
|
143
181
|
const url = new URL("https://registry.npmjs.org/-/v1/search");
|
|
144
182
|
url.searchParams.set("text", "keywords:skillmarket");
|
|
145
|
-
url.searchParams.set("size",
|
|
183
|
+
url.searchParams.set("size", String(Math.max(size, 100)));
|
|
184
|
+
url.searchParams.set("from", String(from));
|
|
146
185
|
const req = https.get(url.toString(), { timeout: 1e4 }, (res) => {
|
|
147
186
|
let data = "";
|
|
148
187
|
res.on("data", (chunk) => {
|
|
@@ -151,14 +190,32 @@ async function searchSkillmarketPackages() {
|
|
|
151
190
|
res.on("end", () => {
|
|
152
191
|
try {
|
|
153
192
|
const result = JSON.parse(data);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
193
|
+
let filteredTotal = 0;
|
|
194
|
+
if (result.objects) {
|
|
195
|
+
for (const item of result.objects) {
|
|
196
|
+
if (item?.package?.name) {
|
|
197
|
+
const pkgName = item.package.name;
|
|
198
|
+
const pkgDesc = item.package.description || "";
|
|
199
|
+
if (keyword) {
|
|
200
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
201
|
+
const shortName = pkgName.includes("/") ? pkgName.split("/")[1] : pkgName;
|
|
202
|
+
const nameMatch = shortName.toLowerCase().includes(lowerKeyword) || pkgName.toLowerCase().includes(lowerKeyword);
|
|
203
|
+
const descMatch = pkgDesc.toLowerCase().includes(lowerKeyword);
|
|
204
|
+
if (nameMatch || descMatch) {
|
|
205
|
+
packages.push(pkgName);
|
|
206
|
+
filteredTotal++;
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
packages.push(pkgName);
|
|
210
|
+
filteredTotal++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
157
213
|
}
|
|
158
214
|
}
|
|
159
|
-
|
|
215
|
+
total = filteredTotal;
|
|
216
|
+
resolve2({ packages, total });
|
|
160
217
|
} catch {
|
|
161
|
-
|
|
218
|
+
resolve2({ packages: [], total: 0 });
|
|
162
219
|
}
|
|
163
220
|
});
|
|
164
221
|
});
|
|
@@ -171,33 +228,128 @@ async function searchSkillmarketPackages() {
|
|
|
171
228
|
}
|
|
172
229
|
|
|
173
230
|
// src/commands/ls.ts
|
|
231
|
+
function filterInstalledSkills(skills, keyword) {
|
|
232
|
+
const lower = keyword.toLowerCase();
|
|
233
|
+
return skills.filter(
|
|
234
|
+
(s) => s.id.toLowerCase().includes(lower) || s.displayName && s.displayName.toLowerCase().includes(lower) || s.description && s.description.toLowerCase().includes(lower)
|
|
235
|
+
);
|
|
236
|
+
}
|
|
174
237
|
async function listSkills(options) {
|
|
175
|
-
const { installed, updates } = options;
|
|
238
|
+
const { installed, updates, page = 1, limit = 20, search } = options;
|
|
176
239
|
if (installed) {
|
|
177
|
-
|
|
240
|
+
let skills = await getInstalledSkills();
|
|
241
|
+
if (search) {
|
|
242
|
+
skills = filterInstalledSkills(skills, search);
|
|
243
|
+
}
|
|
244
|
+
const total = skills.length;
|
|
245
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
246
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
178
247
|
if (skills.length === 0) {
|
|
179
|
-
|
|
248
|
+
if (search) {
|
|
249
|
+
console.log(`No skills found matching "${search}".`);
|
|
250
|
+
} else {
|
|
251
|
+
console.log('No skills installed yet. Run "skm ls" to see available skills.');
|
|
252
|
+
}
|
|
180
253
|
return;
|
|
181
254
|
}
|
|
182
|
-
|
|
183
|
-
|
|
255
|
+
if (search) {
|
|
256
|
+
console.log(`Found ${total} match(es) for "${search}":
|
|
257
|
+
`);
|
|
258
|
+
} else {
|
|
259
|
+
console.log(`Installed Skills (${total}):
|
|
260
|
+
`);
|
|
261
|
+
}
|
|
262
|
+
const start = (currentPage - 1) * limit;
|
|
263
|
+
const end = Math.min(start + limit, total);
|
|
264
|
+
const pageSkills = skills.slice(start, end);
|
|
265
|
+
for (const skill of pageSkills) {
|
|
184
266
|
console.log(` ${skill.id}@${skill.version}`);
|
|
185
267
|
console.log(` Platforms: ${skill.platforms.join(", ")}`);
|
|
186
268
|
console.log(` Installed: ${skill.installedAt}`);
|
|
187
269
|
console.log();
|
|
188
270
|
}
|
|
271
|
+
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
189
272
|
return;
|
|
190
273
|
}
|
|
191
|
-
|
|
274
|
+
if (search) {
|
|
275
|
+
console.log(`Searching npm for "${search}"...
|
|
276
|
+
`);
|
|
277
|
+
} else {
|
|
278
|
+
console.log("Searching npm registry...\n");
|
|
279
|
+
}
|
|
192
280
|
try {
|
|
193
|
-
const
|
|
281
|
+
const offset = (page - 1) * limit;
|
|
282
|
+
const { packages, total } = await searchSkillmarketPackages({
|
|
283
|
+
from: offset,
|
|
284
|
+
size: limit,
|
|
285
|
+
keyword: search
|
|
286
|
+
});
|
|
287
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
288
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
194
289
|
if (packages.length === 0) {
|
|
195
|
-
|
|
290
|
+
if (search) {
|
|
291
|
+
console.log(`No skills found matching "${search}".`);
|
|
292
|
+
} else {
|
|
293
|
+
console.log("No skills found. Check back later!");
|
|
294
|
+
}
|
|
196
295
|
return;
|
|
197
296
|
}
|
|
198
|
-
|
|
297
|
+
if (search) {
|
|
298
|
+
console.log(`Found ${total} match(es) for "${search}":
|
|
199
299
|
`);
|
|
300
|
+
} else {
|
|
301
|
+
console.log(`Found ${total} skill(s):
|
|
302
|
+
`);
|
|
303
|
+
}
|
|
200
304
|
for (const pkgName of packages) {
|
|
305
|
+
try {
|
|
306
|
+
const info = await fetchNpmPackage(pkgName);
|
|
307
|
+
if (!info) {
|
|
308
|
+
console.log(`\u{1F4E6} ${pkgName} (\u4FE1\u606F\u83B7\u53D6\u5931\u8D25)`);
|
|
309
|
+
console.log();
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const latestVersion = info["dist-tags"]?.latest || "unknown";
|
|
313
|
+
const pkg = info.versions?.[latestVersion];
|
|
314
|
+
const skillMeta = pkg?.skillmarket;
|
|
315
|
+
console.log(`\u{1F4E6} ${info.name}@${latestVersion}`);
|
|
316
|
+
const displayName = skillMeta?.displayName || info.name;
|
|
317
|
+
console.log(` \u540D\u79F0: ${displayName}`);
|
|
318
|
+
console.log(` \u63CF\u8FF0: ${pkg?.description || "N/A"}`);
|
|
319
|
+
const platforms = skillMeta?.platforms || [];
|
|
320
|
+
console.log(` \u5E73\u53F0: ${platforms.length > 0 ? platforms.join(", ") : "N/A"}`);
|
|
321
|
+
const npmLink = pkg?.links?.npm || `https://www.npmjs.com/package/${info.name}`;
|
|
322
|
+
console.log(` \u94FE\u63A5: ${npmLink}`);
|
|
323
|
+
console.log();
|
|
324
|
+
} catch (e) {
|
|
325
|
+
console.log(`\u{1F4E6} ${pkgName} (\u83B7\u53D6\u5931\u8D25: ${e})`);
|
|
326
|
+
console.log();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.log(`Error fetching skills: ${error}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/commands/search.ts
|
|
336
|
+
async function searchSkills(keyword, options = {}) {
|
|
337
|
+
const limit = options.limit ?? 20;
|
|
338
|
+
console.log(`Searching for "${keyword}"...
|
|
339
|
+
`);
|
|
340
|
+
try {
|
|
341
|
+
const { packages } = await searchSkillmarketPackages({ keyword });
|
|
342
|
+
const filtered = packages.filter(
|
|
343
|
+
(pkg) => pkg.toLowerCase().includes(keyword.toLowerCase())
|
|
344
|
+
);
|
|
345
|
+
if (filtered.length === 0) {
|
|
346
|
+
console.log(`No skills found matching "${keyword}".`);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const results = filtered.slice(0, limit);
|
|
350
|
+
console.log(`Found ${filtered.length} skill(s) (showing ${results.length}):
|
|
351
|
+
`);
|
|
352
|
+
for (const pkgName of results) {
|
|
201
353
|
const info = await fetchNpmPackage(pkgName);
|
|
202
354
|
if (info && info["dist-tags"]?.latest) {
|
|
203
355
|
const latestVersion = info["dist-tags"].latest;
|
|
@@ -208,17 +360,16 @@ async function listSkills(options) {
|
|
|
208
360
|
}
|
|
209
361
|
}
|
|
210
362
|
} catch (error) {
|
|
211
|
-
console.log(`Error
|
|
363
|
+
console.log(`Error searching skills: ${error}`);
|
|
212
364
|
}
|
|
213
365
|
}
|
|
214
366
|
|
|
215
367
|
// src/commands/info.ts
|
|
216
368
|
async function showSkillInfo(skillId) {
|
|
217
|
-
|
|
218
|
-
console.log(`Fetching info for: ${packageName}
|
|
369
|
+
console.log(`Fetching info for: ${skillId}
|
|
219
370
|
`);
|
|
220
371
|
try {
|
|
221
|
-
const info = await
|
|
372
|
+
const info = await fetchSkillPackage(skillId);
|
|
222
373
|
if (!info) {
|
|
223
374
|
console.log(`Skill "${skillId}" not found in npm registry.`);
|
|
224
375
|
return;
|
|
@@ -258,174 +409,344 @@ Status: Not installed (use skm install ${skillId} to install)`);
|
|
|
258
409
|
}
|
|
259
410
|
|
|
260
411
|
// src/commands/install.ts
|
|
261
|
-
import
|
|
262
|
-
import
|
|
412
|
+
import fs7 from "fs-extra";
|
|
413
|
+
import path6 from "path";
|
|
263
414
|
import { exec } from "child_process";
|
|
264
415
|
import { promisify } from "util";
|
|
265
416
|
|
|
266
|
-
// src/
|
|
267
|
-
function detectPlatform() {
|
|
268
|
-
if (process.env.OPENCODE) return "opencode";
|
|
269
|
-
if (process.env.CURSOR) return "cursor";
|
|
270
|
-
if (process.env.VSCODE) return "vscode";
|
|
271
|
-
if (process.env.CLAUDE_CODE) return "claude";
|
|
272
|
-
if (process.env.ANTIGRAVITY) return "antigravity";
|
|
273
|
-
return "codex";
|
|
274
|
-
}
|
|
275
|
-
function isValidPlatform(name) {
|
|
276
|
-
return PLATFORMS.includes(name);
|
|
277
|
-
}
|
|
278
|
-
function getPlatformFromInput(name) {
|
|
279
|
-
const lowerName = name.toLowerCase();
|
|
280
|
-
if (isValidPlatform(lowerName)) {
|
|
281
|
-
return lowerName;
|
|
282
|
-
}
|
|
283
|
-
return null;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// src/commands/sync.ts
|
|
417
|
+
// src/adapters/base.ts
|
|
287
418
|
import fs3 from "fs-extra";
|
|
288
419
|
import path2 from "path";
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
420
|
+
var BaseAdapter = class {
|
|
421
|
+
/**
|
|
422
|
+
* Get the path where a specific skill should be installed
|
|
423
|
+
*/
|
|
424
|
+
getSkillPath(skillId) {
|
|
425
|
+
return path2.join(this.skillDir, skillId);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Get the path to the SKILL.md file for a skill
|
|
429
|
+
*/
|
|
430
|
+
getSkillFilePath(skillId) {
|
|
431
|
+
return path2.join(this.getSkillPath(skillId), "SKILL.md");
|
|
432
|
+
}
|
|
433
|
+
async isAvailable() {
|
|
434
|
+
try {
|
|
435
|
+
await fs3.ensureDir(this.skillDir);
|
|
436
|
+
return true;
|
|
437
|
+
} catch {
|
|
438
|
+
return false;
|
|
302
439
|
}
|
|
303
|
-
} else {
|
|
304
|
-
platformsToSync = PLATFORMS;
|
|
305
440
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
441
|
+
async isInstalled(skillId) {
|
|
442
|
+
const skillFile = this.getSkillFilePath(skillId);
|
|
443
|
+
return fs3.pathExists(skillFile);
|
|
444
|
+
}
|
|
445
|
+
async install(skillId, sourceDir) {
|
|
446
|
+
const targetDir = this.getSkillPath(skillId);
|
|
447
|
+
const targetFile = this.getSkillFilePath(skillId);
|
|
448
|
+
await fs3.ensureDir(targetDir);
|
|
449
|
+
const sourceFile = path2.join(sourceDir, "SKILL.md");
|
|
450
|
+
if (!await fs3.pathExists(sourceFile)) {
|
|
451
|
+
throw new Error(`SKILL.md not found in ${sourceDir}`);
|
|
452
|
+
}
|
|
453
|
+
await fs3.copy(sourceFile, targetFile, { overwrite: true });
|
|
454
|
+
}
|
|
455
|
+
async uninstall(skillId) {
|
|
456
|
+
const targetDir = this.getSkillPath(skillId);
|
|
457
|
+
if (await fs3.pathExists(targetDir)) {
|
|
458
|
+
await fs3.remove(targetDir);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async listInstalled() {
|
|
462
|
+
if (!await fs3.pathExists(this.skillDir)) {
|
|
463
|
+
return [];
|
|
464
|
+
}
|
|
465
|
+
const entries = await fs3.readdir(this.skillDir, { withFileTypes: true });
|
|
466
|
+
const skills = [];
|
|
467
|
+
for (const entry of entries) {
|
|
468
|
+
if (entry.isDirectory()) {
|
|
469
|
+
const skillFile = path2.join(this.skillDir, entry.name, "SKILL.md");
|
|
470
|
+
if (await fs3.pathExists(skillFile)) {
|
|
471
|
+
skills.push(entry.name);
|
|
326
472
|
}
|
|
327
473
|
}
|
|
328
474
|
}
|
|
475
|
+
return skills;
|
|
329
476
|
}
|
|
330
|
-
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
// src/adapters/opencode.ts
|
|
480
|
+
import path3 from "path";
|
|
481
|
+
import os2 from "os";
|
|
482
|
+
import fs4 from "fs-extra";
|
|
483
|
+
var OpenCodeAdapter = class extends BaseAdapter {
|
|
484
|
+
id = "opencode";
|
|
485
|
+
name = "OpenCode";
|
|
486
|
+
get skillDir() {
|
|
487
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os2.homedir(), ".config", "opencode");
|
|
488
|
+
return path3.join(configDir, "skills");
|
|
489
|
+
}
|
|
490
|
+
async isAvailable() {
|
|
491
|
+
if (process.env.OPENCODE) return true;
|
|
492
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os2.homedir(), ".config", "opencode");
|
|
493
|
+
try {
|
|
494
|
+
await fs4.ensureDir(path3.join(configDir, "skills"));
|
|
495
|
+
return true;
|
|
496
|
+
} catch {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// src/adapters/claude.ts
|
|
503
|
+
import path4 from "path";
|
|
504
|
+
import os3 from "os";
|
|
505
|
+
import fs5 from "fs-extra";
|
|
506
|
+
var ClaudeAdapter = class extends BaseAdapter {
|
|
507
|
+
id = "claude";
|
|
508
|
+
name = "Claude Code";
|
|
509
|
+
get skillDir() {
|
|
510
|
+
return path4.join(os3.homedir(), ".claude", "skills");
|
|
511
|
+
}
|
|
512
|
+
async isAvailable() {
|
|
513
|
+
if (process.env.CLAUDE_CODE) return true;
|
|
514
|
+
const claudeDir = path4.join(os3.homedir(), ".claude");
|
|
515
|
+
return fs5.pathExists(claudeDir);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
// src/adapters/vscode.ts
|
|
520
|
+
import path5 from "path";
|
|
521
|
+
import os4 from "os";
|
|
522
|
+
import fs6 from "fs-extra";
|
|
523
|
+
var VSCodeAdapter = class extends BaseAdapter {
|
|
524
|
+
id = "vscode";
|
|
525
|
+
name = "VSCode";
|
|
526
|
+
get skillDir() {
|
|
527
|
+
return path5.join(os4.homedir(), ".copilot", "skills");
|
|
528
|
+
}
|
|
529
|
+
async isAvailable() {
|
|
530
|
+
const possibleDirs = [
|
|
531
|
+
path5.join(os4.homedir(), ".copilot", "skills"),
|
|
532
|
+
path5.join(os4.homedir(), ".claude", "skills")
|
|
533
|
+
];
|
|
534
|
+
for (const dir of possibleDirs) {
|
|
535
|
+
try {
|
|
536
|
+
await fs6.ensureDir(dir);
|
|
537
|
+
return true;
|
|
538
|
+
} catch {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
async install(skillId, sourceDir) {
|
|
545
|
+
await super.install(skillId, sourceDir);
|
|
546
|
+
const claudeSkillDir = path5.join(os4.homedir(), ".claude", "skills");
|
|
547
|
+
const targetPath = this.getSkillPath(skillId);
|
|
548
|
+
const claudeTargetPath = path5.join(claudeSkillDir, skillId);
|
|
549
|
+
try {
|
|
550
|
+
await fs6.ensureDir(claudeSkillDir);
|
|
551
|
+
await fs6.remove(claudeTargetPath);
|
|
552
|
+
await fs6.symlink(targetPath, claudeTargetPath, "junction");
|
|
553
|
+
} catch {
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
// src/adapters/registry.ts
|
|
559
|
+
var adapters = /* @__PURE__ */ new Map();
|
|
560
|
+
function registerAdapters() {
|
|
561
|
+
const opencode = new OpenCodeAdapter();
|
|
562
|
+
const claude = new ClaudeAdapter();
|
|
563
|
+
const vscode = new VSCodeAdapter();
|
|
564
|
+
adapters.set(opencode.id, opencode);
|
|
565
|
+
adapters.set(claude.id, claude);
|
|
566
|
+
adapters.set(vscode.id, vscode);
|
|
567
|
+
}
|
|
568
|
+
registerAdapters();
|
|
569
|
+
async function detectPlatforms() {
|
|
570
|
+
const available = [];
|
|
571
|
+
for (const adapter of adapters.values()) {
|
|
572
|
+
if (await adapter.isAvailable()) {
|
|
573
|
+
available.push(adapter);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return available;
|
|
577
|
+
}
|
|
578
|
+
function getAdapterByPlatform(platform) {
|
|
579
|
+
const idMap = {
|
|
580
|
+
opencode: "opencode",
|
|
581
|
+
claude: "claude",
|
|
582
|
+
vscode: "vscode",
|
|
583
|
+
cursor: "opencode",
|
|
584
|
+
// Cursor uses OpenCode-compatible structure
|
|
585
|
+
codex: "opencode",
|
|
586
|
+
// Codex uses OpenCode-compatible structure
|
|
587
|
+
antigravity: "opencode"
|
|
588
|
+
// Antigravity uses OpenCode-compatible structure
|
|
589
|
+
};
|
|
590
|
+
return adapters.get(idMap[platform]);
|
|
331
591
|
}
|
|
332
592
|
|
|
333
593
|
// src/commands/install.ts
|
|
334
594
|
var execAsync = promisify(exec);
|
|
335
|
-
async function installSkill(skillId, version,
|
|
595
|
+
async function installSkill(skillId, version, options) {
|
|
336
596
|
await ensureMarketDirs();
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
const shortName = skillId;
|
|
340
|
-
console.log(`Installing ${packageName}${version ? `@${version}` : ""}...`);
|
|
341
|
-
const pkgInfo = await fetchNpmPackage(packageName);
|
|
597
|
+
console.log(`Installing ${skillId}${version ? `@${version}` : ""}...`);
|
|
598
|
+
const pkgInfo = await fetchSkillPackage(skillId);
|
|
342
599
|
if (!pkgInfo) {
|
|
343
|
-
throw new Error(`Package ${
|
|
600
|
+
throw new Error(`Package ${skillId} not found`);
|
|
344
601
|
}
|
|
602
|
+
const packageName = pkgInfo.name;
|
|
345
603
|
const targetVersion = version || pkgInfo["dist-tags"]?.latest;
|
|
346
604
|
if (!targetVersion) {
|
|
347
605
|
throw new Error(`No version found for ${packageName}`);
|
|
348
606
|
}
|
|
349
607
|
const cacheDir = getCacheDir();
|
|
350
|
-
const targetDir =
|
|
351
|
-
if (!await
|
|
608
|
+
const targetDir = path6.join(cacheDir, `${packageName}@${targetVersion}`);
|
|
609
|
+
if (!await fs7.pathExists(targetDir)) {
|
|
352
610
|
console.log("Downloading package...");
|
|
353
|
-
await
|
|
611
|
+
await fs7.ensureDir(cacheDir);
|
|
354
612
|
try {
|
|
355
613
|
await execAsync(`npm pack ${packageName}@${targetVersion} --pack-destination ${cacheDir}`);
|
|
356
|
-
const files = await
|
|
614
|
+
const files = await fs7.readdir(cacheDir);
|
|
357
615
|
const tarball = files.find(
|
|
358
|
-
(f) => f.endsWith(".tgz") && f.includes(packageName.replace("/", "-"))
|
|
616
|
+
(f) => f.endsWith(".tgz") && f.includes(packageName.replace(/^@/, "").replace("/", "-"))
|
|
359
617
|
);
|
|
360
618
|
if (tarball) {
|
|
361
|
-
await execAsync(`tar -xzf "${
|
|
362
|
-
await
|
|
363
|
-
const extractedDir =
|
|
619
|
+
await execAsync(`tar -xzf "${path6.join(cacheDir, tarball)}" -C "${cacheDir}"`);
|
|
620
|
+
await fs7.remove(path6.join(cacheDir, tarball));
|
|
621
|
+
const extractedDir = path6.join(cacheDir, "package");
|
|
364
622
|
const finalDir = targetDir;
|
|
365
|
-
await
|
|
623
|
+
await fs7.move(extractedDir, finalDir, { overwrite: true });
|
|
366
624
|
}
|
|
367
625
|
} catch (err) {
|
|
368
626
|
throw new Error(`Failed to download package: ${err}`);
|
|
369
627
|
}
|
|
370
628
|
}
|
|
371
629
|
const skillsDir = getSkillsDir();
|
|
372
|
-
const skillVersionDir =
|
|
630
|
+
const skillVersionDir = path6.join(skillsDir, `${skillId}@${targetVersion}`);
|
|
373
631
|
console.log("Setting up skill...");
|
|
374
|
-
await
|
|
632
|
+
await fs7.ensureDir(skillVersionDir);
|
|
375
633
|
const pkgRoot = targetDir;
|
|
376
|
-
if (await
|
|
377
|
-
await
|
|
378
|
-
|
|
379
|
-
|
|
634
|
+
if (await fs7.pathExists(path6.join(pkgRoot, "SKILL.md"))) {
|
|
635
|
+
await fs7.copy(
|
|
636
|
+
path6.join(pkgRoot, "SKILL.md"),
|
|
637
|
+
path6.join(skillVersionDir, "SKILL.md")
|
|
380
638
|
);
|
|
381
639
|
}
|
|
382
|
-
if (await
|
|
383
|
-
await
|
|
384
|
-
|
|
385
|
-
|
|
640
|
+
if (await fs7.pathExists(path6.join(pkgRoot, "metadata.json"))) {
|
|
641
|
+
await fs7.copy(
|
|
642
|
+
path6.join(pkgRoot, "metadata.json"),
|
|
643
|
+
path6.join(skillVersionDir, "metadata.json")
|
|
386
644
|
);
|
|
387
645
|
}
|
|
388
|
-
const skillDir =
|
|
389
|
-
await
|
|
390
|
-
const latestLink =
|
|
646
|
+
const skillDir = path6.join(skillsDir, skillId);
|
|
647
|
+
await fs7.ensureDir(skillDir);
|
|
648
|
+
const latestLink = path6.join(skillDir, LATEST_LINK);
|
|
391
649
|
try {
|
|
392
|
-
await
|
|
393
|
-
await
|
|
650
|
+
await fs7.remove(latestLink);
|
|
651
|
+
await fs7.symlink(skillVersionDir, latestLink, "junction");
|
|
394
652
|
} catch {
|
|
395
|
-
await
|
|
653
|
+
await fs7.copy(skillVersionDir, path6.join(skillDir, LATEST_LINK), { overwrite: true });
|
|
396
654
|
}
|
|
397
|
-
let
|
|
398
|
-
if (
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
655
|
+
let targetAdapters = [];
|
|
656
|
+
if (options?.platforms && options.platforms.length > 0) {
|
|
657
|
+
for (const platformStr of options.platforms) {
|
|
658
|
+
const platform = platformStr;
|
|
659
|
+
const adapter = getAdapterByPlatform(platform);
|
|
660
|
+
if (adapter) {
|
|
661
|
+
targetAdapters.push(adapter);
|
|
662
|
+
} else {
|
|
663
|
+
console.warn(`\u26A0\uFE0F Unknown platform: ${platformStr}`);
|
|
664
|
+
}
|
|
402
665
|
}
|
|
403
|
-
finalPlatform = parsed;
|
|
404
666
|
} else {
|
|
405
|
-
|
|
667
|
+
targetAdapters = await detectPlatforms();
|
|
668
|
+
}
|
|
669
|
+
if (targetAdapters.length === 0) {
|
|
670
|
+
console.log("No target platforms detected.");
|
|
671
|
+
console.log("Use --platform to specify platforms manually.");
|
|
672
|
+
} else {
|
|
673
|
+
console.log(`
|
|
674
|
+
Installing to ${targetAdapters.length} platform(s)...
|
|
675
|
+
`);
|
|
676
|
+
const results = [];
|
|
677
|
+
for (const adapter of targetAdapters) {
|
|
678
|
+
try {
|
|
679
|
+
const isInstalled = await adapter.isInstalled(skillId);
|
|
680
|
+
if (isInstalled && !options?.force) {
|
|
681
|
+
console.log(`${adapter.name.padEnd(12)} \u26A0\uFE0F Already installed (use --force to overwrite)`);
|
|
682
|
+
results.push({ name: adapter.name, status: "skipped" });
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
await adapter.install(skillId, skillVersionDir);
|
|
686
|
+
console.log(`${adapter.name.padEnd(12)} \u2705 Installed successfully`);
|
|
687
|
+
results.push({ name: adapter.name, status: "installed" });
|
|
688
|
+
} catch (error) {
|
|
689
|
+
console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${error}`);
|
|
690
|
+
results.push({ name: adapter.name, status: "failed", error: String(error) });
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
const installed = results.filter((r) => r.status === "installed").length;
|
|
694
|
+
const skipped = results.filter((r) => r.status === "skipped").length;
|
|
695
|
+
const failed = results.filter((r) => r.status === "failed").length;
|
|
696
|
+
console.log(`
|
|
697
|
+
\u{1F4CA} Summary: ${installed} installed, ${skipped} skipped, ${failed} failed`);
|
|
406
698
|
}
|
|
407
|
-
console.log(`Target platform: ${finalPlatform}`);
|
|
408
699
|
const registry = await loadRegistry();
|
|
409
|
-
const
|
|
410
|
-
const existingPlatforms = existingSkill?.platforms || [];
|
|
700
|
+
const installedPlatforms = targetAdapters.map((a) => a.id);
|
|
411
701
|
registry.skills[skillId] = {
|
|
412
702
|
id: skillId,
|
|
413
703
|
version: targetVersion,
|
|
414
704
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
415
|
-
platforms:
|
|
705
|
+
platforms: installedPlatforms
|
|
416
706
|
};
|
|
417
707
|
await saveRegistry(registry);
|
|
418
|
-
console.log(`Syncing to ${finalPlatform}...`);
|
|
419
|
-
await syncPlatformLinks(finalPlatform);
|
|
420
708
|
console.log(`
|
|
421
709
|
\u2705 ${skillId}@${targetVersion} installed successfully!`);
|
|
422
710
|
console.log(` Use "skm info ${skillId}" for more details`);
|
|
423
711
|
}
|
|
424
712
|
|
|
713
|
+
// src/commands/sync.ts
|
|
714
|
+
import fs8 from "fs-extra";
|
|
715
|
+
import path7 from "path";
|
|
716
|
+
async function syncPlatformLinks() {
|
|
717
|
+
await ensureMarketDirs();
|
|
718
|
+
const skillsDir = getSkillsDir();
|
|
719
|
+
const platformLinksDir = getPlatformLinksDir();
|
|
720
|
+
const registry = await loadRegistry();
|
|
721
|
+
console.log("Syncing platform links...\n");
|
|
722
|
+
for (const platform of PLATFORMS) {
|
|
723
|
+
const platformDir = path7.join(platformLinksDir, platform, "skills");
|
|
724
|
+
await fs8.ensureDir(platformDir);
|
|
725
|
+
for (const [skillId, skillInfo] of Object.entries(registry.skills)) {
|
|
726
|
+
const skillLatestLink = path7.join(skillsDir, skillId, LATEST_LINK);
|
|
727
|
+
const targetPlatformDir = path7.join(skillLatestLink, platform);
|
|
728
|
+
const platformSkillDir = path7.join(platformDir, skillId);
|
|
729
|
+
if (await fs8.pathExists(skillLatestLink)) {
|
|
730
|
+
if (await fs8.pathExists(targetPlatformDir)) {
|
|
731
|
+
try {
|
|
732
|
+
await fs8.remove(platformSkillDir);
|
|
733
|
+
await fs8.symlink(targetPlatformDir, platformSkillDir, "junction");
|
|
734
|
+
console.log(` Linked: ${platform}/${skillId}`);
|
|
735
|
+
} catch {
|
|
736
|
+
await fs8.copy(targetPlatformDir, platformSkillDir, { overwrite: true });
|
|
737
|
+
console.log(` Copied: ${platform}/${skillId}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
console.log("\n\u2705 Sync complete!");
|
|
744
|
+
}
|
|
745
|
+
|
|
425
746
|
// src/commands/update.ts
|
|
426
747
|
async function updateSkill(skillId) {
|
|
427
748
|
if (skillId) {
|
|
428
|
-
const pkgInfo = await fetchNpmPackage(`@
|
|
749
|
+
const pkgInfo = await fetchNpmPackage(`@itismyskillmarket/${skillId}`);
|
|
429
750
|
if (pkgInfo) {
|
|
430
751
|
const latestVersion = pkgInfo["dist-tags"]?.latest;
|
|
431
752
|
console.log(`Updating ${skillId} to ${latestVersion}...`);
|
|
@@ -442,7 +763,7 @@ async function updateSkill(skillId) {
|
|
|
442
763
|
`);
|
|
443
764
|
let hasUpdates = false;
|
|
444
765
|
for (const skill of installed) {
|
|
445
|
-
const pkgInfo = await fetchNpmPackage(`@
|
|
766
|
+
const pkgInfo = await fetchNpmPackage(`@wanxuchen/${skill.id}`);
|
|
446
767
|
if (pkgInfo) {
|
|
447
768
|
const latestVersion = pkgInfo["dist-tags"]?.latest;
|
|
448
769
|
if (latestVersion && latestVersion !== skill.version) {
|
|
@@ -464,118 +785,306 @@ async function updateSkill(skillId) {
|
|
|
464
785
|
}
|
|
465
786
|
|
|
466
787
|
// src/commands/uninstall.ts
|
|
467
|
-
import
|
|
468
|
-
import
|
|
469
|
-
|
|
788
|
+
import fs9 from "fs-extra";
|
|
789
|
+
import path8 from "path";
|
|
790
|
+
import readline from "readline";
|
|
791
|
+
async function askConfirmation(message) {
|
|
792
|
+
const rl = readline.createInterface({
|
|
793
|
+
input: process.stdin,
|
|
794
|
+
output: process.stdout
|
|
795
|
+
});
|
|
796
|
+
return new Promise((resolve2) => {
|
|
797
|
+
rl.question(`${message} (y/N): `, (answer) => {
|
|
798
|
+
rl.close();
|
|
799
|
+
resolve2(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
800
|
+
});
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
async function getUninstallPreview(skillId, options) {
|
|
804
|
+
const registry = await loadRegistry();
|
|
805
|
+
const skillInfo = registry.skills[skillId];
|
|
806
|
+
let platformNames = [];
|
|
807
|
+
if (options?.platforms && options.platforms.length > 0) {
|
|
808
|
+
platformNames = options.platforms;
|
|
809
|
+
} else {
|
|
810
|
+
const adapters2 = await detectPlatforms();
|
|
811
|
+
platformNames = adapters2.map((a) => a.name);
|
|
812
|
+
}
|
|
813
|
+
const skillsDir = getSkillsDir();
|
|
814
|
+
const localPath = path8.join(skillsDir, skillId);
|
|
815
|
+
const platformLinksDir = getPlatformLinksDir();
|
|
816
|
+
const platformLinks = [];
|
|
817
|
+
for (const platform of PLATFORMS) {
|
|
818
|
+
const linkPath = path8.join(platformLinksDir, platform, "skills", skillId);
|
|
819
|
+
if (await fs9.pathExists(linkPath)) {
|
|
820
|
+
platformLinks.push(linkPath);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return { skillInfo, platforms: platformNames, localPath, platformLinks };
|
|
824
|
+
}
|
|
825
|
+
async function uninstallSkill(skillId, options) {
|
|
470
826
|
const registry = await loadRegistry();
|
|
471
827
|
if (!(skillId in registry.skills)) {
|
|
472
|
-
console.log(
|
|
473
|
-
return;
|
|
828
|
+
console.log(`\u274C Skill "${skillId}" is not installed.`);
|
|
829
|
+
return false;
|
|
474
830
|
}
|
|
475
831
|
const skillInfo = registry.skills[skillId];
|
|
476
|
-
|
|
832
|
+
if (options?.dryRun) {
|
|
833
|
+
const preview = await getUninstallPreview(skillId, options);
|
|
834
|
+
console.log(`
|
|
835
|
+
\u{1F4CB} Uninstall Preview for "${skillId}":`);
|
|
836
|
+
console.log(` Version: ${skillInfo.version}`);
|
|
837
|
+
console.log(` Installed: ${skillInfo.installedAt}`);
|
|
838
|
+
console.log(` Platforms (from registry): ${preview.platforms.join(", ") || "none"}`);
|
|
839
|
+
console.log(`
|
|
840
|
+
Local files to remove:`);
|
|
841
|
+
console.log(` - ${preview.localPath}`);
|
|
842
|
+
if (preview.platformLinks.length > 0) {
|
|
843
|
+
console.log(`
|
|
844
|
+
Platform links to remove:`);
|
|
845
|
+
for (const link of preview.platformLinks) {
|
|
846
|
+
console.log(` - ${link}`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
console.log(`
|
|
850
|
+
\u26A0\uFE0F This was a dry-run. No files were actually deleted.`);
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
if (!options?.yes) {
|
|
854
|
+
const confirmed = await askConfirmation(`Are you sure you want to uninstall "${skillId}"?`);
|
|
855
|
+
if (!confirmed) {
|
|
856
|
+
console.log("Uninstall cancelled.");
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
console.log(`
|
|
861
|
+
Uninstalling ${skillId}@${skillInfo.version}...`);
|
|
862
|
+
let targetAdapters = [];
|
|
863
|
+
let platformUninstallErrors = [];
|
|
864
|
+
if (options?.platforms && options.platforms.length > 0) {
|
|
865
|
+
for (const platformStr of options.platforms) {
|
|
866
|
+
const platform = platformStr;
|
|
867
|
+
targetAdapters.push(getAdapterByPlatform(platform));
|
|
868
|
+
}
|
|
869
|
+
} else {
|
|
870
|
+
targetAdapters = await detectPlatforms();
|
|
871
|
+
}
|
|
872
|
+
const validAdapters = targetAdapters.filter((a) => a !== void 0);
|
|
873
|
+
if (validAdapters.length > 0) {
|
|
874
|
+
console.log(`
|
|
875
|
+
Uninstalling from ${validAdapters.length} platform(s)...
|
|
876
|
+
`);
|
|
877
|
+
for (const adapter of validAdapters) {
|
|
878
|
+
try {
|
|
879
|
+
await adapter.uninstall(skillId);
|
|
880
|
+
console.log(`${adapter.name.padEnd(12)} \u2705 Uninstalled`);
|
|
881
|
+
} catch (error) {
|
|
882
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
883
|
+
console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${errorMsg}`);
|
|
884
|
+
platformUninstallErrors.push({ name: adapter.name, error: errorMsg });
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (platformUninstallErrors.length > 0 && !options?.yes) {
|
|
889
|
+
const continueAnyway = await askConfirmation(
|
|
890
|
+
`\u26A0\uFE0F ${platformUninstallErrors.length} platform(s) failed to uninstall. Continue with local cleanup?`
|
|
891
|
+
);
|
|
892
|
+
if (!continueAnyway) {
|
|
893
|
+
console.log("Uninstall aborted. Platform files may still exist.");
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
477
897
|
const skillsDir = getSkillsDir();
|
|
478
|
-
const skillDir =
|
|
479
|
-
await
|
|
898
|
+
const skillDir = path8.join(skillsDir, skillId);
|
|
899
|
+
if (await fs9.pathExists(skillDir)) {
|
|
900
|
+
await fs9.remove(skillDir);
|
|
901
|
+
console.log(`\u2705 Removed local files: ${skillDir}`);
|
|
902
|
+
}
|
|
480
903
|
const platformLinksDir = getPlatformLinksDir();
|
|
904
|
+
let removedLinks = 0;
|
|
481
905
|
for (const platform of PLATFORMS) {
|
|
482
|
-
const linkPath =
|
|
483
|
-
if (await
|
|
484
|
-
await
|
|
906
|
+
const linkPath = path8.join(platformLinksDir, platform, "skills", skillId);
|
|
907
|
+
if (await fs9.pathExists(linkPath)) {
|
|
908
|
+
await fs9.remove(linkPath);
|
|
909
|
+
removedLinks++;
|
|
485
910
|
}
|
|
486
911
|
}
|
|
912
|
+
if (removedLinks > 0) {
|
|
913
|
+
console.log(`\u2705 Removed ${removedLinks} platform link(s)`);
|
|
914
|
+
}
|
|
487
915
|
delete registry.skills[skillId];
|
|
488
916
|
await saveRegistry(registry);
|
|
917
|
+
console.log(`\u2705 Registry updated`);
|
|
489
918
|
console.log(`
|
|
490
919
|
\u2705 ${skillId} uninstalled successfully!`);
|
|
920
|
+
return true;
|
|
491
921
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
922
|
+
async function uninstallAll(options) {
|
|
923
|
+
const registry = await loadRegistry();
|
|
924
|
+
const installedSkills = Object.keys(registry.skills);
|
|
925
|
+
if (installedSkills.length === 0) {
|
|
926
|
+
console.log("No skills installed.");
|
|
927
|
+
return { success: 0, failed: 0 };
|
|
928
|
+
}
|
|
929
|
+
console.log(`
|
|
930
|
+
Found ${installedSkills.length} installed skill(s):`);
|
|
931
|
+
for (const skillId of installedSkills) {
|
|
932
|
+
const info = registry.skills[skillId];
|
|
933
|
+
console.log(` - ${skillId}@${info.version}`);
|
|
934
|
+
}
|
|
935
|
+
if (options?.dryRun) {
|
|
936
|
+
console.log(`
|
|
937
|
+
\u{1F4CB} Dry-run: Would uninstall ${installedSkills.length} skill(s).`);
|
|
938
|
+
console.log(`\u26A0\uFE0F No files were actually deleted.`);
|
|
939
|
+
return { success: installedSkills.length, failed: 0 };
|
|
940
|
+
}
|
|
941
|
+
if (!options?.yes) {
|
|
942
|
+
const confirmed = await askConfirmation(
|
|
943
|
+
`
|
|
944
|
+
\u26A0\uFE0F Are you sure you want to uninstall ALL ${installedSkills.length} skill(s)? This action cannot be undone.`
|
|
501
945
|
);
|
|
502
|
-
if (
|
|
503
|
-
console.log(
|
|
504
|
-
return;
|
|
946
|
+
if (!confirmed) {
|
|
947
|
+
console.log("Uninstall cancelled.");
|
|
948
|
+
return { success: 0, failed: 0 };
|
|
505
949
|
}
|
|
506
|
-
|
|
950
|
+
}
|
|
951
|
+
console.log(`
|
|
952
|
+
Uninstalling all skills...
|
|
507
953
|
`);
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
954
|
+
let successCount = 0;
|
|
955
|
+
let failedCount = 0;
|
|
956
|
+
for (const skillId of installedSkills) {
|
|
957
|
+
try {
|
|
958
|
+
const success = await uninstallSkill(skillId, { ...options, yes: true });
|
|
959
|
+
if (success) {
|
|
960
|
+
successCount++;
|
|
961
|
+
} else {
|
|
962
|
+
failedCount++;
|
|
516
963
|
}
|
|
964
|
+
} catch (error) {
|
|
965
|
+
console.log(`\u274C Failed to uninstall ${skillId}: ${error}`);
|
|
966
|
+
failedCount++;
|
|
517
967
|
}
|
|
518
|
-
} catch (error) {
|
|
519
|
-
console.log(`Error searching skills: ${error}`);
|
|
520
968
|
}
|
|
969
|
+
console.log(`
|
|
970
|
+
\u{1F4CA} Summary:`);
|
|
971
|
+
console.log(` \u2705 Success: ${successCount}`);
|
|
972
|
+
console.log(` \u274C Failed: ${failedCount}`);
|
|
973
|
+
console.log(` \u{1F4E6} Total: ${installedSkills.length}`);
|
|
974
|
+
return { success: successCount, failed: failedCount };
|
|
521
975
|
}
|
|
522
976
|
|
|
523
977
|
// src/cli.ts
|
|
978
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
979
|
+
var __dirname = dirname(__filename);
|
|
980
|
+
var packageJson = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf-8"));
|
|
981
|
+
var VERSION = packageJson.version || "1.3.1";
|
|
524
982
|
var program = new Command();
|
|
525
|
-
program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
983
|
+
program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version(VERSION);
|
|
984
|
+
program.hook("preAction", (thisCommand) => {
|
|
985
|
+
if (thisCommand.opts().help) {
|
|
986
|
+
console.log(`
|
|
529
987
|
SkillMarket CLI
|
|
530
988
|
|
|
531
989
|
Usage: skm <command> [options]
|
|
532
990
|
|
|
533
991
|
Commands:
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
992
|
+
ls [options] List available skills
|
|
993
|
+
--installed Show only installed skills
|
|
994
|
+
--updates Check for updates
|
|
995
|
+
--page <n> Page number (default: 1)
|
|
996
|
+
--limit <n> Items per page (default: 20)
|
|
997
|
+
-s, --search Search by keyword
|
|
998
|
+
info <skill-id> Display skill information
|
|
999
|
+
install <skill> Install a skill
|
|
1000
|
+
@version Install specific version
|
|
1001
|
+
--platform Target platforms (opencode,claude,vscode)
|
|
1002
|
+
--force Overwrite if already installed
|
|
1003
|
+
uninstall <skill> Remove an installed skill
|
|
1004
|
+
--platform Target platforms
|
|
1005
|
+
--all Uninstall ALL installed skills
|
|
1006
|
+
--dry-run Preview without deleting
|
|
1007
|
+
-y, --yes Skip confirmation
|
|
1008
|
+
update [options] Update skills
|
|
1009
|
+
--all Update all skills
|
|
1010
|
+
sync Synchronize platform links
|
|
1011
|
+
platforms Show available platforms
|
|
547
1012
|
|
|
548
1013
|
Examples:
|
|
549
|
-
skm
|
|
550
|
-
skm
|
|
551
|
-
skm --
|
|
552
|
-
skm --
|
|
553
|
-
skm
|
|
554
|
-
skm
|
|
555
|
-
skm --
|
|
556
|
-
|
|
1014
|
+
skm ls List all available skills (page 1)
|
|
1015
|
+
skm ls --page 2 Go to page 2
|
|
1016
|
+
skm ls --limit 10 Show 10 items per page
|
|
1017
|
+
skm ls --search brain Search skills by keyword
|
|
1018
|
+
skm ls -s brain Search with short form
|
|
1019
|
+
skm ls --installed Show installed skills only
|
|
1020
|
+
skm ls --installed --search test Search installed skills
|
|
1021
|
+
skm ls --installed --page 2
|
|
1022
|
+
skm info brainstorming View skill details
|
|
1023
|
+
skm install brainstorming Install to all platforms
|
|
1024
|
+
skm install brainstorming --platform opencode Install to OpenCode only
|
|
1025
|
+
skm install brainstorming --platform claude,vscode Install to multiple
|
|
1026
|
+
skm uninstall brainstorming
|
|
1027
|
+
skm uninstall --all Uninstall all skills (with confirmation)
|
|
1028
|
+
skm uninstall --all --yes Force uninstall all without confirmation
|
|
1029
|
+
skm uninstall brainstorming --dry-run Preview uninstall
|
|
1030
|
+
skm platforms Show available platforms
|
|
1031
|
+
`);
|
|
1032
|
+
process.exit(0);
|
|
1033
|
+
}
|
|
557
1034
|
});
|
|
558
1035
|
var lsCmd = program.command("ls").description("List available skills");
|
|
559
|
-
lsCmd.option("--installed", "Show only installed skills").option("--updates", "Check for updates").action((opts) => {
|
|
560
|
-
|
|
1036
|
+
lsCmd.option("--installed", "Show only installed skills").option("--updates", "Check for updates").option("-p, --page <number>", "Page number (default: 1)", parseInt).option("-l, --limit <number>", "Items per page (default: 20)", parseInt).option("-s, --search <keyword>", "Search by keyword (id, displayName, description)").action((opts) => {
|
|
1037
|
+
const options = {
|
|
1038
|
+
...opts,
|
|
1039
|
+
page: opts.page ?? 1,
|
|
1040
|
+
limit: opts.limit ?? 20,
|
|
1041
|
+
search: opts.search
|
|
1042
|
+
};
|
|
1043
|
+
listSkills(options);
|
|
1044
|
+
});
|
|
1045
|
+
var searchCmd = program.command("search").description("Search skills from npm registry");
|
|
1046
|
+
searchCmd.argument("<keyword>", "Keyword to search").option("-l, --limit <number>", "Max results to show (default: 20)", parseInt).action(async (keyword, opts) => {
|
|
1047
|
+
const limit = opts.limit ?? 20;
|
|
1048
|
+
await searchSkills(keyword, limit);
|
|
561
1049
|
});
|
|
562
1050
|
var infoCmd = program.command("info").description("Display skill information");
|
|
563
1051
|
infoCmd.argument("<skill-id>", "Skill ID to show info").action((skillId) => {
|
|
564
1052
|
showSkillInfo(skillId);
|
|
565
1053
|
});
|
|
566
|
-
var installCmd = program.command("install").description("Install a skill");
|
|
567
|
-
installCmd.argument("<skill>", "Skill ID to install (e.g., brainstorming or @scope/name)").option("--
|
|
1054
|
+
var installCmd = program.command("install").description("Install a skill to local and platform directories");
|
|
1055
|
+
installCmd.argument("<skill>", "Skill ID to install (e.g., brainstorming or @scope/name)").option("-p, --platform <platforms>", "Target platforms (comma-separated: opencode,claude,vscode)").option("-f, --force", "Overwrite if already installed").option("-v, --version <version>", "Specific version to install").action(async (skill, opts) => {
|
|
568
1056
|
try {
|
|
569
|
-
|
|
1057
|
+
const platforms = opts.platform ? opts.platform.split(",").map((p) => p.trim()) : void 0;
|
|
1058
|
+
await installSkill(skill, opts.version, {
|
|
1059
|
+
platforms,
|
|
1060
|
+
force: opts.force
|
|
1061
|
+
});
|
|
570
1062
|
} catch (err) {
|
|
571
1063
|
console.error("Installation failed:", err);
|
|
572
1064
|
process.exit(1);
|
|
573
1065
|
}
|
|
574
1066
|
});
|
|
575
|
-
var uninstallCmd = program.command("uninstall").description("Remove an installed skill");
|
|
576
|
-
uninstallCmd.argument("
|
|
1067
|
+
var uninstallCmd = program.command("uninstall").description("Remove an installed skill from local and platform directories");
|
|
1068
|
+
uninstallCmd.argument("[skill]", "Skill ID to uninstall (required unless using --all)").option("-p, --platform <platforms>", "Target platforms (comma-separated)").option("-a, --all", "Uninstall ALL installed skills (requires confirmation)").option("-d, --dry-run", "Preview what would be uninstalled without actually deleting").option("-y, --yes", "Skip confirmation prompts").action(async (skill, opts) => {
|
|
577
1069
|
try {
|
|
578
|
-
|
|
1070
|
+
const platforms = opts.platform ? opts.platform.split(",").map((p) => p.trim()) : void 0;
|
|
1071
|
+
if (opts.all) {
|
|
1072
|
+
await uninstallAll({
|
|
1073
|
+
platforms,
|
|
1074
|
+
dryRun: opts.dryRun,
|
|
1075
|
+
yes: opts.yes
|
|
1076
|
+
});
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
if (!skill) {
|
|
1080
|
+
console.error("Error: Skill ID is required (or use --all to uninstall all)");
|
|
1081
|
+
process.exit(1);
|
|
1082
|
+
}
|
|
1083
|
+
await uninstallSkill(skill, {
|
|
1084
|
+
platforms,
|
|
1085
|
+
dryRun: opts.dryRun,
|
|
1086
|
+
yes: opts.yes
|
|
1087
|
+
});
|
|
579
1088
|
} catch (err) {
|
|
580
1089
|
console.error("Uninstall failed:", err);
|
|
581
1090
|
process.exit(1);
|
|
@@ -602,16 +1111,28 @@ program.command("sync").description("Synchronize platform links").action(async (
|
|
|
602
1111
|
process.exit(1);
|
|
603
1112
|
}
|
|
604
1113
|
});
|
|
605
|
-
var
|
|
606
|
-
|
|
607
|
-
console.log("Platform command - name:", name);
|
|
608
|
-
});
|
|
609
|
-
var searchCmd = program.command("search").description("Search skills from npm registry");
|
|
610
|
-
searchCmd.argument("<keyword>", "Keyword to search").action(async (keyword) => {
|
|
1114
|
+
var platformsCmd = program.command("platforms").description("Show available platforms");
|
|
1115
|
+
platformsCmd.action(async () => {
|
|
611
1116
|
try {
|
|
612
|
-
await
|
|
1117
|
+
const available = await detectPlatforms();
|
|
1118
|
+
console.log("\n\u{1F4CD} Available Platforms:\n");
|
|
1119
|
+
const allPlatforms = [
|
|
1120
|
+
{ name: "OpenCode", adapter: new OpenCodeAdapter() },
|
|
1121
|
+
{ name: "Claude Code", adapter: new ClaudeAdapter() },
|
|
1122
|
+
{ name: "VSCode", adapter: new VSCodeAdapter() }
|
|
1123
|
+
];
|
|
1124
|
+
for (const { name, adapter } of allPlatforms) {
|
|
1125
|
+
const isAvailable = available.find((a) => a.id === adapter.id);
|
|
1126
|
+
const installed = await adapter.listInstalled();
|
|
1127
|
+
if (isAvailable) {
|
|
1128
|
+
console.log(`${name.padEnd(12)} \u2705 Available (${installed.length} skills installed)`);
|
|
1129
|
+
} else {
|
|
1130
|
+
console.log(`${name.padEnd(12)} \u274C Not detected`);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
console.log("");
|
|
613
1134
|
} catch (err) {
|
|
614
|
-
console.error("
|
|
1135
|
+
console.error("Failed to list platforms:", err);
|
|
615
1136
|
process.exit(1);
|
|
616
1137
|
}
|
|
617
1138
|
});
|