itismyskillmarket 1.0.10

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.
@@ -0,0 +1,1031 @@
1
+ # SkillMarket Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers/subagent-driven-development to implement this plan task-by-task.
4
+
5
+ **Goal:** 创建跨平台 npm CLI 工具 `skillmarket`,用于管理 AI 编程工具的 skills。
6
+
7
+ **Architecture:**
8
+ - TypeScript + tsup 构建
9
+ - Commander.js CLI 框架
10
+ - 统一的 skill 目录结构 + 平台软链接适配
11
+ - npm registry 缓存本地管理
12
+
13
+ **Tech Stack:** TypeScript, tsup, commander, fs-extra, semver
14
+
15
+ ---
16
+
17
+ ## Task 1: 项目初始化 (M1)
18
+
19
+ **Files:**
20
+ - Create: `package.json`
21
+ - Create: `tsconfig.json`
22
+ - Create: `tsup.config.ts`
23
+ - Create: `src/index.ts` (入口)
24
+ - Create: `src/cli.ts` (CLI 入口)
25
+ - Create: `src/commands/help.ts`
26
+
27
+ **Step 1: 创建 package.json**
28
+
29
+ ```json
30
+ {
31
+ "name": "skillmarket",
32
+ "version": "1.0.0",
33
+ "description": "Cross-platform skill manager for AI coding tools",
34
+ "type": "module",
35
+ "bin": {
36
+ "skm": "./dist/index.js"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev": "tsup --watch",
41
+ "test": "vitest"
42
+ },
43
+ "dependencies": {
44
+ "commander": "^12.0.0",
45
+ "fs-extra": "^11.2.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/fs-extra": "^11.0.4",
49
+ "tsup": "^8.0.0",
50
+ "typescript": "^5.3.0",
51
+ "vitest": "^1.2.0"
52
+ }
53
+ }
54
+ ```
55
+
56
+ **Step 2: 创建 tsconfig.json**
57
+
58
+ ```json
59
+ {
60
+ "compilerOptions": {
61
+ "target": "ES2022",
62
+ "module": "ESNext",
63
+ "moduleResolution": "bundler",
64
+ "strict": true,
65
+ "outDir": "dist",
66
+ "rootDir": "src"
67
+ }
68
+ }
69
+ ```
70
+
71
+ **Step 3: 创建 tsup.config.ts**
72
+
73
+ ```typescript
74
+ import { defineConfig } from 'tsup';
75
+
76
+ export default defineConfig({
77
+ entry: ['src/index.ts'],
78
+ format: ['esm'],
79
+ dts: true,
80
+ shims: true
81
+ });
82
+ ```
83
+
84
+ **Step 4: 创建 src/index.ts**
85
+
86
+ ```typescript
87
+ #!/usr/bin/env node
88
+ import './cli.js';
89
+ ```
90
+
91
+ **Step 5: 创建 src/cli.ts**
92
+
93
+ ```typescript
94
+ import { Command } from 'commander';
95
+
96
+ const program = new Command();
97
+
98
+ program
99
+ .name('skm')
100
+ .description('SkillMarket - Cross-platform skill manager')
101
+ .version('1.0.0');
102
+
103
+ program.parse();
104
+ ```
105
+
106
+ **Step 6: 提交**
107
+
108
+ ```bash
109
+ git add package.json tsconfig.json tsup.config.ts src/
110
+ git commit -m "chore: project initialization with TypeScript and tsup"
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Task 2: 目录结构和常量定义 (M1)
116
+
117
+ **Files:**
118
+ - Create: `src/constants.ts`
119
+ - Create: `src/types.ts`
120
+ - Create: `src/utils/dirs.ts`
121
+
122
+ **Step 1: 创建 src/constants.ts**
123
+
124
+ ```typescript
125
+ export const MARKET_DIR = 'skillmarket';
126
+
127
+ export const SUBDIRS = {
128
+ CACHE: 'cache',
129
+ SKILLS: 'skills',
130
+ PLATFORM_LINKS: 'platform-links'
131
+ } as const;
132
+
133
+ export const PLATFORMS = [
134
+ 'cursor',
135
+ 'vscode',
136
+ 'codex',
137
+ 'opencode',
138
+ 'claude',
139
+ 'antigravity'
140
+ ] as const;
141
+
142
+ export const REGISTRY_FILE = 'registry.json';
143
+ export const LATEST_LINK = 'latest';
144
+ ```
145
+
146
+ **Step 2: 创建 src/types.ts**
147
+
148
+ ```typescript
149
+ export interface SkillMetadata {
150
+ id: string;
151
+ displayName: string;
152
+ description: string;
153
+ platforms: string[];
154
+ defaultVersion: string;
155
+ }
156
+
157
+ export interface InstalledSkill {
158
+ id: string;
159
+ version: string;
160
+ installedAt: string;
161
+ platforms: string[];
162
+ }
163
+
164
+ export interface RegistryData {
165
+ skills: Record<string, InstalledSkill>;
166
+ lastUpdated: string;
167
+ }
168
+ ```
169
+
170
+ **Step 3: 创建 src/utils/dirs.ts**
171
+
172
+ ```typescript
173
+ import os from 'os';
174
+ import path from 'path';
175
+ import fs from 'fs-extra';
176
+ import { MARKET_DIR, SUBDIRS, REGISTRY_FILE } from '../constants.js';
177
+
178
+ export function getMarketHome(): string {
179
+ return path.join(os.homedir(), '.skillmarket');
180
+ }
181
+
182
+ export function getCacheDir(): string {
183
+ return path.join(getMarketHome(), SUBDIRS.CACHE);
184
+ }
185
+
186
+ export function getSkillsDir(): string {
187
+ return path.join(getMarketHome(), SUBDIRS.SKILLS);
188
+ }
189
+
190
+ export function getPlatformLinksDir(): string {
191
+ return path.join(getMarketHome(), SUBDIRS.PLATFORM_LINKS);
192
+ }
193
+
194
+ export function getRegistryPath(): string {
195
+ return path.join(getMarketHome(), REGISTRY_FILE);
196
+ }
197
+
198
+ export async function ensureMarketDirs(): Promise<void> {
199
+ await fs.ensureDir(getCacheDir());
200
+ await fs.ensureDir(getSkillsDir());
201
+ await fs.ensureDir(getPlatformLinksDir());
202
+ }
203
+ ```
204
+
205
+ **Step 4: 提交**
206
+
207
+ ```bash
208
+ git add src/constants.ts src/types.ts src/utils/dirs.ts
209
+ git commit -m "feat: add directory structure and types"
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Task 3: --help 命令实现 (M1)
215
+
216
+ **Files:**
217
+ - Modify: `src/cli.ts`
218
+
219
+ **Step 1: 更新 src/cli.ts**
220
+
221
+ ```typescript
222
+ import { Command } from 'commander';
223
+ import { PLATFORMS } from './constants.js';
224
+
225
+ const program = new Command();
226
+
227
+ program
228
+ .name('skm')
229
+ .description('SkillMarket - Cross-platform skill manager for AI coding tools')
230
+ .version('1.0.0');
231
+
232
+ // Display help with commands
233
+ program
234
+ .option('-h, --help', 'Display help information')
235
+ .action(() => {
236
+ console.log(`
237
+ SkillMarket CLI
238
+
239
+ Usage: skm <command> [options]
240
+
241
+ Commands:
242
+ --help, -h Display this help message
243
+ --ls [options] List available skills
244
+ --installed Show only installed skills
245
+ --updates Check for updates
246
+ --info <skill-id> Display skill information
247
+ --install <skill> Install a skill (e.g., skm --install brainstorming)
248
+ @version Install specific version
249
+ --all Install all available skills
250
+ --uninstall <skill> Remove an installed skill
251
+ --update [options] Update skills
252
+ --all Update all skills
253
+ --sync Synchronize platform links
254
+ --platform <name> Set target platform (${PLATFORMS.join(', ')})
255
+
256
+ Examples:
257
+ skm --ls List all available skills
258
+ skm --ls --installed Show installed skills only
259
+ skm --info brainstorming View skill details
260
+ skm --install brainstorming Install a skill
261
+ skm --install brainstorming@1.0.0 Install specific version
262
+ skm --update --all Update all installed skills
263
+ `);
264
+ });
265
+
266
+ program.parse();
267
+ ```
268
+
269
+ **Step 2: 提交**
270
+
271
+ ```bash
272
+ git add src/cli.ts
273
+ git commit -m "feat: add help command with usage examples"
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Task 4: --ls 命令基础实现 (M2)
279
+
280
+ **Files:**
281
+ - Create: `src/commands/ls.ts`
282
+ - Create: `src/commands/registry.ts`
283
+
284
+ **Step 1: 创建 src/commands/registry.ts**
285
+
286
+ ```typescript
287
+ import fs from 'fs-extra';
288
+ import path from 'path';
289
+ import { getRegistryPath, getMarketHome } from '../utils/dirs.js';
290
+ import type { RegistryData, InstalledSkill } from '../types.js';
291
+
292
+ const DEFAULT_REGISTRY: RegistryData = {
293
+ skills: {},
294
+ lastUpdated: new Date().toISOString()
295
+ };
296
+
297
+ export async function loadRegistry(): Promise<RegistryData> {
298
+ const registryPath = getRegistryPath();
299
+
300
+ if (!(await fs.pathExists(registryPath))) {
301
+ return DEFAULT_REGISTRY;
302
+ }
303
+
304
+ try {
305
+ const data = await fs.readJson(registryPath);
306
+ return data as RegistryData;
307
+ } catch {
308
+ return DEFAULT_REGISTRY;
309
+ }
310
+ }
311
+
312
+ export async function saveRegistry(registry: RegistryData): Promise<void> {
313
+ await fs.ensureDir(getMarketHome());
314
+ registry.lastUpdated = new Date().toISOString();
315
+ await fs.writeJson(getRegistryPath(), registry, { spaces: 2 });
316
+ }
317
+
318
+ export async function getInstalledSkills(): Promise<InstalledSkill[]> {
319
+ const registry = await loadRegistry();
320
+ return Object.values(registry.skills);
321
+ }
322
+
323
+ export async function isSkillInstalled(skillId: string): Promise<boolean> {
324
+ const registry = await loadRegistry();
325
+ return skillId in registry.skills;
326
+ }
327
+ ```
328
+
329
+ **Step 2: 创建 src/commands/ls.ts**
330
+
331
+ ```typescript
332
+ import { loadRegistry, getInstalledSkills } from './registry.js';
333
+
334
+ interface LsOptions {
335
+ installed?: boolean;
336
+ updates?: boolean;
337
+ }
338
+
339
+ export async function listSkills(options: LsOptions): Promise<void> {
340
+ const { installed, updates } = options;
341
+
342
+ if (installed) {
343
+ const skills = await getInstalledSkills();
344
+
345
+ if (skills.length === 0) {
346
+ console.log('No skills installed yet. Run "skm --ls" to see available skills.');
347
+ return;
348
+ }
349
+
350
+ console.log('Installed Skills:\n');
351
+ for (const skill of skills) {
352
+ console.log(` ${skill.id}@${skill.version}`);
353
+ console.log(` Platforms: ${skill.platforms.join(', ')}`);
354
+ console.log(` Installed: ${skill.installedAt}`);
355
+ console.log();
356
+ }
357
+ return;
358
+ }
359
+
360
+ // TODO: Query npm registry for available skills
361
+ console.log('Available Skills (from npm registry):\n');
362
+ console.log(' Loading...');
363
+
364
+ // Placeholder - will be implemented in M2
365
+ }
366
+ ```
367
+
368
+ **Step 3: 更新 src/cli.ts 集成 --ls 命令**
369
+
370
+ ```typescript
371
+ import { listSkills } from './commands/ls.js';
372
+
373
+ // In program definition, add:
374
+ program
375
+ .option('-ls, --ls', 'List available skills')
376
+ .option('--installed', 'Show only installed skills')
377
+ .option('--updates', 'Check for updates')
378
+ .action((opts) => {
379
+ listSkills(opts);
380
+ });
381
+ ```
382
+
383
+ **Step 4: 提交**
384
+
385
+ ```bash
386
+ git add src/commands/ls.ts src/commands/registry.ts src/cli.ts
387
+ git commit -m "feat: add --ls command with registry support"
388
+ ```
389
+
390
+ ---
391
+
392
+ ## Task 5: npm registry 查询 (M2)
393
+
394
+ **Files:**
395
+ - Create: `src/commands/npm.ts`
396
+
397
+ **Step 1: 创建 src/commands/npm.ts**
398
+
399
+ ```typescript
400
+ import https from 'https';
401
+ import { URL } from 'url';
402
+
403
+ interface NpmPackage {
404
+ name: string;
405
+ version: string;
406
+ description?: string;
407
+ }
408
+
409
+ interface NpmRegistryResponse {
410
+ name: string;
411
+ versions: Record<string, NpmPackage>;
412
+ 'dist-tags': Record<string, string>;
413
+ }
414
+
415
+ export async function fetchNpmPackage(packageName: string): Promise<NpmRegistryResponse | null> {
416
+ return new Promise((resolve, reject) => {
417
+ const url = new URL(`https://registry.npmjs.org/${packageName}`);
418
+
419
+ https.get(url.toString(), { timeout: 10000 }, (res) => {
420
+ let data = '';
421
+
422
+ res.on('data', chunk => { data += chunk; });
423
+ res.on('end', () => {
424
+ try {
425
+ resolve(JSON.parse(data));
426
+ } catch {
427
+ resolve(null);
428
+ }
429
+ });
430
+ }).on('error', reject);
431
+ });
432
+ }
433
+
434
+ export async function searchSkillmarketPackages(): Promise<string[]> {
435
+ const packages: string[] = [];
436
+
437
+ // Search for @skillmarket/* packages
438
+ return new Promise((resolve, reject) => {
439
+ const url = new URL('https://registry.npmjs.org/-/v1/search');
440
+ url.searchParams.set('text', 'keywords:skillmarket');
441
+ url.searchParams.set('size', '100');
442
+
443
+ https.get(url.toString(), { timeout: 10000 }, (res) => {
444
+ let data = '';
445
+
446
+ res.on('data', chunk => { data += chunk; });
447
+ res.on('end', () => {
448
+ try {
449
+ const result = JSON.parse(data);
450
+ for (const pkg of result.objects || []) {
451
+ packages.push(pkg.package.name);
452
+ }
453
+ resolve(packages);
454
+ } catch {
455
+ resolve([]);
456
+ }
457
+ });
458
+ }).on('error', reject);
459
+ });
460
+ }
461
+ ```
462
+
463
+ **Step 2: 更新 ls.ts 使用 npm 查询**
464
+
465
+ ```typescript
466
+ import { searchSkillmarketPackages, fetchNpmPackage } from './npm.js';
467
+
468
+ export async function listSkills(options: LsOptions): Promise<void> {
469
+ const { installed } = options;
470
+
471
+ if (installed) {
472
+ // Show installed skills
473
+ const skills = await getInstalledSkills();
474
+ // ... existing code
475
+ } else {
476
+ // Show available skills from npm
477
+ console.log('Searching npm registry...\n');
478
+ const packages = await searchSkillmarketPackages();
479
+
480
+ if (packages.length === 0) {
481
+ console.log('No skills found. Check back later!');
482
+ return;
483
+ }
484
+
485
+ console.log(`Found ${packages.length} skill(s):\n`);
486
+
487
+ for (const pkgName of packages) {
488
+ const info = await fetchNpmPackage(pkgName);
489
+ if (info) {
490
+ const latestVersion = info['dist-tags']?.latest;
491
+ console.log(` ${info.name}@${latestVersion}`);
492
+ console.log(` ${info.description || 'No description'}`);
493
+ console.log();
494
+ }
495
+ }
496
+ }
497
+ }
498
+ ```
499
+
500
+ **Step 3: 提交**
501
+
502
+ ```bash
503
+ git add src/commands/npm.ts src/commands/ls.ts
504
+ git commit -m "feat: add npm registry query support"
505
+ ```
506
+
507
+ ---
508
+
509
+ ## Task 6: --info 命令实现 (M2)
510
+
511
+ **Files:**
512
+ - Create: `src/commands/info.ts`
513
+
514
+ **Step 1: 创建 src/commands/info.ts**
515
+
516
+ ```typescript
517
+ import { fetchNpmPackage } from './npm.js';
518
+ import { isSkillInstalled, getInstalledSkills } from './registry.js';
519
+
520
+ export async function showSkillInfo(skillId: string): Promise<void> {
521
+ // Try @skillmarket/<skillId> first, then bare name
522
+ const packageName = skillId.startsWith('@')
523
+ ? skillId
524
+ : `@skillmarket/${skillId}`;
525
+
526
+ console.log(`Fetching info for: ${packageName}\n`);
527
+
528
+ const info = await fetchNpmPackage(packageName);
529
+
530
+ if (!info) {
531
+ console.log(`Skill "${skillId}" not found in npm registry.`);
532
+ return;
533
+ }
534
+
535
+ const latestVersion = info['dist-tags']?.latest;
536
+ const pkg = info.versions[latestVersion];
537
+
538
+ // Check installation status
539
+ const installed = await isSkillInstalled(skillId);
540
+ const installedSkills = await getInstalledSkills();
541
+ const installedSkill = installedSkills.find(s => s.id === skillId);
542
+
543
+ console.log(`=== ${info.name} ===`);
544
+ console.log(`Version: ${latestVersion}${installedSkill ? ` (installed: ${installedSkill.version})` : ''}`);
545
+ console.log(`Description: ${pkg.description || 'N/A'}\n`);
546
+
547
+ // Show skillmarket metadata if available
548
+ const skillmarketMeta = (pkg as any).skillmarket;
549
+ if (skillmarketMeta) {
550
+ console.log(`Platforms: ${skillmarketMeta.platforms?.join(', ') || 'N/A'}`);
551
+ console.log(`Default Version: ${skillmarketMeta.defaultVersion || 'N/A'}\n`);
552
+ }
553
+
554
+ // Show available versions
555
+ const versions = Object.keys(info.versions).slice(-10); // Last 10
556
+ console.log(`Recent versions: ${versions.join(', ')}`);
557
+
558
+ if (installed) {
559
+ console.log(`\nStatus: Installed (use skm --update ${skillId} to update)`);
560
+ } else {
561
+ console.log(`\nStatus: Not installed (use skm --install ${skillId} to install)`);
562
+ }
563
+ }
564
+ ```
565
+
566
+ **Step 2: 更新 cli.ts**
567
+
568
+ ```typescript
569
+ import { showSkillInfo } from './commands/info.js';
570
+
571
+ // Add command:
572
+ program
573
+ .argument('<skill-id>', 'Skill ID to show info')
574
+ .action((skillId) => {
575
+ showSkillInfo(skillId);
576
+ });
577
+ ```
578
+
579
+ **Step 3: 提交**
580
+
581
+ ```bash
582
+ git add src/commands/info.ts src/cli.ts
583
+ git commit -m "feat: add --info command for skill details"
584
+ ```
585
+
586
+ ---
587
+
588
+ ## Task 7: --install 命令基础实现 (M3)
589
+
590
+ **Files:**
591
+ - Create: `src/commands/install.ts`
592
+ - Create: `src/utils/platform.ts`
593
+
594
+ **Step 1: 创建 src/utils/platform.ts**
595
+
596
+ ```typescript
597
+ import { PLATFORMS } from '../constants.js';
598
+
599
+ export type Platform = typeof PLATFORMS[number];
600
+
601
+ export function detectPlatform(): Platform {
602
+ if (process.env.OPENCODE) return 'opencode';
603
+ if (process.env.CURSOR) return 'cursor';
604
+ if (process.env.VSCODE) return 'vscode';
605
+ if (process.env.CLAUDE_CODE) return 'claude';
606
+ if (process.env.ANTIGRAVITY) return 'antigravity';
607
+ return 'codex'; // Default fallback
608
+ }
609
+
610
+ export function isValidPlatform(name: string): name is Platform {
611
+ return PLATFORMS.includes(name as Platform);
612
+ }
613
+
614
+ export function getPlatformFromInput(name: string): Platform | null {
615
+ return isValidPlatform(name.toLowerCase()) ? name.toLowerCase() as Platform : null;
616
+ }
617
+ ```
618
+
619
+ **Step 2: 创建 src/commands/install.ts**
620
+
621
+ ```typescript
622
+ import fs from 'fs-extra';
623
+ import path from 'path';
624
+ import { exec } from 'child_process';
625
+ import { promisify } from 'util';
626
+ import { fetchNpmPackage } from './npm.js';
627
+ import { loadRegistry, saveRegistry } from './registry.js';
628
+ import { getCacheDir, getSkillsDir, ensureMarketDirs } from '../utils/dirs.js';
629
+ import { detectPlatform } from '../utils/platform.js';
630
+ import { LATEST_LINK } from '../constants.js';
631
+ import type { InstalledSkill } from '../types.js';
632
+
633
+ const execAsync = promisify(exec);
634
+
635
+ export async function installSkill(
636
+ skillId: string,
637
+ version?: string
638
+ ): Promise<void> {
639
+ await ensureMarketDirs();
640
+
641
+ const packageName = skillId.startsWith('@')
642
+ ? skillId
643
+ : `@skillmarket/${skillId}`;
644
+
645
+ console.log(`Installing ${packageName}${version ? `@${version}` : ''}...`);
646
+
647
+ // 1. Fetch package info
648
+ const pkgInfo = await fetchNpmPackage(packageName);
649
+ if (!pkgInfo) {
650
+ throw new Error(`Package ${packageName} not found`);
651
+ }
652
+
653
+ const targetVersion = version || pkgInfo['dist-tags'].latest;
654
+
655
+ // 2. Download package via npm pack
656
+ const cacheDir = getCacheDir();
657
+ const targetDir = path.join(cacheDir, `${packageName}@${targetVersion}`);
658
+
659
+ if (!(await fs.pathExists(targetDir))) {
660
+ console.log('Downloading package...');
661
+ await fs.ensureDir(cacheDir);
662
+
663
+ try {
664
+ await execAsync(`npm pack ${packageName}@${targetVersion} --pack-destination ${cacheDir}`);
665
+
666
+ // Find and extract the tarball
667
+ const tarball = (await fs.readdir(cacheDir))
668
+ .find(f => f.endsWith('.tgz') && f.includes(packageName.replace('/', '-')));
669
+
670
+ if (tarball) {
671
+ await execAsync(`tar -xzf ${path.join(cacheDir, tarball)} -C ${cacheDir}`);
672
+ await fs.remove(path.join(cacheDir, tarball));
673
+
674
+ // Rename extracted folder
675
+ const extractedDir = path.join(cacheDir, 'package');
676
+ const finalDir = targetDir;
677
+ await fs.move(extractedDir, finalDir, { overwrite: true });
678
+ }
679
+ } catch (err) {
680
+ throw new Error(`Failed to download package: ${err}`);
681
+ }
682
+ }
683
+
684
+ // 3. Extract platform-specific files
685
+ const skillsDir = getSkillsDir();
686
+ const skillVersionDir = path.join(skillsDir, `${skillId}@${targetVersion}`);
687
+
688
+ console.log('Setting up skill...');
689
+ await fs.ensureDir(skillVersionDir);
690
+
691
+ // Copy SKILL.md and metadata if they exist
692
+ const pkgRoot = targetDir;
693
+
694
+ if (await fs.pathExists(path.join(pkgRoot, 'SKILL.md'))) {
695
+ await fs.copy(path.join(pkgRoot, 'SKILL.md'), path.join(skillVersionDir, 'SKILL.md'));
696
+ }
697
+
698
+ if (await fs.pathExists(path.join(pkgRoot, 'metadata.json'))) {
699
+ await fs.copy(path.join(pkgRoot, 'metadata.json'), path.join(skillVersionDir, 'metadata.json'));
700
+ }
701
+
702
+ // 4. Create latest symlink
703
+ const latestLink = path.join(skillsDir, skillId, LATEST_LINK);
704
+ await fs.ensureSymlink(skillVersionDir, latestLink, 'junction');
705
+
706
+ // 5. Update registry
707
+ const registry = await loadRegistry();
708
+ const platforms = detectPlatform(); // For now, just current platform
709
+
710
+ registry.skills[skillId] = {
711
+ id: skillId,
712
+ version: targetVersion,
713
+ installedAt: new Date().toISOString(),
714
+ platforms: [platforms]
715
+ } as InstalledSkill;
716
+
717
+ await saveRegistry(registry);
718
+
719
+ console.log(`\n✅ ${skillId}@${targetVersion} installed successfully!`);
720
+ console.log(` Use "skm --info ${skillId}" for more details`);
721
+ }
722
+ ```
723
+
724
+ **Step 3: 更新 cli.ts**
725
+
726
+ ```typescript
727
+ import { installSkill } from './commands/install.js';
728
+
729
+ // Add command:
730
+ program
731
+ .command('install <skill>')
732
+ .description('Install a skill')
733
+ .option('--all', 'Install all available skills')
734
+ .action(async (skill, options) => {
735
+ try {
736
+ await installSkill(skill);
737
+ } catch (err) {
738
+ console.error('Installation failed:', err);
739
+ process.exit(1);
740
+ }
741
+ });
742
+ ```
743
+
744
+ **Step 4: 提交**
745
+
746
+ ```bash
747
+ git add src/commands/install.ts src/utils/platform.ts src/cli.ts
748
+ git commit -m "feat: add --install command with npm download"
749
+ ```
750
+
751
+ ---
752
+
753
+ ## Task 8: 软链接策略实现 (M3)
754
+
755
+ **Files:**
756
+ - Create: `src/commands/sync.ts`
757
+
758
+ **Step 1: 创建 src/commands/sync.ts**
759
+
760
+ ```typescript
761
+ import fs from 'fs-extra';
762
+ import path from 'path';
763
+ import {
764
+ getSkillsDir,
765
+ getPlatformLinksDir,
766
+ ensureMarketDirs
767
+ } from '../utils/dirs.js';
768
+ import { loadRegistry } from './registry.js';
769
+ import { PLATFORMS, LATEST_LINK } from '../constants.js';
770
+
771
+ export async function syncPlatformLinks(): Promise<void> {
772
+ await ensureMarketDirs();
773
+
774
+ const skillsDir = getSkillsDir();
775
+ const platformLinksDir = getPlatformLinksDir();
776
+ const registry = await loadRegistry();
777
+
778
+ console.log('Syncing platform links...\n');
779
+
780
+ // For each platform
781
+ for (const platform of PLATFORMS) {
782
+ const platformDir = path.join(platformLinksDir, platform, 'skills');
783
+ await fs.ensureDir(platformDir);
784
+
785
+ // For each installed skill
786
+ for (const [skillId, skillInfo] of Object.entries(registry.skills)) {
787
+ const skillLatestLink = path.join(skillsDir, skillId, LATEST_LINK);
788
+ const platformSkillDir = path.join(platformDir, skillId);
789
+
790
+ if (await fs.pathExists(skillLatestLink)) {
791
+ // Create link to platform-specific version inside the skill
792
+ const targetPlatformDir = path.join(skillLatestLink, platform);
793
+
794
+ if (await fs.pathExists(targetPlatformDir)) {
795
+ await fs.ensureSymlink(targetPlatformDir, platformSkillDir, 'junction');
796
+ console.log(` Linked: ${platform}/${skillId}`);
797
+ }
798
+ }
799
+ }
800
+ }
801
+
802
+ console.log('\n✅ Sync complete!');
803
+ }
804
+ ```
805
+
806
+ **Step 2: 更新 cli.ts 添加 --sync**
807
+
808
+ ```typescript
809
+ import { syncPlatformLinks } from './commands/sync.js';
810
+
811
+ program
812
+ .command('sync')
813
+ .description('Synchronize platform links')
814
+ .action(async () => {
815
+ try {
816
+ await syncPlatformLinks();
817
+ } catch (err) {
818
+ console.error('Sync failed:', err);
819
+ process.exit(1);
820
+ }
821
+ });
822
+ ```
823
+
824
+ **Step 3: 提交**
825
+
826
+ ```bash
827
+ git add src/commands/sync.ts src/cli.ts
828
+ git commit -m "feat: add --sync for platform link management"
829
+ ```
830
+
831
+ ---
832
+
833
+ ## Task 9: --update 和 --uninstall 实现 (M4)
834
+
835
+ **Files:**
836
+ - Modify: `src/commands/install.ts`
837
+ - Create: `src/commands/update.ts`
838
+ - Create: `src/commands/uninstall.ts`
839
+
840
+ **Step 1: 创建 src/commands/update.ts**
841
+
842
+ ```typescript
843
+ import { installSkill } from './install.js';
844
+ import { getInstalledSkills, loadRegistry } from './registry.js';
845
+ import { fetchNpmPackage } from './npm.js';
846
+
847
+ export async function updateSkill(skillId?: string): Promise<void> {
848
+ if (skillId) {
849
+ // Update specific skill
850
+ const pkgInfo = await fetchNpmPackage(`@skillmarket/${skillId}`);
851
+ if (pkgInfo) {
852
+ const latestVersion = pkgInfo['dist-tags'].latest;
853
+ console.log(`Updating ${skillId} to ${latestVersion}...`);
854
+ await installSkill(skillId, latestVersion);
855
+ }
856
+ return;
857
+ }
858
+
859
+ // Update all skills
860
+ const installed = await getInstalledSkills();
861
+
862
+ if (installed.length === 0) {
863
+ console.log('No skills installed to update.');
864
+ return;
865
+ }
866
+
867
+ console.log(`Checking updates for ${installed.length} skill(s)...\n`);
868
+
869
+ let hasUpdates = false;
870
+
871
+ for (const skill of installed) {
872
+ const pkgInfo = await fetchNpmPackage(`@skillmarket/${skill.id}`);
873
+ if (pkgInfo) {
874
+ const latestVersion = pkgInfo['dist-tags'].latest;
875
+
876
+ if (latestVersion !== skill.version) {
877
+ console.log(` ${skill.id}: ${skill.version} → ${latestVersion} [UPDATE]`);
878
+ hasUpdates = true;
879
+
880
+ try {
881
+ await installSkill(skill.id, latestVersion);
882
+ } catch (err) {
883
+ console.error(` Failed to update ${skill.id}:`, err);
884
+ }
885
+ } else {
886
+ console.log(` ${skill.id}: ${skill.version} (up to date)`);
887
+ }
888
+ }
889
+ }
890
+
891
+ if (!hasUpdates) {
892
+ console.log('\nAll skills are up to date!');
893
+ }
894
+ }
895
+ ```
896
+
897
+ **Step 2: 创建 src/commands/uninstall.ts**
898
+
899
+ ```typescript
900
+ import fs from 'fs-extra';
901
+ import path from 'path';
902
+ import { loadRegistry, saveRegistry } from './registry.js';
903
+ import { getSkillsDir, getPlatformLinksDir } from '../utils/dirs.js';
904
+ import { PLATFORMS } from '../constants.js';
905
+
906
+ export async function uninstallSkill(skillId: string): Promise<void> {
907
+ const registry = await loadRegistry();
908
+
909
+ if (!(skillId in registry.skills)) {
910
+ console.log(`Skill "${skillId}" is not installed.`);
911
+ return;
912
+ }
913
+
914
+ const skillInfo = registry.skills[skillId];
915
+
916
+ console.log(`Uninstalling ${skillId}@${skillInfo.version}...`);
917
+
918
+ // Remove skill directory
919
+ const skillsDir = getSkillsDir();
920
+ const skillDir = path.join(skillsDir, skillId);
921
+ await fs.remove(skillDir);
922
+
923
+ // Remove from platform links
924
+ const platformLinksDir = getPlatformLinksDir();
925
+ for (const platform of PLATFORMS) {
926
+ const linkPath = path.join(platformLinksDir, platform, 'skills', skillId);
927
+ if (await fs.pathExists(linkPath)) {
928
+ await fs.remove(linkPath);
929
+ }
930
+ }
931
+
932
+ // Update registry
933
+ delete registry.skills[skillId];
934
+ await saveRegistry(registry);
935
+
936
+ console.log(`\n✅ ${skillId} uninstalled successfully!`);
937
+ }
938
+ ```
939
+
940
+ **Step 3: 提交**
941
+
942
+ ```bash
943
+ git add src/commands/update.ts src/commands/uninstall.ts
944
+ git commit -m "feat: add --update and --uninstall commands"
945
+ ```
946
+
947
+ ---
948
+
949
+ ## Task 10: 清理和发布准备
950
+
951
+ **Files:**
952
+ - Create: `README.md`
953
+ - Create: `src/index.ts` (bin entry)
954
+
955
+ **Step 1: 更新 package.json bin 字段**
956
+
957
+ ```json
958
+ {
959
+ "bin": {
960
+ "skm": "./dist/index.js"
961
+ }
962
+ }
963
+ ```
964
+
965
+ **Step 2: 创建 README.md**
966
+
967
+ ```markdown
968
+ # SkillMarket
969
+
970
+ Cross-platform skill manager for AI coding tools (Cursor, VSCode, Codex, OpenCode, Claude Code, Antigravity).
971
+
972
+ ## Installation
973
+
974
+ ```bash
975
+ npm install -g skillmarket
976
+ ```
977
+
978
+ Or use directly:
979
+
980
+ ```bash
981
+ npx skillmarket --help
982
+ ```
983
+
984
+ ## Usage
985
+
986
+ ```bash
987
+ # List available skills
988
+ skm --ls
989
+
990
+ # Show installed skills
991
+ skm --ls --installed
992
+
993
+ # View skill information
994
+ skm --info brainstorming
995
+
996
+ # Install a skill
997
+ skm --install brainstorming
998
+
999
+ # Update all skills
1000
+ skm --update --all
1001
+
1002
+ # Sync platform links
1003
+ skm --sync
1004
+ ```
1005
+
1006
+ ## Development
1007
+
1008
+ ```bash
1009
+ npm install
1010
+ npm run build
1011
+ npm link
1012
+ ```
1013
+ ```
1014
+
1015
+ **Step 3: 提交**
1016
+
1017
+ ```bash
1018
+ git add README.md package.json
1019
+ git commit -m "docs: add README and finalize package.json"
1020
+ git tag v1.0.0
1021
+ git push origin main --tags
1022
+ ```
1023
+
1024
+ ---
1025
+
1026
+ ## 后续任务 (M5-M6)
1027
+
1028
+ - [ ] 测试覆盖
1029
+ - [ ] 完善错误处理
1030
+ - [ ] Windows 平台软链接兼容
1031
+ - [ ] 发布到 npm