@tec-explorer/skillpot 0.9.0 → 0.11.0
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 +15 -0
- package/README.en.md +87 -0
- package/README.md +2 -0
- package/dist/cli.mjs +276 -7
- package/dist/gui/assets/index-BqsgUmKh.js +40 -0
- package/dist/gui/index.html +1 -1
- package/package.json +1 -1
- package/dist/gui/assets/index-DN1MQ9V0.js +0 -40
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
所有显著变更记录于此。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
4
4
|
版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。
|
|
5
5
|
|
|
6
|
+
## [0.11.0] - 2026-09-04
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **主线 C——落地策略**:B 档 copy 机制(适配器 `materialize: 'copy'`,enable 即重新拷贝刷新副本;doctor/台账/矩阵全链路兼容 copy)与**广播模式** `skillpot broadcast <skill> [--off]`(跨工具共享目录 `~/.agents/skills/`,显式开关、台账化)
|
|
10
|
+
- **主线 D——skills.sh 目录集成**:`skillpot search <关键词>`(匿名 `/api/search`,带安装量,与官方 npx skills CLI 同源)与 `skillpot install-search <owner/repo/slug> [--for agents]`(克隆缓存解析子目录后安装)
|
|
11
|
+
- Agent 注册表扩至八家:新增 **Amp**(`~/.config/amp/skills/`,官方文档确认);Cursor 验证依据升级为官方文档;dsh verified 按调研结论如实降级
|
|
12
|
+
- 英文版 `README.en.md`(中英互链)
|
|
13
|
+
|
|
14
|
+
## [0.10.0] - 2026-09-04
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **GUI「团队」页签**:团队对齐可视化——填入清单路径即可**预览**(逐项标注本机安装状态/版本锁一致性/中央仓库缺失/local 仅本机有效),**对齐**一键执行(可**预演**不落地),**导出清单**从当前中央仓库生成;清单路径跨会话记忆
|
|
18
|
+
- 新增 `inspectManifest`(清单体检)与 `/api/team/inspect|sync|export` 三个路由
|
|
19
|
+
- 修复:清单体检对"config 有登记但中央仓库目录已删除"的损坏状态误报 ENOENT——现精确标注"中央仓库缺失(对齐将重装)"
|
|
20
|
+
|
|
6
21
|
## [0.9.0] - 2026-09-04
|
|
7
22
|
|
|
8
23
|
### Added
|
package/README.en.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# SkillPot
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://www.npmjs.com/package/@tec-explorer/skillpot)
|
|
5
|
+
[](package.json)
|
|
6
|
+
|
|
7
|
+
**The skill manager for coding agents — install once, expose per agent, update once.**
|
|
8
|
+
|
|
9
|
+
> Coding agents (Claude Code, ZCode, Codex, OpenCode, Gemini CLI, DeepSeek CLI, Cursor, Amp…) have converged on the same `SKILL.md` open standard, but each discovers skills from its own directory: a skill installed into `~/.claude/skills` only works for Claude Code. SkillPot keeps every skill in one central store and exposes it per agent via symlinks — with a switch matrix, consistency doctor, security lint, and team alignment.
|
|
10
|
+
|
|
11
|
+
[中文文档](README.md) | 📖 [Feature guide with screenshots](docs/guide.md)
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
## Why SkillPot
|
|
16
|
+
|
|
17
|
+
Registries and marketplaces (skills.sh, Anthropic marketplace) answer *"where do I find skills"* — they are the **upstream**. SkillPot answers *"where does it install, who sees it, how do I stop it, how do I update it"* — the **management layer**:
|
|
18
|
+
|
|
19
|
+
- **One central store** at `~/.skillpot/skills/` — a single source of truth with checksums and a lockfile
|
|
20
|
+
- **Per-agent switch matrix** — `config.yaml` drives a symlink sync engine; flip switches in the GUI, the TUI, or the CLI
|
|
21
|
+
- **8 agents**: Claude Code, ZCode, Codex CLI, OpenCode, Gemini CLI, DeepSeek CLI (dsh), Cursor, Amp
|
|
22
|
+
- **Doctor**: broken links, drift, shadowed names, orphaned links — `--fix` repairs automatically
|
|
23
|
+
- **Security lint** on install: frontmatter integrity + dangerous script patterns (`rm -rf`, `curl | sh`, credential access, data exfiltration…)
|
|
24
|
+
- **Adopt** existing skills scattered across agent directories (copy or move mode)
|
|
25
|
+
- **Update** git-sourced skills in place (symlinks keep working, no re-linking) with file-level diffs
|
|
26
|
+
- **Market**: browse and one-click install from built-in sources — Anthropic, Vercel, Superpowers, Matt Pocock — or any custom git repo; search the skills.sh directory anonymously
|
|
27
|
+
- **Team alignment**: commit a `.skillpot.yaml` manifest, teammates run `skillpot sync` to match it
|
|
28
|
+
- **MCP bridge**: any MCP-capable agent can consume the central store, still filtered by the switch matrix
|
|
29
|
+
- **Broadcast mode**: expose a skill to the cross-tool `~/.agents/skills/` shared directory on demand
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
Requires Node ≥ 18.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g @tec-explorer/skillpot # or: npx @tec-explorer/skillpot (alias: spot)
|
|
37
|
+
|
|
38
|
+
skillpot init # create ~/.skillpot and detect installed agents
|
|
39
|
+
skillpot adopt --dry-run # preview: existing skills found in agent dirs
|
|
40
|
+
skillpot adopt --move # adopt with move mode (original dir becomes a symlink)
|
|
41
|
+
skillpot gui # web console: matrix / doctor / adopt / install / market / maintain / team
|
|
42
|
+
skillpot add ~/demo/my-skill # install a skill (exposed to no agent by default)
|
|
43
|
+
skillpot enable my-skill --for claude-code,codex
|
|
44
|
+
skillpot doctor # consistency check (--fix to repair)
|
|
45
|
+
skillpot search "commit message" # search the skills.sh directory
|
|
46
|
+
skillpot install-search anthropics/skills/pdf # install a directory result
|
|
47
|
+
skillpot sync # align with a project .skillpot.yaml manifest
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> Agents scan their skill directories at session start — restart a session after enable/disable.
|
|
51
|
+
|
|
52
|
+
## How it works
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
~/.skillpot/
|
|
56
|
+
├── skills/<name>/SKILL.md # central store: the single copy (self-contained, symlinks dereferenced)
|
|
57
|
+
├── config.yaml # sources / checksums + the skill × agent switch matrix
|
|
58
|
+
├── state.json # ledger of links this tool created (uninstall only touches these)
|
|
59
|
+
├── skillspot.lock.json # machine-readable snapshot (team sharing / audit)
|
|
60
|
+
└── cache/market/ # clone cache for market sources
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`enable` creates a symlink from the agent's skills directory into the central store — agents discover it on their next session scan. `disable` removes it. The tool only ever touches paths recorded in its own ledger.
|
|
64
|
+
|
|
65
|
+
Landing strategies per agent: **A** symlink (default) → **B** copy + resync (agents that don't follow symlinks) → **C** MCP bridge (universal fallback).
|
|
66
|
+
|
|
67
|
+
## Documentation
|
|
68
|
+
|
|
69
|
+
- [Feature guide (screenshots)](docs/guide.md) — Chinese, with screenshots of every surface
|
|
70
|
+
- [Design: agent adapters](docs/design/agent-adapters.md) · [Design: MCP bridge](docs/design/mcp-bridge.md) · [Product plan](docs/product/product-plan.md)
|
|
71
|
+
|
|
72
|
+
## Security
|
|
73
|
+
|
|
74
|
+
Skills are instructions injected into model context plus optionally executable scripts. SkillPot's defaults: install lints before exposing, nothing is exposed to any agent until you say so, uninstall/disable only touches ledgered paths, and the web console listens on 127.0.0.1 with token-gated writes. Report vulnerabilities via [SECURITY.md](SECURITY.md).
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install
|
|
80
|
+
npm test # vitest, sandboxed (never touches your real HOME)
|
|
81
|
+
npm run test:e2e # sandboxed end-to-end smoke
|
|
82
|
+
npm run build # typecheck + esbuild bundle + web GUI build
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
[MIT](LICENSE)
|
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
**跨编程 Agent 的 Skill 管理器 —— 一处安装,按 Agent 开关,一处更新。**
|
|
10
10
|
*Cross-agent skill manager for coding agents: install once, expose per agent, update once.*
|
|
11
11
|
|
|
12
|
+
[English](README.en.md) | 中文
|
|
13
|
+
|
|
12
14
|
> 编程 Agent(Claude Code、ZCode、Codex、OpenCode、Gemini CLI、DeepSeek CLI、Cursor…)已收敛到同一套 `SKILL.md` 开放标准,但发现路径各自为政:装进 `~/.claude/skills` 就只对 Claude Code 生效。SkillPot 把 skill 收进一个中央仓库,按 Agent 粒度开关暴露,并提供冲突体检与安全扫描。
|
|
13
15
|
|
|
14
16
|

|
package/dist/cli.mjs
CHANGED
|
@@ -38684,7 +38684,7 @@ var AGENTS = [
|
|
|
38684
38684
|
binaries: ["dsh"],
|
|
38685
38685
|
fingerprints: (home) => [path2.join(home, ".dsh")],
|
|
38686
38686
|
skillsDir: (home) => path2.join(home, ".dsh", "skills"),
|
|
38687
|
-
verified: "\u76EE\u5F55\u7EA6\u5B9A\u540C Claude\uFF08~/.dsh/skills + SKILL.md\uFF09\uFF1B\
|
|
38687
|
+
verified: "\u76EE\u5F55\u7EA6\u5B9A\u540C Claude\uFF08~/.dsh/skills + SKILL.md\uFF09\uFF1B\u4F46 0.1.2-rc.1 \u4EE3\u7801\u672A\u89C1 skills \u8BFB\u53D6\u903B\u8F91\uFF08\u8BE5\u76EE\u5F55\u7684\u6D88\u8D39\u65B9\u5F85\u786E\u8BA4\uFF09\uFF0C\u7EB3\u7BA1\u4E3A\u524D\u77BB\u6027\u7EA6\u5B9A"
|
|
38688
38688
|
},
|
|
38689
38689
|
{
|
|
38690
38690
|
id: "cursor",
|
|
@@ -38692,7 +38692,15 @@ var AGENTS = [
|
|
|
38692
38692
|
binaries: ["cursor"],
|
|
38693
38693
|
fingerprints: (home) => [path2.join(home, ".cursor")],
|
|
38694
38694
|
skillsDir: (home) => path2.join(home, ".cursor", "skills"),
|
|
38695
|
-
verified: "\u5B98\u65B9
|
|
38695
|
+
verified: "\u5B98\u65B9\u6587\u6863\u786E\u8BA4 ~/.cursor/skills/\uFF08\u4E2A\u4EBA\uFF09\u4E0E .cursor/skills/\uFF08\u9879\u76EE\uFF09\uFF1Bsymlink \u53D1\u73B0\u4EFB\u5F85\u5B9E\u673A\u786E\u8BA4"
|
|
38696
|
+
},
|
|
38697
|
+
{
|
|
38698
|
+
id: "amp",
|
|
38699
|
+
name: "Amp",
|
|
38700
|
+
binaries: ["amp"],
|
|
38701
|
+
fingerprints: (home) => [path2.join(home, ".config", "amp"), path2.join(home, ".amp")],
|
|
38702
|
+
skillsDir: (home) => path2.join(home, ".config", "amp", "skills"),
|
|
38703
|
+
verified: "\u5B98\u65B9\u6587\u6863\u786E\u8BA4 ~/.config/amp/skills/\uFF08\u7528\u6237\u7EA7\uFF0C\u591A\u76EE\u5F55\u5E76\u8BFB\uFF09\uFF1Bsymlink \u53D1\u73B0\u4EFB\u5F85\u5B9E\u673A\u786E\u8BA4"
|
|
38696
38704
|
}
|
|
38697
38705
|
];
|
|
38698
38706
|
function getAgent(id) {
|
|
@@ -38959,9 +38967,9 @@ function ledgerHas(state, skill, agent, linkPath) {
|
|
|
38959
38967
|
(l) => l.skill === skill && l.agent === agent && l.link_path === linkPath
|
|
38960
38968
|
);
|
|
38961
38969
|
}
|
|
38962
|
-
function addLedger(state, skill, agent, linkPath) {
|
|
38970
|
+
function addLedger(state, skill, agent, linkPath, kind = "symlink") {
|
|
38963
38971
|
if (!ledgerHas(state, skill, agent, linkPath)) {
|
|
38964
|
-
state.links.push({ skill, agent, link_path: linkPath });
|
|
38972
|
+
state.links.push({ skill, agent, link_path: linkPath, ...kind === "copy" ? { kind } : {} });
|
|
38965
38973
|
}
|
|
38966
38974
|
}
|
|
38967
38975
|
function dropLedger(state, skill, agent) {
|
|
@@ -38972,6 +38980,13 @@ function linkTarget(agentId, skill) {
|
|
|
38972
38980
|
if (!agent) throw new Error(`\u672A\u77E5 agent '${agentId}'`);
|
|
38973
38981
|
return path7.join(agent.skillsDir(agentHome()), skill);
|
|
38974
38982
|
}
|
|
38983
|
+
function materializeOf(agentId) {
|
|
38984
|
+
return getAgent(agentId)?.materialize ?? "symlink";
|
|
38985
|
+
}
|
|
38986
|
+
function copyInto(src, target) {
|
|
38987
|
+
fs5.mkdirSync(path7.dirname(target), { recursive: true });
|
|
38988
|
+
fs5.cpSync(src, target, { recursive: true, dereference: true });
|
|
38989
|
+
}
|
|
38975
38990
|
function enableSkill(skill, agentIds) {
|
|
38976
38991
|
const src = skillDir(skill);
|
|
38977
38992
|
if (!fs5.existsSync(path7.join(src, "SKILL.md"))) {
|
|
@@ -38985,11 +39000,34 @@ function enableSkill(skill, agentIds) {
|
|
|
38985
39000
|
const skipped = [];
|
|
38986
39001
|
for (const agentId of agentIds) {
|
|
38987
39002
|
const target = linkTarget(agentId, skill);
|
|
39003
|
+
const kind = materializeOf(agentId);
|
|
38988
39004
|
let existing = null;
|
|
38989
39005
|
try {
|
|
38990
39006
|
existing = fs5.lstatSync(target);
|
|
38991
39007
|
} catch {
|
|
38992
39008
|
}
|
|
39009
|
+
if (kind === "copy") {
|
|
39010
|
+
if (existing) {
|
|
39011
|
+
if (ledgerHas(state, skill, agentId, target)) {
|
|
39012
|
+
fs5.rmSync(target, { recursive: true, force: true });
|
|
39013
|
+
copyInto(src, target);
|
|
39014
|
+
addLedger(state, skill, agentId, target, "copy");
|
|
39015
|
+
entry.expose[agentId] = true;
|
|
39016
|
+
linked.push(agentId);
|
|
39017
|
+
continue;
|
|
39018
|
+
}
|
|
39019
|
+
skipped.push({
|
|
39020
|
+
agent: agentId,
|
|
39021
|
+
reason: `${target} \u5DF2\u5B58\u5728\u771F\u5B9E${existing.isDirectory() ? "\u76EE\u5F55" : "\u6587\u4EF6"}\uFF08\u53EF\u80FD\u662F\u540C\u540D skill\uFF09\uFF0C\u62D2\u7EDD\u8986\u76D6`
|
|
39022
|
+
});
|
|
39023
|
+
continue;
|
|
39024
|
+
}
|
|
39025
|
+
copyInto(src, target);
|
|
39026
|
+
addLedger(state, skill, agentId, target, "copy");
|
|
39027
|
+
entry.expose[agentId] = true;
|
|
39028
|
+
linked.push(agentId);
|
|
39029
|
+
continue;
|
|
39030
|
+
}
|
|
38993
39031
|
if (existing) {
|
|
38994
39032
|
if (existing.isSymbolicLink()) {
|
|
38995
39033
|
let resolved = null;
|
|
@@ -39053,6 +39091,13 @@ function disableSkill(skill, agentIds) {
|
|
|
39053
39091
|
} catch {
|
|
39054
39092
|
}
|
|
39055
39093
|
if (st) {
|
|
39094
|
+
if (!st.isSymbolicLink() && ledgerHas(state, skill, agentId, target)) {
|
|
39095
|
+
fs5.rmSync(target, { recursive: true, force: true });
|
|
39096
|
+
dropLedger(state, skill, agentId);
|
|
39097
|
+
if (entry) entry.expose[agentId] = false;
|
|
39098
|
+
linked.push(agentId);
|
|
39099
|
+
continue;
|
|
39100
|
+
}
|
|
39056
39101
|
if (st.isSymbolicLink()) {
|
|
39057
39102
|
let pointsToStore = false;
|
|
39058
39103
|
try {
|
|
@@ -39101,6 +39146,61 @@ function safeReadlink(p) {
|
|
|
39101
39146
|
return "?";
|
|
39102
39147
|
}
|
|
39103
39148
|
}
|
|
39149
|
+
function broadcastSkill(skill, off = false) {
|
|
39150
|
+
const src = skillDir(skill);
|
|
39151
|
+
const state = loadState();
|
|
39152
|
+
const target = path7.join(agentHome(), ".agents", "skills", skill);
|
|
39153
|
+
if (off) {
|
|
39154
|
+
if (!fs5.existsSync(src)) throw new Error(`\u4E2D\u592E\u4ED3\u5E93\u4E2D\u627E\u4E0D\u5230 skill '${skill}'`);
|
|
39155
|
+
let st2 = null;
|
|
39156
|
+
try {
|
|
39157
|
+
st2 = fs5.lstatSync(target);
|
|
39158
|
+
} catch {
|
|
39159
|
+
}
|
|
39160
|
+
let pointsToStore = false;
|
|
39161
|
+
if (st2?.isSymbolicLink()) {
|
|
39162
|
+
try {
|
|
39163
|
+
pointsToStore = fs5.realpathSync(target) === fs5.realpathSync(src);
|
|
39164
|
+
} catch {
|
|
39165
|
+
pointsToStore = false;
|
|
39166
|
+
}
|
|
39167
|
+
}
|
|
39168
|
+
if (st2 && (pointsToStore || ledgerHas(state, skill, "broadcast", target))) {
|
|
39169
|
+
fs5.rmSync(target, { recursive: true, force: true });
|
|
39170
|
+
dropLedger(state, skill, "broadcast");
|
|
39171
|
+
saveState(state);
|
|
39172
|
+
return { changed: true, message: `'${skill}' \u5DF2\u64A4\u4E0B\u5E7F\u64AD\uFF08~/.agents/skills\uFF09` };
|
|
39173
|
+
}
|
|
39174
|
+
return { changed: false, message: "\u5E7F\u64AD\u76EE\u5F55\u4E2D\u6CA1\u6709\u8BE5 skill" };
|
|
39175
|
+
}
|
|
39176
|
+
if (!fs5.existsSync(path7.join(src, "SKILL.md"))) {
|
|
39177
|
+
throw new Error(`\u4E2D\u592E\u4ED3\u5E93\u4E2D\u627E\u4E0D\u5230 skill '${skill}'\uFF08${src}\uFF09`);
|
|
39178
|
+
}
|
|
39179
|
+
let st = null;
|
|
39180
|
+
try {
|
|
39181
|
+
st = fs5.lstatSync(target);
|
|
39182
|
+
} catch {
|
|
39183
|
+
}
|
|
39184
|
+
if (st) {
|
|
39185
|
+
let pointsToStore = false;
|
|
39186
|
+
if (st.isSymbolicLink()) {
|
|
39187
|
+
try {
|
|
39188
|
+
pointsToStore = fs5.realpathSync(target) === fs5.realpathSync(src);
|
|
39189
|
+
} catch {
|
|
39190
|
+
pointsToStore = false;
|
|
39191
|
+
}
|
|
39192
|
+
}
|
|
39193
|
+
if (pointsToStore || ledgerHas(state, skill, "broadcast", target)) {
|
|
39194
|
+
return { changed: false, message: `'${skill}' \u5DF2\u5728\u5E7F\u64AD\u4E2D` };
|
|
39195
|
+
}
|
|
39196
|
+
throw new Error(`${target} \u5DF2\u5B58\u5728\u4E14\u975E\u672C\u5DE5\u5177\u521B\u5EFA\u7684\u5E7F\u64AD\u94FE\u63A5\uFF0C\u62D2\u7EDD\u8986\u76D6`);
|
|
39197
|
+
}
|
|
39198
|
+
fs5.mkdirSync(path7.dirname(target), { recursive: true });
|
|
39199
|
+
fs5.symlinkSync(src, target, "dir");
|
|
39200
|
+
addLedger(state, skill, "broadcast", target);
|
|
39201
|
+
saveState(state);
|
|
39202
|
+
return { changed: true, message: `'${skill}' \u5DF2\u5E7F\u64AD\u5230 ~/.agents/skills\uFF08\u6240\u6709\u652F\u6301\u8BE5\u7EA6\u5B9A\u7684 Agent \u53EF\u89C1\uFF09` };
|
|
39203
|
+
}
|
|
39104
39204
|
|
|
39105
39205
|
// src/core/lint.ts
|
|
39106
39206
|
import fs6 from "node:fs";
|
|
@@ -39341,12 +39441,55 @@ async function installFromMarket(url, subdir, opts = {}) {
|
|
|
39341
39441
|
}
|
|
39342
39442
|
return addSkill(`${url}#${subdir}`, { name: opts.name, for: opts.for });
|
|
39343
39443
|
}
|
|
39444
|
+
async function searchDirectory(query, limit = 20) {
|
|
39445
|
+
const q = query.trim();
|
|
39446
|
+
if (!q) throw new Error("\u641C\u7D22\u8BCD\u4E0D\u80FD\u4E3A\u7A7A");
|
|
39447
|
+
const res = await fetch(
|
|
39448
|
+
`https://skills.sh/api/search?q=${encodeURIComponent(q)}&limit=${Math.min(Math.max(limit, 1), 200)}`
|
|
39449
|
+
);
|
|
39450
|
+
if (!res.ok) throw new Error(`skills.sh \u641C\u7D22\u5931\u8D25\uFF1AHTTP ${res.status}`);
|
|
39451
|
+
const data = await res.json();
|
|
39452
|
+
return (data.skills ?? []).filter((s) => s.id && s.source).map((s) => ({
|
|
39453
|
+
id: s.id,
|
|
39454
|
+
name: s.name ?? s.id,
|
|
39455
|
+
source: s.source,
|
|
39456
|
+
installs: s.installs ?? 0
|
|
39457
|
+
}));
|
|
39458
|
+
}
|
|
39459
|
+
async function resolveDirectorySkill(id) {
|
|
39460
|
+
const parts = id.split("/").filter(Boolean);
|
|
39461
|
+
if (parts.length < 3) throw new Error(`id \u5F62\u5982 owner/repo/slug\uFF1A${id}`);
|
|
39462
|
+
const source = parts.slice(0, 2).join("/");
|
|
39463
|
+
const slug = parts.slice(2).join("/").toLowerCase();
|
|
39464
|
+
const repoUrl = `https://github.com/${source}.git`;
|
|
39465
|
+
const { skills } = await scanSource(repoUrl);
|
|
39466
|
+
const hit = matchDirectorySkill(skills, slug);
|
|
39467
|
+
if (!hit) {
|
|
39468
|
+
throw new Error(`\u5728 ${source} \u4E2D\u627E\u4E0D\u5230 skill '${slug}'\uFF08\u53EF\u8FD0\u884C skillpot market ${repoUrl} \u67E5\u770B\uFF09`);
|
|
39469
|
+
}
|
|
39470
|
+
return { repoUrl, subdir: hit.subdir };
|
|
39471
|
+
}
|
|
39472
|
+
function matchDirectorySkill(skills, slug) {
|
|
39473
|
+
const q = slug.toLowerCase();
|
|
39474
|
+
return skills.find((s) => s.name.toLowerCase() === q) ?? skills.find((s) => (s.subdir.split("/").pop() ?? "").toLowerCase() === q);
|
|
39475
|
+
}
|
|
39476
|
+
async function installFromDirectory(id, opts = {}) {
|
|
39477
|
+
const { repoUrl, subdir } = await resolveDirectorySkill(id);
|
|
39478
|
+
return installFromMarket(repoUrl, subdir, opts);
|
|
39479
|
+
}
|
|
39480
|
+
function formatInstalls(n) {
|
|
39481
|
+
if (!n || n <= 0) return "";
|
|
39482
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1).replace(/\.0$/, "")}M`;
|
|
39483
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1).replace(/\.0$/, "")}K`;
|
|
39484
|
+
return String(n);
|
|
39485
|
+
}
|
|
39344
39486
|
|
|
39345
39487
|
// src/tui/matrix.ts
|
|
39346
39488
|
import fs8 from "node:fs";
|
|
39347
39489
|
import path11 from "node:path";
|
|
39348
39490
|
function deriveMatrix(agents) {
|
|
39349
39491
|
const config = loadConfig();
|
|
39492
|
+
const state = loadState();
|
|
39350
39493
|
const skills = Object.keys(config.skills).sort();
|
|
39351
39494
|
const list = agents ?? detectAll().map((r) => ({
|
|
39352
39495
|
id: r.id,
|
|
@@ -39355,6 +39498,9 @@ function deriveMatrix(agents) {
|
|
|
39355
39498
|
skillsDir: r.skillsDir
|
|
39356
39499
|
}));
|
|
39357
39500
|
const cells = {};
|
|
39501
|
+
const ledgeredCopy = (skill, agentId, target) => state.links.some(
|
|
39502
|
+
(l) => l.skill === skill && l.agent === agentId && l.link_path === target && l.kind === "copy"
|
|
39503
|
+
);
|
|
39358
39504
|
for (const s of skills) {
|
|
39359
39505
|
cells[s] = {};
|
|
39360
39506
|
for (const a of list) {
|
|
@@ -39364,7 +39510,7 @@ function deriveMatrix(agents) {
|
|
|
39364
39510
|
try {
|
|
39365
39511
|
const st = fs8.lstatSync(target);
|
|
39366
39512
|
actual = true;
|
|
39367
|
-
managed = st.isSymbolicLink() && fs8.realpathSync(target) === fs8.realpathSync(skillDir(s));
|
|
39513
|
+
managed = ledgeredCopy(s, a.id, target) || st.isSymbolicLink() && fs8.realpathSync(target) === fs8.realpathSync(skillDir(s));
|
|
39368
39514
|
} catch {
|
|
39369
39515
|
}
|
|
39370
39516
|
cells[s][a.id] = {
|
|
@@ -39512,7 +39658,15 @@ async function syncManifest(file, opts = {}) {
|
|
|
39512
39658
|
out.push({ skill: name, action: "install", detail });
|
|
39513
39659
|
continue;
|
|
39514
39660
|
}
|
|
39515
|
-
|
|
39661
|
+
let drifted = false;
|
|
39662
|
+
if (entry.checksum) {
|
|
39663
|
+
try {
|
|
39664
|
+
drifted = dirChecksum(skillDir(name)) !== entry.checksum;
|
|
39665
|
+
} catch {
|
|
39666
|
+
drifted = true;
|
|
39667
|
+
}
|
|
39668
|
+
}
|
|
39669
|
+
if (drifted) {
|
|
39516
39670
|
if (opts.dryRun) {
|
|
39517
39671
|
out.push({
|
|
39518
39672
|
skill: name,
|
|
@@ -39550,6 +39704,42 @@ var SYNC_ACTION_LABELS = {
|
|
|
39550
39704
|
skip: "\u8DF3\u8FC7",
|
|
39551
39705
|
error: "\u5931\u8D25"
|
|
39552
39706
|
};
|
|
39707
|
+
function inspectManifest(file) {
|
|
39708
|
+
const manifest = loadManifest(file);
|
|
39709
|
+
const config = loadConfig();
|
|
39710
|
+
const skills = [];
|
|
39711
|
+
const warnings = [];
|
|
39712
|
+
for (const [name, entry] of Object.entries(manifest.skills)) {
|
|
39713
|
+
const installed = !!config.skills[name];
|
|
39714
|
+
const localOnly = entry.source.startsWith("local:");
|
|
39715
|
+
let checksumMatch = null;
|
|
39716
|
+
let storeMissing = false;
|
|
39717
|
+
if (installed && entry.checksum) {
|
|
39718
|
+
try {
|
|
39719
|
+
checksumMatch = dirChecksum(skillDir(name)) === entry.checksum;
|
|
39720
|
+
} catch (e) {
|
|
39721
|
+
checksumMatch = false;
|
|
39722
|
+
storeMissing = !fs9.existsSync(skillDir(name));
|
|
39723
|
+
}
|
|
39724
|
+
}
|
|
39725
|
+
if (localOnly) {
|
|
39726
|
+
warnings.push(
|
|
39727
|
+
installed ? `'${name}' \u4E3A local \u6765\u6E90\uFF0C\u8DF3\u8FC7\u5BF9\u9F50\uFF08\u4EC5\u672C\u673A\u6709\u610F\u4E49\uFF09` : `'${name}' \u4E3A local \u6765\u6E90\uFF08${entry.source}\uFF09\u5728\u672C\u673A\u4E0D\u5B58\u5728\uFF0Csync \u5C06\u8DF3\u8FC7`
|
|
39728
|
+
);
|
|
39729
|
+
}
|
|
39730
|
+
skills.push({
|
|
39731
|
+
skill: name,
|
|
39732
|
+
source: entry.source,
|
|
39733
|
+
checksum: entry.checksum,
|
|
39734
|
+
expose: Object.fromEntries(Object.entries(entry.expose ?? {}).filter(([, v]) => v === true)),
|
|
39735
|
+
installed,
|
|
39736
|
+
checksumMatch,
|
|
39737
|
+
storeMissing,
|
|
39738
|
+
localOnly
|
|
39739
|
+
});
|
|
39740
|
+
}
|
|
39741
|
+
return { file, skills, warnings };
|
|
39742
|
+
}
|
|
39553
39743
|
|
|
39554
39744
|
// src/core/doctor.ts
|
|
39555
39745
|
import fs10 from "node:fs";
|
|
@@ -39584,6 +39774,16 @@ function runDoctor() {
|
|
|
39584
39774
|
st = fs10.lstatSync(link2.link_path);
|
|
39585
39775
|
} catch {
|
|
39586
39776
|
}
|
|
39777
|
+
if (link2.kind === "copy") {
|
|
39778
|
+
if (!st || !st.isDirectory()) {
|
|
39779
|
+
issues.push({
|
|
39780
|
+
level: "error",
|
|
39781
|
+
message: `\u526F\u672C\u7F3A\u5931\uFF1A${link2.link_path}\uFF08${link2.skill} @ ${link2.agent}\uFF0C\u91CD\u65B0 enable \u53EF\u91CD\u5EFA\uFF09`,
|
|
39782
|
+
fix: "drop-ledger"
|
|
39783
|
+
});
|
|
39784
|
+
}
|
|
39785
|
+
continue;
|
|
39786
|
+
}
|
|
39587
39787
|
if (!st) {
|
|
39588
39788
|
issues.push({
|
|
39589
39789
|
level: "error",
|
|
@@ -39663,7 +39863,11 @@ function fixDoctor() {
|
|
|
39663
39863
|
state.links = state.links.filter((l) => {
|
|
39664
39864
|
let ok = false;
|
|
39665
39865
|
try {
|
|
39666
|
-
|
|
39866
|
+
if (l.kind === "copy") {
|
|
39867
|
+
ok = fs10.existsSync(l.link_path) && fs10.lstatSync(l.link_path).isDirectory();
|
|
39868
|
+
} else {
|
|
39869
|
+
ok = fs10.existsSync(l.link_path) && fs10.lstatSync(l.link_path).isSymbolicLink();
|
|
39870
|
+
}
|
|
39667
39871
|
} catch {
|
|
39668
39872
|
ok = false;
|
|
39669
39873
|
}
|
|
@@ -40372,6 +40576,31 @@ async function handleApiRequest(method, pathname, query, body, expectedToken, go
|
|
|
40372
40576
|
}
|
|
40373
40577
|
return { status: 200, body: { changed, skipped } };
|
|
40374
40578
|
}
|
|
40579
|
+
if (method === "GET" && pathname === "/api/team/inspect") {
|
|
40580
|
+
const file = query.get("file");
|
|
40581
|
+
if (!file) return { status: 400, body: { error: "\u9700\u8981 file \u67E5\u8BE2\u53C2\u6570\uFF08\u6E05\u5355\u8DEF\u5F84\uFF09" } };
|
|
40582
|
+
const report = inspectManifest(file);
|
|
40583
|
+
return { status: 200, body: report };
|
|
40584
|
+
}
|
|
40585
|
+
if (method === "POST" && pathname === "/api/team/sync") {
|
|
40586
|
+
const b = body ?? {};
|
|
40587
|
+
if (typeof b.file !== "string" || !b.file) {
|
|
40588
|
+
return { status: 400, body: { error: "\u9700\u8981 file \u5B57\u6BB5\uFF08\u6E05\u5355\u8DEF\u5F84\uFF09" } };
|
|
40589
|
+
}
|
|
40590
|
+
const items = await syncManifest(b.file, { dryRun: b.dryRun === true });
|
|
40591
|
+
return { status: 200, body: { items } };
|
|
40592
|
+
}
|
|
40593
|
+
if (method === "POST" && pathname === "/api/team/export") {
|
|
40594
|
+
const b = body ?? {};
|
|
40595
|
+
if (typeof b.file !== "string" || !b.file) {
|
|
40596
|
+
return { status: 400, body: { error: "\u9700\u8981 file \u5B57\u6BB5\uFF08\u5BFC\u51FA\u76EE\u6807\u8DEF\u5F84\uFF09" } };
|
|
40597
|
+
}
|
|
40598
|
+
const result = exportManifest(
|
|
40599
|
+
b.file,
|
|
40600
|
+
Array.isArray(b.skills) ? b.skills.filter((x) => typeof x === "string") : void 0
|
|
40601
|
+
);
|
|
40602
|
+
return { status: 200, body: result };
|
|
40603
|
+
}
|
|
40375
40604
|
if (method === "GET" && pathname === "/api/market/sources") {
|
|
40376
40605
|
return { status: 200, body: { sources: listSources() } };
|
|
40377
40606
|
}
|
|
@@ -46784,6 +47013,46 @@ program2.command("sync").description("\u56E2\u961F\u5BF9\u9F50\uFF1A\u6309\u9879
|
|
|
46784
47013
|
if (errors.length) process.exitCode = 1;
|
|
46785
47014
|
})
|
|
46786
47015
|
);
|
|
47016
|
+
program2.command("broadcast <skill>").description("\u5E7F\u64AD\u6A21\u5F0F\uFF1A\u628A skill \u653E\u8FDB\u8DE8\u5DE5\u5177\u5171\u4EAB\u76EE\u5F55 ~/.agents/skills\uFF08\u7C97\u7C92\u5EA6\uFF0C\u6240\u6709\u652F\u6301\u8BE5\u7EA6\u5B9A\u7684 Agent \u53EF\u89C1\uFF09").option("--off", "\u64A4\u4E0B\u5E7F\u64AD").action(
|
|
47017
|
+
run((skill, opts) => {
|
|
47018
|
+
const r = broadcastSkill(skill, opts.off === true);
|
|
47019
|
+
if (r.changed) console.log(import_picocolors2.default.green(`\u2714 ${r.message}`));
|
|
47020
|
+
else console.log(import_picocolors2.default.dim(r.message));
|
|
47021
|
+
})
|
|
47022
|
+
);
|
|
47023
|
+
program2.command("search <query>").description("\u5728 skills.sh \u76EE\u5F55\u4E2D\u641C\u7D22 skill\uFF08\u533F\u540D\u8054\u7F51\uFF0C\u65E0\u9700 token\uFF09").option("--limit <n>", "\u7ED3\u679C\u6570\u91CF\u4E0A\u9650\uFF08\u9ED8\u8BA4 20\uFF09").action(
|
|
47024
|
+
run(async (query, opts) => {
|
|
47025
|
+
const skills = await searchDirectory(query, Number(opts.limit) || 20);
|
|
47026
|
+
if (!skills.length) {
|
|
47027
|
+
console.log(import_picocolors2.default.dim("\u65E0\u7ED3\u679C"));
|
|
47028
|
+
return;
|
|
47029
|
+
}
|
|
47030
|
+
console.log(
|
|
47031
|
+
renderTable(
|
|
47032
|
+
["Skill", "\u5B89\u88C5\u91CF", "id"],
|
|
47033
|
+
skills.map((s) => [s.name, formatInstalls(s.installs), s.id])
|
|
47034
|
+
)
|
|
47035
|
+
);
|
|
47036
|
+
console.log(import_picocolors2.default.dim(`\u5B89\u88C5\uFF1Askillpot install-search <id>\uFF08\u4F8B\uFF1A${skills[0].id}\uFF09`));
|
|
47037
|
+
})
|
|
47038
|
+
);
|
|
47039
|
+
program2.command("install-search <id>").description("\u5B89\u88C5 skills.sh \u76EE\u5F55\u4E2D\u7684 skill\uFF08id \u5F62\u5982 owner/repo/slug\uFF09").option("-f, --for <agents>", "\u5B89\u88C5\u540E\u5F00\u653E\u7ED9\u6307\u5B9A Agent\uFF08\u9017\u53F7\u5206\u9694\u6216 all\uFF09").action(
|
|
47040
|
+
run(async (id, opts) => {
|
|
47041
|
+
const r = await installFromDirectory(id, {
|
|
47042
|
+
for: opts.for ? resolveAgentIds(opts.for) : void 0
|
|
47043
|
+
});
|
|
47044
|
+
console.log(import_picocolors2.default.green(`\u2714 \u5DF2\u5B89\u88C5 ${r.name}`));
|
|
47045
|
+
if (r.description) console.log(import_picocolors2.default.dim(` ${r.description.slice(0, 120)}`));
|
|
47046
|
+
for (const i of r.lint) {
|
|
47047
|
+
console.log(` ${i.level === "error" ? import_picocolors2.default.red("error") : import_picocolors2.default.yellow("warn ")} ${i.message}`);
|
|
47048
|
+
}
|
|
47049
|
+
if (r.enabled.length) console.log(import_picocolors2.default.green(`\u5DF2\u5F00\u653E\uFF1A${r.enabled.join(", ")}`));
|
|
47050
|
+
else
|
|
47051
|
+
console.log(
|
|
47052
|
+
import_picocolors2.default.dim("\u9ED8\u8BA4\u672A\u5F00\u653E\u3002skillpot enable <skill> --for <agents> \u6216\u5728 GUI \u77E9\u9635\u6253\u52FE")
|
|
47053
|
+
);
|
|
47054
|
+
})
|
|
47055
|
+
);
|
|
46787
47056
|
for (const cmd of program2.commands) cmd.allowExcessArguments(false);
|
|
46788
47057
|
await program2.parseAsync(process.argv);
|
|
46789
47058
|
/*! Bundled license information:
|