@tec-explorer/skillpot 0.10.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 +8 -0
- package/README.en.md +87 -0
- package/README.md +2 -0
- package/dist/cli.mjs +206 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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
|
+
|
|
6
14
|
## [0.10.0] - 2026-09-04
|
|
7
15
|
|
|
8
16
|
### 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] = {
|
|
@@ -39628,6 +39774,16 @@ function runDoctor() {
|
|
|
39628
39774
|
st = fs10.lstatSync(link2.link_path);
|
|
39629
39775
|
} catch {
|
|
39630
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
|
+
}
|
|
39631
39787
|
if (!st) {
|
|
39632
39788
|
issues.push({
|
|
39633
39789
|
level: "error",
|
|
@@ -39707,7 +39863,11 @@ function fixDoctor() {
|
|
|
39707
39863
|
state.links = state.links.filter((l) => {
|
|
39708
39864
|
let ok = false;
|
|
39709
39865
|
try {
|
|
39710
|
-
|
|
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
|
+
}
|
|
39711
39871
|
} catch {
|
|
39712
39872
|
ok = false;
|
|
39713
39873
|
}
|
|
@@ -46853,6 +47013,46 @@ program2.command("sync").description("\u56E2\u961F\u5BF9\u9F50\uFF1A\u6309\u9879
|
|
|
46853
47013
|
if (errors.length) process.exitCode = 1;
|
|
46854
47014
|
})
|
|
46855
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
|
+
);
|
|
46856
47056
|
for (const cmd of program2.commands) cmd.allowExcessArguments(false);
|
|
46857
47057
|
await program2.parseAsync(process.argv);
|
|
46858
47058
|
/*! Bundled license information:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tec-explorer/skillpot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Cross-agent skill manager: install once, expose per coding agent (Claude Code / ZCode / Codex / OpenCode / Gemini CLI / DeepSeek CLI / Cursor).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|