@yottameta/yotta-skills-plugin 0.24.1 → 0.25.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.
@@ -12,7 +12,7 @@
12
12
  "authentication": "ON_INSTALL"
13
13
  },
14
14
  "category": "Developer Tools",
15
- "version": "0.24.1",
15
+ "version": "0.25.0",
16
16
  "description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
17
17
  "interface": {
18
18
  "displayName": "元阁 yotta-skills",
@@ -12,7 +12,7 @@
12
12
  "authentication": "ON_INSTALL"
13
13
  },
14
14
  "category": "Developer Tools",
15
- "version": "0.24.1",
15
+ "version": "0.25.0",
16
16
  "description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
17
17
  "interface": {
18
18
  "displayName": "元阁 yotta-skills",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-skills-plugin",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "description": "YuanGe (元阁) — orchestration and routing for the YottaMeta skill family, packaged as a YottaMeta Agent Plugin and DeepSeek Harness profile bundle.",
5
5
  "license": "MIT",
6
6
  "keywords": [
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "yotta-skills",
4
- "version": "0.24.1",
4
+ "version": "0.25.0",
5
5
  "description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
6
6
  "author": {
7
7
  "name": "YottaMeta",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yotta-skills
3
- version: 0.24.1
4
- description: 元阁 -- 元阁全家技能的总编排策划 + 编排路由 + 一键安装器 + 技能盘点 + 运行时 hook 适配。路由层:--route / route_request 按需求摘要给出候选组合、调用顺序、角色、置信度、依据、已装/缺失状态与安装命令,只建议不自动安装;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);M1 记忆裁决层:usage enable/mark 本地结构化记录 + decide-memory / MCP decide_memory 输出 promote / hold / demote 只读建议(需授权 provider;只建议不删除、不自动写元忆);策划层:按场景给出「该组合哪几个元技能、组合强在哪、怎么组合使用(安装与调用均由用户确认后执行)」;安装层:一条命令把 YottaMeta 已发布的全部 yotta-* 技能装进指定智能体或目录(默认 --pin 锁死清单精确版本);盘点层:--inventory / --reindex 扫描本机已装技能生成/更新注册表,新装技能自动被发现(install/update 后自动 re-index,会话开工只建议跑本地 --reindex;更新检查走手动 --check 或后台 --check --scheduled);运行时适配层:hook capabilities / evaluate / bind / unbind 按宿主能力矩阵执行六个统一事件并留证降级,元信 before_install 已接入安装管线;自包含零依赖,不依赖任何元技能;MCP 按需加载且需用户确认后写入配置(可选:list_installed_skills/describe_skill/reindex/route_request/decide_memory,不常驻,未加载降级 CLI)。支持 --list 清单 / --route 路由 / usage 使用记录 / decide-memory 记忆裁决 / install / update / update --check(只读检查)/ update --check --scheduled(后台周检)/ update --auto(家族自动更新)/ hook 适配 / --inventory / --reindex / --dry-run 预览 / --pin(默认)/ --range。触发:需要批量安装或更新元阁全家技能、按场景组合多个元技能、路由或判断该用哪些技能、判断哪些技能值得长期记忆、查看或记录技能使用信号、盘点或查看本机已装技能、重扫技能注册表、给某个智能体或目录一次性铺齐 yotta-* 技能、评估宿主 hook 能力、预览安装清单、锁版本安装、或用户说 元阁/装全家/一次装齐/yotta-skills/install-all/更新全家/检查更新/自动更新/hook 适配/该用哪个技能/路由技能/记忆裁决/技能该不该记住/盘点技能/查看已装技能 等。边界(Do NOT trigger):只做「组合策划 + 静态路由建议 + M1 记忆裁决只读建议 + 清单 + 下载 + 落位 + 汇总 + 盘点 + re-index + hook 适配」,不含技能本体、不做技能内容开发、不 -g 污染全局、不自动安装缺失技能、不静默写宿主配置或全局记忆、不自动删除技能或记忆;家族安装先自举或调用元信装前门禁,DO NOT INSTALL 阻断,非元阁家族包不自动安装。
3
+ version: 0.25.0
4
+ description: 元阁 -- 元阁全家技能的总编排策划 + 编排路由 + 一键安装器 + 技能盘点 + 本机技能 Hub + 运行时 hook 适配。Hub 层:hub hosts 只读发现本机已装智能体与技能目录(文件系统优先,不读元忆);hub install / update 把技能装到 ~/.yottaskills/hub 单点真源;hub link --all 用 Windows junction / POSIX symlink 分发到全部已发现宿主;hub unlink 只删链接、fail-closed;hub status 显示来源、版本、链接与异常;兼容 agentskills.io 技能格式、Vercel Labs skills CLI 的 .agents/skills 通用目录与 .skill-lock.json v3(只读)。路由层:--route / route_request 按需求摘要给出候选组合、调用顺序、角色、置信度、依据、已装/缺失状态与安装命令,只建议不自动安装;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);M1 记忆裁决层:usage enable/mark 本地结构化记录 + decide-memory / MCP decide_memory 输出 promote / hold / demote 只读建议(需授权 provider;只建议不删除、不自动写元忆);策划层:按场景给出「该组合哪几个元技能、组合强在哪、怎么组合使用(安装与调用均由用户确认后执行)」;安装层:一条命令把 YottaMeta 已发布的全部 yotta-* 技能装进指定智能体或目录(默认 --pin 锁死清单精确版本);盘点层:--inventory / --reindex 扫描本机已装技能生成/更新注册表,新装技能自动被发现(install/update 后自动 re-index,会话开工只建议跑本地 --reindex;更新检查走手动 --check 或后台 --check --scheduled);运行时适配层:hook capabilities / evaluate / bind / unbind 按宿主能力矩阵执行六个统一事件并留证降级,元信 before_install 已接入安装管线;自包含零依赖,不依赖任何元技能;MCP 按需加载且需用户确认后写入配置(可选:list_installed_skills/describe_skill/reindex/route_request/decide_memory,不常驻,未加载降级 CLI)。支持 --list 清单 / --route 路由 / usage 使用记录 / decide-memory 记忆裁决 / hub 技能 Hub / install / update / update --check(只读检查)/ update --check --scheduled(后台周检)/ update --auto(家族自动更新)/ hook 适配 / --inventory / --reindex / --dry-run 预览 / --pin(默认)/ --range。触发:需要批量安装或更新元阁全家技能、单点安装并分发到多个智能体、查看本机装了哪些智能体与技能目录、接管非元技能、按场景组合多个元技能、路由或判断该用哪些技能、判断哪些技能值得长期记忆、查看或记录技能使用信号、盘点或查看本机已装技能、重扫技能注册表、给某个智能体或目录一次性铺齐 yotta-* 技能、评估宿主 hook 能力、预览安装清单、锁版本安装、或用户说 元阁/装全家/一次装齐/yotta-skills/install-all/更新全家/检查更新/自动更新/hook 适配/该用哪个技能/路由技能/记忆裁决/技能该不该记住/盘点技能/查看已装技能/技能 Hub/单点安装/链接分发/接管技能 等。边界(Do NOT trigger):只做「组合策划 + 静态路由建议 + M1 记忆裁决只读建议 + 清单 + 下载 + 落位 + 汇总 + 盘点 + re-index + Hub 本机真源与链接分发 + hook 适配」,不含技能本体、不做技能内容开发、不 -g 污染全局、不自动安装缺失技能、不静默写宿主配置或全局记忆、不自动删除技能或记忆;家族安装先自举或调用元信装前门禁,DO NOT INSTALL 阻断,非元阁家族包不自动安装。
5
5
  license: MIT
6
6
  metadata:
7
7
  zh_name: 元阁
@@ -154,6 +154,38 @@ openclaw skills update @yottameta/yotta-skills
154
154
  > **OpenClaw / QClaw 用户**:技能安装与更新走宿主官方命令 `openclaw skills install|update @yottameta/<slug>`,
155
155
  > 以保留宿主的 ClawHub 安全审计卡与更新追踪;元阁的 `--inventory` / `--reindex` 已能识别 `~/.openclaw/skills`(含 `OPENCLAW_STATE_DIR` 覆盖)。
156
156
 
157
+ ## 技能 Hub(单点安装 + 链接分发)
158
+
159
+ Hub 把「每个宿主装一份」改成「本机一份真源,按宿主链接分发」。默认真源
160
+ `~/.yottaskills/hub`,可用 `--hub <dir>` 或 `YOTTA_SKILLS_HUB` 覆盖。
161
+
162
+ ```bash
163
+ # 1) 只读发现本机已装智能体与技能目录(文件系统 + 环境变量;不读元忆 / 注册表)
164
+ npx -y @yottameta/yotta-skills hub hosts
165
+
166
+ # 2) 把元技能装进 Hub 真源(复用安装管线 + 元信装前扫描)
167
+ npx -y @yottameta/yotta-skills hub install
168
+
169
+ # 3) 接管各宿主现有技能:先只读预演,再复制收编(原目录保留)
170
+ npx -y @yottameta/yotta-skills hub adopt --scan
171
+ npx -y @yottameta/yotta-skills hub adopt --apply
172
+
173
+ # 4) 链接分发到全部已发现宿主(Windows junction / macOS·Linux symlink)
174
+ npx -y @yottameta/yotta-skills hub link --all
175
+
176
+ # 5) 查看来源、版本、链接宿主与异常
177
+ npx -y @yottameta/yotta-skills hub status
178
+ npx -y @yottameta/yotta-skills hub doctor
179
+
180
+ # 只删链接,不动 Hub 真源(真目录 / 外部链接一律拒绝)
181
+ npx -y @yottameta/yotta-skills hub unlink --all
182
+ ```
183
+
184
+ 宿主发现兼容 `agentskills.io` 技能格式与 Vercel Labs `skills` CLI 的
185
+ `.agents/skills` 通用目录;已收录 79 条宿主映射,并只读兼容
186
+ `.skill-lock.json` v3(`$XDG_STATE_HOME/skills/` 或 `~/.agents/`)。
187
+ 不读元忆,不重写官方锁文件,不静默降级为复制。
188
+
157
189
  ## 命令与选项
158
190
 
159
191
  | 命令 / 选项 | 作用 |
@@ -174,6 +206,15 @@ openclaw skills update @yottameta/yotta-skills
174
206
  | `hook bind --host <name> --manifest <file>` | 幂等注册 hook 声明;`hook unbind <id>` 反注册 |
175
207
  | `--inventory` | 盘点本机已装技能:扫描技能目录生成/更新注册表(自包含,不依赖元技能);`--json` 输出 JSON、`--project` 附扫项目级目录 |
176
208
  | `--reindex` | 重扫注册表:扫描技能目录并增量合并变化(install / update 完成后 CLI 自动重扫;也可在会话开工等时机手动运行);`--json` 输出 JSON、`--project` 附扫项目级目录;`--rescan` 同义 |
209
+ | `hub hosts` | 发现本机已装智能体与技能目录(文件系统 + 环境变量;不读元忆 / 注册表);`--json` 输出 JSON |
210
+ | `hub install [skill...]` | 把元技能安装到 Hub 真源(默认 `~/.yottaskills/hub`;`--hub` / `YOTTA_SKILLS_HUB` 覆盖) |
211
+ | `hub adopt --scan` | 只读预演:扫描各宿主现有技能,输出收编候选、多副本冲突、已在 Hub 状态 |
212
+ | `hub adopt --apply [--include <a,b>] [--force] [--in-place]` | 把选中技能收编进 Hub;默认复制保真 + 原目录保留;非元阁来源标注「无更新源」 |
213
+ | `hub refresh <slug> --from <path>` | 手动同步非元阁技能(重新收编 + 更新哈希 + 台账) |
214
+ | `hub link --agent <id> \| --dir <dir> \| --all` | 把 Hub 技能链接到指定宿主 / 目录 / 全部已发现宿主;`--dry-run` 只预览 |
215
+ | `hub unlink --agent <id> \| --dir <dir> \| --all` | 只删除链接;lstat + readlink 校验目标在 Hub 内,fail-closed |
216
+ | `hub status [--json]` | 查看 Hub 技能来源、版本、链接宿主、异常链接与宿主发现摘要 |
217
+ | `hub doctor [--json]` | 检查断链 / Hub 目标缺失 / slug 不一致 / 目录权限;异常时退出码 1 |
177
218
  | `--route <需求摘要>` | 编排路由:输出组合、调用顺序、技能角色、置信度、依据、已装/缺失状态与安装建议;可选本地 provider 增补 / 重排并返回 `confidence` / `reasons` / `summary` / `alternatives`;`--json` 输出 JSON、`--project` 附扫项目级目录 |
178
219
  | `usage status` | 查看本地使用记录开关与计数;默认关闭,不创建文件 |
179
220
  | `usage enable` / `usage disable` | 开启 / 关闭 `--route` 的结构化使用记录;不记录需求原文 |
@@ -45,11 +45,15 @@ const routeDynamicLib = require('../lib/route-dynamic');
45
45
  const depsLib = require('../lib/deps');
46
46
  const untarLib = require('../lib/untar');
47
47
  const registryFetchLib = require('../lib/registry-fetch');
48
+ const agentDirsLib = require('../lib/agent-dirs');
49
+ const agentDiscoveryLib = require('../lib/agent-discovery');
50
+ const hubLib = require('../lib/hub');
51
+ const hubAdoptLib = require('../lib/hub-adopt');
48
52
  const { createInstaller, isSafeTarEntry } = require('../lib/install-pipeline');
49
53
  const { COPY_SKIP, copyDir } = require('../lib/copy-tree');
50
54
 
51
55
  const PKG_ROOT = path.join(__dirname, '..');
52
- let VERSION = '0.24.1';
56
+ let VERSION = '0.25.0';
53
57
  try { VERSION = require(path.join(PKG_ROOT, 'package.json')).version; } catch (_) { /* keep fallback */ }
54
58
 
55
59
  function loadManifest() {
@@ -72,39 +76,18 @@ function loadManifest() {
72
76
 
73
77
  const MANIFEST = loadManifest();
74
78
 
75
- // 智能体 -> 用户级默认技能目录(与各技能 install.js 同源;.agents/skills 并非通用目录)
76
- const AGENT_DIRS = {
77
- claude: { label: 'Claude Code', dirs: ['.claude/skills'] },
78
- cursor: { label: 'Cursor', dirs: ['.cursor/skills', '.agents/skills'] },
79
- codex: { label: 'Codex', dirs: ['.codex/skills'] }, // 特判:$CODEX_HOME/skills
80
- gemini: { label: 'Gemini CLI', dirs: ['.gemini/skills', '.agents/skills'] },
81
- goose: { label: 'Goose', dirs: ['.config/goose/skills', '.agents/skills'] },
82
- amp: { label: 'Amp', dirs: ['.config/agents/skills', '.agents/skills'] },
83
- opencode: { label: 'OpenCode', dirs: ['.config/opencode/skills'] }, // 特判:$XDG_CONFIG_HOME
84
- windsurf: { label: 'Windsurf', dirs: ['.codeium/windsurf/skills'] },
85
- workbuddy: { label: 'WorkBuddy', dirs: ['.workbuddy/skills'] },
86
- kiro: { label: 'Kiro', dirs: ['.kiro/skills'] },
87
- trae: { label: 'Trae Code CLI', dirs: ['.traecli/skills'] },
88
- 'trae-cn': { label: 'Trae IDE(国内)', dirs: ['.trae-cn/skills'] },
89
- qwen: { label: 'Qwen Code', dirs: ['.qwen/skills'] },
90
- comate: { label: 'Comate 文心快码', dirs: ['.comate/skills'] },
91
- codebuddy: { label: 'CodeBuddy Code', dirs: ['.codebuddy/skills'] },
92
- kimi: { label: 'Kimi Code CLI', dirs: ['.kimi/skills'] },
93
- agents: { label: '通用 AGENTS.md', dirs: ['.agents/skills'] },
94
- };
79
+ // 智能体 -> 用户级默认技能目录。单一真源在 lib/agent-dirs.js
80
+ // (兼容 Vercel Labs `skills` CLI / agentskills.io 的宿主表,MIT)。
81
+ const AGENT_DIRS = agentDirsLib.AGENT_DIRS;
95
82
 
96
83
  function codexUserDir() {
97
- const base = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
98
- return path.join(base, 'skills');
84
+ return agentDirsLib.resolveUserDir('.codex/skills', { homeDir: os.homedir(), env: process.env });
99
85
  }
100
86
  function opencodeUserDir() {
101
- const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
102
- return path.join(base, 'opencode', 'skills');
87
+ return agentDirsLib.resolveUserDir('.config/opencode/skills', { homeDir: os.homedir(), env: process.env });
103
88
  }
104
89
  function resolveUserDir(rel) {
105
- if (rel === '.codex/skills') return codexUserDir();
106
- if (rel === '.config/opencode/skills') return opencodeUserDir();
107
- return path.join(os.homedir(), rel);
90
+ return agentDirsLib.resolveUserDir(rel, { homeDir: os.homedir(), env: process.env });
108
91
  }
109
92
 
110
93
  // ── 工具函数 ───────────────────────────────────────────────────────────────
@@ -170,6 +153,9 @@ function parseArgs(argv) {
170
153
  host: null, event: null, manifest: null, context: null,
171
154
  signal: null, yes: false, explain: false, promote: false,
172
155
  only: [], domain: null, installedOnly: false,
156
+ hub: null, all: false, inPlace: false, allowUnverified: false,
157
+ from: null, include: [], as: null, skillsCli: null,
158
+ scan: false, apply: false,
173
159
  };
174
160
  const positionals = [];
175
161
  for (let i = 0; i < argv.length; i++) {
@@ -219,6 +205,22 @@ function parseArgs(argv) {
219
205
  }
220
206
  else if (a === '--domain') opts.domain = take('--domain').toLowerCase();
221
207
  else if (a === '--installed-only') opts.installedOnly = true;
208
+ else if (a === '--hub') opts.hub = take('--hub');
209
+ else if (a === '--all') opts.all = true;
210
+ else if (a === '--in-place') opts.inPlace = true;
211
+ else if (a === '--scan') opts.scan = true;
212
+ else if (a === '--apply') opts.apply = true;
213
+ else if (a === '--allow-unverified') opts.allowUnverified = true;
214
+ else if (a === '--from') opts.from = take('--from');
215
+ else if (a === '--include') {
216
+ const value = take('--include');
217
+ for (const item of value.split(',')) {
218
+ const slug = item.trim().toLowerCase();
219
+ if (slug) opts.include.push(slug);
220
+ }
221
+ }
222
+ else if (a === '--as') opts.as = take('--as').toLowerCase();
223
+ else if (a === '--skills-cli') opts.skillsCli = take('--skills-cli');
222
224
  else if (a === '--host') opts.host = take('--host').toLowerCase();
223
225
  else if (a === '--event') opts.event = take('--event').toLowerCase();
224
226
  else if (a === '--manifest') opts.manifest = take('--manifest');
@@ -233,7 +235,12 @@ function parseArgs(argv) {
233
235
  }
234
236
  // 命令解析:install / update / doctor / rollback,其余位置参数 = 技能 slug(可多个)
235
237
  for (const p of positionals) {
238
+ if (opts.command === 'hub') {
239
+ opts.rest.push(p);
240
+ continue;
241
+ }
236
242
  if (p === 'install' || p === 'update' || p === 'doctor' || p === 'rollback' || p === 'hook'
243
+ || p === 'hub'
237
244
  || p === 'usage' || p === 'decide-memory') {
238
245
  if (opts.command && opts.command !== p) die('命令冲突:' + opts.command + ' 与 ' + p);
239
246
  opts.command = p;
@@ -244,12 +251,21 @@ function parseArgs(argv) {
244
251
  // 直接给 slug 且无命令 → 视为 install 单个/多个
245
252
  if (!opts.command && opts.rest.length > 0) opts.command = 'install';
246
253
  opts.skills = opts.rest.map(s => s.toLowerCase());
254
+ if (opts.command === 'hub') {
255
+ opts.hubAction = opts.skills[0] || 'status';
256
+ opts.skills = opts.skills.slice(1);
257
+ }
247
258
  if (opts.scheduled && (opts.command !== 'update' || !opts.check || opts.auto)) {
248
259
  die('--scheduled 只能与 update --check 一起使用', 2, '请使用 update --check --scheduled;自动更新不使用后台调度入口。');
249
260
  }
250
261
  if (opts.installedOnly && opts.command !== 'update') {
251
262
  die('--installed-only 只能与 update 一起使用', 2, '标准配方:yotta-skills update --installed-only --dir <技能目录>。');
252
263
  }
264
+ if ((opts.all || opts.inPlace || opts.allowUnverified || opts.from || opts.include.length || opts.as || opts.scan || opts.apply)
265
+ && opts.command !== 'hub') {
266
+ die('--all / --in-place / --allow-unverified / --from / --include / --as / --scan / --apply 只能与 hub 命令一起使用', 2,
267
+ 'Hub 用法:yotta-skills hub link --all 或 yotta-skills hub adopt --scan。');
268
+ }
253
269
  if (opts.domain && !domainValues().includes(opts.domain)) {
254
270
  die('未知 domain: ' + opts.domain + '。可用: ' + domainValues().join(', '), 2, 'domain 对齐家族索引 9 类;用 yotta-skills --list 查看技能清单。');
255
271
  }
@@ -1306,6 +1322,16 @@ function printHelp() {
1306
1322
  out(' yotta-skills hook evaluate --event <event> --manifest <file> 评估 hook 声明并留证');
1307
1323
  out(' yotta-skills hook bind --manifest <file> 注册 hook 声明(幂等)');
1308
1324
  out(' yotta-skills hook unbind <binding-id> 反注册 hook 声明');
1325
+ out(' yotta-skills hub hosts 发现本机已装智能体与技能目录(不读元忆)');
1326
+ out(' yotta-skills hub install [skill...] 把元技能装进本机 Hub(单点真源)');
1327
+ out(' yotta-skills hub adopt --scan 只读预演:扫描各宿主现有技能,输出收编候选与冲突');
1328
+ out(' yotta-skills hub adopt --apply 把选中技能复制进 Hub(默认保留原目录;--in-place 原地登记)');
1329
+ out(' yotta-skills hub refresh <slug> --from <path> 手动同步非元阁技能(无统一更新源)');
1330
+ out(' yotta-skills hub link --all 把 Hub 技能链接到全部已发现宿主(junction / symlink)');
1331
+ out(' yotta-skills hub link --agent <id> 只链接到一个宿主(未知宿主用 --dir)');
1332
+ out(' yotta-skills hub unlink --all 只删除链接,不动 Hub 真源(fail-closed)');
1333
+ out(' yotta-skills hub status 查看 Hub 技能 / 来源 / 链接 / 宿主');
1334
+ out(' yotta-skills hub doctor 检查断链 / 目标缺失 / slug 不一致 / 目录权限');
1309
1335
  out('');
1310
1336
  out('选项:');
1311
1337
  out(' --agent <name> 智能体键名(--list 可查看;未知智能体请用 --dir)');
@@ -1336,12 +1362,18 @@ function printHelp() {
1336
1362
  out(' --yes usage reset 确认清空');
1337
1363
  out(' --explain decide-memory 文本报告追加信号明细');
1338
1364
  out(' --promote decide-memory 只写本地建议文件(不写元忆)');
1365
+ out(' --hub <path> Hub 目录(默认 ~/.yottaskills/hub;YOTTA_SKILLS_HUB 覆盖)');
1366
+ out(' --all hub link / unlink 时作用于全部已发现宿主');
1367
+ out(' --scan hub adopt 只读预演(默认行为)');
1368
+ out(' --apply hub adopt 执行收编');
1369
+ out(' --in-place hub adopt 原地登记,不复制(原目录删除即断链)');
1370
+ out(' --allow-unverified 显式允许未扫描导入(默认 high / critical 阻断)');
1339
1371
  out(' --project inventory / reindex 时附加扫描当前项目 .agents/skills / .codex/skills');
1340
1372
  out(' --no-reindex 安装 / 更新后不自动重扫注册表');
1341
1373
  out(' -h, --help 帮助');
1342
1374
  out(' -v, --version 版本');
1343
1375
  out('');
1344
- out('支持智能体: ' + Object.keys(AGENT_DIRS).join(', '));
1376
+ out('支持智能体: ' + Object.keys(AGENT_DIRS).length + ' 个已收录映射(本机实际发现用 yotta-skills hub hosts)');
1345
1377
  out('依赖: Node.js 18+(必需);npm 与系统 tar 为回退通道(内置拉包 / 内置解包为主);元信 scan 需要 Python 3.8+(--python / YOTTA_SKILLS_PYTHON 可指向宿主自带 Python)。');
1346
1378
  out('环境变量: YOTTA_SKILLS_FETCH(builtin|npm)/ YOTTA_SKILLS_EXTRACT(builtin|tar)/ YOTTA_SKILLS_NPM / YOTTA_SKILLS_PYTHON / YOTTA_SKILLS_VERIFY / YOTTA_SKILLS_NPM_FLAGS / YOTTA_SKILLS_REGISTRY / YOTTA_SKILLS_REGISTRY_FILE / YOTTA_SKILLS_MANIFEST 可覆盖。');
1347
1379
  }
@@ -1800,6 +1832,356 @@ function runHook(opts) {
1800
1832
  die('未知 hook 子命令: ' + action, 2, '支持 capabilities / evaluate / bind / unbind。');
1801
1833
  }
1802
1834
 
1835
+ // ── Hub(本机单点安装 + 链接分发) ─────────────────────────────────────────
1836
+ function universalSkillDirs() {
1837
+ const home = os.homedir();
1838
+ const xdg = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
1839
+ return [
1840
+ { dir: path.join(xdg, 'agents', 'skills'), agentId: 'universal', label: 'Universal .agents' },
1841
+ { dir: path.join(home, '.agents', 'skills'), agentId: 'agents', label: '通用 AGENTS.md' },
1842
+ ];
1843
+ }
1844
+
1845
+ function hubTargetDirs(opts, discovery) {
1846
+ const targets = [];
1847
+ const seen = new Set();
1848
+ const add = (dir, agentId, label) => {
1849
+ if (!dir) return;
1850
+ const resolved = path.resolve(dir);
1851
+ const key = process.platform === 'win32' ? resolved.toLowerCase() : resolved;
1852
+ if (seen.has(key)) return;
1853
+ seen.add(key);
1854
+ targets.push({ dir: resolved, agentId: agentId || null, label: label || '指定目录' });
1855
+ };
1856
+
1857
+ if (opts.dir) {
1858
+ add(opts.dir, null, '指定目录');
1859
+ return targets;
1860
+ }
1861
+ if (opts.agent) {
1862
+ const info = AGENT_DIRS[opts.agent];
1863
+ if (!info) {
1864
+ die('未收录智能体: ' + opts.agent + '。可用: ' + Object.keys(AGENT_DIRS).join(', ') + ';或改用 --dir <路径>。',
1865
+ 2, '先运行 yotta-skills hub hosts 查看本机实际技能目录。');
1866
+ }
1867
+ add(resolveUserDir(info.dirs[0]), opts.agent, info.label);
1868
+ return targets;
1869
+ }
1870
+ if (!opts.all) {
1871
+ die('hub link / unlink 需要 --agent <id>、--dir <path> 或 --all', 2,
1872
+ '示例:yotta-skills hub link --all;或 yotta-skills hub link --agent codex。');
1873
+ }
1874
+
1875
+ for (const item of universalSkillDirs()) add(item.dir, item.agentId, item.label);
1876
+ for (const host of discovery.hosts) {
1877
+ if (!host.exists) continue;
1878
+ add(host.dir, host.agentId, host.label);
1879
+ }
1880
+ return targets;
1881
+ }
1882
+
1883
+ function hubHostLines(discovery) {
1884
+ const lines = [];
1885
+ const existing = discovery.hosts.filter((host) => host.exists);
1886
+ const missing = discovery.hosts.filter((host) => !host.exists);
1887
+ for (const host of existing) {
1888
+ lines.push(' ' + (host.known ? '✔' : '?') + ' ' +
1889
+ String(host.label).padEnd(28) + host.dir.padEnd(64) +
1890
+ String(host.skillCount).padStart(3) + ' 个技能' +
1891
+ (host.known ? ' [已收录]' : ' [自动发现]'));
1892
+ }
1893
+ const noSkillDir = discovery.installed.filter((item) => !item.hasSkillsDir);
1894
+ for (const item of noSkillDir) {
1895
+ lines.push(' △ ' + String(item.label).padEnd(28) + '已安装,未发现技能目录 [不适用]');
1896
+ }
1897
+ return { lines, existing, missing, noSkillDir };
1898
+ }
1899
+
1900
+ function printHubHosts(discovery, json) {
1901
+ if (json) {
1902
+ out(JSON.stringify(discovery, null, 2));
1903
+ return;
1904
+ }
1905
+ const view = hubHostLines(discovery);
1906
+ out('yotta-skills(元阁)v' + VERSION + ' —— Hub 宿主发现(文件系统优先,不读元忆)');
1907
+ out('本机发现技能目录 ' + view.existing.length + ' 个 / 已装应用标记 ' + discovery.installed.length +
1908
+ ' 个 / 未发现技能目录 ' + view.noSkillDir.length + ' 个');
1909
+ for (const line of view.lines) out(line);
1910
+ if (view.missing.length > 0) {
1911
+ out('');
1912
+ out('未安装 / 未发现目录 ' + view.missing.length + ' 个(已收录映射,等安装后自动出现)。');
1913
+ }
1914
+ }
1915
+
1916
+ function printHubStatus(payload, discovery, json) {
1917
+ if (json) {
1918
+ out(JSON.stringify({ ...payload, discovery }, null, 2));
1919
+ return;
1920
+ }
1921
+ out('yotta-skills(元阁)v' + VERSION + ' —— Hub 状态');
1922
+ out('标准: ' + payload.standard);
1923
+ out('Hub: ' + payload.hubDir);
1924
+ out('技能: ' + payload.summary.skills + ' / 非元阁技能: ' + payload.summary.external +
1925
+ ' / 链接: ' + payload.summary.links + ' / 异常链接: ' + payload.summary.brokenLinks);
1926
+ out('');
1927
+ for (const skill of payload.skills) {
1928
+ const origin = skill.origin === 'yotta' ? '元技能' : '非元阁技能(无更新源)';
1929
+ const links = skill.links.length > 0 ? skill.links.map((link) => link.label || link.agent || '链接').join(', ') : '-';
1930
+ out(' ' + skill.slug.padEnd(26) + 'v' + String(skill.version || '-').padEnd(10) +
1931
+ origin.padEnd(22) + '链接: ' + links +
1932
+ (skill.status === 'missing' ? ' [目标缺失]' : ''));
1933
+ }
1934
+ if (payload.skills.length === 0) out(' (Hub 为空;先运行 yotta-skills hub install)');
1935
+ const broken = payload.links.filter((link) => link.status !== 'ok');
1936
+ if (broken.length > 0) {
1937
+ out('');
1938
+ out('异常链接:');
1939
+ for (const link of broken) out(' ✘ ' + link.slug + ' ' + link.target + '(' + link.status + ')');
1940
+ }
1941
+ out('');
1942
+ printHubHosts(discovery, false);
1943
+ }
1944
+
1945
+ function hubScanEngine(hubDir, discovery, opts) {
1946
+ const direct = findVerifyEngine(hubDir, opts);
1947
+ if (direct) return direct;
1948
+ for (const host of discovery.hosts) {
1949
+ if (!host.exists) continue;
1950
+ const engine = findVerifyEngine(host.dir, opts);
1951
+ if (engine) return engine;
1952
+ }
1953
+ return null;
1954
+ }
1955
+
1956
+ function hubScanSkill(hubDir, discovery, opts, skillDir) {
1957
+ if (opts.skipScan) {
1958
+ return { ok: true, verdict: 'explicit-unverified', counts: null, block: false };
1959
+ }
1960
+ const engine = hubScanEngine(hubDir, discovery, opts);
1961
+ if (!engine) {
1962
+ return {
1963
+ ok: false,
1964
+ error: '未找到元信扫描引擎(可先安装 yotta-verify,或用 --allow-unverified 显式降级)',
1965
+ };
1966
+ }
1967
+ const scan = runScan(engine, skillDir, opts);
1968
+ if (!scan.ok) return scan;
1969
+ const verdict = gateLib.evaluateVerdict(scan.verdict);
1970
+ return { ok: true, verdict: scan.verdict, counts: scan.counts, block: verdict.block };
1971
+ }
1972
+
1973
+ function printAdoptScan(payload, json) {
1974
+ if (json) {
1975
+ out(JSON.stringify(payload, null, 2));
1976
+ return;
1977
+ }
1978
+ out('yotta-skills(元阁)v' + VERSION + ' —— Hub 收编预演(只读)');
1979
+ out('Hub: ' + payload.hubDir);
1980
+ out('候选 ' + payload.summary.candidates + ' / 冲突 ' + payload.summary.conflicts +
1981
+ ' / 已在 Hub ' + payload.summary.alreadyInHub);
1982
+ out('');
1983
+ for (const item of payload.candidates) {
1984
+ const mark = item.conflict ? '△' : item.inHub ? '·' : '✔';
1985
+ out(' ' + mark + ' ' + item.slug.padEnd(26) + 'v' + String(item.version || '-').padEnd(10) +
1986
+ (item.sourceHost || '-') + ' ' + (item.source || ''));
1987
+ if (item.conflict) {
1988
+ out(' 冲突副本: ' + item.variants.map((variant) =>
1989
+ (variant.host || '?') + ' v' + (variant.version || '?')).join(', '));
1990
+ }
1991
+ }
1992
+ if (payload.candidates.length === 0) out(' (没有发现可收编技能)');
1993
+ }
1994
+
1995
+ function printAdoptApply(payload, json) {
1996
+ if (json) {
1997
+ out(JSON.stringify(payload, null, 2));
1998
+ return;
1999
+ }
2000
+ out('yotta-skills(元阁)v' + VERSION + ' —— Hub 收编');
2001
+ out('Hub: ' + payload.hubDir);
2002
+ for (const item of payload.results) {
2003
+ const mark = item.status === 'imported' ? '✔' : item.status === 'skip' ? '·' : item.status === 'conflict' ? '△' : '✘';
2004
+ out(' ' + mark + ' ' + item.slug.padEnd(26) + item.note);
2005
+ }
2006
+ out('汇总: 收编 ' + payload.results.filter((item) => item.status === 'imported').length +
2007
+ ' / 跳过 ' + payload.results.filter((item) => item.status === 'skip').length +
2008
+ ' / 冲突 ' + payload.results.filter((item) => item.status === 'conflict').length +
2009
+ ' / 失败 ' + payload.results.filter((item) => !['imported', 'skip', 'conflict'].includes(item.status)).length);
2010
+ }
2011
+
2012
+ function printHubDoctor(payload, json) {
2013
+ if (json) {
2014
+ out(JSON.stringify(payload, null, 2));
2015
+ return;
2016
+ }
2017
+ out('yotta-skills(元阁)v' + VERSION + ' —— Hub doctor');
2018
+ out('Hub: ' + payload.hubDir);
2019
+ for (const check of payload.checks) {
2020
+ if (check.ok && check.severity !== 'info') continue;
2021
+ const mark = check.ok ? '✔' : check.severity === 'warning' ? '△' : '✘';
2022
+ out(' ' + mark + ' ' + check.message + (check.hint ? '(修复: ' + check.hint + ')' : ''));
2023
+ }
2024
+ out('汇总: 错误 ' + payload.summary.errors + ' / 警告 ' + payload.summary.warnings);
2025
+ if (!payload.ok) out('Hub doctor 未通过:请按上面的修复建议处理后重试。');
2026
+ }
2027
+
2028
+ function runHub(opts) {
2029
+ const hubDir = hubLib.resolveHubDir(opts);
2030
+ const action = opts.hubAction || 'status';
2031
+ const discovery = agentDiscoveryLib.discoverHosts({ homeDir: os.homedir(), env: process.env });
2032
+
2033
+ if (action === 'hosts' || action === 'discover') {
2034
+ printHubHosts(discovery, opts.json);
2035
+ return;
2036
+ }
2037
+
2038
+ if (action === 'install' || action === 'update') {
2039
+ out('Hub: ' + hubDir);
2040
+ const result = action === 'install' ? runInstall(opts, hubDir) : runUpdate(opts, hubDir);
2041
+ const synced = hubLib.syncHubState(hubDir, { manifest: MANIFEST });
2042
+ const count = Object.values(synced.state.skills).filter((item) => item.status === 'present').length;
2043
+ out('');
2044
+ out('Hub 台账已更新: ' + count + ' 个技能;标准 ' + hubLib.STANDARD_ID);
2045
+ if (result.failed > 0) process.exitCode = result.exitCode || 1;
2046
+ return;
2047
+ }
2048
+
2049
+ if (action === 'adopt') {
2050
+ if (opts.as) {
2051
+ die('hub adopt --as 暂未启用', 2, '先收编原 slug;改名 / 冲突改写放到后续里程碑,避免静默改技能身份。');
2052
+ }
2053
+ const scan = hubAdoptLib.scanCandidates({ hubDir, discovery });
2054
+ if (!opts.apply) {
2055
+ printAdoptScan(scan, opts.json);
2056
+ return;
2057
+ }
2058
+ const payload = hubAdoptLib.applyCandidates({
2059
+ hubDir,
2060
+ candidates: scan.candidates,
2061
+ include: opts.include,
2062
+ force: opts.force,
2063
+ inPlace: opts.inPlace,
2064
+ skipScan: opts.skipScan,
2065
+ allowUnverified: opts.allowUnverified,
2066
+ manifest: MANIFEST,
2067
+ homeDir: os.homedir(),
2068
+ env: process.env,
2069
+ scan: (skillDir) => hubScanSkill(hubDir, discovery, opts, skillDir),
2070
+ });
2071
+ printAdoptApply(payload, opts.json);
2072
+ if (payload.results.some((item) => !['imported', 'skip', 'conflict'].includes(item.status))) process.exitCode = 1;
2073
+ return;
2074
+ }
2075
+
2076
+ if (action === 'refresh') {
2077
+ if (!opts.from) die('hub refresh 缺少 --from <path>', 2, '示例:yotta-skills hub refresh my-skill --from <技能目录>。');
2078
+ if (opts.skills.length !== 1) die('hub refresh 需要一个且仅一个技能 slug', 2, '示例:yotta-skills hub refresh my-skill --from <技能目录>。');
2079
+ const result = hubAdoptLib.refreshFrom({
2080
+ hubDir,
2081
+ slug: opts.skills[0],
2082
+ from: opts.from,
2083
+ skipScan: opts.skipScan,
2084
+ allowUnverified: opts.allowUnverified,
2085
+ manifest: MANIFEST,
2086
+ homeDir: os.homedir(),
2087
+ env: process.env,
2088
+ scan: (skillDir) => hubScanSkill(hubDir, discovery, opts, skillDir),
2089
+ });
2090
+ if (opts.json) out(JSON.stringify(result, null, 2));
2091
+ else if (result.ok) out('已刷新: ' + result.slug + '(来源 ' + result.source + ')');
2092
+ else out('刷新失败: ' + result.error);
2093
+ if (!result.ok) process.exitCode = 1;
2094
+ return;
2095
+ }
2096
+
2097
+ if (action === 'link') {
2098
+ const targets = hubTargetDirs(opts, discovery);
2099
+ let failed = 0;
2100
+ const targetPayloads = [];
2101
+ for (const target of targets) {
2102
+ if (path.resolve(target.dir) === path.resolve(hubDir)) {
2103
+ targetPayloads.push({ dir: target.dir, label: target.label, skipped: true, results: [] });
2104
+ if (!opts.json) out('跳过 Hub 自身: ' + target.dir);
2105
+ continue;
2106
+ }
2107
+ const result = hubLib.linkSkills({
2108
+ hubDir,
2109
+ targetDir: target.dir,
2110
+ agentId: target.agentId,
2111
+ label: target.label,
2112
+ slugs: opts.skills,
2113
+ force: opts.force,
2114
+ dryRun: opts.dryRun,
2115
+ });
2116
+ targetPayloads.push({ dir: target.dir, label: target.label, results: result.results });
2117
+ if (!opts.json) {
2118
+ out('');
2119
+ out('目标: ' + target.dir + '(' + target.label + ')');
2120
+ for (const item of result.results) {
2121
+ const mark = item.status === 'linked' ? '✔' : item.status === 'conflict' ? '△' : item.status === 'error' ? '✘' : '·';
2122
+ out(' ' + mark + ' ' + item.slug.padEnd(26) + item.note);
2123
+ if (item.status === 'error') failed++;
2124
+ }
2125
+ } else {
2126
+ failed += result.results.filter((item) => item.status === 'error').length;
2127
+ }
2128
+ }
2129
+ if (opts.json) out(JSON.stringify({ hubDir, targets: targetPayloads, failed }, null, 2));
2130
+ if (failed > 0) process.exitCode = 1;
2131
+ return;
2132
+ }
2133
+
2134
+ if (action === 'unlink') {
2135
+ const targets = hubTargetDirs(opts, discovery);
2136
+ let failed = 0;
2137
+ const targetPayloads = [];
2138
+ for (const target of targets) {
2139
+ const result = hubLib.unlinkSkills({
2140
+ hubDir,
2141
+ targetDir: target.dir,
2142
+ slugs: opts.skills,
2143
+ dryRun: opts.dryRun,
2144
+ });
2145
+ targetPayloads.push({ dir: target.dir, label: target.label, results: result.results });
2146
+ if (!opts.json) {
2147
+ out('');
2148
+ out('目标: ' + target.dir + '(' + target.label + ')');
2149
+ for (const item of result.results) {
2150
+ const mark = item.status === 'unlinked' ? '✔' : item.status === 'refused' ? '△' : item.status === 'error' ? '✘' : '·';
2151
+ out(' ' + mark + ' ' + item.slug.padEnd(26) + item.note);
2152
+ if (item.status === 'error') failed++;
2153
+ }
2154
+ } else {
2155
+ failed += result.results.filter((item) => item.status === 'error').length;
2156
+ }
2157
+ }
2158
+ if (opts.json) out(JSON.stringify({ hubDir, targets: targetPayloads, failed }, null, 2));
2159
+ if (failed > 0) process.exitCode = 1;
2160
+ return;
2161
+ }
2162
+
2163
+ if (action === 'status') {
2164
+ const payload = hubLib.status({ hubDir, manifest: MANIFEST });
2165
+ printHubStatus(payload, discovery, opts.json);
2166
+ return;
2167
+ }
2168
+
2169
+ if (action === 'doctor') {
2170
+ const payload = hubLib.doctor({
2171
+ hubDir,
2172
+ manifest: MANIFEST,
2173
+ homeDir: os.homedir(),
2174
+ env: process.env,
2175
+ });
2176
+ printHubDoctor(payload, opts.json);
2177
+ if (!payload.ok) process.exitCode = 1;
2178
+ return;
2179
+ }
2180
+
2181
+ die('未知 hub 子命令: ' + action, 2,
2182
+ '支持 install / update / adopt / refresh / link / unlink / status / hosts / doctor。');
2183
+ }
2184
+
1803
2185
  // ── main ───────────────────────────────────────────────────────────────────
1804
2186
  function main() {
1805
2187
  const node = depsLib.nodeCheck();
@@ -1828,6 +2210,10 @@ function main() {
1828
2210
  runHook(opts);
1829
2211
  return;
1830
2212
  }
2213
+ if (command === 'hub') {
2214
+ runHub(opts);
2215
+ return;
2216
+ }
1831
2217
  if (command === 'rollback' && opts.list) {
1832
2218
  runRollback(opts, null);
1833
2219
  return;