skillhelper 0.4.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/LICENSE +21 -0
- package/README.md +105 -0
- package/bin/lib/atomic-write.mjs +72 -0
- package/bin/lib/paths.mjs +65 -0
- package/bin/lib/port.mjs +23 -0
- package/bin/skillhelper.mjs +720 -0
- package/build/build-site.mjs +35 -0
- package/build/build-web.mjs +29 -0
- package/build/pack-smoke.mjs +195 -0
- package/build/prepare-publish.mjs +3 -0
- package/build/verify.mjs +157 -0
- package/config/sources.example.yaml +126 -0
- package/docs/Frontend-Spec.md +1207 -0
- package/docs/scan_skills_rules.md +432 -0
- package/package.json +95 -0
- package/packages/scanner/package.json +15 -0
- package/packages/scanner/src/adapters/claude-agents.mjs +134 -0
- package/packages/scanner/src/adapters/claude-plugin.mjs +260 -0
- package/packages/scanner/src/adapters/codex-plugin.mjs +330 -0
- package/packages/scanner/src/adapters/directory-skill.mjs +239 -0
- package/packages/scanner/src/adapters/file-docs.mjs +158 -0
- package/packages/scanner/src/adapters/hermes-plugin.mjs +149 -0
- package/packages/scanner/src/adapters/markdown-skill.mjs +229 -0
- package/packages/scanner/src/adapters/mcp-config.mjs +190 -0
- package/packages/scanner/src/adapters/scan-tier.mjs +99 -0
- package/packages/scanner/src/adapters/skill-adapter.mjs +345 -0
- package/packages/scanner/src/adapters/tier1-editor-skills.mjs +278 -0
- package/packages/scanner/src/adapters/tier2-user-skills.mjs +218 -0
- package/packages/scanner/src/adapters/tier3-other-skills.mjs +225 -0
- package/packages/scanner/src/config/editor-tiers.mjs +191 -0
- package/packages/scanner/src/core/atomic-write.mjs +11 -0
- package/packages/scanner/src/core/descriptor.mjs +143 -0
- package/packages/scanner/src/core/path-hash.mjs +89 -0
- package/packages/scanner/src/core/registry.mjs +128 -0
- package/packages/scanner/src/hash/path-hash.mjs +8 -0
- package/packages/scanner/src/icon/brand-map.mjs +268 -0
- package/packages/scanner/src/icon/icon-extractor.mjs +466 -0
- package/packages/scanner/src/index.mjs +450 -0
- package/packages/scanner/src/types.d.ts +144 -0
- package/packages/scanner/src/utils.mjs +194 -0
- package/packages/server/package.json +15 -0
- package/packages/server/src/index.mjs +1071 -0
- package/packages/server/src/labels.mjs +64 -0
- package/packages/server/src/langdetect.mjs +34 -0
- package/packages/server/src/llm-client.mjs +42 -0
- package/packages/server/src/translate-cache.mjs +132 -0
- package/packages/server/src/translator.mjs +53 -0
- package/packages/web/README.md +36 -0
- package/packages/web/dist/assets/index-CGt04vt0.js +304 -0
- package/packages/web/dist/assets/index-CLSDoa3H.css +1 -0
- package/packages/web/dist/brand-icon.png +0 -0
- package/packages/web/dist/brand-logo.png +0 -0
- package/packages/web/dist/favicon-16x16.png +0 -0
- package/packages/web/dist/favicon-192x192.png +0 -0
- package/packages/web/dist/favicon-32x32.png +0 -0
- package/packages/web/dist/favicon-512x512.png +0 -0
- package/packages/web/dist/favicon-source.svg +5 -0
- package/packages/web/dist/favicon.ico +0 -0
- package/packages/web/dist/favicon.svg +5 -0
- package/packages/web/dist/icons/hermes-128.png +0 -0
- package/packages/web/dist/icons/hermes-192.png +0 -0
- package/packages/web/dist/icons/hermes-256.png +0 -0
- package/packages/web/dist/icons/hermes-32.png +0 -0
- package/packages/web/dist/icons/hermes-512.png +0 -0
- package/packages/web/dist/icons/hermes-64.png +0 -0
- package/packages/web/dist/icons/hermes.png +0 -0
- package/packages/web/dist/index.html +37 -0
- package/packages/web/dist/robots.txt +3 -0
- package/packages/web/dist/site.webmanifest +24 -0
- package/packages/web/public/brand-icon.png +0 -0
- package/packages/web/public/brand-logo.png +0 -0
- package/packages/web/public/favicon-16x16.png +0 -0
- package/packages/web/public/favicon-192x192.png +0 -0
- package/packages/web/public/favicon-32x32.png +0 -0
- package/packages/web/public/favicon-512x512.png +0 -0
- package/packages/web/public/favicon-source.svg +5 -0
- package/packages/web/public/favicon.ico +0 -0
- package/packages/web/public/favicon.svg +5 -0
- package/packages/web/public/icons/hermes-128.png +0 -0
- package/packages/web/public/icons/hermes-192.png +0 -0
- package/packages/web/public/icons/hermes-256.png +0 -0
- package/packages/web/public/icons/hermes-32.png +0 -0
- package/packages/web/public/icons/hermes-512.png +0 -0
- package/packages/web/public/icons/hermes-64.png +0 -0
- package/packages/web/public/icons/hermes.png +0 -0
- package/packages/web/public/robots.txt +3 -0
- package/packages/web/public/site.webmanifest +24 -0
- package/scripts/install-and-sync.sh +209 -0
- package/scripts/sync-skills.sh +605 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SkillHelper
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# SkillHelper
|
|
2
|
+
|
|
3
|
+
> 把散落在本机、编辑器、插件与 MCP 配置中的 AI 能力,整理成一个可搜索、可理解、可持续维护的工作台。
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/skillhelper)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
|
|
9
|
+
**SkillHelper** 是产品对外名称。npm 包名和 CLI 命令当前使用可发布的小写形式 `skillhelper`。
|
|
10
|
+
|
|
11
|
+
## 不再靠记忆找技能
|
|
12
|
+
|
|
13
|
+
AI 工作流越来越复杂:技能文件散在不同编辑器里,插件与 MCP 配置各有入口,说明大多是英文,想确认“我到底装了什么、在哪、怎么用”往往要翻目录、查配置、切窗口。
|
|
14
|
+
|
|
15
|
+
SkillHelper 把这些本地能力汇总为一个清晰的面板:
|
|
16
|
+
|
|
17
|
+
- **发现**:扫描编辑器技能、个人技能库、插件、MCP 配置和项目运行手册。
|
|
18
|
+
- **整理**:按来源与三级结构归类,支持搜索、筛选、排序和分组。
|
|
19
|
+
- **理解**:保留原始内容,并为可翻译的说明提供中文对照;命令和代码块不被误译。
|
|
20
|
+
- **识别**:优先展示本机应用的真实图标,缺失时自动回退到清晰的标识。
|
|
21
|
+
|
|
22
|
+
```mermaid
|
|
23
|
+
flowchart LR
|
|
24
|
+
A[本机技能与配置] --> B[扫描与去重]
|
|
25
|
+
B --> C[统一索引]
|
|
26
|
+
C --> D[搜索 / 分类 / 翻译]
|
|
27
|
+
D --> E[SkillHelper 工作台]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 适合谁
|
|
31
|
+
|
|
32
|
+
- 同时使用 Codex、Claude、Cursor、VS Code 等 AI 开发工具的人。
|
|
33
|
+
- 在维护个人技能库、插件和 MCP 服务,却缺少统一视图的人。
|
|
34
|
+
- 希望让本地 AI 工作流更可见、更可检索、更便于交接的开发者和工具作者。
|
|
35
|
+
|
|
36
|
+
## 60 秒上手
|
|
37
|
+
|
|
38
|
+
需要 Node.js 20 或更高版本。
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g skillhelper@latest
|
|
42
|
+
skillhelper start
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
命令会扫描已配置的来源、在后台启动本地服务并打开浏览器。首次使用时,可先生成并调整扫描来源:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
skillhelper init
|
|
49
|
+
# 编辑 ~/.config/skillhelper/sources.yaml
|
|
50
|
+
skillhelper restart
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
常用命令:
|
|
54
|
+
|
|
55
|
+
| 命令 | 用途 |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `skillhelper start` | 扫描并启动本地工作台 |
|
|
58
|
+
| `skillhelper stats` | 查看扫描统计与来源概览 |
|
|
59
|
+
| `skillhelper scan` | 仅扫描并输出结构化数据 |
|
|
60
|
+
| `skillhelper sync` | 将当前技能同步到选定编辑器 |
|
|
61
|
+
| `skillhelper stop` | 停止后台服务 |
|
|
62
|
+
|
|
63
|
+
## 开发
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/aquarkgn/SkillsHelper.git
|
|
67
|
+
cd SkillsHelper
|
|
68
|
+
npm install
|
|
69
|
+
npm run dev
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`npm run dev` 会重启本地 API 并启动 Vite 开发服务器。常用校验:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm test
|
|
76
|
+
npm run typecheck:web
|
|
77
|
+
npm run typecheck:site
|
|
78
|
+
npm run build:site
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 本地优先,也说明边界
|
|
82
|
+
|
|
83
|
+
- 扫描、索引、图标缓存和用户配置保存在本机;扫描器只读取已配置的文件来源。
|
|
84
|
+
- MCP 配置在进入展示层前会做敏感字段脱敏。
|
|
85
|
+
- 中文对照使用翻译服务时,需要网络连接;翻译结果会缓存在本地,避免重复请求。代码块和命令保持原样。
|
|
86
|
+
|
|
87
|
+
详细扫描范围、分级与图标降级规则见 [扫描技能规则](./docs/scan_skills_rules.md)。
|
|
88
|
+
|
|
89
|
+
## 项目结构
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
packages/
|
|
93
|
+
├── scanner/ # 多来源扫描、去重、图标解析
|
|
94
|
+
├── server/ # Fastify API、翻译与缓存
|
|
95
|
+
├── web/ # 本地工作台
|
|
96
|
+
└── site/ # SkillHelper 对外介绍页
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 推广与产品资料
|
|
100
|
+
|
|
101
|
+
推广计划、镜头资产管理、发布素材规范和视频文件索引统一维护在 `.hermes/plans/skillhelper-promotion/`。这里的文档以“真实录屏为证据、AI 只辅助包装”为原则,并会随着版本发布更新。
|
|
102
|
+
|
|
103
|
+
## 贡献与许可
|
|
104
|
+
|
|
105
|
+
欢迎通过 [Issues](https://github.com/aquarkgn/SkillsHelper/issues) 提交问题或建议。项目采用 [MIT License](./LICENSE)。
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file atomic-write.mjs
|
|
3
|
+
* 原子写工具:临时文件 + rename,保证进程中断不产生半截文件。
|
|
4
|
+
*
|
|
5
|
+
* 对齐 cockpit-tools 报告的"结构化编辑 + 原子写回"原则。
|
|
6
|
+
* SkillHelper 是只读扫描工具,真实写只有三处(翻译缓存、图标缓存、
|
|
7
|
+
* state.json),频率低风险低,这里只提供原子写 + 失败回退,不上
|
|
8
|
+
* 完整审计链(价值不匹配,见迭代计划 2.3)。
|
|
9
|
+
*
|
|
10
|
+
* rename 在同文件系统内原子有效。临时文件与目标文件在同一目录,
|
|
11
|
+
* 必同文件系统。若目标路径被设到跨挂载点导致 rename 失败,
|
|
12
|
+
* 回退为直接写(保留原行为,不比现状更差)。
|
|
13
|
+
*
|
|
14
|
+
* 2026-07 阶段三:从 packages/scanner/src/core/atomic-write.mjs 提到 bin/lib,
|
|
15
|
+
* 作为底层公共模块。scanner/src/core/atomic-write.mjs 改为 re-export,保持
|
|
16
|
+
* 现有 scanner 包内引用不变。paths.mjs 的 writeJson 改调本模块。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 原子写文本文件。
|
|
24
|
+
* @param {string} file - 目标文件绝对路径
|
|
25
|
+
* @param {string} content - 文本内容
|
|
26
|
+
* @param {string} [encoding='utf8']
|
|
27
|
+
* @returns {void}
|
|
28
|
+
*/
|
|
29
|
+
export function atomicWriteText(file, content, encoding = 'utf8') {
|
|
30
|
+
const dir = path.dirname(file);
|
|
31
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
const tmp = `${file}.tmp.${process.pid}`;
|
|
33
|
+
try {
|
|
34
|
+
fs.writeFileSync(tmp, content, encoding);
|
|
35
|
+
fs.renameSync(tmp, file);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// 回退:删除可能残留的临时文件,避免堆积
|
|
38
|
+
try { fs.unlinkSync(tmp); } catch { /* ignore */ }
|
|
39
|
+
// rename 失败(如跨文件系统)回退为直接写,保留原行为
|
|
40
|
+
fs.writeFileSync(file, content, encoding);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 原子写二进制文件(图标 png 等)。
|
|
46
|
+
* @param {string} file - 目标文件绝对路径
|
|
47
|
+
* @param {Buffer} bytes - 二进制内容
|
|
48
|
+
* @returns {void}
|
|
49
|
+
*/
|
|
50
|
+
export function atomicWriteBytes(file, bytes) {
|
|
51
|
+
const dir = path.dirname(file);
|
|
52
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
53
|
+
const tmp = `${file}.tmp.${process.pid}`;
|
|
54
|
+
try {
|
|
55
|
+
fs.writeFileSync(tmp, bytes);
|
|
56
|
+
fs.renameSync(tmp, file);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
try { fs.unlinkSync(tmp); } catch { /* ignore */ }
|
|
59
|
+
fs.writeFileSync(file, bytes);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 原子写 JSON 文件(pretty 格式,与原 writeJson 输出一致)。
|
|
65
|
+
* @param {string} file - 目标文件绝对路径
|
|
66
|
+
* @param {unknown} obj - 可序列化对象
|
|
67
|
+
* @param {number} [space=2] - JSON 缩进
|
|
68
|
+
* @returns {void}
|
|
69
|
+
*/
|
|
70
|
+
export function atomicWriteJson(file, obj, space = 2) {
|
|
71
|
+
atomicWriteText(file, JSON.stringify(obj, null, space));
|
|
72
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// XDG-style paths for skillhelper.
|
|
2
|
+
// All user-side state lives under SKILLHELPER_HOME (default: ~/.config/skillhelper).
|
|
3
|
+
// Nothing is written outside this dir, so `purge` cleans everything.
|
|
4
|
+
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import { atomicWriteJson } from './atomic-write.mjs';
|
|
9
|
+
|
|
10
|
+
export function homeDir() {
|
|
11
|
+
if (process.env.SKILLHELPER_HOME && process.env.SKILLHELPER_HOME.trim()) {
|
|
12
|
+
return path.resolve(expandTilde(process.env.SKILLHELPER_HOME));
|
|
13
|
+
}
|
|
14
|
+
// XDG_CONFIG_HOME respected if set, else ~/.config
|
|
15
|
+
const xdg = process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME.trim()
|
|
16
|
+
? path.resolve(expandTilde(process.env.XDG_CONFIG_HOME))
|
|
17
|
+
: path.join(os.homedir(), '.config');
|
|
18
|
+
return path.join(xdg, 'skillhelper');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function configFile() {
|
|
22
|
+
return path.join(homeDir(), 'sources.yaml');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function cacheFile() {
|
|
26
|
+
return path.join(homeDir(), 'cache.json');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 翻译结果持久化缓存(md5 key → 译文)。purge 删除整个 homeDir 时随之清理。
|
|
30
|
+
export function translateCacheFile() {
|
|
31
|
+
return path.join(homeDir(), 'translate-cache.json');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function stateFile() {
|
|
35
|
+
return path.join(homeDir(), 'state.json');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function ensureHomeDir() {
|
|
39
|
+
const dir = homeDir();
|
|
40
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
41
|
+
return dir;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function expandTilde(p) {
|
|
45
|
+
if (!p) return p;
|
|
46
|
+
if (p === '~' || p.startsWith('~/')) {
|
|
47
|
+
return path.join(os.homedir(), p.slice(2));
|
|
48
|
+
}
|
|
49
|
+
return p;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function writeJson(file, obj) {
|
|
53
|
+
// v0.4 阶段三:原子写(临时文件 + rename),进程中断不残留半截 JSON。
|
|
54
|
+
// ensureHomeDir 由 atomicWriteJson 内部 mkdirSync 处理,但保留以确保与旧行为一致。
|
|
55
|
+
ensureHomeDir();
|
|
56
|
+
atomicWriteJson(file, obj);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function readJson(file, fallback = null) {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
62
|
+
} catch {
|
|
63
|
+
return fallback;
|
|
64
|
+
}
|
|
65
|
+
}
|
package/bin/lib/port.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Pick a free port starting from `preferred`, walking up to `preferred + range`.
|
|
2
|
+
// Returns the port that successfully bound (we then close immediately so the
|
|
3
|
+
// real server can claim it). On any error or full exhaustion, returns null.
|
|
4
|
+
|
|
5
|
+
import net from 'node:net';
|
|
6
|
+
|
|
7
|
+
export async function pickPort(preferred = 11520, range = 10) {
|
|
8
|
+
for (let p = preferred; p <= preferred + range; p++) {
|
|
9
|
+
if (await isFree(p)) return p;
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isFree(port) {
|
|
15
|
+
return new Promise(resolve => {
|
|
16
|
+
const srv = net.createServer();
|
|
17
|
+
srv.unref();
|
|
18
|
+
srv.once('error', () => resolve(false));
|
|
19
|
+
srv.listen({ port, host: '127.0.0.1' }, () => {
|
|
20
|
+
srv.close(() => resolve(true));
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|