itismyskillmarket 1.2.5 → 1.2.7
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/CHANGELOG.md +112 -0
- package/dist/index.js +67 -21
- package/docs/plans/2026-04-16-keyword-search-design.md +143 -0
- package/package.json +1 -1
- package/src/cli.ts +14 -2
- package/src/commands/ls.ts +66 -10
- package/src/commands/npm.ts +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,115 @@
|
|
|
1
|
+
# SkillMarket v1.2.6 更新日志
|
|
2
|
+
|
|
3
|
+
**日期**: 2026-04-22
|
|
4
|
+
**版本**: 1.2.6
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🔍 新功能:skm ls 搜索支持
|
|
9
|
+
|
|
10
|
+
### 功能说明
|
|
11
|
+
|
|
12
|
+
新增搜索功能,可以通过关键字搜索 skills。
|
|
13
|
+
|
|
14
|
+
### 新增选项
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# 搜索 npm 上的 skills
|
|
18
|
+
skm ls --search brain
|
|
19
|
+
skm ls -s brain
|
|
20
|
+
|
|
21
|
+
# 搜索已安装的 skills
|
|
22
|
+
skm ls --installed --search test
|
|
23
|
+
|
|
24
|
+
# 组合分页和搜索
|
|
25
|
+
skm ls --search brain --page 1 --limit 10
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 搜索字段
|
|
29
|
+
|
|
30
|
+
- skill ID
|
|
31
|
+
- displayName(显示名称)
|
|
32
|
+
- description(描述)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🎉 新功能:skm ls 分页支持
|
|
37
|
+
|
|
38
|
+
### 功能说明
|
|
39
|
+
|
|
40
|
+
当 skill 数量较多时,现在支持分页浏览。
|
|
41
|
+
|
|
42
|
+
### 新增选项
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 默认每页 20 个
|
|
46
|
+
skm ls
|
|
47
|
+
|
|
48
|
+
# 指定页码
|
|
49
|
+
skm ls --page 2
|
|
50
|
+
|
|
51
|
+
# 指定每页数量
|
|
52
|
+
skm ls --limit 10
|
|
53
|
+
|
|
54
|
+
# 组合使用
|
|
55
|
+
skm ls --page 2 --limit 10
|
|
56
|
+
|
|
57
|
+
# 已安装的 skills 也支持分页
|
|
58
|
+
skm ls --installed --page 2
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 输出示例
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Found 85 skill(s):
|
|
65
|
+
|
|
66
|
+
📦 @skillmarket/brainstorming@1.2.0
|
|
67
|
+
名称: Brainstorming
|
|
68
|
+
描述: Feature brainstorming skill
|
|
69
|
+
平台: opencode, cursor, vscode, claude
|
|
70
|
+
链接: https://www.npmjs.com/package/@skillmarket/brainstorming
|
|
71
|
+
|
|
72
|
+
Page 1/5 (20 per page) | Use --page N to navigate
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 实现细节
|
|
76
|
+
|
|
77
|
+
- npm search API 使用 `from` 和 `size` 参数实现服务端分页
|
|
78
|
+
- 本地已安装 skills 使用数组 slice 实现客户端分页
|
|
79
|
+
- 默认每页 20 个,可自定义
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 🔧 改进
|
|
84
|
+
|
|
85
|
+
1. **文档更新**
|
|
86
|
+
- 更新 README.md 添加分页使用示例
|
|
87
|
+
- 更新 SKILLMARKET-GUIDE.md 修复安装命令
|
|
88
|
+
- 更新 skills/README.md 添加 test-skill-1/2
|
|
89
|
+
|
|
90
|
+
2. **版本管理优化**
|
|
91
|
+
- 版本号现在从 `package.json` 动态读取
|
|
92
|
+
- 不再需要手动同步版本号
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 📦 完整版本历史
|
|
97
|
+
|
|
98
|
+
| 版本 | 日期 | 描述 |
|
|
99
|
+
|------|------|------|
|
|
100
|
+
| 1.2.6 | 2026-04-16 | 修复版本号动态读取 |
|
|
101
|
+
| 1.2.5 | 2026-04-16 | 文档更新 |
|
|
102
|
+
| 1.2.4 | 2026-04-16 | 修复版本号硬编码问题 |
|
|
103
|
+
| 1.2.3 | 2026-04-15 | 跨平台 Skill 安装支持 |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 贡献者
|
|
108
|
+
|
|
109
|
+
- wxc2004 (wanxuchen)
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
1
113
|
# SkillMarket v1.2.3 发布总结
|
|
2
114
|
|
|
3
115
|
**日期**: 2026-04-15
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { dirname, resolve } from "path";
|
|
5
8
|
|
|
6
9
|
// src/commands/registry.ts
|
|
7
10
|
import fs2 from "fs-extra";
|
|
@@ -97,7 +100,7 @@ async function isSkillInstalled(skillId) {
|
|
|
97
100
|
import https from "https";
|
|
98
101
|
import { URL } from "url";
|
|
99
102
|
async function fetchNpmPackage(packageName) {
|
|
100
|
-
return new Promise((
|
|
103
|
+
return new Promise((resolve2, reject) => {
|
|
101
104
|
const isScoped = packageName.startsWith("@");
|
|
102
105
|
let encodedName;
|
|
103
106
|
if (isScoped) {
|
|
@@ -123,12 +126,12 @@ async function fetchNpmPackage(packageName) {
|
|
|
123
126
|
try {
|
|
124
127
|
const parsed = JSON.parse(data);
|
|
125
128
|
if (parsed.error) {
|
|
126
|
-
|
|
129
|
+
resolve2(null);
|
|
127
130
|
return;
|
|
128
131
|
}
|
|
129
|
-
|
|
132
|
+
resolve2(parsed);
|
|
130
133
|
} catch {
|
|
131
|
-
|
|
134
|
+
resolve2(null);
|
|
132
135
|
}
|
|
133
136
|
});
|
|
134
137
|
});
|
|
@@ -171,12 +174,16 @@ async function fetchSkillPackage(skillId) {
|
|
|
171
174
|
return null;
|
|
172
175
|
}
|
|
173
176
|
async function searchSkillmarketPackages(options = {}) {
|
|
174
|
-
const { from = 0, size = 100 } = options;
|
|
177
|
+
const { from = 0, size = 100, keyword } = options;
|
|
175
178
|
const packages = [];
|
|
176
179
|
let total = 0;
|
|
177
|
-
return new Promise((
|
|
180
|
+
return new Promise((resolve2, reject) => {
|
|
178
181
|
const url = new URL("https://registry.npmjs.org/-/v1/search");
|
|
179
|
-
|
|
182
|
+
if (keyword) {
|
|
183
|
+
url.searchParams.set("text", `${keyword} keywords:skillmarket`);
|
|
184
|
+
} else {
|
|
185
|
+
url.searchParams.set("text", "keywords:skillmarket");
|
|
186
|
+
}
|
|
180
187
|
url.searchParams.set("size", String(size));
|
|
181
188
|
url.searchParams.set("from", String(from));
|
|
182
189
|
const req = https.get(url.toString(), { timeout: 1e4 }, (res) => {
|
|
@@ -195,9 +202,9 @@ async function searchSkillmarketPackages(options = {}) {
|
|
|
195
202
|
}
|
|
196
203
|
}
|
|
197
204
|
}
|
|
198
|
-
|
|
205
|
+
resolve2({ packages, total });
|
|
199
206
|
} catch {
|
|
200
|
-
|
|
207
|
+
resolve2({ packages: [], total: 0 });
|
|
201
208
|
}
|
|
202
209
|
});
|
|
203
210
|
});
|
|
@@ -210,22 +217,40 @@ async function searchSkillmarketPackages(options = {}) {
|
|
|
210
217
|
}
|
|
211
218
|
|
|
212
219
|
// src/commands/ls.ts
|
|
220
|
+
function filterInstalledSkills(skills, keyword) {
|
|
221
|
+
const lower = keyword.toLowerCase();
|
|
222
|
+
return skills.filter(
|
|
223
|
+
(s) => s.id.toLowerCase().includes(lower) || s.displayName && s.displayName.toLowerCase().includes(lower) || s.description && s.description.toLowerCase().includes(lower)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
213
226
|
async function listSkills(options) {
|
|
214
|
-
const { installed, updates, page = 1, limit = 20 } = options;
|
|
227
|
+
const { installed, updates, page = 1, limit = 20, search } = options;
|
|
215
228
|
if (installed) {
|
|
216
|
-
|
|
229
|
+
let skills = await getInstalledSkills();
|
|
230
|
+
if (search) {
|
|
231
|
+
skills = filterInstalledSkills(skills, search);
|
|
232
|
+
}
|
|
217
233
|
const total = skills.length;
|
|
218
234
|
const totalPages = Math.ceil(total / limit) || 1;
|
|
219
235
|
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
220
236
|
if (skills.length === 0) {
|
|
221
|
-
|
|
237
|
+
if (search) {
|
|
238
|
+
console.log(`No skills found matching "${search}".`);
|
|
239
|
+
} else {
|
|
240
|
+
console.log('No skills installed yet. Run "skm ls" to see available skills.');
|
|
241
|
+
}
|
|
222
242
|
return;
|
|
223
243
|
}
|
|
244
|
+
if (search) {
|
|
245
|
+
console.log(`Found ${total} match(es) for "${search}":
|
|
246
|
+
`);
|
|
247
|
+
} else {
|
|
248
|
+
console.log(`Installed Skills (${total}):
|
|
249
|
+
`);
|
|
250
|
+
}
|
|
224
251
|
const start = (currentPage - 1) * limit;
|
|
225
252
|
const end = Math.min(start + limit, total);
|
|
226
253
|
const pageSkills = skills.slice(start, end);
|
|
227
|
-
console.log(`Installed Skills (${total}):
|
|
228
|
-
`);
|
|
229
254
|
for (const skill of pageSkills) {
|
|
230
255
|
console.log(` ${skill.id}@${skill.version}`);
|
|
231
256
|
console.log(` Platforms: ${skill.platforms.join(", ")}`);
|
|
@@ -235,21 +260,36 @@ async function listSkills(options) {
|
|
|
235
260
|
console.log(`Page ${currentPage}/${totalPages} (${limit} per page) | Use --page N to navigate`);
|
|
236
261
|
return;
|
|
237
262
|
}
|
|
238
|
-
|
|
263
|
+
if (search) {
|
|
264
|
+
console.log(`Searching npm for "${search}"...
|
|
265
|
+
`);
|
|
266
|
+
} else {
|
|
267
|
+
console.log("Searching npm registry...\n");
|
|
268
|
+
}
|
|
239
269
|
try {
|
|
240
270
|
const offset = (page - 1) * limit;
|
|
241
271
|
const { packages, total } = await searchSkillmarketPackages({
|
|
242
272
|
from: offset,
|
|
243
|
-
size: limit
|
|
273
|
+
size: limit,
|
|
274
|
+
keyword: search
|
|
244
275
|
});
|
|
245
276
|
const totalPages = Math.ceil(total / limit) || 1;
|
|
246
277
|
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
247
278
|
if (packages.length === 0) {
|
|
248
|
-
|
|
279
|
+
if (search) {
|
|
280
|
+
console.log(`No skills found matching "${search}".`);
|
|
281
|
+
} else {
|
|
282
|
+
console.log("No skills found. Check back later!");
|
|
283
|
+
}
|
|
249
284
|
return;
|
|
250
285
|
}
|
|
251
|
-
|
|
286
|
+
if (search) {
|
|
287
|
+
console.log(`Found ${total} match(es) for "${search}":
|
|
288
|
+
`);
|
|
289
|
+
} else {
|
|
290
|
+
console.log(`Found ${total} skill(s):
|
|
252
291
|
`);
|
|
292
|
+
}
|
|
253
293
|
for (const pkgName of packages) {
|
|
254
294
|
try {
|
|
255
295
|
const info = await fetchNpmPackage(pkgName);
|
|
@@ -752,8 +792,12 @@ Uninstalling from ${validAdapters.length} platform(s)...
|
|
|
752
792
|
}
|
|
753
793
|
|
|
754
794
|
// src/cli.ts
|
|
795
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
796
|
+
var __dirname = dirname(__filename);
|
|
797
|
+
var packageJson = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf-8"));
|
|
798
|
+
var VERSION = packageJson.version;
|
|
755
799
|
var program = new Command();
|
|
756
|
-
program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version(
|
|
800
|
+
program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version(VERSION);
|
|
757
801
|
program.hook("preAction", (thisCommand) => {
|
|
758
802
|
if (thisCommand.opts().help) {
|
|
759
803
|
console.log(`
|
|
@@ -767,6 +811,7 @@ Commands:
|
|
|
767
811
|
--updates Check for updates
|
|
768
812
|
--page <n> Page number (default: 1)
|
|
769
813
|
--limit <n> Items per page (default: 20)
|
|
814
|
+
-s, --search Search by keyword
|
|
770
815
|
info <skill-id> Display skill information
|
|
771
816
|
install <skill> Install a skill
|
|
772
817
|
@version Install specific version
|
|
@@ -796,11 +841,12 @@ Examples:
|
|
|
796
841
|
}
|
|
797
842
|
});
|
|
798
843
|
var lsCmd = program.command("ls").description("List available skills");
|
|
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) => {
|
|
844
|
+
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).option("-s, --search <keyword>", "Search by keyword (id, displayName, description)").action((opts) => {
|
|
800
845
|
const options = {
|
|
801
846
|
...opts,
|
|
802
847
|
page: opts.page ?? 1,
|
|
803
|
-
limit: opts.limit ?? 20
|
|
848
|
+
limit: opts.limit ?? 20,
|
|
849
|
+
search: opts.search
|
|
804
850
|
};
|
|
805
851
|
listSkills(options);
|
|
806
852
|
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# SkillMarket 关键字搜索功能设计方案
|
|
2
|
+
|
|
3
|
+
**版本**: 1.0
|
|
4
|
+
**日期**: 2026-04-16
|
|
5
|
+
**状态**: 待开发
|
|
6
|
+
|
|
7
|
+
## 1. 功能概述
|
|
8
|
+
|
|
9
|
+
为 SkillMarket CLI 添加关键字搜索功能,支持在 npm registry 和本地已安装 skills 中按关键字检索。
|
|
10
|
+
|
|
11
|
+
## 2. 推荐方案
|
|
12
|
+
|
|
13
|
+
### 2.1 新增命令
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 搜索 npm registry(默认行为,与现有 skm ls 等效但带过滤)
|
|
17
|
+
skm search <keyword>
|
|
18
|
+
|
|
19
|
+
# 搜索本地已安装的 skills
|
|
20
|
+
skm search <keyword> --installed
|
|
21
|
+
|
|
22
|
+
# 组合:分页 + 搜索关键字
|
|
23
|
+
skm search <keyword> --page 1 --limit 20
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2.2 搜索字段
|
|
27
|
+
|
|
28
|
+
支持匹配以下字段:
|
|
29
|
+
- `id`: skill 包名(精确匹配 + 模糊匹配)
|
|
30
|
+
- `displayName`: 显示名称
|
|
31
|
+
- `description`: 描述
|
|
32
|
+
|
|
33
|
+
### 2.3 实现方式
|
|
34
|
+
|
|
35
|
+
复用现有 `skm ls` 命令,添加 `--search` / `-s` 选项:
|
|
36
|
+
|
|
37
|
+
| 命令 | 说明 |
|
|
38
|
+
|------|------|
|
|
39
|
+
| `skm ls --search <keyword>` | 在 npm registry 中搜索 |
|
|
40
|
+
| `skm ls --search <keyword> --installed` | 在本地已安装 skills 中搜索 |
|
|
41
|
+
| `skm ls -s <keyword>` | 简写形式 |
|
|
42
|
+
|
|
43
|
+
**推荐**:将搜索功能作为 `skm ls` 的选项,而不是新增独立命令。原因:
|
|
44
|
+
- 代码复用度高
|
|
45
|
+
- 用户学习成本低
|
|
46
|
+
- 与分页功能自然组合
|
|
47
|
+
|
|
48
|
+
## 3. 技术方案
|
|
49
|
+
|
|
50
|
+
### 3.1 代码修改
|
|
51
|
+
|
|
52
|
+
**文件**: `src/commands/ls.ts`
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
interface LsOptions {
|
|
56
|
+
// ... 现有字段
|
|
57
|
+
search?: string; // 新增:搜索关键字
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3.2 npm search 实现
|
|
62
|
+
|
|
63
|
+
修改 `searchSkillmarketPackages` 函数,添加关键字过滤参数:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
interface SearchOptions {
|
|
67
|
+
from: number;
|
|
68
|
+
size: number;
|
|
69
|
+
keyword?: string; // 新增
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**注意**:npm search API 本身支持通过 `q` 参数进行关键字搜索,可以直接传入。
|
|
74
|
+
|
|
75
|
+
### 3.3 本地搜索实现
|
|
76
|
+
|
|
77
|
+
在已有本地 skills 列表上进行内存过滤:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
function filterInstalledSkills(skills: InstalledSkill[], keyword: string): InstalledSkill[] {
|
|
81
|
+
const lower = keyword.toLowerCase();
|
|
82
|
+
return skills.filter(s =>
|
|
83
|
+
s.id.toLowerCase().includes(lower) ||
|
|
84
|
+
s.displayName?.toLowerCase().includes(lower) ||
|
|
85
|
+
s.description?.toLowerCase().includes(lower)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3.4 UI 输出
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
# npm 搜索结果
|
|
94
|
+
$ skm ls -s "brainstorm"
|
|
95
|
+
|
|
96
|
+
📦 brainstorming@1.0.0 (Found 1 match for "brainstorm")
|
|
97
|
+
名称: Brainstorming
|
|
98
|
+
描述: 多智能体头脑风暴能力
|
|
99
|
+
平台: opencode
|
|
100
|
+
链接: https://npmjs.com/package/brainstorming
|
|
101
|
+
|
|
102
|
+
# 本地搜索结果
|
|
103
|
+
$ skm ls --installed -s "test"
|
|
104
|
+
|
|
105
|
+
🟢 test-skill-1@1.0.0
|
|
106
|
+
Platforms: opencode
|
|
107
|
+
Installed: 2026-04-16
|
|
108
|
+
|
|
109
|
+
🟢 test-skill-2@1.0.0
|
|
110
|
+
Platforms: opencode
|
|
111
|
+
Installed: 2026-04-15
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 4. 实施步骤
|
|
115
|
+
|
|
116
|
+
1. **修改 `ls.ts` 命令模块**
|
|
117
|
+
- 添加 `search` 选项到 `LsOptions` 接口
|
|
118
|
+
- 实现关键字过滤逻辑
|
|
119
|
+
- 支持 npm search API 和本地过滤
|
|
120
|
+
|
|
121
|
+
2. **修改 `cli.ts`**
|
|
122
|
+
- 在 `ls` 命令中添加 `--search` / `-s` 选项
|
|
123
|
+
|
|
124
|
+
3. **测试**
|
|
125
|
+
- `skm ls -s <keyword>`
|
|
126
|
+
- `skm ls --installed -s <keyword>`
|
|
127
|
+
- 边界情况:无结果、分页
|
|
128
|
+
|
|
129
|
+
## 5. 优先级
|
|
130
|
+
|
|
131
|
+
**P1 - 高**
|
|
132
|
+
- 核心搜索功能
|
|
133
|
+
- npm registry 搜索
|
|
134
|
+
|
|
135
|
+
**P2 - 中**
|
|
136
|
+
- 本地已安装 skills 搜索
|
|
137
|
+
- 搜索结果高亮(可选)
|
|
138
|
+
|
|
139
|
+
## 6. 风险与注意事项
|
|
140
|
+
|
|
141
|
+
- npm search API 有速率限制,生产环境考虑缓存
|
|
142
|
+
- 中文关键字搜索需要确认 npm API 支持
|
|
143
|
+
- 大结果集需要分页处理
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -29,6 +29,15 @@
|
|
|
29
29
|
|
|
30
30
|
// Commander.js - 轻量级的命令行界面框架
|
|
31
31
|
import { Command } from 'commander';
|
|
32
|
+
import { readFileSync } from 'fs';
|
|
33
|
+
import { fileURLToPath } from 'url';
|
|
34
|
+
import { dirname, resolve } from 'path';
|
|
35
|
+
|
|
36
|
+
// 获取 package.json 中的版本号
|
|
37
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
38
|
+
const __dirname = dirname(__filename);
|
|
39
|
+
const packageJson = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'));
|
|
40
|
+
const VERSION = packageJson.version;
|
|
32
41
|
|
|
33
42
|
// 内部模块导入
|
|
34
43
|
import { PLATFORMS } from './constants.js'; // 平台常量
|
|
@@ -65,7 +74,7 @@ const program = new Command();
|
|
|
65
74
|
program
|
|
66
75
|
.name('skm')
|
|
67
76
|
.description('SkillMarket - Cross-platform skill manager for AI coding tools')
|
|
68
|
-
.version(
|
|
77
|
+
.version(VERSION);
|
|
69
78
|
|
|
70
79
|
// -----------------------------------------------------------------------------
|
|
71
80
|
// 帮助命令 (-h, --help)
|
|
@@ -90,6 +99,7 @@ Commands:
|
|
|
90
99
|
--updates Check for updates
|
|
91
100
|
--page <n> Page number (default: 1)
|
|
92
101
|
--limit <n> Items per page (default: 20)
|
|
102
|
+
-s, --search Search by keyword
|
|
93
103
|
info <skill-id> Display skill information
|
|
94
104
|
install <skill> Install a skill
|
|
95
105
|
@version Install specific version
|
|
@@ -139,12 +149,14 @@ lsCmd
|
|
|
139
149
|
.option('--updates', 'Check for updates')
|
|
140
150
|
.option('-p, --page <number>', 'Page number (default: 1)', parseInt)
|
|
141
151
|
.option('-l, --limit <number>', 'Items per page (default: 20)', parseInt)
|
|
152
|
+
.option('-s, --search <keyword>', 'Search by keyword (id, displayName, description)')
|
|
142
153
|
.action((opts) => {
|
|
143
154
|
// Ensure numeric options have default values if not provided
|
|
144
155
|
const options = {
|
|
145
156
|
...opts,
|
|
146
157
|
page: opts.page ?? 1,
|
|
147
|
-
limit: opts.limit ?? 20
|
|
158
|
+
limit: opts.limit ?? 20,
|
|
159
|
+
search: opts.search
|
|
148
160
|
};
|
|
149
161
|
listSkills(options);
|
|
150
162
|
});
|
package/src/commands/ls.ts
CHANGED
|
@@ -43,6 +43,9 @@ interface LsOptions {
|
|
|
43
43
|
|
|
44
44
|
/** 每页数量 */
|
|
45
45
|
limit?: number;
|
|
46
|
+
|
|
47
|
+
/** 搜索关键字(支持 id, displayName, description) */
|
|
48
|
+
search?: string;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
// -----------------------------------------------------------------------------
|
|
@@ -65,32 +68,72 @@ interface LsOptions {
|
|
|
65
68
|
* // 列出已安装的 skills
|
|
66
69
|
* await listSkills({ installed: true });
|
|
67
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
|
+
|
|
68
97
|
export async function listSkills(options: LsOptions): Promise<void> {
|
|
69
|
-
const { installed, updates, page = 1, limit = 20 } = options;
|
|
98
|
+
const { installed, updates, page = 1, limit = 20, search } = options;
|
|
70
99
|
|
|
71
100
|
// -------------------------------------------------------------------------
|
|
72
101
|
// 模式1: 显示已安装的 skills
|
|
73
102
|
// -------------------------------------------------------------------------
|
|
74
103
|
if (installed) {
|
|
75
|
-
|
|
104
|
+
let skills = await getInstalledSkills();
|
|
105
|
+
|
|
106
|
+
// 搜索关键字过滤(仅本地)
|
|
107
|
+
if (search) {
|
|
108
|
+
skills = filterInstalledSkills(skills, search);
|
|
109
|
+
}
|
|
110
|
+
|
|
76
111
|
const total = skills.length;
|
|
77
112
|
const totalPages = Math.ceil(total / limit) || 1;
|
|
78
113
|
const currentPage = Math.min(Math.max(1, page), totalPages);
|
|
79
114
|
|
|
80
115
|
// 无已安装 skills 时给出提示
|
|
81
116
|
if (skills.length === 0) {
|
|
82
|
-
|
|
117
|
+
if (search) {
|
|
118
|
+
console.log(`No skills found matching "${search}".`);
|
|
119
|
+
} else {
|
|
120
|
+
console.log('No skills installed yet. Run "skm ls" to see available skills.');
|
|
121
|
+
}
|
|
83
122
|
return;
|
|
84
123
|
}
|
|
85
124
|
|
|
125
|
+
// 搜索结果提示
|
|
126
|
+
if (search) {
|
|
127
|
+
console.log(`Found ${total} match(es) for "${search}":\n`);
|
|
128
|
+
} else {
|
|
129
|
+
console.log(`Installed Skills (${total}):\n`);
|
|
130
|
+
}
|
|
131
|
+
|
|
86
132
|
// 计算分页范围
|
|
87
133
|
const start = (currentPage - 1) * limit;
|
|
88
134
|
const end = Math.min(start + limit, total);
|
|
89
135
|
const pageSkills = skills.slice(start, end);
|
|
90
136
|
|
|
91
|
-
// 打印表头
|
|
92
|
-
console.log(`Installed Skills (${total}):\n`);
|
|
93
|
-
|
|
94
137
|
// 遍历并打印每个 skill 的详细信息
|
|
95
138
|
for (const skill of pageSkills) {
|
|
96
139
|
// skill 名称和版本
|
|
@@ -117,7 +160,11 @@ export async function listSkills(options: LsOptions): Promise<void> {
|
|
|
117
160
|
// -------------------------------------------------------------------------
|
|
118
161
|
|
|
119
162
|
// 提示用户正在搜索
|
|
120
|
-
|
|
163
|
+
if (search) {
|
|
164
|
+
console.log(`Searching npm for "${search}"...\n`);
|
|
165
|
+
} else {
|
|
166
|
+
console.log('Searching npm registry...\n');
|
|
167
|
+
}
|
|
121
168
|
|
|
122
169
|
try {
|
|
123
170
|
// 计算分页偏移量
|
|
@@ -126,7 +173,8 @@ export async function listSkills(options: LsOptions): Promise<void> {
|
|
|
126
173
|
// 调用 npm search API 搜索 skillmarket 相关包
|
|
127
174
|
const { packages, total } = await searchSkillmarketPackages({
|
|
128
175
|
from: offset,
|
|
129
|
-
size: limit
|
|
176
|
+
size: limit,
|
|
177
|
+
keyword: search
|
|
130
178
|
});
|
|
131
179
|
|
|
132
180
|
const totalPages = Math.ceil(total / limit) || 1;
|
|
@@ -134,12 +182,20 @@ export async function listSkills(options: LsOptions): Promise<void> {
|
|
|
134
182
|
|
|
135
183
|
// 无搜索结果时
|
|
136
184
|
if (packages.length === 0) {
|
|
137
|
-
|
|
185
|
+
if (search) {
|
|
186
|
+
console.log(`No skills found matching "${search}".`);
|
|
187
|
+
} else {
|
|
188
|
+
console.log('No skills found. Check back later!');
|
|
189
|
+
}
|
|
138
190
|
return;
|
|
139
191
|
}
|
|
140
192
|
|
|
141
193
|
// 打印找到的包数量
|
|
142
|
-
|
|
194
|
+
if (search) {
|
|
195
|
+
console.log(`Found ${total} match(es) for "${search}":\n`);
|
|
196
|
+
} else {
|
|
197
|
+
console.log(`Found ${total} skill(s):\n`);
|
|
198
|
+
}
|
|
143
199
|
|
|
144
200
|
// 遍历每个包,获取详细信息并显示
|
|
145
201
|
for (const pkgName of packages) {
|
package/src/commands/npm.ts
CHANGED
|
@@ -265,8 +265,9 @@ export async function fetchSkillPackage(skillId: string): Promise<NpmRegistryRes
|
|
|
265
265
|
export async function searchSkillmarketPackages(options: {
|
|
266
266
|
from?: number;
|
|
267
267
|
size?: number;
|
|
268
|
+
keyword?: string;
|
|
268
269
|
} = {}): Promise<{ packages: string[]; total: number }> {
|
|
269
|
-
const { from = 0, size = 100 } = options;
|
|
270
|
+
const { from = 0, size = 100, keyword } = options;
|
|
270
271
|
const packages: string[] = [];
|
|
271
272
|
let total = 0;
|
|
272
273
|
|
|
@@ -278,7 +279,15 @@ export async function searchSkillmarketPackages(options: {
|
|
|
278
279
|
// text: 搜索关键字
|
|
279
280
|
// size: 返回结果数量上限
|
|
280
281
|
// from: 起始位置(分页用)
|
|
281
|
-
|
|
282
|
+
// 如果有关键字,组合搜索条件
|
|
283
|
+
if (keyword) {
|
|
284
|
+
// 组合搜索:keywords:skillmarket AND 关键字
|
|
285
|
+
// 注意:npm search 不直接支持 AND,这里用多个 text 参数
|
|
286
|
+
// 或者直接在 text 中组合
|
|
287
|
+
url.searchParams.set('text', `${keyword} keywords:skillmarket`);
|
|
288
|
+
} else {
|
|
289
|
+
url.searchParams.set('text', 'keywords:skillmarket');
|
|
290
|
+
}
|
|
282
291
|
url.searchParams.set('size', String(size));
|
|
283
292
|
url.searchParams.set('from', String(from));
|
|
284
293
|
|