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.
- 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 +87 -0
- package/SKILLMARKET-GUIDE.md +277 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +561 -0
- package/docs/plans/2026-04-01-skillmarket-design.md +267 -0
- package/docs/plans/2026-04-01-skillmarket-implementation.md +1031 -0
- package/package.json +24 -0
- 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/cli.ts +300 -0
- package/src/commands/info.ts +154 -0
- package/src/commands/install.ts +237 -0
- package/src/commands/ls.ts +169 -0
- package/src/commands/npm.ts +261 -0
- package/src/commands/registry.ts +159 -0
- package/src/commands/sync.ts +137 -0
- package/src/commands/uninstall.ts +102 -0
- package/src/commands/update.ts +113 -0
- package/src/constants.ts +126 -0
- package/src/index.ts +62 -0
- package/src/types.ts +137 -0
- package/src/utils/dirs.ts +166 -0
- package/src/utils/platform.ts +139 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +22 -0
- package/wanxuchen-skillmarket-1.0.1.tgz +0 -0
|
@@ -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
|