itismyskillmarket 1.2.2 → 1.2.4
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 +54 -0
- package/.github/workflows/publish-skill.yml +72 -0
- package/5e51cb7aa8b8e60d49d86f4689f5d4d1.png +0 -0
- package/CHANGELOG.md +143 -0
- package/DEVELOPMENT.md +376 -0
- package/README.md +70 -4
- package/SKILLMARKET-GUIDE.md +277 -0
- package/dist/index.js +478 -177
- 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/package.json +1 -6
- package/skills/README.md +52 -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 +113 -49
- package/src/commands/info.ts +4 -15
- package/src/commands/install.ts +93 -54
- package/src/commands/ls.ts +69 -13
- package/src/commands/npm.ts +87 -12
- package/src/commands/sync.ts +6 -27
- package/src/commands/uninstall.ts +60 -7
- 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
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
|
+
// src/commands/registry.ts
|
|
7
|
+
import fs2 from "fs-extra";
|
|
8
|
+
|
|
9
|
+
// src/utils/dirs.ts
|
|
10
|
+
import os from "os";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import fs from "fs-extra";
|
|
13
|
+
|
|
6
14
|
// src/constants.ts
|
|
7
15
|
var MARKET_DIR = "skillmarket";
|
|
8
16
|
var SUBDIRS = {
|
|
@@ -30,13 +38,7 @@ var PLATFORMS = [
|
|
|
30
38
|
var REGISTRY_FILE = "registry.json";
|
|
31
39
|
var LATEST_LINK = "latest";
|
|
32
40
|
|
|
33
|
-
// src/commands/registry.ts
|
|
34
|
-
import fs2 from "fs-extra";
|
|
35
|
-
|
|
36
41
|
// src/utils/dirs.ts
|
|
37
|
-
import os from "os";
|
|
38
|
-
import path from "path";
|
|
39
|
-
import fs from "fs-extra";
|
|
40
42
|
function getMarketHome() {
|
|
41
43
|
return path.join(os.homedir(), MARKET_DIR);
|
|
42
44
|
}
|
|
@@ -137,12 +139,46 @@ async function fetchNpmPackage(packageName) {
|
|
|
137
139
|
});
|
|
138
140
|
});
|
|
139
141
|
}
|
|
140
|
-
|
|
142
|
+
var SKILL_SCOPES = [
|
|
143
|
+
"@wanxuchen",
|
|
144
|
+
// 原作者 scope
|
|
145
|
+
"@itismyskillmarket",
|
|
146
|
+
// 当前包名 scope
|
|
147
|
+
"@thisisskillmarket",
|
|
148
|
+
// 曾用 scope
|
|
149
|
+
"@this-is-skillmarket",
|
|
150
|
+
// 曾用 scope (带横线)
|
|
151
|
+
"@skillmarket"
|
|
152
|
+
// 通用 scope
|
|
153
|
+
];
|
|
154
|
+
function getPossiblePackageNames(skillId) {
|
|
155
|
+
if (skillId.startsWith("@")) {
|
|
156
|
+
return [skillId];
|
|
157
|
+
}
|
|
158
|
+
return SKILL_SCOPES.map((scope) => `${scope}/${skillId}`);
|
|
159
|
+
}
|
|
160
|
+
async function fetchSkillPackage(skillId) {
|
|
161
|
+
const packageNames = getPossiblePackageNames(skillId);
|
|
162
|
+
for (const packageName of packageNames) {
|
|
163
|
+
try {
|
|
164
|
+
const info = await fetchNpmPackage(packageName);
|
|
165
|
+
if (info) {
|
|
166
|
+
return info;
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
async function searchSkillmarketPackages(options = {}) {
|
|
174
|
+
const { from = 0, size = 100 } = options;
|
|
141
175
|
const packages = [];
|
|
176
|
+
let total = 0;
|
|
142
177
|
return new Promise((resolve, reject) => {
|
|
143
178
|
const url = new URL("https://registry.npmjs.org/-/v1/search");
|
|
144
179
|
url.searchParams.set("text", "keywords:skillmarket");
|
|
145
|
-
url.searchParams.set("size",
|
|
180
|
+
url.searchParams.set("size", String(size));
|
|
181
|
+
url.searchParams.set("from", String(from));
|
|
146
182
|
const req = https.get(url.toString(), { timeout: 1e4 }, (res) => {
|
|
147
183
|
let data = "";
|
|
148
184
|
res.on("data", (chunk) => {
|
|
@@ -151,14 +187,17 @@ async function searchSkillmarketPackages() {
|
|
|
151
187
|
res.on("end", () => {
|
|
152
188
|
try {
|
|
153
189
|
const result = JSON.parse(data);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
190
|
+
total = result.total || 0;
|
|
191
|
+
if (result.objects) {
|
|
192
|
+
for (const item of result.objects) {
|
|
193
|
+
if (item?.package?.name) {
|
|
194
|
+
packages.push(item.package.name);
|
|
195
|
+
}
|
|
157
196
|
}
|
|
158
197
|
}
|
|
159
|
-
resolve(packages);
|
|
198
|
+
resolve({ packages, total });
|
|
160
199
|
} catch {
|
|
161
|
-
resolve([]);
|
|
200
|
+
resolve({ packages: [], total: 0 });
|
|
162
201
|
}
|
|
163
202
|
});
|
|
164
203
|
});
|
|
@@ -172,41 +211,71 @@ async function searchSkillmarketPackages() {
|
|
|
172
211
|
|
|
173
212
|
// src/commands/ls.ts
|
|
174
213
|
async function listSkills(options) {
|
|
175
|
-
const { installed, updates } = options;
|
|
214
|
+
const { installed, updates, page = 1, limit = 20 } = options;
|
|
176
215
|
if (installed) {
|
|
177
216
|
const skills = await getInstalledSkills();
|
|
217
|
+
const total = skills.length;
|
|
218
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
219
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
178
220
|
if (skills.length === 0) {
|
|
179
|
-
console.log('No skills installed yet. Run "skm
|
|
221
|
+
console.log('No skills installed yet. Run "skm ls" to see available skills.');
|
|
180
222
|
return;
|
|
181
223
|
}
|
|
182
|
-
|
|
183
|
-
|
|
224
|
+
const start = (currentPage - 1) * limit;
|
|
225
|
+
const end = Math.min(start + limit, total);
|
|
226
|
+
const pageSkills = skills.slice(start, end);
|
|
227
|
+
console.log(`Installed Skills (${total}):
|
|
228
|
+
`);
|
|
229
|
+
for (const skill of pageSkills) {
|
|
184
230
|
console.log(` ${skill.id}@${skill.version}`);
|
|
185
231
|
console.log(` Platforms: ${skill.platforms.join(", ")}`);
|
|
186
232
|
console.log(` Installed: ${skill.installedAt}`);
|
|
187
233
|
console.log();
|
|
188
234
|
}
|
|
235
|
+
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
189
236
|
return;
|
|
190
237
|
}
|
|
191
238
|
console.log("Searching npm registry...\n");
|
|
192
239
|
try {
|
|
193
|
-
const
|
|
240
|
+
const offset = (page - 1) * limit;
|
|
241
|
+
const { packages, total } = await searchSkillmarketPackages({
|
|
242
|
+
from: offset,
|
|
243
|
+
size: limit
|
|
244
|
+
});
|
|
245
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
246
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
194
247
|
if (packages.length === 0) {
|
|
195
248
|
console.log("No skills found. Check back later!");
|
|
196
249
|
return;
|
|
197
250
|
}
|
|
198
|
-
console.log(`Found ${
|
|
251
|
+
console.log(`Found ${total} skill(s):
|
|
199
252
|
`);
|
|
200
253
|
for (const pkgName of packages) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
254
|
+
try {
|
|
255
|
+
const info = await fetchNpmPackage(pkgName);
|
|
256
|
+
if (!info) {
|
|
257
|
+
console.log(`\u{1F4E6} ${pkgName} (\u4FE1\u606F\u83B7\u53D6\u5931\u8D25)`);
|
|
258
|
+
console.log();
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const latestVersion = info["dist-tags"]?.latest || "unknown";
|
|
204
262
|
const pkg = info.versions?.[latestVersion];
|
|
205
|
-
|
|
206
|
-
console.log(
|
|
263
|
+
const skillMeta = pkg?.skillmarket;
|
|
264
|
+
console.log(`\u{1F4E6} ${info.name}@${latestVersion}`);
|
|
265
|
+
const displayName = skillMeta?.displayName || info.name;
|
|
266
|
+
console.log(` \u540D\u79F0: ${displayName}`);
|
|
267
|
+
console.log(` \u63CF\u8FF0: ${pkg?.description || "N/A"}`);
|
|
268
|
+
const platforms = skillMeta?.platforms || [];
|
|
269
|
+
console.log(` \u5E73\u53F0: ${platforms.length > 0 ? platforms.join(", ") : "N/A"}`);
|
|
270
|
+
const npmLink = pkg?.links?.npm || `https://www.npmjs.com/package/${info.name}`;
|
|
271
|
+
console.log(` \u94FE\u63A5: ${npmLink}`);
|
|
272
|
+
console.log();
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.log(`\u{1F4E6} ${pkgName} (\u83B7\u53D6\u5931\u8D25: ${e})`);
|
|
207
275
|
console.log();
|
|
208
276
|
}
|
|
209
277
|
}
|
|
278
|
+
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
210
279
|
} catch (error) {
|
|
211
280
|
console.log(`Error fetching skills: ${error}`);
|
|
212
281
|
}
|
|
@@ -214,11 +283,10 @@ async function listSkills(options) {
|
|
|
214
283
|
|
|
215
284
|
// src/commands/info.ts
|
|
216
285
|
async function showSkillInfo(skillId) {
|
|
217
|
-
|
|
218
|
-
console.log(`Fetching info for: ${packageName}
|
|
286
|
+
console.log(`Fetching info for: ${skillId}
|
|
219
287
|
`);
|
|
220
288
|
try {
|
|
221
|
-
const info = await
|
|
289
|
+
const info = await fetchSkillPackage(skillId);
|
|
222
290
|
if (!info) {
|
|
223
291
|
console.log(`Skill "${skillId}" not found in npm registry.`);
|
|
224
292
|
return;
|
|
@@ -258,174 +326,344 @@ Status: Not installed (use skm install ${skillId} to install)`);
|
|
|
258
326
|
}
|
|
259
327
|
|
|
260
328
|
// src/commands/install.ts
|
|
261
|
-
import
|
|
262
|
-
import
|
|
329
|
+
import fs7 from "fs-extra";
|
|
330
|
+
import path6 from "path";
|
|
263
331
|
import { exec } from "child_process";
|
|
264
332
|
import { promisify } from "util";
|
|
265
333
|
|
|
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
|
|
334
|
+
// src/adapters/base.ts
|
|
287
335
|
import fs3 from "fs-extra";
|
|
288
336
|
import path2 from "path";
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
337
|
+
var BaseAdapter = class {
|
|
338
|
+
/**
|
|
339
|
+
* Get the path where a specific skill should be installed
|
|
340
|
+
*/
|
|
341
|
+
getSkillPath(skillId) {
|
|
342
|
+
return path2.join(this.skillDir, skillId);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get the path to the SKILL.md file for a skill
|
|
346
|
+
*/
|
|
347
|
+
getSkillFilePath(skillId) {
|
|
348
|
+
return path2.join(this.getSkillPath(skillId), "SKILL.md");
|
|
349
|
+
}
|
|
350
|
+
async isAvailable() {
|
|
351
|
+
try {
|
|
352
|
+
await fs3.ensureDir(this.skillDir);
|
|
353
|
+
return true;
|
|
354
|
+
} catch {
|
|
355
|
+
return false;
|
|
302
356
|
}
|
|
303
|
-
} else {
|
|
304
|
-
platformsToSync = PLATFORMS;
|
|
305
357
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
358
|
+
async isInstalled(skillId) {
|
|
359
|
+
const skillFile = this.getSkillFilePath(skillId);
|
|
360
|
+
return fs3.pathExists(skillFile);
|
|
361
|
+
}
|
|
362
|
+
async install(skillId, sourceDir) {
|
|
363
|
+
const targetDir = this.getSkillPath(skillId);
|
|
364
|
+
const targetFile = this.getSkillFilePath(skillId);
|
|
365
|
+
await fs3.ensureDir(targetDir);
|
|
366
|
+
const sourceFile = path2.join(sourceDir, "SKILL.md");
|
|
367
|
+
if (!await fs3.pathExists(sourceFile)) {
|
|
368
|
+
throw new Error(`SKILL.md not found in ${sourceDir}`);
|
|
369
|
+
}
|
|
370
|
+
await fs3.copy(sourceFile, targetFile, { overwrite: true });
|
|
371
|
+
}
|
|
372
|
+
async uninstall(skillId) {
|
|
373
|
+
const targetDir = this.getSkillPath(skillId);
|
|
374
|
+
if (await fs3.pathExists(targetDir)) {
|
|
375
|
+
await fs3.remove(targetDir);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async listInstalled() {
|
|
379
|
+
if (!await fs3.pathExists(this.skillDir)) {
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
const entries = await fs3.readdir(this.skillDir, { withFileTypes: true });
|
|
383
|
+
const skills = [];
|
|
384
|
+
for (const entry of entries) {
|
|
385
|
+
if (entry.isDirectory()) {
|
|
386
|
+
const skillFile = path2.join(this.skillDir, entry.name, "SKILL.md");
|
|
387
|
+
if (await fs3.pathExists(skillFile)) {
|
|
388
|
+
skills.push(entry.name);
|
|
326
389
|
}
|
|
327
390
|
}
|
|
328
391
|
}
|
|
392
|
+
return skills;
|
|
329
393
|
}
|
|
330
|
-
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/adapters/opencode.ts
|
|
397
|
+
import path3 from "path";
|
|
398
|
+
import os2 from "os";
|
|
399
|
+
import fs4 from "fs-extra";
|
|
400
|
+
var OpenCodeAdapter = class extends BaseAdapter {
|
|
401
|
+
id = "opencode";
|
|
402
|
+
name = "OpenCode";
|
|
403
|
+
get skillDir() {
|
|
404
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os2.homedir(), ".config", "opencode");
|
|
405
|
+
return path3.join(configDir, "skills");
|
|
406
|
+
}
|
|
407
|
+
async isAvailable() {
|
|
408
|
+
if (process.env.OPENCODE) return true;
|
|
409
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os2.homedir(), ".config", "opencode");
|
|
410
|
+
try {
|
|
411
|
+
await fs4.ensureDir(path3.join(configDir, "skills"));
|
|
412
|
+
return true;
|
|
413
|
+
} catch {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// src/adapters/claude.ts
|
|
420
|
+
import path4 from "path";
|
|
421
|
+
import os3 from "os";
|
|
422
|
+
import fs5 from "fs-extra";
|
|
423
|
+
var ClaudeAdapter = class extends BaseAdapter {
|
|
424
|
+
id = "claude";
|
|
425
|
+
name = "Claude Code";
|
|
426
|
+
get skillDir() {
|
|
427
|
+
return path4.join(os3.homedir(), ".claude", "skills");
|
|
428
|
+
}
|
|
429
|
+
async isAvailable() {
|
|
430
|
+
if (process.env.CLAUDE_CODE) return true;
|
|
431
|
+
const claudeDir = path4.join(os3.homedir(), ".claude");
|
|
432
|
+
return fs5.pathExists(claudeDir);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// src/adapters/vscode.ts
|
|
437
|
+
import path5 from "path";
|
|
438
|
+
import os4 from "os";
|
|
439
|
+
import fs6 from "fs-extra";
|
|
440
|
+
var VSCodeAdapter = class extends BaseAdapter {
|
|
441
|
+
id = "vscode";
|
|
442
|
+
name = "VSCode";
|
|
443
|
+
get skillDir() {
|
|
444
|
+
return path5.join(os4.homedir(), ".copilot", "skills");
|
|
445
|
+
}
|
|
446
|
+
async isAvailable() {
|
|
447
|
+
const possibleDirs = [
|
|
448
|
+
path5.join(os4.homedir(), ".copilot", "skills"),
|
|
449
|
+
path5.join(os4.homedir(), ".claude", "skills")
|
|
450
|
+
];
|
|
451
|
+
for (const dir of possibleDirs) {
|
|
452
|
+
try {
|
|
453
|
+
await fs6.ensureDir(dir);
|
|
454
|
+
return true;
|
|
455
|
+
} catch {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
async install(skillId, sourceDir) {
|
|
462
|
+
await super.install(skillId, sourceDir);
|
|
463
|
+
const claudeSkillDir = path5.join(os4.homedir(), ".claude", "skills");
|
|
464
|
+
const targetPath = this.getSkillPath(skillId);
|
|
465
|
+
const claudeTargetPath = path5.join(claudeSkillDir, skillId);
|
|
466
|
+
try {
|
|
467
|
+
await fs6.ensureDir(claudeSkillDir);
|
|
468
|
+
await fs6.remove(claudeTargetPath);
|
|
469
|
+
await fs6.symlink(targetPath, claudeTargetPath, "junction");
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// src/adapters/registry.ts
|
|
476
|
+
var adapters = /* @__PURE__ */ new Map();
|
|
477
|
+
function registerAdapters() {
|
|
478
|
+
const opencode = new OpenCodeAdapter();
|
|
479
|
+
const claude = new ClaudeAdapter();
|
|
480
|
+
const vscode = new VSCodeAdapter();
|
|
481
|
+
adapters.set(opencode.id, opencode);
|
|
482
|
+
adapters.set(claude.id, claude);
|
|
483
|
+
adapters.set(vscode.id, vscode);
|
|
484
|
+
}
|
|
485
|
+
registerAdapters();
|
|
486
|
+
async function detectPlatforms() {
|
|
487
|
+
const available = [];
|
|
488
|
+
for (const adapter of adapters.values()) {
|
|
489
|
+
if (await adapter.isAvailable()) {
|
|
490
|
+
available.push(adapter);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return available;
|
|
494
|
+
}
|
|
495
|
+
function getAdapterByPlatform(platform) {
|
|
496
|
+
const idMap = {
|
|
497
|
+
opencode: "opencode",
|
|
498
|
+
claude: "claude",
|
|
499
|
+
vscode: "vscode",
|
|
500
|
+
cursor: "opencode",
|
|
501
|
+
// Cursor uses OpenCode-compatible structure
|
|
502
|
+
codex: "opencode",
|
|
503
|
+
// Codex uses OpenCode-compatible structure
|
|
504
|
+
antigravity: "opencode"
|
|
505
|
+
// Antigravity uses OpenCode-compatible structure
|
|
506
|
+
};
|
|
507
|
+
return adapters.get(idMap[platform]);
|
|
331
508
|
}
|
|
332
509
|
|
|
333
510
|
// src/commands/install.ts
|
|
334
511
|
var execAsync = promisify(exec);
|
|
335
|
-
async function installSkill(skillId, version,
|
|
512
|
+
async function installSkill(skillId, version, options) {
|
|
336
513
|
await ensureMarketDirs();
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
const shortName = skillId;
|
|
340
|
-
console.log(`Installing ${packageName}${version ? `@${version}` : ""}...`);
|
|
341
|
-
const pkgInfo = await fetchNpmPackage(packageName);
|
|
514
|
+
console.log(`Installing ${skillId}${version ? `@${version}` : ""}...`);
|
|
515
|
+
const pkgInfo = await fetchSkillPackage(skillId);
|
|
342
516
|
if (!pkgInfo) {
|
|
343
|
-
throw new Error(`Package ${
|
|
517
|
+
throw new Error(`Package ${skillId} not found`);
|
|
344
518
|
}
|
|
519
|
+
const packageName = pkgInfo.name;
|
|
345
520
|
const targetVersion = version || pkgInfo["dist-tags"]?.latest;
|
|
346
521
|
if (!targetVersion) {
|
|
347
522
|
throw new Error(`No version found for ${packageName}`);
|
|
348
523
|
}
|
|
349
524
|
const cacheDir = getCacheDir();
|
|
350
|
-
const targetDir =
|
|
351
|
-
if (!await
|
|
525
|
+
const targetDir = path6.join(cacheDir, `${packageName}@${targetVersion}`);
|
|
526
|
+
if (!await fs7.pathExists(targetDir)) {
|
|
352
527
|
console.log("Downloading package...");
|
|
353
|
-
await
|
|
528
|
+
await fs7.ensureDir(cacheDir);
|
|
354
529
|
try {
|
|
355
530
|
await execAsync(`npm pack ${packageName}@${targetVersion} --pack-destination ${cacheDir}`);
|
|
356
|
-
const files = await
|
|
531
|
+
const files = await fs7.readdir(cacheDir);
|
|
357
532
|
const tarball = files.find(
|
|
358
|
-
(f) => f.endsWith(".tgz") && f.includes(packageName.replace("/", "-"))
|
|
533
|
+
(f) => f.endsWith(".tgz") && f.includes(packageName.replace(/^@/, "").replace("/", "-"))
|
|
359
534
|
);
|
|
360
535
|
if (tarball) {
|
|
361
|
-
await execAsync(`tar -xzf "${
|
|
362
|
-
await
|
|
363
|
-
const extractedDir =
|
|
536
|
+
await execAsync(`tar -xzf "${path6.join(cacheDir, tarball)}" -C "${cacheDir}"`);
|
|
537
|
+
await fs7.remove(path6.join(cacheDir, tarball));
|
|
538
|
+
const extractedDir = path6.join(cacheDir, "package");
|
|
364
539
|
const finalDir = targetDir;
|
|
365
|
-
await
|
|
540
|
+
await fs7.move(extractedDir, finalDir, { overwrite: true });
|
|
366
541
|
}
|
|
367
542
|
} catch (err) {
|
|
368
543
|
throw new Error(`Failed to download package: ${err}`);
|
|
369
544
|
}
|
|
370
545
|
}
|
|
371
546
|
const skillsDir = getSkillsDir();
|
|
372
|
-
const skillVersionDir =
|
|
547
|
+
const skillVersionDir = path6.join(skillsDir, `${skillId}@${targetVersion}`);
|
|
373
548
|
console.log("Setting up skill...");
|
|
374
|
-
await
|
|
549
|
+
await fs7.ensureDir(skillVersionDir);
|
|
375
550
|
const pkgRoot = targetDir;
|
|
376
|
-
if (await
|
|
377
|
-
await
|
|
378
|
-
|
|
379
|
-
|
|
551
|
+
if (await fs7.pathExists(path6.join(pkgRoot, "SKILL.md"))) {
|
|
552
|
+
await fs7.copy(
|
|
553
|
+
path6.join(pkgRoot, "SKILL.md"),
|
|
554
|
+
path6.join(skillVersionDir, "SKILL.md")
|
|
380
555
|
);
|
|
381
556
|
}
|
|
382
|
-
if (await
|
|
383
|
-
await
|
|
384
|
-
|
|
385
|
-
|
|
557
|
+
if (await fs7.pathExists(path6.join(pkgRoot, "metadata.json"))) {
|
|
558
|
+
await fs7.copy(
|
|
559
|
+
path6.join(pkgRoot, "metadata.json"),
|
|
560
|
+
path6.join(skillVersionDir, "metadata.json")
|
|
386
561
|
);
|
|
387
562
|
}
|
|
388
|
-
const skillDir =
|
|
389
|
-
await
|
|
390
|
-
const latestLink =
|
|
563
|
+
const skillDir = path6.join(skillsDir, skillId);
|
|
564
|
+
await fs7.ensureDir(skillDir);
|
|
565
|
+
const latestLink = path6.join(skillDir, LATEST_LINK);
|
|
391
566
|
try {
|
|
392
|
-
await
|
|
393
|
-
await
|
|
567
|
+
await fs7.remove(latestLink);
|
|
568
|
+
await fs7.symlink(skillVersionDir, latestLink, "junction");
|
|
394
569
|
} catch {
|
|
395
|
-
await
|
|
570
|
+
await fs7.copy(skillVersionDir, path6.join(skillDir, LATEST_LINK), { overwrite: true });
|
|
396
571
|
}
|
|
397
|
-
let
|
|
398
|
-
if (
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
572
|
+
let targetAdapters = [];
|
|
573
|
+
if (options?.platforms && options.platforms.length > 0) {
|
|
574
|
+
for (const platformStr of options.platforms) {
|
|
575
|
+
const platform = platformStr;
|
|
576
|
+
const adapter = getAdapterByPlatform(platform);
|
|
577
|
+
if (adapter) {
|
|
578
|
+
targetAdapters.push(adapter);
|
|
579
|
+
} else {
|
|
580
|
+
console.warn(`\u26A0\uFE0F Unknown platform: ${platformStr}`);
|
|
581
|
+
}
|
|
402
582
|
}
|
|
403
|
-
finalPlatform = parsed;
|
|
404
583
|
} else {
|
|
405
|
-
|
|
584
|
+
targetAdapters = await detectPlatforms();
|
|
585
|
+
}
|
|
586
|
+
if (targetAdapters.length === 0) {
|
|
587
|
+
console.log("No target platforms detected.");
|
|
588
|
+
console.log("Use --platform to specify platforms manually.");
|
|
589
|
+
} else {
|
|
590
|
+
console.log(`
|
|
591
|
+
Installing to ${targetAdapters.length} platform(s)...
|
|
592
|
+
`);
|
|
593
|
+
const results = [];
|
|
594
|
+
for (const adapter of targetAdapters) {
|
|
595
|
+
try {
|
|
596
|
+
const isInstalled = await adapter.isInstalled(skillId);
|
|
597
|
+
if (isInstalled && !options?.force) {
|
|
598
|
+
console.log(`${adapter.name.padEnd(12)} \u26A0\uFE0F Already installed (use --force to overwrite)`);
|
|
599
|
+
results.push({ name: adapter.name, status: "skipped" });
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
await adapter.install(skillId, skillVersionDir);
|
|
603
|
+
console.log(`${adapter.name.padEnd(12)} \u2705 Installed successfully`);
|
|
604
|
+
results.push({ name: adapter.name, status: "installed" });
|
|
605
|
+
} catch (error) {
|
|
606
|
+
console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${error}`);
|
|
607
|
+
results.push({ name: adapter.name, status: "failed", error: String(error) });
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const installed = results.filter((r) => r.status === "installed").length;
|
|
611
|
+
const skipped = results.filter((r) => r.status === "skipped").length;
|
|
612
|
+
const failed = results.filter((r) => r.status === "failed").length;
|
|
613
|
+
console.log(`
|
|
614
|
+
\u{1F4CA} Summary: ${installed} installed, ${skipped} skipped, ${failed} failed`);
|
|
406
615
|
}
|
|
407
|
-
console.log(`Target platform: ${finalPlatform}`);
|
|
408
616
|
const registry = await loadRegistry();
|
|
409
|
-
const
|
|
410
|
-
const existingPlatforms = existingSkill?.platforms || [];
|
|
617
|
+
const installedPlatforms = targetAdapters.map((a) => a.id);
|
|
411
618
|
registry.skills[skillId] = {
|
|
412
619
|
id: skillId,
|
|
413
620
|
version: targetVersion,
|
|
414
621
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
415
|
-
platforms:
|
|
622
|
+
platforms: installedPlatforms
|
|
416
623
|
};
|
|
417
624
|
await saveRegistry(registry);
|
|
418
|
-
console.log(`Syncing to ${finalPlatform}...`);
|
|
419
|
-
await syncPlatformLinks(finalPlatform);
|
|
420
625
|
console.log(`
|
|
421
626
|
\u2705 ${skillId}@${targetVersion} installed successfully!`);
|
|
422
627
|
console.log(` Use "skm info ${skillId}" for more details`);
|
|
423
628
|
}
|
|
424
629
|
|
|
630
|
+
// src/commands/sync.ts
|
|
631
|
+
import fs8 from "fs-extra";
|
|
632
|
+
import path7 from "path";
|
|
633
|
+
async function syncPlatformLinks() {
|
|
634
|
+
await ensureMarketDirs();
|
|
635
|
+
const skillsDir = getSkillsDir();
|
|
636
|
+
const platformLinksDir = getPlatformLinksDir();
|
|
637
|
+
const registry = await loadRegistry();
|
|
638
|
+
console.log("Syncing platform links...\n");
|
|
639
|
+
for (const platform of PLATFORMS) {
|
|
640
|
+
const platformDir = path7.join(platformLinksDir, platform, "skills");
|
|
641
|
+
await fs8.ensureDir(platformDir);
|
|
642
|
+
for (const [skillId, skillInfo] of Object.entries(registry.skills)) {
|
|
643
|
+
const skillLatestLink = path7.join(skillsDir, skillId, LATEST_LINK);
|
|
644
|
+
const targetPlatformDir = path7.join(skillLatestLink, platform);
|
|
645
|
+
const platformSkillDir = path7.join(platformDir, skillId);
|
|
646
|
+
if (await fs8.pathExists(skillLatestLink)) {
|
|
647
|
+
if (await fs8.pathExists(targetPlatformDir)) {
|
|
648
|
+
try {
|
|
649
|
+
await fs8.remove(platformSkillDir);
|
|
650
|
+
await fs8.symlink(targetPlatformDir, platformSkillDir, "junction");
|
|
651
|
+
console.log(` Linked: ${platform}/${skillId}`);
|
|
652
|
+
} catch {
|
|
653
|
+
await fs8.copy(targetPlatformDir, platformSkillDir, { overwrite: true });
|
|
654
|
+
console.log(` Copied: ${platform}/${skillId}`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
console.log("\n\u2705 Sync complete!");
|
|
661
|
+
}
|
|
662
|
+
|
|
425
663
|
// src/commands/update.ts
|
|
426
664
|
async function updateSkill(skillId) {
|
|
427
665
|
if (skillId) {
|
|
428
|
-
const pkgInfo = await fetchNpmPackage(`@
|
|
666
|
+
const pkgInfo = await fetchNpmPackage(`@itismyskillmarket/${skillId}`);
|
|
429
667
|
if (pkgInfo) {
|
|
430
668
|
const latestVersion = pkgInfo["dist-tags"]?.latest;
|
|
431
669
|
console.log(`Updating ${skillId} to ${latestVersion}...`);
|
|
@@ -442,7 +680,7 @@ async function updateSkill(skillId) {
|
|
|
442
680
|
`);
|
|
443
681
|
let hasUpdates = false;
|
|
444
682
|
for (const skill of installed) {
|
|
445
|
-
const pkgInfo = await fetchNpmPackage(`@
|
|
683
|
+
const pkgInfo = await fetchNpmPackage(`@wanxuchen/${skill.id}`);
|
|
446
684
|
if (pkgInfo) {
|
|
447
685
|
const latestVersion = pkgInfo["dist-tags"]?.latest;
|
|
448
686
|
if (latestVersion && latestVersion !== skill.version) {
|
|
@@ -464,9 +702,9 @@ async function updateSkill(skillId) {
|
|
|
464
702
|
}
|
|
465
703
|
|
|
466
704
|
// src/commands/uninstall.ts
|
|
467
|
-
import
|
|
468
|
-
import
|
|
469
|
-
async function uninstallSkill(skillId) {
|
|
705
|
+
import fs9 from "fs-extra";
|
|
706
|
+
import path8 from "path";
|
|
707
|
+
async function uninstallSkill(skillId, options) {
|
|
470
708
|
const registry = await loadRegistry();
|
|
471
709
|
if (!(skillId in registry.skills)) {
|
|
472
710
|
console.log(`Skill "${skillId}" is not installed.`);
|
|
@@ -474,14 +712,37 @@ async function uninstallSkill(skillId) {
|
|
|
474
712
|
}
|
|
475
713
|
const skillInfo = registry.skills[skillId];
|
|
476
714
|
console.log(`Uninstalling ${skillId}@${skillInfo.version}...`);
|
|
715
|
+
let targetAdapters = [];
|
|
716
|
+
if (options?.platforms && options.platforms.length > 0) {
|
|
717
|
+
for (const platformStr of options.platforms) {
|
|
718
|
+
const platform = platformStr;
|
|
719
|
+
targetAdapters.push(getAdapterByPlatform(platform));
|
|
720
|
+
}
|
|
721
|
+
} else {
|
|
722
|
+
targetAdapters = await detectPlatforms();
|
|
723
|
+
}
|
|
724
|
+
const validAdapters = targetAdapters.filter((a) => a !== void 0);
|
|
725
|
+
if (validAdapters.length > 0) {
|
|
726
|
+
console.log(`
|
|
727
|
+
Uninstalling from ${validAdapters.length} platform(s)...
|
|
728
|
+
`);
|
|
729
|
+
for (const adapter of validAdapters) {
|
|
730
|
+
try {
|
|
731
|
+
await adapter.uninstall(skillId);
|
|
732
|
+
console.log(`${adapter.name.padEnd(12)} \u2705 Uninstalled`);
|
|
733
|
+
} catch (error) {
|
|
734
|
+
console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${error}`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
477
738
|
const skillsDir = getSkillsDir();
|
|
478
|
-
const skillDir =
|
|
479
|
-
await
|
|
739
|
+
const skillDir = path8.join(skillsDir, skillId);
|
|
740
|
+
await fs9.remove(skillDir);
|
|
480
741
|
const platformLinksDir = getPlatformLinksDir();
|
|
481
742
|
for (const platform of PLATFORMS) {
|
|
482
|
-
const linkPath =
|
|
483
|
-
if (await
|
|
484
|
-
await
|
|
743
|
+
const linkPath = path8.join(platformLinksDir, platform, "skills", skillId);
|
|
744
|
+
if (await fs9.pathExists(linkPath)) {
|
|
745
|
+
await fs9.remove(linkPath);
|
|
485
746
|
}
|
|
486
747
|
}
|
|
487
748
|
delete registry.skills[skillId];
|
|
@@ -492,60 +753,79 @@ async function uninstallSkill(skillId) {
|
|
|
492
753
|
|
|
493
754
|
// src/cli.ts
|
|
494
755
|
var program = new Command();
|
|
495
|
-
program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version("1.
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
756
|
+
program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version("1.0.0");
|
|
757
|
+
program.hook("preAction", (thisCommand) => {
|
|
758
|
+
if (thisCommand.opts().help) {
|
|
759
|
+
console.log(`
|
|
499
760
|
SkillMarket CLI
|
|
500
761
|
|
|
501
762
|
Usage: skm <command> [options]
|
|
502
763
|
|
|
503
764
|
Commands:
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
765
|
+
ls [options] List available skills
|
|
766
|
+
--installed Show only installed skills
|
|
767
|
+
--updates Check for updates
|
|
768
|
+
--page <n> Page number (default: 1)
|
|
769
|
+
--limit <n> Items per page (default: 20)
|
|
770
|
+
info <skill-id> Display skill information
|
|
771
|
+
install <skill> Install a skill
|
|
772
|
+
@version Install specific version
|
|
773
|
+
--platform Target platforms (opencode,claude,vscode)
|
|
774
|
+
--force Overwrite if already installed
|
|
775
|
+
uninstall <skill> Remove an installed skill
|
|
776
|
+
--platform Target platforms
|
|
777
|
+
update [options] Update skills
|
|
778
|
+
--all Update all skills
|
|
779
|
+
sync Synchronize platform links
|
|
780
|
+
platforms Show available platforms
|
|
517
781
|
|
|
518
782
|
Examples:
|
|
519
|
-
skm
|
|
520
|
-
skm
|
|
521
|
-
skm --
|
|
522
|
-
skm --
|
|
523
|
-
skm --
|
|
524
|
-
skm
|
|
525
|
-
skm
|
|
526
|
-
|
|
783
|
+
skm ls List all available skills (page 1)
|
|
784
|
+
skm ls --page 2 Go to page 2
|
|
785
|
+
skm ls --limit 10 Show 10 items per page
|
|
786
|
+
skm ls --installed Show installed skills only
|
|
787
|
+
skm ls --installed --page 2
|
|
788
|
+
skm info brainstorming View skill details
|
|
789
|
+
skm install brainstorming Install to all platforms
|
|
790
|
+
skm install brainstorming --platform opencode Install to OpenCode only
|
|
791
|
+
skm install brainstorming --platform claude,vscode Install to multiple
|
|
792
|
+
skm uninstall brainstorming
|
|
793
|
+
skm platforms Show available platforms
|
|
794
|
+
`);
|
|
795
|
+
process.exit(0);
|
|
796
|
+
}
|
|
527
797
|
});
|
|
528
798
|
var lsCmd = program.command("ls").description("List available skills");
|
|
529
|
-
lsCmd.option("--installed", "Show only installed skills").option("--updates", "Check for updates").action((opts) => {
|
|
530
|
-
|
|
799
|
+
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).action((opts) => {
|
|
800
|
+
const options = {
|
|
801
|
+
...opts,
|
|
802
|
+
page: opts.page ?? 1,
|
|
803
|
+
limit: opts.limit ?? 20
|
|
804
|
+
};
|
|
805
|
+
listSkills(options);
|
|
531
806
|
});
|
|
532
807
|
var infoCmd = program.command("info").description("Display skill information");
|
|
533
808
|
infoCmd.argument("<skill-id>", "Skill ID to show info").action((skillId) => {
|
|
534
809
|
showSkillInfo(skillId);
|
|
535
810
|
});
|
|
536
|
-
var installCmd = program.command("install").description("Install a skill");
|
|
537
|
-
installCmd.argument("<skill>", "Skill ID to install (e.g., brainstorming or @scope/name)").option("--
|
|
811
|
+
var installCmd = program.command("install").description("Install a skill to local and platform directories");
|
|
812
|
+
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) => {
|
|
538
813
|
try {
|
|
539
|
-
|
|
814
|
+
const platforms = opts.platform ? opts.platform.split(",").map((p) => p.trim()) : void 0;
|
|
815
|
+
await installSkill(skill, opts.version, {
|
|
816
|
+
platforms,
|
|
817
|
+
force: opts.force
|
|
818
|
+
});
|
|
540
819
|
} catch (err) {
|
|
541
820
|
console.error("Installation failed:", err);
|
|
542
821
|
process.exit(1);
|
|
543
822
|
}
|
|
544
823
|
});
|
|
545
|
-
var uninstallCmd = program.command("uninstall").description("Remove an installed skill");
|
|
546
|
-
uninstallCmd.argument("<skill>", "Skill ID to uninstall").action(async (skill) => {
|
|
824
|
+
var uninstallCmd = program.command("uninstall").description("Remove an installed skill from local and platform directories");
|
|
825
|
+
uninstallCmd.argument("<skill>", "Skill ID to uninstall").option("-p, --platform <platforms>", "Target platforms (comma-separated)").action(async (skill, opts) => {
|
|
547
826
|
try {
|
|
548
|
-
|
|
827
|
+
const platforms = opts.platform ? opts.platform.split(",").map((p) => p.trim()) : void 0;
|
|
828
|
+
await uninstallSkill(skill, { platforms });
|
|
549
829
|
} catch (err) {
|
|
550
830
|
console.error("Uninstall failed:", err);
|
|
551
831
|
process.exit(1);
|
|
@@ -572,8 +852,29 @@ program.command("sync").description("Synchronize platform links").action(async (
|
|
|
572
852
|
process.exit(1);
|
|
573
853
|
}
|
|
574
854
|
});
|
|
575
|
-
var
|
|
576
|
-
|
|
577
|
-
|
|
855
|
+
var platformsCmd = program.command("platforms").description("Show available platforms");
|
|
856
|
+
platformsCmd.action(async () => {
|
|
857
|
+
try {
|
|
858
|
+
const available = await detectPlatforms();
|
|
859
|
+
console.log("\n\u{1F4CD} Available Platforms:\n");
|
|
860
|
+
const allPlatforms = [
|
|
861
|
+
{ name: "OpenCode", adapter: new OpenCodeAdapter() },
|
|
862
|
+
{ name: "Claude Code", adapter: new ClaudeAdapter() },
|
|
863
|
+
{ name: "VSCode", adapter: new VSCodeAdapter() }
|
|
864
|
+
];
|
|
865
|
+
for (const { name, adapter } of allPlatforms) {
|
|
866
|
+
const isAvailable = available.find((a) => a.id === adapter.id);
|
|
867
|
+
const installed = await adapter.listInstalled();
|
|
868
|
+
if (isAvailable) {
|
|
869
|
+
console.log(`${name.padEnd(12)} \u2705 Available (${installed.length} skills installed)`);
|
|
870
|
+
} else {
|
|
871
|
+
console.log(`${name.padEnd(12)} \u274C Not detected`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
console.log("");
|
|
875
|
+
} catch (err) {
|
|
876
|
+
console.error("Failed to list platforms:", err);
|
|
877
|
+
process.exit(1);
|
|
878
|
+
}
|
|
578
879
|
});
|
|
579
880
|
program.parse();
|