itismyskillmarket 1.2.3 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish-skill.yml +2 -0
- package/CHANGELOG.md +143 -0
- package/dist/index.js +50 -21
- package/package.json +1 -1
- 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/cli.ts +23 -10
- package/src/commands/install.ts +2 -2
- package/src/commands/ls.ts +35 -6
- package/src/commands/npm.ts +20 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# SkillMarket v1.2.3 发布总结
|
|
2
|
+
|
|
3
|
+
**日期**: 2026-04-15
|
|
4
|
+
**版本**: 1.2.3
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🎉 新功能:跨平台 Skill 安装
|
|
9
|
+
|
|
10
|
+
### 支持的平台
|
|
11
|
+
|
|
12
|
+
| 平台 | Skill 目录 | 状态 |
|
|
13
|
+
|------|-----------|------|
|
|
14
|
+
| OpenCode | `~/.config/opencode/skills/` | ✅ |
|
|
15
|
+
| Claude Code | `~/.claude/skills/` | ✅ |
|
|
16
|
+
| VSCode | `~/.copilot/skills/` | ✅ |
|
|
17
|
+
|
|
18
|
+
### 新增命令
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# 查看可用平台
|
|
22
|
+
skm platforms
|
|
23
|
+
|
|
24
|
+
# 安装到所有检测到的平台
|
|
25
|
+
skm install <skill>
|
|
26
|
+
|
|
27
|
+
# 安装到指定平台
|
|
28
|
+
skm install <skill> --platform opencode
|
|
29
|
+
skm install <skill> --platform opencode,claude,vscode
|
|
30
|
+
|
|
31
|
+
# 卸载
|
|
32
|
+
skm uninstall <skill>
|
|
33
|
+
skm uninstall <skill> --platform claude
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 安装输出示例
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
skm install test-skill-1
|
|
40
|
+
|
|
41
|
+
Installing test-skill-1...
|
|
42
|
+
Downloading package...
|
|
43
|
+
Setting up skill...
|
|
44
|
+
|
|
45
|
+
Installing to 3 platform(s)...
|
|
46
|
+
|
|
47
|
+
OpenCode ✅ Installed successfully
|
|
48
|
+
Claude Code ✅ Installed successfully
|
|
49
|
+
VSCode ✅ Installed successfully
|
|
50
|
+
|
|
51
|
+
📊 Summary: 3 installed, 0 skipped, 0 failed
|
|
52
|
+
|
|
53
|
+
✅ test-skill-1@1.1.0 installed successfully!
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 平台状态查看
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
skm platforms
|
|
60
|
+
|
|
61
|
+
📍 Available Platforms:
|
|
62
|
+
|
|
63
|
+
OpenCode ✅ Available (3 skills installed)
|
|
64
|
+
Claude Code ✅ Available (2 skills installed)
|
|
65
|
+
VSCode ✅ Available (3 skills installed)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 🐛 Bug 修复
|
|
71
|
+
|
|
72
|
+
1. **npm scope 兼容问题**
|
|
73
|
+
- 修复了 `@wanxuchen/`、`@itismyskillmarket/` 等多个 scope 的自动检测
|
|
74
|
+
- 现在安装 `test-skill-1` 会自动查找正确的 npm 包
|
|
75
|
+
|
|
76
|
+
2. **tarball 文件名匹配**
|
|
77
|
+
- 修复了 scoped 包文件名匹配逻辑
|
|
78
|
+
- 之前 `@scope/package` 被错误匹配为 `@scope-package`
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 📦 Skill 包列表
|
|
83
|
+
|
|
84
|
+
| Skill | npm 包名 | 用途 |
|
|
85
|
+
|-------|---------|------|
|
|
86
|
+
| test-skill | @wanxuchen/test-skill | 通用测试 |
|
|
87
|
+
| test-skill-1 | @wanxuchen/test-skill-1 | 测试安装和 info 功能 |
|
|
88
|
+
| test-skill-2 | @wanxuchen/test-skill-2 | 测试卸载和更新功能 |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🔧 技术实现
|
|
93
|
+
|
|
94
|
+
### 架构
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
src/adapters/
|
|
98
|
+
├── base.ts # 平台适配器基类
|
|
99
|
+
├── opencode.ts # OpenCode 适配器
|
|
100
|
+
├── claude.ts # Claude Code 适配器
|
|
101
|
+
├── vscode.ts # VSCode 适配器
|
|
102
|
+
├── registry.ts # 平台注册和检测
|
|
103
|
+
└── index.ts # 导出
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 平台适配器接口
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
interface PlatformAdapter {
|
|
110
|
+
readonly id: string;
|
|
111
|
+
readonly name: string;
|
|
112
|
+
readonly skillDir: string;
|
|
113
|
+
|
|
114
|
+
isAvailable(): Promise<boolean>;
|
|
115
|
+
isInstalled(skillId: string): Promise<boolean>;
|
|
116
|
+
install(skillId: string, sourceDir: string): Promise<void>;
|
|
117
|
+
uninstall(skillId: string): Promise<void>;
|
|
118
|
+
listInstalled(): Promise<string[]>;
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 📚 文档更新
|
|
125
|
+
|
|
126
|
+
- `README.md` - 更新使用说明
|
|
127
|
+
- `docs/plans/2026-04-15-cross-platform-adapter-design.md` - 设计文档
|
|
128
|
+
- `docs/plans/2026-04-15-cross-platform-adapter-plan.md` - 实现计划
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 🚀 下一步
|
|
133
|
+
|
|
134
|
+
- [ ] 发布到 VSCode Marketplace
|
|
135
|
+
- [ ] 发布 Claude Code 插件市场
|
|
136
|
+
- [ ] 创建 skill 市场网站
|
|
137
|
+
- [ ] 添加 skill 搜索功能
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 贡献者
|
|
142
|
+
|
|
143
|
+
- wxc2004 (wanxuchen)
|
package/dist/index.js
CHANGED
|
@@ -170,12 +170,15 @@ async function fetchSkillPackage(skillId) {
|
|
|
170
170
|
}
|
|
171
171
|
return null;
|
|
172
172
|
}
|
|
173
|
-
async function searchSkillmarketPackages() {
|
|
173
|
+
async function searchSkillmarketPackages(options = {}) {
|
|
174
|
+
const { from = 0, size = 100 } = options;
|
|
174
175
|
const packages = [];
|
|
176
|
+
let total = 0;
|
|
175
177
|
return new Promise((resolve, reject) => {
|
|
176
178
|
const url = new URL("https://registry.npmjs.org/-/v1/search");
|
|
177
179
|
url.searchParams.set("text", "keywords:skillmarket");
|
|
178
|
-
url.searchParams.set("size",
|
|
180
|
+
url.searchParams.set("size", String(size));
|
|
181
|
+
url.searchParams.set("from", String(from));
|
|
179
182
|
const req = https.get(url.toString(), { timeout: 1e4 }, (res) => {
|
|
180
183
|
let data = "";
|
|
181
184
|
res.on("data", (chunk) => {
|
|
@@ -184,6 +187,7 @@ async function searchSkillmarketPackages() {
|
|
|
184
187
|
res.on("end", () => {
|
|
185
188
|
try {
|
|
186
189
|
const result = JSON.parse(data);
|
|
190
|
+
total = result.total || 0;
|
|
187
191
|
if (result.objects) {
|
|
188
192
|
for (const item of result.objects) {
|
|
189
193
|
if (item?.package?.name) {
|
|
@@ -191,9 +195,9 @@ async function searchSkillmarketPackages() {
|
|
|
191
195
|
}
|
|
192
196
|
}
|
|
193
197
|
}
|
|
194
|
-
resolve(packages);
|
|
198
|
+
resolve({ packages, total });
|
|
195
199
|
} catch {
|
|
196
|
-
resolve([]);
|
|
200
|
+
resolve({ packages: [], total: 0 });
|
|
197
201
|
}
|
|
198
202
|
});
|
|
199
203
|
});
|
|
@@ -207,30 +211,44 @@ async function searchSkillmarketPackages() {
|
|
|
207
211
|
|
|
208
212
|
// src/commands/ls.ts
|
|
209
213
|
async function listSkills(options) {
|
|
210
|
-
const { installed, updates } = options;
|
|
214
|
+
const { installed, updates, page = 1, limit = 20 } = options;
|
|
211
215
|
if (installed) {
|
|
212
216
|
const skills = await getInstalledSkills();
|
|
217
|
+
const total = skills.length;
|
|
218
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
219
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
213
220
|
if (skills.length === 0) {
|
|
214
|
-
console.log('No skills installed yet. Run "skm
|
|
221
|
+
console.log('No skills installed yet. Run "skm ls" to see available skills.');
|
|
215
222
|
return;
|
|
216
223
|
}
|
|
217
|
-
|
|
218
|
-
|
|
224
|
+
const start = (currentPage - 1) * limit;
|
|
225
|
+
const end = Math.min(start + limit, total);
|
|
226
|
+
const pageSkills = skills.slice(start, end);
|
|
227
|
+
console.log(`Installed Skills (${total}):
|
|
228
|
+
`);
|
|
229
|
+
for (const skill of pageSkills) {
|
|
219
230
|
console.log(` ${skill.id}@${skill.version}`);
|
|
220
231
|
console.log(` Platforms: ${skill.platforms.join(", ")}`);
|
|
221
232
|
console.log(` Installed: ${skill.installedAt}`);
|
|
222
233
|
console.log();
|
|
223
234
|
}
|
|
235
|
+
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
224
236
|
return;
|
|
225
237
|
}
|
|
226
238
|
console.log("Searching npm registry...\n");
|
|
227
239
|
try {
|
|
228
|
-
const
|
|
240
|
+
const offset = (page - 1) * limit;
|
|
241
|
+
const { packages, total } = await searchSkillmarketPackages({
|
|
242
|
+
from: offset,
|
|
243
|
+
size: limit
|
|
244
|
+
});
|
|
245
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
246
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
229
247
|
if (packages.length === 0) {
|
|
230
248
|
console.log("No skills found. Check back later!");
|
|
231
249
|
return;
|
|
232
250
|
}
|
|
233
|
-
console.log(`Found ${
|
|
251
|
+
console.log(`Found ${total} skill(s):
|
|
234
252
|
`);
|
|
235
253
|
for (const pkgName of packages) {
|
|
236
254
|
try {
|
|
@@ -257,6 +275,7 @@ async function listSkills(options) {
|
|
|
257
275
|
console.log();
|
|
258
276
|
}
|
|
259
277
|
}
|
|
278
|
+
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
260
279
|
} catch (error) {
|
|
261
280
|
console.log(`Error fetching skills: ${error}`);
|
|
262
281
|
}
|
|
@@ -511,7 +530,7 @@ async function installSkill(skillId, version, options) {
|
|
|
511
530
|
await execAsync(`npm pack ${packageName}@${targetVersion} --pack-destination ${cacheDir}`);
|
|
512
531
|
const files = await fs7.readdir(cacheDir);
|
|
513
532
|
const tarball = files.find(
|
|
514
|
-
(f) => f.endsWith(".tgz") && f.includes(packageName.replace("/", "-"))
|
|
533
|
+
(f) => f.endsWith(".tgz") && f.includes(packageName.replace(/^@/, "").replace("/", "-"))
|
|
515
534
|
);
|
|
516
535
|
if (tarball) {
|
|
517
536
|
await execAsync(`tar -xzf "${path6.join(cacheDir, tarball)}" -C "${cacheDir}"`);
|
|
@@ -744,23 +763,28 @@ Usage: skm <command> [options]
|
|
|
744
763
|
|
|
745
764
|
Commands:
|
|
746
765
|
ls [options] List available skills
|
|
747
|
-
|
|
748
|
-
|
|
766
|
+
--installed Show only installed skills
|
|
767
|
+
--updates Check for updates
|
|
768
|
+
--page <n> Page number (default: 1)
|
|
769
|
+
--limit <n> Items per page (default: 20)
|
|
749
770
|
info <skill-id> Display skill information
|
|
750
771
|
install <skill> Install a skill
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
772
|
+
@version Install specific version
|
|
773
|
+
--platform Target platforms (opencode,claude,vscode)
|
|
774
|
+
--force Overwrite if already installed
|
|
754
775
|
uninstall <skill> Remove an installed skill
|
|
755
|
-
|
|
776
|
+
--platform Target platforms
|
|
756
777
|
update [options] Update skills
|
|
757
|
-
|
|
778
|
+
--all Update all skills
|
|
758
779
|
sync Synchronize platform links
|
|
759
780
|
platforms Show available platforms
|
|
760
781
|
|
|
761
782
|
Examples:
|
|
762
|
-
skm ls List all available skills
|
|
783
|
+
skm ls List all available skills (page 1)
|
|
784
|
+
skm ls --page 2 Go to page 2
|
|
785
|
+
skm ls --limit 10 Show 10 items per page
|
|
763
786
|
skm ls --installed Show installed skills only
|
|
787
|
+
skm ls --installed --page 2
|
|
764
788
|
skm info brainstorming View skill details
|
|
765
789
|
skm install brainstorming Install to all platforms
|
|
766
790
|
skm install brainstorming --platform opencode Install to OpenCode only
|
|
@@ -772,8 +796,13 @@ Examples:
|
|
|
772
796
|
}
|
|
773
797
|
});
|
|
774
798
|
var lsCmd = program.command("ls").description("List available skills");
|
|
775
|
-
lsCmd.option("--installed", "Show only installed skills").option("--updates", "Check for updates").action((opts) => {
|
|
776
|
-
|
|
799
|
+
lsCmd.option("--installed", "Show only installed skills").option("--updates", "Check for updates").option("-p, --page <number>", "Page number (default: 1)", parseInt).option("-l, --limit <number>", "Items per page (default: 20)", parseInt).action((opts) => {
|
|
800
|
+
const options = {
|
|
801
|
+
...opts,
|
|
802
|
+
page: opts.page ?? 1,
|
|
803
|
+
limit: opts.limit ?? 20
|
|
804
|
+
};
|
|
805
|
+
listSkills(options);
|
|
777
806
|
});
|
|
778
807
|
var infoCmd = program.command("info").description("Display skill information");
|
|
779
808
|
infoCmd.argument("<skill-id>", "Skill ID to show info").action((skillId) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Test Skill 1
|
|
2
|
+
|
|
3
|
+
用于测试 SkillMarket 安装和 info 功能的 skill。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 验证 skm install 功能
|
|
8
|
+
- 验证 skm info 功能
|
|
9
|
+
|
|
10
|
+
## 使用方法
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
skm install test-skill-1
|
|
14
|
+
skm info test-skill-1
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 平台支持
|
|
18
|
+
|
|
19
|
+
- OpenCode
|
|
20
|
+
- Cursor
|
|
21
|
+
- VSCode
|
|
22
|
+
- Claude Code
|
|
23
|
+
- Codex
|
|
24
|
+
- Antigravity
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "test-skill-1",
|
|
3
|
+
"displayName": "测试技能1",
|
|
4
|
+
"description": "用于测试 SkillMarket 安装和 info 功能",
|
|
5
|
+
"version": "1.0.3",
|
|
6
|
+
"author": "SkillMarket",
|
|
7
|
+
"platforms": ["opencode", "cursor", "vscode", "claude", "codex", "antigravity"],
|
|
8
|
+
"tags": ["test", "install", "info"]
|
|
9
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wanxuchen/test-skill-1",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Test Skill 1 - 用于测试 SkillMarket 安装流程",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"keywords": ["skillmarket", "test"],
|
|
8
|
+
"author": "SkillMarket",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"skillmarket": {
|
|
11
|
+
"id": "test-skill-1",
|
|
12
|
+
"displayName": "测试技能1",
|
|
13
|
+
"description": "用于测试 SkillMarket 安装和 info 功能",
|
|
14
|
+
"platforms": ["opencode", "cursor", "vscode", "claude", "codex", "antigravity"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Test Skill 2
|
|
2
|
+
|
|
3
|
+
用于测试 SkillMarket 卸载和更新流程的 skill。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 验证 skm uninstall 功能
|
|
8
|
+
- 验证 skm update 功能
|
|
9
|
+
|
|
10
|
+
## 使用方法
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
skm install test-skill-2
|
|
14
|
+
skm update test-skill-2
|
|
15
|
+
skm uninstall test-skill-2
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 平台支持
|
|
19
|
+
|
|
20
|
+
- OpenCode
|
|
21
|
+
- Cursor
|
|
22
|
+
- VSCode
|
|
23
|
+
- Claude Code
|
|
24
|
+
- Codex
|
|
25
|
+
- Antigravity
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "test-skill-2",
|
|
3
|
+
"displayName": "测试技能2",
|
|
4
|
+
"description": "用于测试 SkillMarket 卸载和更新功能",
|
|
5
|
+
"version": "1.0.3",
|
|
6
|
+
"author": "SkillMarket",
|
|
7
|
+
"platforms": ["opencode", "cursor", "vscode", "claude", "codex", "antigravity"],
|
|
8
|
+
"tags": ["test", "uninstall", "update"]
|
|
9
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wanxuchen/test-skill-2",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Test Skill 2 - 用于测试 SkillMarket 卸载和更新流程",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"keywords": ["skillmarket", "test"],
|
|
8
|
+
"author": "SkillMarket",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"skillmarket": {
|
|
11
|
+
"id": "test-skill-2",
|
|
12
|
+
"displayName": "测试技能2",
|
|
13
|
+
"description": "用于测试 SkillMarket 卸载和更新功能",
|
|
14
|
+
"platforms": ["opencode", "cursor", "vscode", "claude", "codex", "antigravity"]
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -79,30 +79,35 @@ program
|
|
|
79
79
|
program
|
|
80
80
|
.hook('preAction', (thisCommand) => {
|
|
81
81
|
if (thisCommand.opts().help) {
|
|
82
|
-
|
|
82
|
+
console.log(`
|
|
83
83
|
SkillMarket CLI
|
|
84
84
|
|
|
85
85
|
Usage: skm <command> [options]
|
|
86
86
|
|
|
87
87
|
Commands:
|
|
88
88
|
ls [options] List available skills
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
--installed Show only installed skills
|
|
90
|
+
--updates Check for updates
|
|
91
|
+
--page <n> Page number (default: 1)
|
|
92
|
+
--limit <n> Items per page (default: 20)
|
|
91
93
|
info <skill-id> Display skill information
|
|
92
94
|
install <skill> Install a skill
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
@version Install specific version
|
|
96
|
+
--platform Target platforms (opencode,claude,vscode)
|
|
97
|
+
--force Overwrite if already installed
|
|
96
98
|
uninstall <skill> Remove an installed skill
|
|
97
|
-
|
|
99
|
+
--platform Target platforms
|
|
98
100
|
update [options] Update skills
|
|
99
|
-
|
|
101
|
+
--all Update all skills
|
|
100
102
|
sync Synchronize platform links
|
|
101
103
|
platforms Show available platforms
|
|
102
104
|
|
|
103
105
|
Examples:
|
|
104
|
-
skm ls List all available skills
|
|
106
|
+
skm ls List all available skills (page 1)
|
|
107
|
+
skm ls --page 2 Go to page 2
|
|
108
|
+
skm ls --limit 10 Show 10 items per page
|
|
105
109
|
skm ls --installed Show installed skills only
|
|
110
|
+
skm ls --installed --page 2
|
|
106
111
|
skm info brainstorming View skill details
|
|
107
112
|
skm install brainstorming Install to all platforms
|
|
108
113
|
skm install brainstorming --platform opencode Install to OpenCode only
|
|
@@ -132,8 +137,16 @@ const lsCmd = program.command('ls').description('List available skills');
|
|
|
132
137
|
lsCmd
|
|
133
138
|
.option('--installed', 'Show only installed skills')
|
|
134
139
|
.option('--updates', 'Check for updates')
|
|
140
|
+
.option('-p, --page <number>', 'Page number (default: 1)', parseInt)
|
|
141
|
+
.option('-l, --limit <number>', 'Items per page (default: 20)', parseInt)
|
|
135
142
|
.action((opts) => {
|
|
136
|
-
|
|
143
|
+
// Ensure numeric options have default values if not provided
|
|
144
|
+
const options = {
|
|
145
|
+
...opts,
|
|
146
|
+
page: opts.page ?? 1,
|
|
147
|
+
limit: opts.limit ?? 20
|
|
148
|
+
};
|
|
149
|
+
listSkills(options);
|
|
137
150
|
});
|
|
138
151
|
|
|
139
152
|
// -----------------------------------------------------------------------------
|
package/src/commands/install.ts
CHANGED
|
@@ -147,10 +147,10 @@ export async function installSkill(
|
|
|
147
147
|
const files = await fs.readdir(cacheDir);
|
|
148
148
|
|
|
149
149
|
// npm pack 生成的文件名格式: <package-name>-<version>.tgz
|
|
150
|
-
// scoped 包格式: @scope-package-name-<version>.tgz
|
|
150
|
+
// scoped 包格式: @scope-package-name-<version>.tgz (注意:@ 不在文件名中)
|
|
151
151
|
const tarball = files.find(f =>
|
|
152
152
|
f.endsWith('.tgz') &&
|
|
153
|
-
f.includes(packageName.replace('/', '-'))
|
|
153
|
+
f.includes(packageName.replace(/^@/, '').replace('/', '-'))
|
|
154
154
|
);
|
|
155
155
|
|
|
156
156
|
if (tarball) {
|
package/src/commands/ls.ts
CHANGED
|
@@ -37,6 +37,12 @@ interface LsOptions {
|
|
|
37
37
|
|
|
38
38
|
/** 检查更新(预留功能) */
|
|
39
39
|
updates?: boolean;
|
|
40
|
+
|
|
41
|
+
/** 页码(从 1 开始) */
|
|
42
|
+
page?: number;
|
|
43
|
+
|
|
44
|
+
/** 每页数量 */
|
|
45
|
+
limit?: number;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
// -----------------------------------------------------------------------------
|
|
@@ -60,25 +66,33 @@ interface LsOptions {
|
|
|
60
66
|
* await listSkills({ installed: true });
|
|
61
67
|
*/
|
|
62
68
|
export async function listSkills(options: LsOptions): Promise<void> {
|
|
63
|
-
const { installed, updates } = options;
|
|
69
|
+
const { installed, updates, page = 1, limit = 20 } = options;
|
|
64
70
|
|
|
65
71
|
// -------------------------------------------------------------------------
|
|
66
72
|
// 模式1: 显示已安装的 skills
|
|
67
73
|
// -------------------------------------------------------------------------
|
|
68
74
|
if (installed) {
|
|
69
75
|
const skills = await getInstalledSkills();
|
|
76
|
+
const total = skills.length;
|
|
77
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
78
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
70
79
|
|
|
71
80
|
// 无已安装 skills 时给出提示
|
|
72
81
|
if (skills.length === 0) {
|
|
73
|
-
console.log('No skills installed yet. Run "skm
|
|
82
|
+
console.log('No skills installed yet. Run "skm ls" to see available skills.');
|
|
74
83
|
return;
|
|
75
84
|
}
|
|
76
85
|
|
|
86
|
+
// 计算分页范围
|
|
87
|
+
const start = (currentPage - 1) * limit;
|
|
88
|
+
const end = Math.min(start + limit, total);
|
|
89
|
+
const pageSkills = skills.slice(start, end);
|
|
90
|
+
|
|
77
91
|
// 打印表头
|
|
78
|
-
console.log(
|
|
92
|
+
console.log(`Installed Skills (${total}):\n`);
|
|
79
93
|
|
|
80
94
|
// 遍历并打印每个 skill 的详细信息
|
|
81
|
-
for (const skill of
|
|
95
|
+
for (const skill of pageSkills) {
|
|
82
96
|
// skill 名称和版本
|
|
83
97
|
console.log(` ${skill.id}@${skill.version}`);
|
|
84
98
|
|
|
@@ -92,6 +106,9 @@ export async function listSkills(options: LsOptions): Promise<void> {
|
|
|
92
106
|
console.log();
|
|
93
107
|
}
|
|
94
108
|
|
|
109
|
+
// 打印分页信息
|
|
110
|
+
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
111
|
+
|
|
95
112
|
return;
|
|
96
113
|
}
|
|
97
114
|
|
|
@@ -103,8 +120,17 @@ export async function listSkills(options: LsOptions): Promise<void> {
|
|
|
103
120
|
console.log('Searching npm registry...\n');
|
|
104
121
|
|
|
105
122
|
try {
|
|
123
|
+
// 计算分页偏移量
|
|
124
|
+
const offset = (page - 1) * limit;
|
|
125
|
+
|
|
106
126
|
// 调用 npm search API 搜索 skillmarket 相关包
|
|
107
|
-
const packages = await searchSkillmarketPackages(
|
|
127
|
+
const { packages, total } = await searchSkillmarketPackages({
|
|
128
|
+
from: offset,
|
|
129
|
+
size: limit
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const totalPages = Math.ceil(total / limit) || 1;
|
|
133
|
+
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
108
134
|
|
|
109
135
|
// 无搜索结果时
|
|
110
136
|
if (packages.length === 0) {
|
|
@@ -113,7 +139,7 @@ export async function listSkills(options: LsOptions): Promise<void> {
|
|
|
113
139
|
}
|
|
114
140
|
|
|
115
141
|
// 打印找到的包数量
|
|
116
|
-
console.log(`Found ${
|
|
142
|
+
console.log(`Found ${total} skill(s):\n`);
|
|
117
143
|
|
|
118
144
|
// 遍历每个包,获取详细信息并显示
|
|
119
145
|
for (const pkgName of packages) {
|
|
@@ -162,6 +188,9 @@ export async function listSkills(options: LsOptions): Promise<void> {
|
|
|
162
188
|
console.log();
|
|
163
189
|
}
|
|
164
190
|
}
|
|
191
|
+
|
|
192
|
+
// 打印分页信息
|
|
193
|
+
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
165
194
|
} catch (error) {
|
|
166
195
|
// 网络错误处理
|
|
167
196
|
console.log(`Error fetching skills: ${error}`);
|
package/src/commands/npm.ts
CHANGED
|
@@ -250,17 +250,25 @@ export async function fetchSkillPackage(skillId: string): Promise<NpmRegistryRes
|
|
|
250
250
|
*
|
|
251
251
|
* API 端点: GET https://registry.npmjs.org/-/v1/search
|
|
252
252
|
*
|
|
253
|
-
* @
|
|
253
|
+
* @param options - 搜索选项
|
|
254
|
+
* @param options.from - 起始位置(分页用)
|
|
255
|
+
* @param options.size - 返回结果数量
|
|
256
|
+
* @returns {Promise<{packages: string[], total: number}>} 匹配的包名数组和总数
|
|
254
257
|
*
|
|
255
258
|
* @example
|
|
256
|
-
* const packages = await searchSkillmarketPackages();
|
|
257
|
-
* console.log(`找到 ${
|
|
259
|
+
* const { packages, total } = await searchSkillmarketPackages();
|
|
260
|
+
* console.log(`找到 ${total} 个 skill 包`);
|
|
258
261
|
* packages.forEach(name => {
|
|
259
262
|
* console.log(`- ${name}`);
|
|
260
263
|
* });
|
|
261
264
|
*/
|
|
262
|
-
export async function searchSkillmarketPackages(
|
|
265
|
+
export async function searchSkillmarketPackages(options: {
|
|
266
|
+
from?: number;
|
|
267
|
+
size?: number;
|
|
268
|
+
} = {}): Promise<{ packages: string[]; total: number }> {
|
|
269
|
+
const { from = 0, size = 100 } = options;
|
|
263
270
|
const packages: string[] = [];
|
|
271
|
+
let total = 0;
|
|
264
272
|
|
|
265
273
|
return new Promise((resolve, reject) => {
|
|
266
274
|
// 构建 search API URL
|
|
@@ -269,8 +277,10 @@ export async function searchSkillmarketPackages(): Promise<string[]> {
|
|
|
269
277
|
// 设置搜索参数
|
|
270
278
|
// text: 搜索关键字
|
|
271
279
|
// size: 返回结果数量上限
|
|
280
|
+
// from: 起始位置(分页用)
|
|
272
281
|
url.searchParams.set('text', 'keywords:skillmarket');
|
|
273
|
-
url.searchParams.set('size',
|
|
282
|
+
url.searchParams.set('size', String(size));
|
|
283
|
+
url.searchParams.set('from', String(from));
|
|
274
284
|
|
|
275
285
|
const req = https.get(url.toString(), { timeout: 10000 }, (res) => {
|
|
276
286
|
let data = '';
|
|
@@ -283,6 +293,9 @@ export async function searchSkillmarketPackages(): Promise<string[]> {
|
|
|
283
293
|
// 解析搜索结果
|
|
284
294
|
const result = JSON.parse(data);
|
|
285
295
|
|
|
296
|
+
// 获取总数
|
|
297
|
+
total = result.total || 0;
|
|
298
|
+
|
|
286
299
|
// 提取所有匹配的包名
|
|
287
300
|
// npm search 返回结构: { objects: [{ package: { name: "..." } }] }
|
|
288
301
|
if (result.objects) {
|
|
@@ -293,10 +306,10 @@ export async function searchSkillmarketPackages(): Promise<string[]> {
|
|
|
293
306
|
}
|
|
294
307
|
}
|
|
295
308
|
|
|
296
|
-
resolve(packages);
|
|
309
|
+
resolve({ packages, total });
|
|
297
310
|
} catch {
|
|
298
311
|
// 解析失败返回空数组
|
|
299
|
-
resolve([]);
|
|
312
|
+
resolve({ packages: [], total: 0 });
|
|
300
313
|
}
|
|
301
314
|
});
|
|
302
315
|
});
|