itismyskillmarket 1.2.1 → 1.2.3
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 +70 -0
- package/5e51cb7aa8b8e60d49d86f4689f5d4d1.png +0 -0
- package/DEVELOPMENT.md +376 -0
- package/README.md +70 -4
- package/SKILLMARKET-GUIDE.md +277 -0
- package/dist/index.js +436 -162
- 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/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 +100 -49
- package/src/commands/info.ts +4 -15
- package/src/commands/install.ts +90 -48
- package/src/commands/ls.ts +34 -7
- package/src/commands/npm.ts +67 -5
- 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,6 +139,37 @@ async function fetchNpmPackage(packageName) {
|
|
|
137
139
|
});
|
|
138
140
|
});
|
|
139
141
|
}
|
|
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
|
+
}
|
|
140
173
|
async function searchSkillmarketPackages() {
|
|
141
174
|
const packages = [];
|
|
142
175
|
return new Promise((resolve, reject) => {
|
|
@@ -151,9 +184,11 @@ async function searchSkillmarketPackages() {
|
|
|
151
184
|
res.on("end", () => {
|
|
152
185
|
try {
|
|
153
186
|
const result = JSON.parse(data);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
187
|
+
if (result.objects) {
|
|
188
|
+
for (const item of result.objects) {
|
|
189
|
+
if (item?.package?.name) {
|
|
190
|
+
packages.push(item.package.name);
|
|
191
|
+
}
|
|
157
192
|
}
|
|
158
193
|
}
|
|
159
194
|
resolve(packages);
|
|
@@ -198,12 +233,27 @@ async function listSkills(options) {
|
|
|
198
233
|
console.log(`Found ${packages.length} skill(s):
|
|
199
234
|
`);
|
|
200
235
|
for (const pkgName of packages) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
236
|
+
try {
|
|
237
|
+
const info = await fetchNpmPackage(pkgName);
|
|
238
|
+
if (!info) {
|
|
239
|
+
console.log(`\u{1F4E6} ${pkgName} (\u4FE1\u606F\u83B7\u53D6\u5931\u8D25)`);
|
|
240
|
+
console.log();
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const latestVersion = info["dist-tags"]?.latest || "unknown";
|
|
204
244
|
const pkg = info.versions?.[latestVersion];
|
|
205
|
-
|
|
206
|
-
console.log(
|
|
245
|
+
const skillMeta = pkg?.skillmarket;
|
|
246
|
+
console.log(`\u{1F4E6} ${info.name}@${latestVersion}`);
|
|
247
|
+
const displayName = skillMeta?.displayName || info.name;
|
|
248
|
+
console.log(` \u540D\u79F0: ${displayName}`);
|
|
249
|
+
console.log(` \u63CF\u8FF0: ${pkg?.description || "N/A"}`);
|
|
250
|
+
const platforms = skillMeta?.platforms || [];
|
|
251
|
+
console.log(` \u5E73\u53F0: ${platforms.length > 0 ? platforms.join(", ") : "N/A"}`);
|
|
252
|
+
const npmLink = pkg?.links?.npm || `https://www.npmjs.com/package/${info.name}`;
|
|
253
|
+
console.log(` \u94FE\u63A5: ${npmLink}`);
|
|
254
|
+
console.log();
|
|
255
|
+
} catch (e) {
|
|
256
|
+
console.log(`\u{1F4E6} ${pkgName} (\u83B7\u53D6\u5931\u8D25: ${e})`);
|
|
207
257
|
console.log();
|
|
208
258
|
}
|
|
209
259
|
}
|
|
@@ -214,11 +264,10 @@ async function listSkills(options) {
|
|
|
214
264
|
|
|
215
265
|
// src/commands/info.ts
|
|
216
266
|
async function showSkillInfo(skillId) {
|
|
217
|
-
|
|
218
|
-
console.log(`Fetching info for: ${packageName}
|
|
267
|
+
console.log(`Fetching info for: ${skillId}
|
|
219
268
|
`);
|
|
220
269
|
try {
|
|
221
|
-
const info = await
|
|
270
|
+
const info = await fetchSkillPackage(skillId);
|
|
222
271
|
if (!info) {
|
|
223
272
|
console.log(`Skill "${skillId}" not found in npm registry.`);
|
|
224
273
|
return;
|
|
@@ -258,172 +307,344 @@ Status: Not installed (use skm install ${skillId} to install)`);
|
|
|
258
307
|
}
|
|
259
308
|
|
|
260
309
|
// src/commands/install.ts
|
|
261
|
-
import
|
|
262
|
-
import
|
|
310
|
+
import fs7 from "fs-extra";
|
|
311
|
+
import path6 from "path";
|
|
263
312
|
import { exec } from "child_process";
|
|
264
313
|
import { promisify } from "util";
|
|
265
314
|
|
|
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
|
|
315
|
+
// src/adapters/base.ts
|
|
287
316
|
import fs3 from "fs-extra";
|
|
288
317
|
import path2 from "path";
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
318
|
+
var BaseAdapter = class {
|
|
319
|
+
/**
|
|
320
|
+
* Get the path where a specific skill should be installed
|
|
321
|
+
*/
|
|
322
|
+
getSkillPath(skillId) {
|
|
323
|
+
return path2.join(this.skillDir, skillId);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get the path to the SKILL.md file for a skill
|
|
327
|
+
*/
|
|
328
|
+
getSkillFilePath(skillId) {
|
|
329
|
+
return path2.join(this.getSkillPath(skillId), "SKILL.md");
|
|
330
|
+
}
|
|
331
|
+
async isAvailable() {
|
|
332
|
+
try {
|
|
333
|
+
await fs3.ensureDir(this.skillDir);
|
|
334
|
+
return true;
|
|
335
|
+
} catch {
|
|
336
|
+
return false;
|
|
302
337
|
}
|
|
303
|
-
} else {
|
|
304
|
-
platformsToSync = PLATFORMS;
|
|
305
338
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
339
|
+
async isInstalled(skillId) {
|
|
340
|
+
const skillFile = this.getSkillFilePath(skillId);
|
|
341
|
+
return fs3.pathExists(skillFile);
|
|
342
|
+
}
|
|
343
|
+
async install(skillId, sourceDir) {
|
|
344
|
+
const targetDir = this.getSkillPath(skillId);
|
|
345
|
+
const targetFile = this.getSkillFilePath(skillId);
|
|
346
|
+
await fs3.ensureDir(targetDir);
|
|
347
|
+
const sourceFile = path2.join(sourceDir, "SKILL.md");
|
|
348
|
+
if (!await fs3.pathExists(sourceFile)) {
|
|
349
|
+
throw new Error(`SKILL.md not found in ${sourceDir}`);
|
|
350
|
+
}
|
|
351
|
+
await fs3.copy(sourceFile, targetFile, { overwrite: true });
|
|
352
|
+
}
|
|
353
|
+
async uninstall(skillId) {
|
|
354
|
+
const targetDir = this.getSkillPath(skillId);
|
|
355
|
+
if (await fs3.pathExists(targetDir)) {
|
|
356
|
+
await fs3.remove(targetDir);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async listInstalled() {
|
|
360
|
+
if (!await fs3.pathExists(this.skillDir)) {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
const entries = await fs3.readdir(this.skillDir, { withFileTypes: true });
|
|
364
|
+
const skills = [];
|
|
365
|
+
for (const entry of entries) {
|
|
366
|
+
if (entry.isDirectory()) {
|
|
367
|
+
const skillFile = path2.join(this.skillDir, entry.name, "SKILL.md");
|
|
368
|
+
if (await fs3.pathExists(skillFile)) {
|
|
369
|
+
skills.push(entry.name);
|
|
326
370
|
}
|
|
327
371
|
}
|
|
328
372
|
}
|
|
373
|
+
return skills;
|
|
329
374
|
}
|
|
330
|
-
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// src/adapters/opencode.ts
|
|
378
|
+
import path3 from "path";
|
|
379
|
+
import os2 from "os";
|
|
380
|
+
import fs4 from "fs-extra";
|
|
381
|
+
var OpenCodeAdapter = class extends BaseAdapter {
|
|
382
|
+
id = "opencode";
|
|
383
|
+
name = "OpenCode";
|
|
384
|
+
get skillDir() {
|
|
385
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os2.homedir(), ".config", "opencode");
|
|
386
|
+
return path3.join(configDir, "skills");
|
|
387
|
+
}
|
|
388
|
+
async isAvailable() {
|
|
389
|
+
if (process.env.OPENCODE) return true;
|
|
390
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os2.homedir(), ".config", "opencode");
|
|
391
|
+
try {
|
|
392
|
+
await fs4.ensureDir(path3.join(configDir, "skills"));
|
|
393
|
+
return true;
|
|
394
|
+
} catch {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/adapters/claude.ts
|
|
401
|
+
import path4 from "path";
|
|
402
|
+
import os3 from "os";
|
|
403
|
+
import fs5 from "fs-extra";
|
|
404
|
+
var ClaudeAdapter = class extends BaseAdapter {
|
|
405
|
+
id = "claude";
|
|
406
|
+
name = "Claude Code";
|
|
407
|
+
get skillDir() {
|
|
408
|
+
return path4.join(os3.homedir(), ".claude", "skills");
|
|
409
|
+
}
|
|
410
|
+
async isAvailable() {
|
|
411
|
+
if (process.env.CLAUDE_CODE) return true;
|
|
412
|
+
const claudeDir = path4.join(os3.homedir(), ".claude");
|
|
413
|
+
return fs5.pathExists(claudeDir);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// src/adapters/vscode.ts
|
|
418
|
+
import path5 from "path";
|
|
419
|
+
import os4 from "os";
|
|
420
|
+
import fs6 from "fs-extra";
|
|
421
|
+
var VSCodeAdapter = class extends BaseAdapter {
|
|
422
|
+
id = "vscode";
|
|
423
|
+
name = "VSCode";
|
|
424
|
+
get skillDir() {
|
|
425
|
+
return path5.join(os4.homedir(), ".copilot", "skills");
|
|
426
|
+
}
|
|
427
|
+
async isAvailable() {
|
|
428
|
+
const possibleDirs = [
|
|
429
|
+
path5.join(os4.homedir(), ".copilot", "skills"),
|
|
430
|
+
path5.join(os4.homedir(), ".claude", "skills")
|
|
431
|
+
];
|
|
432
|
+
for (const dir of possibleDirs) {
|
|
433
|
+
try {
|
|
434
|
+
await fs6.ensureDir(dir);
|
|
435
|
+
return true;
|
|
436
|
+
} catch {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
async install(skillId, sourceDir) {
|
|
443
|
+
await super.install(skillId, sourceDir);
|
|
444
|
+
const claudeSkillDir = path5.join(os4.homedir(), ".claude", "skills");
|
|
445
|
+
const targetPath = this.getSkillPath(skillId);
|
|
446
|
+
const claudeTargetPath = path5.join(claudeSkillDir, skillId);
|
|
447
|
+
try {
|
|
448
|
+
await fs6.ensureDir(claudeSkillDir);
|
|
449
|
+
await fs6.remove(claudeTargetPath);
|
|
450
|
+
await fs6.symlink(targetPath, claudeTargetPath, "junction");
|
|
451
|
+
} catch {
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// src/adapters/registry.ts
|
|
457
|
+
var adapters = /* @__PURE__ */ new Map();
|
|
458
|
+
function registerAdapters() {
|
|
459
|
+
const opencode = new OpenCodeAdapter();
|
|
460
|
+
const claude = new ClaudeAdapter();
|
|
461
|
+
const vscode = new VSCodeAdapter();
|
|
462
|
+
adapters.set(opencode.id, opencode);
|
|
463
|
+
adapters.set(claude.id, claude);
|
|
464
|
+
adapters.set(vscode.id, vscode);
|
|
465
|
+
}
|
|
466
|
+
registerAdapters();
|
|
467
|
+
async function detectPlatforms() {
|
|
468
|
+
const available = [];
|
|
469
|
+
for (const adapter of adapters.values()) {
|
|
470
|
+
if (await adapter.isAvailable()) {
|
|
471
|
+
available.push(adapter);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return available;
|
|
475
|
+
}
|
|
476
|
+
function getAdapterByPlatform(platform) {
|
|
477
|
+
const idMap = {
|
|
478
|
+
opencode: "opencode",
|
|
479
|
+
claude: "claude",
|
|
480
|
+
vscode: "vscode",
|
|
481
|
+
cursor: "opencode",
|
|
482
|
+
// Cursor uses OpenCode-compatible structure
|
|
483
|
+
codex: "opencode",
|
|
484
|
+
// Codex uses OpenCode-compatible structure
|
|
485
|
+
antigravity: "opencode"
|
|
486
|
+
// Antigravity uses OpenCode-compatible structure
|
|
487
|
+
};
|
|
488
|
+
return adapters.get(idMap[platform]);
|
|
331
489
|
}
|
|
332
490
|
|
|
333
491
|
// src/commands/install.ts
|
|
334
492
|
var execAsync = promisify(exec);
|
|
335
|
-
async function installSkill(skillId, version,
|
|
493
|
+
async function installSkill(skillId, version, options) {
|
|
336
494
|
await ensureMarketDirs();
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const pkgInfo = await fetchNpmPackage(packageName);
|
|
495
|
+
console.log(`Installing ${skillId}${version ? `@${version}` : ""}...`);
|
|
496
|
+
const pkgInfo = await fetchSkillPackage(skillId);
|
|
340
497
|
if (!pkgInfo) {
|
|
341
|
-
throw new Error(`Package ${
|
|
498
|
+
throw new Error(`Package ${skillId} not found`);
|
|
342
499
|
}
|
|
500
|
+
const packageName = pkgInfo.name;
|
|
343
501
|
const targetVersion = version || pkgInfo["dist-tags"]?.latest;
|
|
344
502
|
if (!targetVersion) {
|
|
345
503
|
throw new Error(`No version found for ${packageName}`);
|
|
346
504
|
}
|
|
347
505
|
const cacheDir = getCacheDir();
|
|
348
|
-
const targetDir =
|
|
349
|
-
if (!await
|
|
506
|
+
const targetDir = path6.join(cacheDir, `${packageName}@${targetVersion}`);
|
|
507
|
+
if (!await fs7.pathExists(targetDir)) {
|
|
350
508
|
console.log("Downloading package...");
|
|
351
|
-
await
|
|
509
|
+
await fs7.ensureDir(cacheDir);
|
|
352
510
|
try {
|
|
353
511
|
await execAsync(`npm pack ${packageName}@${targetVersion} --pack-destination ${cacheDir}`);
|
|
354
|
-
const files = await
|
|
512
|
+
const files = await fs7.readdir(cacheDir);
|
|
355
513
|
const tarball = files.find(
|
|
356
514
|
(f) => f.endsWith(".tgz") && f.includes(packageName.replace("/", "-"))
|
|
357
515
|
);
|
|
358
516
|
if (tarball) {
|
|
359
|
-
await execAsync(`tar -xzf "${
|
|
360
|
-
await
|
|
361
|
-
const extractedDir =
|
|
517
|
+
await execAsync(`tar -xzf "${path6.join(cacheDir, tarball)}" -C "${cacheDir}"`);
|
|
518
|
+
await fs7.remove(path6.join(cacheDir, tarball));
|
|
519
|
+
const extractedDir = path6.join(cacheDir, "package");
|
|
362
520
|
const finalDir = targetDir;
|
|
363
|
-
await
|
|
521
|
+
await fs7.move(extractedDir, finalDir, { overwrite: true });
|
|
364
522
|
}
|
|
365
523
|
} catch (err) {
|
|
366
524
|
throw new Error(`Failed to download package: ${err}`);
|
|
367
525
|
}
|
|
368
526
|
}
|
|
369
527
|
const skillsDir = getSkillsDir();
|
|
370
|
-
const skillVersionDir =
|
|
528
|
+
const skillVersionDir = path6.join(skillsDir, `${skillId}@${targetVersion}`);
|
|
371
529
|
console.log("Setting up skill...");
|
|
372
|
-
await
|
|
530
|
+
await fs7.ensureDir(skillVersionDir);
|
|
373
531
|
const pkgRoot = targetDir;
|
|
374
|
-
if (await
|
|
375
|
-
await
|
|
376
|
-
|
|
377
|
-
|
|
532
|
+
if (await fs7.pathExists(path6.join(pkgRoot, "SKILL.md"))) {
|
|
533
|
+
await fs7.copy(
|
|
534
|
+
path6.join(pkgRoot, "SKILL.md"),
|
|
535
|
+
path6.join(skillVersionDir, "SKILL.md")
|
|
378
536
|
);
|
|
379
537
|
}
|
|
380
|
-
if (await
|
|
381
|
-
await
|
|
382
|
-
|
|
383
|
-
|
|
538
|
+
if (await fs7.pathExists(path6.join(pkgRoot, "metadata.json"))) {
|
|
539
|
+
await fs7.copy(
|
|
540
|
+
path6.join(pkgRoot, "metadata.json"),
|
|
541
|
+
path6.join(skillVersionDir, "metadata.json")
|
|
384
542
|
);
|
|
385
543
|
}
|
|
386
|
-
const skillDir =
|
|
387
|
-
await
|
|
388
|
-
const latestLink =
|
|
544
|
+
const skillDir = path6.join(skillsDir, skillId);
|
|
545
|
+
await fs7.ensureDir(skillDir);
|
|
546
|
+
const latestLink = path6.join(skillDir, LATEST_LINK);
|
|
389
547
|
try {
|
|
390
|
-
await
|
|
391
|
-
await
|
|
548
|
+
await fs7.remove(latestLink);
|
|
549
|
+
await fs7.symlink(skillVersionDir, latestLink, "junction");
|
|
392
550
|
} catch {
|
|
393
|
-
await
|
|
551
|
+
await fs7.copy(skillVersionDir, path6.join(skillDir, LATEST_LINK), { overwrite: true });
|
|
394
552
|
}
|
|
395
|
-
let
|
|
396
|
-
if (
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
553
|
+
let targetAdapters = [];
|
|
554
|
+
if (options?.platforms && options.platforms.length > 0) {
|
|
555
|
+
for (const platformStr of options.platforms) {
|
|
556
|
+
const platform = platformStr;
|
|
557
|
+
const adapter = getAdapterByPlatform(platform);
|
|
558
|
+
if (adapter) {
|
|
559
|
+
targetAdapters.push(adapter);
|
|
560
|
+
} else {
|
|
561
|
+
console.warn(`\u26A0\uFE0F Unknown platform: ${platformStr}`);
|
|
562
|
+
}
|
|
400
563
|
}
|
|
401
|
-
finalPlatform = parsed;
|
|
402
564
|
} else {
|
|
403
|
-
|
|
565
|
+
targetAdapters = await detectPlatforms();
|
|
566
|
+
}
|
|
567
|
+
if (targetAdapters.length === 0) {
|
|
568
|
+
console.log("No target platforms detected.");
|
|
569
|
+
console.log("Use --platform to specify platforms manually.");
|
|
570
|
+
} else {
|
|
571
|
+
console.log(`
|
|
572
|
+
Installing to ${targetAdapters.length} platform(s)...
|
|
573
|
+
`);
|
|
574
|
+
const results = [];
|
|
575
|
+
for (const adapter of targetAdapters) {
|
|
576
|
+
try {
|
|
577
|
+
const isInstalled = await adapter.isInstalled(skillId);
|
|
578
|
+
if (isInstalled && !options?.force) {
|
|
579
|
+
console.log(`${adapter.name.padEnd(12)} \u26A0\uFE0F Already installed (use --force to overwrite)`);
|
|
580
|
+
results.push({ name: adapter.name, status: "skipped" });
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
await adapter.install(skillId, skillVersionDir);
|
|
584
|
+
console.log(`${adapter.name.padEnd(12)} \u2705 Installed successfully`);
|
|
585
|
+
results.push({ name: adapter.name, status: "installed" });
|
|
586
|
+
} catch (error) {
|
|
587
|
+
console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${error}`);
|
|
588
|
+
results.push({ name: adapter.name, status: "failed", error: String(error) });
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const installed = results.filter((r) => r.status === "installed").length;
|
|
592
|
+
const skipped = results.filter((r) => r.status === "skipped").length;
|
|
593
|
+
const failed = results.filter((r) => r.status === "failed").length;
|
|
594
|
+
console.log(`
|
|
595
|
+
\u{1F4CA} Summary: ${installed} installed, ${skipped} skipped, ${failed} failed`);
|
|
404
596
|
}
|
|
405
|
-
console.log(`Target platform: ${finalPlatform}`);
|
|
406
597
|
const registry = await loadRegistry();
|
|
407
|
-
const
|
|
408
|
-
const existingPlatforms = existingSkill?.platforms || [];
|
|
598
|
+
const installedPlatforms = targetAdapters.map((a) => a.id);
|
|
409
599
|
registry.skills[skillId] = {
|
|
410
600
|
id: skillId,
|
|
411
601
|
version: targetVersion,
|
|
412
602
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
413
|
-
platforms:
|
|
603
|
+
platforms: installedPlatforms
|
|
414
604
|
};
|
|
415
605
|
await saveRegistry(registry);
|
|
416
|
-
console.log(`Syncing to ${finalPlatform}...`);
|
|
417
|
-
await syncPlatformLinks(finalPlatform);
|
|
418
606
|
console.log(`
|
|
419
607
|
\u2705 ${skillId}@${targetVersion} installed successfully!`);
|
|
420
608
|
console.log(` Use "skm info ${skillId}" for more details`);
|
|
421
609
|
}
|
|
422
610
|
|
|
611
|
+
// src/commands/sync.ts
|
|
612
|
+
import fs8 from "fs-extra";
|
|
613
|
+
import path7 from "path";
|
|
614
|
+
async function syncPlatformLinks() {
|
|
615
|
+
await ensureMarketDirs();
|
|
616
|
+
const skillsDir = getSkillsDir();
|
|
617
|
+
const platformLinksDir = getPlatformLinksDir();
|
|
618
|
+
const registry = await loadRegistry();
|
|
619
|
+
console.log("Syncing platform links...\n");
|
|
620
|
+
for (const platform of PLATFORMS) {
|
|
621
|
+
const platformDir = path7.join(platformLinksDir, platform, "skills");
|
|
622
|
+
await fs8.ensureDir(platformDir);
|
|
623
|
+
for (const [skillId, skillInfo] of Object.entries(registry.skills)) {
|
|
624
|
+
const skillLatestLink = path7.join(skillsDir, skillId, LATEST_LINK);
|
|
625
|
+
const targetPlatformDir = path7.join(skillLatestLink, platform);
|
|
626
|
+
const platformSkillDir = path7.join(platformDir, skillId);
|
|
627
|
+
if (await fs8.pathExists(skillLatestLink)) {
|
|
628
|
+
if (await fs8.pathExists(targetPlatformDir)) {
|
|
629
|
+
try {
|
|
630
|
+
await fs8.remove(platformSkillDir);
|
|
631
|
+
await fs8.symlink(targetPlatformDir, platformSkillDir, "junction");
|
|
632
|
+
console.log(` Linked: ${platform}/${skillId}`);
|
|
633
|
+
} catch {
|
|
634
|
+
await fs8.copy(targetPlatformDir, platformSkillDir, { overwrite: true });
|
|
635
|
+
console.log(` Copied: ${platform}/${skillId}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
console.log("\n\u2705 Sync complete!");
|
|
642
|
+
}
|
|
643
|
+
|
|
423
644
|
// src/commands/update.ts
|
|
424
645
|
async function updateSkill(skillId) {
|
|
425
646
|
if (skillId) {
|
|
426
|
-
const pkgInfo = await fetchNpmPackage(`@
|
|
647
|
+
const pkgInfo = await fetchNpmPackage(`@itismyskillmarket/${skillId}`);
|
|
427
648
|
if (pkgInfo) {
|
|
428
649
|
const latestVersion = pkgInfo["dist-tags"]?.latest;
|
|
429
650
|
console.log(`Updating ${skillId} to ${latestVersion}...`);
|
|
@@ -440,7 +661,7 @@ async function updateSkill(skillId) {
|
|
|
440
661
|
`);
|
|
441
662
|
let hasUpdates = false;
|
|
442
663
|
for (const skill of installed) {
|
|
443
|
-
const pkgInfo = await fetchNpmPackage(`@
|
|
664
|
+
const pkgInfo = await fetchNpmPackage(`@wanxuchen/${skill.id}`);
|
|
444
665
|
if (pkgInfo) {
|
|
445
666
|
const latestVersion = pkgInfo["dist-tags"]?.latest;
|
|
446
667
|
if (latestVersion && latestVersion !== skill.version) {
|
|
@@ -462,9 +683,9 @@ async function updateSkill(skillId) {
|
|
|
462
683
|
}
|
|
463
684
|
|
|
464
685
|
// src/commands/uninstall.ts
|
|
465
|
-
import
|
|
466
|
-
import
|
|
467
|
-
async function uninstallSkill(skillId) {
|
|
686
|
+
import fs9 from "fs-extra";
|
|
687
|
+
import path8 from "path";
|
|
688
|
+
async function uninstallSkill(skillId, options) {
|
|
468
689
|
const registry = await loadRegistry();
|
|
469
690
|
if (!(skillId in registry.skills)) {
|
|
470
691
|
console.log(`Skill "${skillId}" is not installed.`);
|
|
@@ -472,14 +693,37 @@ async function uninstallSkill(skillId) {
|
|
|
472
693
|
}
|
|
473
694
|
const skillInfo = registry.skills[skillId];
|
|
474
695
|
console.log(`Uninstalling ${skillId}@${skillInfo.version}...`);
|
|
696
|
+
let targetAdapters = [];
|
|
697
|
+
if (options?.platforms && options.platforms.length > 0) {
|
|
698
|
+
for (const platformStr of options.platforms) {
|
|
699
|
+
const platform = platformStr;
|
|
700
|
+
targetAdapters.push(getAdapterByPlatform(platform));
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
targetAdapters = await detectPlatforms();
|
|
704
|
+
}
|
|
705
|
+
const validAdapters = targetAdapters.filter((a) => a !== void 0);
|
|
706
|
+
if (validAdapters.length > 0) {
|
|
707
|
+
console.log(`
|
|
708
|
+
Uninstalling from ${validAdapters.length} platform(s)...
|
|
709
|
+
`);
|
|
710
|
+
for (const adapter of validAdapters) {
|
|
711
|
+
try {
|
|
712
|
+
await adapter.uninstall(skillId);
|
|
713
|
+
console.log(`${adapter.name.padEnd(12)} \u2705 Uninstalled`);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${error}`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
475
719
|
const skillsDir = getSkillsDir();
|
|
476
|
-
const skillDir =
|
|
477
|
-
await
|
|
720
|
+
const skillDir = path8.join(skillsDir, skillId);
|
|
721
|
+
await fs9.remove(skillDir);
|
|
478
722
|
const platformLinksDir = getPlatformLinksDir();
|
|
479
723
|
for (const platform of PLATFORMS) {
|
|
480
|
-
const linkPath =
|
|
481
|
-
if (await
|
|
482
|
-
await
|
|
724
|
+
const linkPath = path8.join(platformLinksDir, platform, "skills", skillId);
|
|
725
|
+
if (await fs9.pathExists(linkPath)) {
|
|
726
|
+
await fs9.remove(linkPath);
|
|
483
727
|
}
|
|
484
728
|
}
|
|
485
729
|
delete registry.skills[skillId];
|
|
@@ -490,38 +734,42 @@ async function uninstallSkill(skillId) {
|
|
|
490
734
|
|
|
491
735
|
// src/cli.ts
|
|
492
736
|
var program = new Command();
|
|
493
|
-
program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version("1.
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
737
|
+
program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version("1.0.0");
|
|
738
|
+
program.hook("preAction", (thisCommand) => {
|
|
739
|
+
if (thisCommand.opts().help) {
|
|
740
|
+
console.log(`
|
|
497
741
|
SkillMarket CLI
|
|
498
742
|
|
|
499
743
|
Usage: skm <command> [options]
|
|
500
744
|
|
|
501
745
|
Commands:
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
746
|
+
ls [options] List available skills
|
|
747
|
+
--installed Show only installed skills
|
|
748
|
+
--updates Check for updates
|
|
749
|
+
info <skill-id> Display skill information
|
|
750
|
+
install <skill> Install a skill
|
|
751
|
+
@version Install specific version
|
|
752
|
+
--platform Target platforms (opencode,claude,vscode)
|
|
753
|
+
--force Overwrite if already installed
|
|
754
|
+
uninstall <skill> Remove an installed skill
|
|
755
|
+
--platform Target platforms
|
|
756
|
+
update [options] Update skills
|
|
757
|
+
--all Update all skills
|
|
758
|
+
sync Synchronize platform links
|
|
759
|
+
platforms Show available platforms
|
|
515
760
|
|
|
516
761
|
Examples:
|
|
517
|
-
skm
|
|
518
|
-
skm
|
|
519
|
-
skm
|
|
520
|
-
skm
|
|
521
|
-
skm
|
|
522
|
-
skm
|
|
523
|
-
skm
|
|
524
|
-
|
|
762
|
+
skm ls List all available skills
|
|
763
|
+
skm ls --installed Show installed skills only
|
|
764
|
+
skm info brainstorming View skill details
|
|
765
|
+
skm install brainstorming Install to all platforms
|
|
766
|
+
skm install brainstorming --platform opencode Install to OpenCode only
|
|
767
|
+
skm install brainstorming --platform claude,vscode Install to multiple
|
|
768
|
+
skm uninstall brainstorming
|
|
769
|
+
skm platforms Show available platforms
|
|
770
|
+
`);
|
|
771
|
+
process.exit(0);
|
|
772
|
+
}
|
|
525
773
|
});
|
|
526
774
|
var lsCmd = program.command("ls").description("List available skills");
|
|
527
775
|
lsCmd.option("--installed", "Show only installed skills").option("--updates", "Check for updates").action((opts) => {
|
|
@@ -531,19 +779,24 @@ var infoCmd = program.command("info").description("Display skill information");
|
|
|
531
779
|
infoCmd.argument("<skill-id>", "Skill ID to show info").action((skillId) => {
|
|
532
780
|
showSkillInfo(skillId);
|
|
533
781
|
});
|
|
534
|
-
var installCmd = program.command("install").description("Install a skill");
|
|
535
|
-
installCmd.argument("<skill>", "Skill ID to install (e.g., brainstorming or @scope/name)").option("--
|
|
782
|
+
var installCmd = program.command("install").description("Install a skill to local and platform directories");
|
|
783
|
+
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) => {
|
|
536
784
|
try {
|
|
537
|
-
|
|
785
|
+
const platforms = opts.platform ? opts.platform.split(",").map((p) => p.trim()) : void 0;
|
|
786
|
+
await installSkill(skill, opts.version, {
|
|
787
|
+
platforms,
|
|
788
|
+
force: opts.force
|
|
789
|
+
});
|
|
538
790
|
} catch (err) {
|
|
539
791
|
console.error("Installation failed:", err);
|
|
540
792
|
process.exit(1);
|
|
541
793
|
}
|
|
542
794
|
});
|
|
543
|
-
var uninstallCmd = program.command("uninstall").description("Remove an installed skill");
|
|
544
|
-
uninstallCmd.argument("<skill>", "Skill ID to uninstall").action(async (skill) => {
|
|
795
|
+
var uninstallCmd = program.command("uninstall").description("Remove an installed skill from local and platform directories");
|
|
796
|
+
uninstallCmd.argument("<skill>", "Skill ID to uninstall").option("-p, --platform <platforms>", "Target platforms (comma-separated)").action(async (skill, opts) => {
|
|
545
797
|
try {
|
|
546
|
-
|
|
798
|
+
const platforms = opts.platform ? opts.platform.split(",").map((p) => p.trim()) : void 0;
|
|
799
|
+
await uninstallSkill(skill, { platforms });
|
|
547
800
|
} catch (err) {
|
|
548
801
|
console.error("Uninstall failed:", err);
|
|
549
802
|
process.exit(1);
|
|
@@ -570,8 +823,29 @@ program.command("sync").description("Synchronize platform links").action(async (
|
|
|
570
823
|
process.exit(1);
|
|
571
824
|
}
|
|
572
825
|
});
|
|
573
|
-
var
|
|
574
|
-
|
|
575
|
-
|
|
826
|
+
var platformsCmd = program.command("platforms").description("Show available platforms");
|
|
827
|
+
platformsCmd.action(async () => {
|
|
828
|
+
try {
|
|
829
|
+
const available = await detectPlatforms();
|
|
830
|
+
console.log("\n\u{1F4CD} Available Platforms:\n");
|
|
831
|
+
const allPlatforms = [
|
|
832
|
+
{ name: "OpenCode", adapter: new OpenCodeAdapter() },
|
|
833
|
+
{ name: "Claude Code", adapter: new ClaudeAdapter() },
|
|
834
|
+
{ name: "VSCode", adapter: new VSCodeAdapter() }
|
|
835
|
+
];
|
|
836
|
+
for (const { name, adapter } of allPlatforms) {
|
|
837
|
+
const isAvailable = available.find((a) => a.id === adapter.id);
|
|
838
|
+
const installed = await adapter.listInstalled();
|
|
839
|
+
if (isAvailable) {
|
|
840
|
+
console.log(`${name.padEnd(12)} \u2705 Available (${installed.length} skills installed)`);
|
|
841
|
+
} else {
|
|
842
|
+
console.log(`${name.padEnd(12)} \u274C Not detected`);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
console.log("");
|
|
846
|
+
} catch (err) {
|
|
847
|
+
console.error("Failed to list platforms:", err);
|
|
848
|
+
process.exit(1);
|
|
849
|
+
}
|
|
576
850
|
});
|
|
577
851
|
program.parse();
|