itismyskillmarket 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish-npm.yml +59 -0
- package/.github/workflows/publish-skill.yml +72 -0
- package/5e51cb7aa8b8e60d49d86f4689f5d4d1.png +0 -0
- package/CHANGELOG.md +410 -0
- package/DEVELOPMENT.md +376 -0
- package/README.md +75 -6
- package/SKILLMARKET-GUIDE.md +288 -0
- package/dist/index.js +733 -212
- package/docs/WEEKLY-UPDATE-2026-04-23.md +43 -0
- package/docs/plans/2026-04-01-skillmarket-design.md +267 -0
- package/docs/plans/2026-04-01-skillmarket-implementation.md +1031 -0
- package/docs/plans/2026-04-15-cross-platform-adapter-design.md +416 -0
- package/docs/plans/2026-04-15-cross-platform-adapter-plan.md +833 -0
- package/docs/plans/2026-04-16-keyword-search-design.md +143 -0
- package/docs/plans/2026-04-29-weekly-update.md +57 -0
- package/package.json +1 -6
- package/skills/README.md +54 -0
- package/skills/test-skill/SKILL.md +25 -0
- package/skills/test-skill/index.js +66 -0
- package/skills/test-skill/metadata.json +9 -0
- package/skills/test-skill/package.json +19 -0
- package/skills/test-skill-1/SKILL.md +24 -0
- package/skills/test-skill-1/index.js +13 -0
- package/skills/test-skill-1/metadata.json +9 -0
- package/skills/test-skill-1/package.json +16 -0
- package/skills/test-skill-2/SKILL.md +25 -0
- package/skills/test-skill-2/index.js +13 -0
- package/skills/test-skill-2/metadata.json +9 -0
- package/skills/test-skill-2/package.json +16 -0
- package/src/adapters/base.ts +87 -0
- package/src/adapters/claude.ts +31 -0
- package/src/adapters/index.ts +9 -0
- package/src/adapters/opencode.ts +40 -0
- package/src/adapters/registry.ts +77 -0
- package/src/adapters/vscode.ts +62 -0
- package/src/cli.ts +189 -75
- package/src/commands/info.ts +4 -15
- package/src/commands/install.ts +93 -54
- package/src/commands/ls.ts +182 -17
- package/src/commands/npm.ts +118 -16
- package/src/commands/search.ts +12 -7
- package/src/commands/sync.ts +6 -27
- package/src/commands/uninstall.ts +313 -15
- package/src/commands/update.ts +2 -2
- package/src/index.ts +27 -0
- package/src/types.ts +35 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +22 -0
- package/wanxuchen-skillmarket-1.0.1.tgz +0 -0
package/src/commands/install.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* SkillMarket 安装命令模块
|
|
4
4
|
* =============================================================================
|
|
5
5
|
*
|
|
6
|
-
* 本模块实现 `skm install` 命令,用于安装 skill
|
|
6
|
+
* 本模块实现 `skm install` 命令,用于安装 skill 到本地和跨平台目录。
|
|
7
7
|
*
|
|
8
8
|
* 安装流程:
|
|
9
9
|
* 1. 确保目录结构存在
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* 3. 下载包到缓存
|
|
12
12
|
* 4. 解压并复制到 skills 目录
|
|
13
13
|
* 5. 创建 latest 软链接
|
|
14
|
-
* 6.
|
|
14
|
+
* 6. 安装到目标平台(OpenCode/Claude Code/VSCode)
|
|
15
|
+
* 7. 更新本地注册表
|
|
15
16
|
*
|
|
16
17
|
* 安装后的目录结构:
|
|
17
18
|
* ~/.skillmarket/
|
|
@@ -23,6 +24,11 @@
|
|
|
23
24
|
* │ └── metadata.json
|
|
24
25
|
* └── ...
|
|
25
26
|
*
|
|
27
|
+
* 跨平台安装:
|
|
28
|
+
* - OpenCode: ~/.config/opencode/skills/<skillId>/SKILL.md
|
|
29
|
+
* - Claude Code: ~/.claude/skills/<skillId>/SKILL.md
|
|
30
|
+
* - VSCode: ~/.copilot/skills/<skillId>/SKILL.md
|
|
31
|
+
*
|
|
26
32
|
* @module commands/install
|
|
27
33
|
*/
|
|
28
34
|
|
|
@@ -36,17 +42,29 @@ import { exec } from 'child_process'; // 执行 shell 命令
|
|
|
36
42
|
import { promisify } from 'util'; // Promise 化工具
|
|
37
43
|
|
|
38
44
|
// 模块导入
|
|
39
|
-
import {
|
|
45
|
+
import { fetchSkillPackage } from './npm.js'; // npm 查询
|
|
40
46
|
import { loadRegistry, saveRegistry } from './registry.js'; // 注册表操作
|
|
41
47
|
import { getCacheDir, getSkillsDir, ensureMarketDirs } from '../utils/dirs.js'; // 目录工具
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import { LATEST_LINK, PLATFORMS } from '../constants.js'; // 常量
|
|
48
|
+
import { detectPlatforms, getAdapterByPlatform } from '../adapters/index.js'; // 平台适配器
|
|
49
|
+
import { LATEST_LINK } from '../constants.js'; // 常量
|
|
45
50
|
import type { InstalledSkill } from '../types.js'; // 类型定义
|
|
51
|
+
import type { Platform } from '../constants.js';
|
|
52
|
+
import type { PlatformAdapter } from '../types.js';
|
|
46
53
|
|
|
47
54
|
// 将 exec 转为 Promise 形式
|
|
48
55
|
const execAsync = promisify(exec);
|
|
49
56
|
|
|
57
|
+
// -----------------------------------------------------------------------------
|
|
58
|
+
// 安装选项接口
|
|
59
|
+
// -----------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
export interface InstallOptions {
|
|
62
|
+
/** 目标平台列表(留空则安装到所有可用平台) */
|
|
63
|
+
platforms?: string[];
|
|
64
|
+
/** 强制覆盖已安装的 skill */
|
|
65
|
+
force?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
// -----------------------------------------------------------------------------
|
|
51
69
|
// 安装函数
|
|
52
70
|
// -----------------------------------------------------------------------------
|
|
@@ -56,7 +74,7 @@ const execAsync = promisify(exec);
|
|
|
56
74
|
*
|
|
57
75
|
* @param {string} skillId - Skill 标识符(支持短格式或 scoped 格式)
|
|
58
76
|
* @param {string} [version] - 指定版本号(可选,不指定则安装最新版本)
|
|
59
|
-
* @param {
|
|
77
|
+
* @param {InstallOptions} [options] - 安装选项
|
|
60
78
|
* @returns {Promise<void>}
|
|
61
79
|
*
|
|
62
80
|
* @example
|
|
@@ -66,44 +84,39 @@ const execAsync = promisify(exec);
|
|
|
66
84
|
* // 安装指定版本
|
|
67
85
|
* await installSkill('brainstorming', '1.0.0');
|
|
68
86
|
*
|
|
69
|
-
* //
|
|
70
|
-
* await installSkill('brainstorming', undefined, 'opencode');
|
|
87
|
+
* // 安装到特定平台
|
|
88
|
+
* await installSkill('brainstorming', undefined, { platforms: ['opencode'] });
|
|
71
89
|
*
|
|
72
|
-
* //
|
|
73
|
-
* await installSkill('
|
|
90
|
+
* // 强制覆盖
|
|
91
|
+
* await installSkill('brainstorming', undefined, { force: true });
|
|
74
92
|
*/
|
|
75
93
|
export async function installSkill(
|
|
76
94
|
skillId: string,
|
|
77
95
|
version?: string,
|
|
78
|
-
|
|
96
|
+
options?: InstallOptions
|
|
79
97
|
): Promise<void> {
|
|
80
98
|
// ==========================================================================
|
|
81
99
|
// 步骤 0: 准备
|
|
82
100
|
// ==========================================================================
|
|
83
101
|
|
|
84
|
-
// 确保所有必要的目录都已创建
|
|
102
|
+
// 确保所有必要的目录都已创建
|
|
85
103
|
await ensureMarketDirs();
|
|
86
|
-
|
|
87
|
-
// 转换包名格式
|
|
88
|
-
// 用户可以直接指定 @scope/package 或不带前缀的 short name
|
|
89
|
-
// short name 会被添加 @skillmarket/ 前缀尝试
|
|
90
|
-
// 如果是 scoped 包 (@scope/name),直接使用
|
|
91
|
-
const isScoped = skillId.startsWith('@');
|
|
92
|
-
const packageName = isScoped ? skillId : `@skillmarket/${skillId}`;
|
|
93
|
-
const shortName = skillId; // 用于本地目录名
|
|
94
104
|
|
|
95
|
-
console.log(`Installing ${
|
|
105
|
+
console.log(`Installing ${skillId}${version ? `@${version}` : ''}...`);
|
|
96
106
|
|
|
97
107
|
// ==========================================================================
|
|
98
108
|
// 步骤 1: 获取包信息
|
|
99
109
|
// ==========================================================================
|
|
100
110
|
|
|
101
|
-
// 从 npm
|
|
102
|
-
const pkgInfo = await
|
|
111
|
+
// 从 npm 查询包的元信息(自动尝试多个可能的 scope)
|
|
112
|
+
const pkgInfo = await fetchSkillPackage(skillId);
|
|
103
113
|
if (!pkgInfo) {
|
|
104
|
-
throw new Error(`Package ${
|
|
114
|
+
throw new Error(`Package ${skillId} not found`);
|
|
105
115
|
}
|
|
106
116
|
|
|
117
|
+
// 获取实际找到的包名
|
|
118
|
+
const packageName = pkgInfo.name;
|
|
119
|
+
|
|
107
120
|
// 确定要安装的版本(用户指定版本 > 最新版本)
|
|
108
121
|
const targetVersion = version || pkgInfo['dist-tags']?.latest;
|
|
109
122
|
if (!targetVersion) {
|
|
@@ -134,10 +147,10 @@ export async function installSkill(
|
|
|
134
147
|
const files = await fs.readdir(cacheDir);
|
|
135
148
|
|
|
136
149
|
// npm pack 生成的文件名格式: <package-name>-<version>.tgz
|
|
137
|
-
// scoped 包格式: @scope-package-name-<version>.tgz
|
|
150
|
+
// scoped 包格式: @scope-package-name-<version>.tgz (注意:@ 不在文件名中)
|
|
138
151
|
const tarball = files.find(f =>
|
|
139
152
|
f.endsWith('.tgz') &&
|
|
140
|
-
f.includes(packageName.replace('/', '-'))
|
|
153
|
+
f.includes(packageName.replace(/^@/, '').replace('/', '-'))
|
|
141
154
|
);
|
|
142
155
|
|
|
143
156
|
if (tarball) {
|
|
@@ -214,56 +227,82 @@ export async function installSkill(
|
|
|
214
227
|
}
|
|
215
228
|
|
|
216
229
|
// ==========================================================================
|
|
217
|
-
// 步骤 5:
|
|
230
|
+
// 步骤 5: 安装到目标平台 (NEW)
|
|
218
231
|
// ==========================================================================
|
|
219
232
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
233
|
+
let targetAdapters: PlatformAdapter[] = [];
|
|
234
|
+
|
|
235
|
+
if (options?.platforms && options.platforms.length > 0) {
|
|
236
|
+
// 用户指定了平台
|
|
237
|
+
for (const platformStr of options.platforms) {
|
|
238
|
+
const platform = platformStr as Platform;
|
|
239
|
+
const adapter = getAdapterByPlatform(platform);
|
|
240
|
+
if (adapter) {
|
|
241
|
+
targetAdapters.push(adapter);
|
|
242
|
+
} else {
|
|
243
|
+
console.warn(`⚠️ Unknown platform: ${platformStr}`);
|
|
244
|
+
}
|
|
228
245
|
}
|
|
229
|
-
finalPlatform = parsed;
|
|
230
246
|
} else {
|
|
231
|
-
//
|
|
232
|
-
|
|
247
|
+
// 自动检测可用平台
|
|
248
|
+
targetAdapters = await detectPlatforms();
|
|
233
249
|
}
|
|
234
250
|
|
|
235
|
-
|
|
251
|
+
if (targetAdapters.length === 0) {
|
|
252
|
+
console.log('No target platforms detected.');
|
|
253
|
+
console.log('Use --platform to specify platforms manually.');
|
|
254
|
+
} else {
|
|
255
|
+
console.log(`\nInstalling to ${targetAdapters.length} platform(s)...\n`);
|
|
256
|
+
|
|
257
|
+
// 安装到每个平台
|
|
258
|
+
const results: { name: string; status: 'installed' | 'skipped' | 'failed'; error?: string }[] = [];
|
|
259
|
+
|
|
260
|
+
for (const adapter of targetAdapters) {
|
|
261
|
+
try {
|
|
262
|
+
const isInstalled = await adapter.isInstalled(skillId);
|
|
263
|
+
|
|
264
|
+
if (isInstalled && !options?.force) {
|
|
265
|
+
console.log(`${adapter.name.padEnd(12)} ⚠️ Already installed (use --force to overwrite)`);
|
|
266
|
+
results.push({ name: adapter.name, status: 'skipped' });
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 安装 skill 到平台目录
|
|
271
|
+
await adapter.install(skillId, skillVersionDir);
|
|
272
|
+
console.log(`${adapter.name.padEnd(12)} ✅ Installed successfully`);
|
|
273
|
+
results.push({ name: adapter.name, status: 'installed' });
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.log(`${adapter.name.padEnd(12)} ❌ Failed: ${error}`);
|
|
276
|
+
results.push({ name: adapter.name, status: 'failed', error: String(error) });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 显示摘要
|
|
281
|
+
const installed = results.filter(r => r.status === 'installed').length;
|
|
282
|
+
const skipped = results.filter(r => r.status === 'skipped').length;
|
|
283
|
+
const failed = results.filter(r => r.status === 'failed').length;
|
|
284
|
+
|
|
285
|
+
console.log(`\n📊 Summary: ${installed} installed, ${skipped} skipped, ${failed} failed`);
|
|
286
|
+
}
|
|
236
287
|
|
|
237
288
|
// ==========================================================================
|
|
238
289
|
// 步骤 6: 更新注册表
|
|
239
290
|
// ==========================================================================
|
|
240
291
|
|
|
241
292
|
const registry = await loadRegistry();
|
|
242
|
-
|
|
243
|
-
// 获取现有平台列表(如果 skill 已安装)
|
|
244
|
-
const existingSkill = registry.skills[skillId];
|
|
245
|
-
const existingPlatforms = existingSkill?.platforms || [];
|
|
293
|
+
const installedPlatforms = targetAdapters.map(a => a.id);
|
|
246
294
|
|
|
247
295
|
// 添加/更新注册表中的 skill 记录
|
|
248
296
|
registry.skills[skillId] = {
|
|
249
297
|
id: skillId,
|
|
250
298
|
version: targetVersion,
|
|
251
299
|
installedAt: new Date().toISOString(),
|
|
252
|
-
platforms:
|
|
253
|
-
? existingPlatforms
|
|
254
|
-
: [...existingPlatforms, finalPlatform as string]
|
|
300
|
+
platforms: installedPlatforms
|
|
255
301
|
} as InstalledSkill;
|
|
256
302
|
|
|
257
303
|
// 保存注册表
|
|
258
304
|
await saveRegistry(registry);
|
|
259
305
|
|
|
260
|
-
// ==========================================================================
|
|
261
|
-
// 步骤 7: 同步到指定平台
|
|
262
|
-
// ==========================================================================
|
|
263
|
-
|
|
264
|
-
console.log(`Syncing to ${finalPlatform}...`);
|
|
265
|
-
await syncPlatformLinks(finalPlatform);
|
|
266
|
-
|
|
267
306
|
// ==========================================================================
|
|
268
307
|
// 完成
|
|
269
308
|
// ==========================================================================
|
package/src/commands/ls.ts
CHANGED
|
@@ -37,6 +37,15 @@ interface LsOptions {
|
|
|
37
37
|
|
|
38
38
|
/** 检查更新(预留功能) */
|
|
39
39
|
updates?: boolean;
|
|
40
|
+
|
|
41
|
+
/** 页码(从 1 开始) */
|
|
42
|
+
page?: number;
|
|
43
|
+
|
|
44
|
+
/** 每页数量 */
|
|
45
|
+
limit?: number;
|
|
46
|
+
|
|
47
|
+
/** 搜索关键字(支持 id, displayName, description) */
|
|
48
|
+
search?: string;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
// -----------------------------------------------------------------------------
|
|
@@ -59,26 +68,127 @@ interface LsOptions {
|
|
|
59
68
|
* // 列出已安装的 skills
|
|
60
69
|
* await listSkills({ installed: true });
|
|
61
70
|
*/
|
|
71
|
+
// -----------------------------------------------------------------------------
|
|
72
|
+
// 搜索过滤函数
|
|
73
|
+
// -----------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 过滤已安装的 skills(按关键字)
|
|
77
|
+
*
|
|
78
|
+
* 匹配 id, displayName, description 字段
|
|
79
|
+
*
|
|
80
|
+
* @param skills - 已安装的 skills 列表
|
|
81
|
+
* @param keyword - 搜索关键字
|
|
82
|
+
* @returns 过滤后的 skills 列表
|
|
83
|
+
*/
|
|
84
|
+
function filterInstalledSkills(skills: any[], keyword: string): any[] {
|
|
85
|
+
const lower = keyword.toLowerCase();
|
|
86
|
+
return skills.filter(s =>
|
|
87
|
+
s.id.toLowerCase().includes(lower) ||
|
|
88
|
+
(s.displayName && s.displayName.toLowerCase().includes(lower)) ||
|
|
89
|
+
(s.description && s.description.toLowerCase().includes(lower))
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// -----------------------------------------------------------------------------
|
|
94
|
+
// 命令实现
|
|
95
|
+
// -----------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 独立搜索 skills(不依赖 ls 命令选项)
|
|
99
|
+
*
|
|
100
|
+
* @param keyword - 搜索关键字
|
|
101
|
+
* @param limit - 返回结果数量限制
|
|
102
|
+
* @returns 搜索结果列表
|
|
103
|
+
*/
|
|
104
|
+
export async function searchSkills(keyword: string, limit: number = 20): Promise<void> {
|
|
105
|
+
console.log(`Searching npm for "${keyword}"...\n`);
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const { packages, total } = await searchSkillmarketPackages({
|
|
109
|
+
from: 0,
|
|
110
|
+
size: limit,
|
|
111
|
+
keyword
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (packages.length === 0) {
|
|
115
|
+
console.log(`No skills found matching "${keyword}".`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(`Found ${total} match(es) for "${keyword}":\n`);
|
|
120
|
+
|
|
121
|
+
for (const pkgName of packages) {
|
|
122
|
+
try {
|
|
123
|
+
const info = await fetchNpmPackage(pkgName);
|
|
124
|
+
|
|
125
|
+
if (!info) {
|
|
126
|
+
console.log(`📦 ${pkgName} (信息获取失败)`);
|
|
127
|
+
console.log();
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const latestVersion = info['dist-tags']?.latest || 'unknown';
|
|
132
|
+
const pkg = info.versions?.[latestVersion];
|
|
133
|
+
const skillMeta = pkg?.skillmarket;
|
|
134
|
+
|
|
135
|
+
console.log(`📦 ${info.name}@${latestVersion}`);
|
|
136
|
+
console.log(` 名称: ${skillMeta?.displayName || info.name}`);
|
|
137
|
+
console.log(` 描述: ${pkg?.description || 'N/A'}`);
|
|
138
|
+
console.log(` 平台: ${(skillMeta?.platforms || []).join(', ') || 'N/A'}`);
|
|
139
|
+
console.log();
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.log(`📦 ${pkgName} (获取失败: ${e})`);
|
|
142
|
+
console.log();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.log(`Error searching skills: ${error}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
62
150
|
export async function listSkills(options: LsOptions): Promise<void> {
|
|
63
|
-
const { installed, updates } = options;
|
|
151
|
+
const { installed, updates, page = 1, limit = 20, search } = options;
|
|
64
152
|
|
|
65
153
|
// -------------------------------------------------------------------------
|
|
66
154
|
// 模式1: 显示已安装的 skills
|
|
67
155
|
// -------------------------------------------------------------------------
|
|
68
156
|
if (installed) {
|
|
69
|
-
|
|
157
|
+
let skills = await getInstalledSkills();
|
|
158
|
+
|
|
159
|
+
// 搜索关键字过滤(仅本地)
|
|
160
|
+
if (search) {
|
|
161
|
+
skills = filterInstalledSkills(skills, search);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const total = skills.length;
|
|
165
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
166
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
70
167
|
|
|
71
168
|
// 无已安装 skills 时给出提示
|
|
72
169
|
if (skills.length === 0) {
|
|
73
|
-
|
|
170
|
+
if (search) {
|
|
171
|
+
console.log(`No skills found matching "${search}".`);
|
|
172
|
+
} else {
|
|
173
|
+
console.log('No skills installed yet. Run "skm ls" to see available skills.');
|
|
174
|
+
}
|
|
74
175
|
return;
|
|
75
176
|
}
|
|
76
177
|
|
|
77
|
-
//
|
|
78
|
-
|
|
178
|
+
// 搜索结果提示
|
|
179
|
+
if (search) {
|
|
180
|
+
console.log(`Found ${total} match(es) for "${search}":\n`);
|
|
181
|
+
} else {
|
|
182
|
+
console.log(`Installed Skills (${total}):\n`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 计算分页范围
|
|
186
|
+
const start = (currentPage - 1) * limit;
|
|
187
|
+
const end = Math.min(start + limit, total);
|
|
188
|
+
const pageSkills = skills.slice(start, end);
|
|
79
189
|
|
|
80
190
|
// 遍历并打印每个 skill 的详细信息
|
|
81
|
-
for (const skill of
|
|
191
|
+
for (const skill of pageSkills) {
|
|
82
192
|
// skill 名称和版本
|
|
83
193
|
console.log(` ${skill.id}@${skill.version}`);
|
|
84
194
|
|
|
@@ -92,6 +202,9 @@ export async function listSkills(options: LsOptions): Promise<void> {
|
|
|
92
202
|
console.log();
|
|
93
203
|
}
|
|
94
204
|
|
|
205
|
+
// 打印分页信息
|
|
206
|
+
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
207
|
+
|
|
95
208
|
return;
|
|
96
209
|
}
|
|
97
210
|
|
|
@@ -100,41 +213,93 @@ export async function listSkills(options: LsOptions): Promise<void> {
|
|
|
100
213
|
// -------------------------------------------------------------------------
|
|
101
214
|
|
|
102
215
|
// 提示用户正在搜索
|
|
103
|
-
|
|
216
|
+
if (search) {
|
|
217
|
+
console.log(`Searching npm for "${search}"...\n`);
|
|
218
|
+
} else {
|
|
219
|
+
console.log('Searching npm registry...\n');
|
|
220
|
+
}
|
|
104
221
|
|
|
105
222
|
try {
|
|
223
|
+
// 计算分页偏移量
|
|
224
|
+
const offset = (page - 1) * limit;
|
|
225
|
+
|
|
106
226
|
// 调用 npm search API 搜索 skillmarket 相关包
|
|
107
|
-
const packages = await searchSkillmarketPackages(
|
|
227
|
+
const { packages, total } = await searchSkillmarketPackages({
|
|
228
|
+
from: offset,
|
|
229
|
+
size: limit,
|
|
230
|
+
keyword: search
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
234
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
108
235
|
|
|
109
236
|
// 无搜索结果时
|
|
110
237
|
if (packages.length === 0) {
|
|
111
|
-
|
|
238
|
+
if (search) {
|
|
239
|
+
console.log(`No skills found matching "${search}".`);
|
|
240
|
+
} else {
|
|
241
|
+
console.log('No skills found. Check back later!');
|
|
242
|
+
}
|
|
112
243
|
return;
|
|
113
244
|
}
|
|
114
245
|
|
|
115
246
|
// 打印找到的包数量
|
|
116
|
-
|
|
247
|
+
if (search) {
|
|
248
|
+
console.log(`Found ${total} match(es) for "${search}":\n`);
|
|
249
|
+
} else {
|
|
250
|
+
console.log(`Found ${total} skill(s):\n`);
|
|
251
|
+
}
|
|
117
252
|
|
|
118
253
|
// 遍历每个包,获取详细信息并显示
|
|
119
254
|
for (const pkgName of packages) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
255
|
+
try {
|
|
256
|
+
const info = await fetchNpmPackage(pkgName);
|
|
257
|
+
|
|
258
|
+
if (!info) {
|
|
259
|
+
// 如果获取失败,仍然显示包名
|
|
260
|
+
console.log(`📦 ${pkgName} (信息获取失败)`);
|
|
261
|
+
console.log();
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 获取最新版本号
|
|
266
|
+
const latestVersion = info['dist-tags']?.latest || 'unknown';
|
|
124
267
|
|
|
125
268
|
// 获取该版本的详细信息
|
|
126
269
|
const pkg = info.versions?.[latestVersion];
|
|
127
270
|
|
|
271
|
+
// 获取 skillmarket 元数据
|
|
272
|
+
const skillMeta = pkg?.skillmarket;
|
|
273
|
+
|
|
128
274
|
// 打印包名和版本
|
|
129
|
-
console.log(
|
|
275
|
+
console.log(`📦 ${info.name}@${latestVersion}`);
|
|
276
|
+
|
|
277
|
+
// 打印显示名称
|
|
278
|
+
const displayName = skillMeta?.displayName || info.name;
|
|
279
|
+
console.log(` 名称: ${displayName}`);
|
|
280
|
+
|
|
281
|
+
// 打印描述
|
|
282
|
+
console.log(` 描述: ${pkg?.description || 'N/A'}`);
|
|
130
283
|
|
|
131
|
-
//
|
|
132
|
-
|
|
284
|
+
// 打印支持平台
|
|
285
|
+
const platforms = skillMeta?.platforms || [];
|
|
286
|
+
console.log(` 平台: ${platforms.length > 0 ? platforms.join(', ') : 'N/A'}`);
|
|
287
|
+
|
|
288
|
+
// 打印 npm 链接
|
|
289
|
+
const npmLink = pkg?.links?.npm || `https://www.npmjs.com/package/${info.name}`;
|
|
290
|
+
console.log(` 链接: ${npmLink}`);
|
|
133
291
|
|
|
134
292
|
// 空行分隔
|
|
135
293
|
console.log();
|
|
294
|
+
} catch (e) {
|
|
295
|
+
// 错误时仍显示包名
|
|
296
|
+
console.log(`📦 ${pkgName} (获取失败: ${e})`);
|
|
297
|
+
console.log();
|
|
136
298
|
}
|
|
137
299
|
}
|
|
300
|
+
|
|
301
|
+
// 打印分页信息
|
|
302
|
+
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
138
303
|
} catch (error) {
|
|
139
304
|
// 网络错误处理
|
|
140
305
|
console.log(`Error fetching skills: ${error}`);
|