@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 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: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
+ [![npm](https://img.shields.io/npm/v/@tec-explorer/skillpot)](https://www.npmjs.com/package/@tec-explorer/skillpot)
5
+ [![Node](https://img.shields.io/badge/node-%E2%89%A518-brightgreen.svg)](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
+ ![GUI switch matrix](docs/images/gui-matrix.png)
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
  ![GUI 开关矩阵](docs/images/gui-matrix.png)
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\u76EE\u5F55\u5185\u5DF2\u5B58\u5728\u7528\u6237\u624B\u5DE5\u521B\u5EFA\u5E76\u65E5\u5E38\u4F7F\u7528\u7684 symlink\uFF0C\u95F4\u63A5\u4F50\u8BC1 symlink \u53EF\u88AB\u53D1\u73B0\uFF1Bdsh CLI \u672C\u673A\u635F\u574F\uFF08@deepseek-ai/dsh \u6A21\u5757\u7F3A\u5931\uFF09\uFF0C\u5F85\u91CD\u88C5\u540E\u6700\u7EC8\u786E\u8BA4"
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 create-skill \u6280\u80FD\u660E\u786E\uFF1A\u4E2A\u4EBA\u6280\u80FD\u76EE\u5F55\u4E3A ~/.cursor/skills/\uFF08\u9879\u76EE\u4E3A .cursor/skills/\uFF09\uFF1Bsymlink \u53D1\u73B0\u4EFB\u5F85\u5B9E\u673A\u786E\u8BA4"
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
- if (entry.checksum && dirChecksum(skillDir(name)) !== entry.checksum) {
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
- ok = fs10.existsSync(l.link_path) && fs10.lstatSync(l.link_path).isSymbolicLink();
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: