@yottameta/yotta-skills-plugin 0.29.10 → 0.30.1
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/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/yotta-skills/SKILL.md +11 -7
- package/skills/yotta-skills/assets/view.html +97 -35
- package/skills/yotta-skills/bin/yotta-skills.js +210 -7
- package/skills/yotta-skills/lib/cli-help.js +115 -58
- package/skills/yotta-skills/lib/hosts-registry.js +29 -1
- package/skills/yotta-skills/lib/hub-adopt.js +12 -1
- package/skills/yotta-skills/lib/hub-scan.js +1 -0
- package/skills/yotta-skills/lib/hub-view-server.js +43 -2
- package/skills/yotta-skills/lib/hub.js +99 -5
- package/skills/yotta-skills/references/hub.md +9 -5
- package/skills/yotta-skills/references/skill-list.md +2 -2
- package/skills/yotta-skills/scan-policy.json +3 -3
- package/skills/yotta-skills/scripts/yotta-skills-mcp.py +1 -1
- package/skills/yotta-skills/skills.json +2 -2
|
@@ -179,24 +179,25 @@ const CLI_HELP_MODEL = [
|
|
|
179
179
|
subcommands: [
|
|
180
180
|
{ name: 'hosts', usage: 'hub hosts [--json] [--state <state>]', what: '发现宿主与技能目录(可用 / 残留 / 未创建 / 仅标记 / 桥接)', when: '先看这台机器有哪些宿主目录时', options: ['--json', '--state'] },
|
|
181
181
|
{ name: 'hosts add', usage: 'hub hosts add <dir> [--label <名称>] [--id <agentId>] [--json]', what: '注册自定义宿主目录(纳入发现 / 分发 / 收编;不创建目录)', when: '宿主未被自动识别或识别错误时', options: ['--label', '--id', '--json'] },
|
|
182
|
-
{ name: 'hosts
|
|
183
|
-
{ name: 'hosts
|
|
184
|
-
{ name: 'hosts
|
|
185
|
-
{ name: 'hosts
|
|
182
|
+
{ name: 'hosts rename', usage: 'hub hosts rename <dir | --id <agentId>> --label <名称> [--json]', what: '一步改名(只改显示名;目录与手动状态保持)', when: '自定义宿主显示名不合适时', options: ['--label', '--id', '--json'] },
|
|
183
|
+
{ name: 'hosts remove', usage: 'hub hosts remove <dir | --id <agentId>> [--purge [--yes]] [--json]', what: '移除注册(绝不删目录);--purge = 清理残留目录(默认预览,--yes 执行)', when: '注销自定义宿主、清理残留目录时', options: ['--id', '--purge', '--yes', '--json'], impact: '影响面:只移除注册表条目(目录保留);--purge 会把残留目录移入回收站(保留 7 天);不会自动清理该目录里已有的 Hub 链接。', alternative: '替代路径:只想暂停接管用 hub hosts exclude;清理该目录链接用 hub unlink --dir "<目录>"。' },
|
|
184
|
+
{ name: 'hosts list', usage: 'hub hosts list [--json]', what: '查看自定义宿主注册表(仅手动 add 的目录;全部宿主用 hub hosts)', when: '核对已注册的自定义目录时', options: ['--json'] },
|
|
185
|
+
{ name: 'hosts mark', usage: 'hub hosts mark <dir> --state <available|orphan|ignored> [--json]', what: '手动标记宿主状态(可用 / 残留 / 隐藏标记);隐藏标记只影响显示,不拦截行为', when: '实体证据不足需要人工确认、或想让某行从列表淡化时', options: ['--state', '--json'], impact: '影响面:纯状态标记(available / orphan / ignored),不移动目录、不改链接。真正拦截行为的是「不接管」(hosts exclude)。' },
|
|
186
|
+
{ name: 'hosts exclude', usage: 'hub hosts exclude <agentId|dir> [--json]', what: '加入「不接管」名单(发现 / 显示 / 链接三层跳过)', when: '某宿主 / 目录不归元阁管理时', options: ['--json'], impact: '影响面:发现 / 显示 / 链接三层全部跳过(--include-discovered 也不纳入);已有链接不动。', alternative: '替代路径:只淡化显示用 hub hosts mark --state ignored;恢复接管用 hub hosts include。' },
|
|
186
187
|
{ name: 'hosts include', usage: 'hub hosts include <agentId|dir> [--json]', what: '恢复接管(移出「不接管」名单)', when: '重新纳入发现 / 链接时', options: ['--json'] },
|
|
187
188
|
{ name: 'hosts set', usage: 'hub hosts set <agentId> --dir <dir> [--label <名称>] [--json] | hub hosts set <agentId> --clear', what: '目录覆盖:用户级覆盖内置映射;--clear 恢复默认', when: '宿主技能目录与内置映射不一致时', options: ['--dir', '--label', '--clear', '--json'] },
|
|
188
189
|
{ name: 'config get', usage: 'hub config get [--json]', what: '查看生效 Hub 位置与来源(--hub / YOTTA_SKILLS_HUB / config.json / 默认)', when: '确认当前 Hub 与配置覆盖时', options: ['--json'] },
|
|
189
190
|
{ name: 'config set', usage: 'hub config set --hub <path> [--move [--clean-residue]]', what: '持久化 Hub 位置;--move 迁移旧 Hub(复制校验 → 重链 → 回收站 7 天);未迁移时记录原位置供面板提示', when: '换 Hub 存储位置时', options: ['--hub', '--move', '--clean-residue', '--json'] },
|
|
190
|
-
{ name: 'config rollback', usage: 'hub config rollback [--yes] [--clean-residue]', what: '回滚到上次迁移前的 Hub 位置(位置回退,用当前内容;反向迁移同内核)', when: '迁移后想退回原位置时', options: ['--yes', '--clean-residue', '--json'] },
|
|
191
|
-
{ name: 'config clear', usage: 'hub config clear [--json]', what: '清除配置覆盖,恢复 flag > env >
|
|
191
|
+
{ name: 'config rollback', usage: 'hub config rollback [--yes] [--clean-residue]', what: '回滚到上次迁移前的 Hub 位置(位置回退,用当前内容;反向迁移同内核)', when: '迁移后想退回原位置时', options: ['--yes', '--clean-residue', '--json'], impact: '影响面:位置回退(用当前内容复制回原位置并重链,不恢复旧快照);目标非空时 fail-closed 阻断。', alternative: '替代路径:只查看历史迁移用 hub config get;继续当前位置则不需要操作。' },
|
|
192
|
+
{ name: 'config clear', usage: 'hub config clear [--yes] [--json]', what: '清除配置覆盖,恢复 flag > env > 默认;当前 Hub 有技能时默认阻断,--yes 显式确认', when: '撤销持久 Hub 位置时', options: ['--yes', '--json'], impact: '影响面:清除后生效位置回落到默认(可能是空目录);Hub 真源与宿主链接不移动,但元阁命令将按新位置工作(表现为台账 / 链接空转)。', alternative: '替代路径:迁移位置用 hub config set --hub "<新路径>" --move;想回原位置用 hub config set --hub "<原路径>"。' },
|
|
192
193
|
{ name: 'cleanup-backups', usage: 'hub cleanup-backups [--yes] [--include-discovered]', what: '清理宿主 / Hub 里的 .yottaskills-backup-* / .yottaskills-staging 历史残留(移入回收站 7 天)', when: '宿主把备份当技能扫描、或迁移后要清残留时', options: ['--yes', '--include-discovered', '--dir', '--agent', '--json'] },
|
|
193
194
|
{ name: 'install', usage: 'hub install [skill...]', what: '把元技能装进 Hub 真源(清单 27 + 特殊家族 5)', when: '初始化 Hub 或补装技能时', options: ['--hub'] },
|
|
194
195
|
{ name: 'update', usage: 'hub update', what: '更新 Hub 内的元技能(非元技能不参与;只升不降)', when: '日常维护 Hub 真源时', options: ['--hub'] },
|
|
195
196
|
{ name: 'adopt', usage: 'hub adopt [--scan | --apply] [--include <a,b>] [--in-place]', what: '把各宿主已有技能收编进 Hub(默认复制保真)', when: '接管机器上已装的技能时', options: ['--scan', '--apply', '--include', '--in-place', '--force', '--skip-scan', '--allow-unverified'] },
|
|
196
197
|
{ name: 'refresh', usage: 'hub refresh <slug> --from <path>', what: '手动同步非元阁技能(无统一更新源)', when: '外部技能更新后刷新 Hub 副本时', options: ['--from'] },
|
|
197
198
|
{ name: 'link', usage: 'hub link [skill...] [--all | --agent <id> | --dir <path>] [--include-discovered] [--dry-run]', what: '把 Hub 技能链接到宿主目录(指定智能体 / 全量)', when: '分发技能到各智能体时', options: ['--all', '--agent', '--dir', '--include-discovered', '--force', '--dry-run'] },
|
|
198
|
-
{ name: 'unlink', usage: 'hub unlink [skill...] [--all | --agent <id> | --dir <path>] [--dry-run]', what: '只删除链接,不动 Hub 真源(fail-closed
|
|
199
|
-
{ name: 'remove', usage: 'hub remove <slug> [--dry-run] [--include-discovered]', what: '删除 Hub 技能:全宿主清链接(含死链)+ 目录入回收站 + 清台账 + 审计', when: '彻底删除某个 Hub 技能时', options: ['--dry-run', '--include-discovered', '--json'] },
|
|
199
|
+
{ name: 'unlink', usage: 'hub unlink [skill...] [--all | --agent <id> | --dir <path>] [--dry-run]', what: '只删除链接,不动 Hub 真源(fail-closed;--dir 无 slug 时以文件系统为真源清理该目录全部 Hub 链接)', when: '解除某个 / 全部宿主的链接时', options: ['--all', '--agent', '--dir', '--include-discovered', '--dry-run'], impact: '影响面:只删除宿主目录中的链接(Hub 真源保留);只处理指向当前 Hub 的链接,指向其它位置的链接 fail-closed 不动并给出错位警告。', alternative: '替代路径:删除整个技能用 hub remove <slug>;重建链接用 hub link --dir "<目录>" --force。' },
|
|
200
|
+
{ name: 'remove', usage: 'hub remove <slug> [--dry-run] [--include-discovered]', what: '删除 Hub 技能:全宿主清链接(含死链)+ 目录入回收站 + 清台账 + 审计', when: '彻底删除某个 Hub 技能时', options: ['--dry-run', '--include-discovered', '--json'], impact: '影响面:全宿主清理该技能链接(含死链)→ Hub 目录移入回收站(保留 7 天)→ 清台账;影响范围大。', alternative: '替代路径:只想解除某宿主链接用 hub unlink;只想清死链用 hub remove <slug> --agent/--dir(目标缺失时)。' },
|
|
200
201
|
{ name: 'status', usage: 'hub status [--json]', what: '查看 Hub 技能 / 来源 / 版本 / 链接 / 宿主', when: '核对 Hub 现状时', options: ['--json'] },
|
|
201
202
|
{ name: 'doctor', usage: 'hub doctor [--json]', what: '检查断链 / 目标缺失 / slug 不一致 / 单一真源 / 链接范围', when: 'Hub 异常后排查时', options: ['--json'] },
|
|
202
203
|
],
|
|
@@ -267,96 +268,152 @@ const CLI_HELP_QUICK = [
|
|
|
267
268
|
{
|
|
268
269
|
group: '安装与更新',
|
|
269
270
|
items: [
|
|
270
|
-
{ cmd: 'install', example: 'yotta-skills install --agent codex' },
|
|
271
|
-
{ cmd: 'install', example: 'yotta-skills install yotta-memory --dir <技能目录>' },
|
|
272
|
-
{ cmd: 'update', example: 'yotta-skills update --installed-only --dir <技能目录>' },
|
|
273
|
-
{ cmd: 'update', example: 'yotta-skills update --check' },
|
|
274
|
-
{ cmd: 'update', example: 'yotta-skills update --auto' },
|
|
275
|
-
{ cmd: '--dry-run', example: 'yotta-skills install --dry-run --agent codex' },
|
|
276
|
-
{ cmd: 'install-self', example: 'yotta-skills install-self' },
|
|
277
|
-
{ cmd: 'where', example: 'yotta-skills where' },
|
|
271
|
+
{ cmd: 'install', example: 'yotta-skills install --agent codex', note: '装全家到指定智能体' },
|
|
272
|
+
{ cmd: 'install', example: 'yotta-skills install yotta-memory --dir <技能目录>', note: '只装一个技能到指定目录' },
|
|
273
|
+
{ cmd: 'update', example: 'yotta-skills update --installed-only --dir <技能目录>', note: '接管现有技能(只升不降)' },
|
|
274
|
+
{ cmd: 'update', example: 'yotta-skills update --check', note: '只读检查 npm 更新(不改动)' },
|
|
275
|
+
{ cmd: 'update', example: 'yotta-skills update --auto', note: '检查到家族更新后自动更新' },
|
|
276
|
+
{ cmd: '--dry-run', example: 'yotta-skills install --dry-run --agent codex', note: '预演安装 / 更新清单(不联网不改动)' },
|
|
277
|
+
{ cmd: 'install-self', example: 'yotta-skills install-self', note: '把元阁装成独立 CLI' },
|
|
278
|
+
{ cmd: 'where', example: 'yotta-skills where', note: '查看运行中 / 独立安装 / Hub 位置' },
|
|
278
279
|
],
|
|
279
280
|
},
|
|
280
281
|
{
|
|
281
282
|
group: '体检与回滚',
|
|
282
283
|
items: [
|
|
283
|
-
{ cmd: 'doctor', example: 'yotta-skills doctor --dir <技能目录>' },
|
|
284
|
-
{ cmd: 'rollback', example: 'yotta-skills rollback --list' },
|
|
285
|
-
{ cmd: 'rollback', example: 'yotta-skills rollback --slug <技能> --dir <技能目录>' },
|
|
284
|
+
{ cmd: 'doctor', example: 'yotta-skills doctor --dir <技能目录>', note: '只读自检技能目录与注册表' },
|
|
285
|
+
{ cmd: 'rollback', example: 'yotta-skills rollback --list', note: '列出可回滚的备份点' },
|
|
286
|
+
{ cmd: 'rollback', example: 'yotta-skills rollback --slug <技能> --dir <技能目录>', note: '回滚指定技能到上一个备份' },
|
|
286
287
|
],
|
|
287
288
|
},
|
|
288
289
|
{
|
|
289
290
|
group: '盘点与路由',
|
|
290
291
|
items: [
|
|
291
|
-
{ cmd: '--list', example: 'yotta-skills --list' },
|
|
292
|
-
{ cmd: '--inventory', example: 'yotta-skills --inventory --json' },
|
|
293
|
-
{ cmd: '--reindex', example: 'yotta-skills --reindex' },
|
|
294
|
-
{ cmd: '--route', example: 'yotta-skills --route "帮我做发布前质量检查"' },
|
|
295
|
-
{ cmd: 'decide-memory', example: 'yotta-skills decide-memory --dry-run' },
|
|
292
|
+
{ cmd: '--list', example: 'yotta-skills --list', note: '列出全家技能与版本' },
|
|
293
|
+
{ cmd: '--inventory', example: 'yotta-skills --inventory --json', note: '盘点本机已装技能' },
|
|
294
|
+
{ cmd: '--reindex', example: 'yotta-skills --reindex', note: '重扫本地技能注册表' },
|
|
295
|
+
{ cmd: '--route', example: 'yotta-skills --route "帮我做发布前质量检查"', note: '按需求给技能组合建议(不自动安装)' },
|
|
296
|
+
{ cmd: 'decide-memory', example: 'yotta-skills decide-memory --dry-run', note: 'M1 记忆裁决只读建议' },
|
|
296
297
|
],
|
|
297
298
|
},
|
|
298
299
|
{
|
|
299
300
|
group: '技能 Hub',
|
|
300
301
|
items: [
|
|
301
|
-
{ cmd: 'hub', example: 'yotta-skills hub' },
|
|
302
|
-
{ cmd: 'hub hosts', example: 'yotta-skills hub hosts' },
|
|
303
|
-
{ cmd: 'hub hosts', example: 'yotta-skills hub hosts --state orphan' },
|
|
304
|
-
{ cmd: 'hub hosts add', example: 'yotta-skills hub hosts add <技能目录>' },
|
|
305
|
-
{ cmd: 'hub hosts
|
|
306
|
-
{ cmd: 'hub hosts
|
|
307
|
-
{ cmd: 'hub hosts
|
|
308
|
-
{ cmd: 'hub hosts
|
|
309
|
-
{ cmd: 'hub hosts
|
|
302
|
+
{ cmd: 'hub', example: 'yotta-skills hub', note: 'Hub 子命令总览' },
|
|
303
|
+
{ cmd: 'hub hosts', example: 'yotta-skills hub hosts', note: '查看全部宿主与技能目录状态' },
|
|
304
|
+
{ cmd: 'hub hosts', example: 'yotta-skills hub hosts --state orphan', note: '只筛选「残留」状态的宿主' },
|
|
305
|
+
{ cmd: 'hub hosts add', example: 'yotta-skills hub hosts add <技能目录>', note: '注册自定义宿主目录(不创建目录)' },
|
|
306
|
+
{ cmd: 'hub hosts rename', example: 'yotta-skills hub hosts rename <技能目录> --label <新名称>', note: '只改显示名;目录与手动状态保持' },
|
|
307
|
+
{ cmd: 'hub hosts list', example: 'yotta-skills hub hosts list', note: '查看手动注册的自定义宿主' },
|
|
308
|
+
{ cmd: 'hub hosts mark', example: 'yotta-skills hub hosts mark <技能目录> --state orphan', note: '手动标记状态(可用 / 残留 / 隐藏标记)' },
|
|
309
|
+
{ cmd: 'hub hosts exclude', example: 'yotta-skills hub hosts exclude <agentId|目录>', note: '加入「不接管」名单' },
|
|
310
|
+
{ cmd: 'hub hosts include', example: 'yotta-skills hub hosts include <agentId|目录>', note: '恢复接管' },
|
|
311
|
+
{ cmd: 'hub hosts set', example: 'yotta-skills hub hosts set <agentId> --dir <目录>', note: '覆盖某智能体的技能目录映射' },
|
|
310
312
|
{ cmd: 'hub config get', example: 'yotta-skills hub config get', note: '查看生效位置与来源' },
|
|
311
313
|
{ cmd: 'hub config set', example: 'yotta-skills hub config set --hub <Hub目录>', note: '持久化 Hub 位置;迁移旧 Hub 加 --move' },
|
|
312
314
|
{ cmd: 'hub config rollback', example: 'yotta-skills hub config rollback --yes', note: '回滚到上次迁移前的位置(位置回退,用当前内容)', danger: true },
|
|
313
|
-
{ cmd: 'hub config clear', example: 'yotta-skills hub config clear', note: '
|
|
315
|
+
{ cmd: 'hub config clear', example: 'yotta-skills hub config clear', danger: true, note: '清除持久覆盖,恢复默认解析;当前 Hub 有技能时默认阻断,确认影响后加 --yes' },
|
|
314
316
|
{ cmd: 'hub cleanup-backups', example: 'yotta-skills hub cleanup-backups --yes', note: '历史备份 / 暂存残留移入回收站(7 天可恢复)', danger: true },
|
|
315
|
-
{ cmd: 'hub hosts remove', example: 'yotta-skills hub hosts remove <技能目录>', note: '
|
|
317
|
+
{ cmd: 'hub hosts remove', example: 'yotta-skills hub hosts remove <技能目录>', note: '只移除注册,不删目录;该目录已有链接不清理(用 hub unlink --dir)' },
|
|
316
318
|
{ cmd: 'hub hosts remove', example: 'yotta-skills hub hosts remove <技能目录> --purge', danger: true, note: '清理残留目录:先预览,--yes 才执行;目录入回收站保留 7 天' },
|
|
317
|
-
{ cmd: 'hub install', example: 'yotta-skills hub install' },
|
|
318
|
-
{ cmd: 'hub update', example: 'yotta-skills hub update' },
|
|
319
|
-
{ cmd: 'hub adopt', example: 'yotta-skills hub adopt --scan' },
|
|
320
|
-
{ cmd: 'hub adopt', example: 'yotta-skills hub adopt --apply --include <技能>' },
|
|
321
|
-
{ cmd: 'hub refresh', example: 'yotta-skills hub refresh <技能> --from <技能目录>' },
|
|
322
|
-
{ cmd: 'hub link', example: 'yotta-skills hub link --all' },
|
|
319
|
+
{ cmd: 'hub install', example: 'yotta-skills hub install', note: '把元技能装进 Hub 真源' },
|
|
320
|
+
{ cmd: 'hub update', example: 'yotta-skills hub update', note: '更新 Hub 内元技能(只升不降)' },
|
|
321
|
+
{ cmd: 'hub adopt', example: 'yotta-skills hub adopt --scan', note: '只读预演收编候选与冲突' },
|
|
322
|
+
{ cmd: 'hub adopt', example: 'yotta-skills hub adopt --apply --include <技能>', note: '执行收编(逐项元信扫描,high / critical 阻断)' },
|
|
323
|
+
{ cmd: 'hub refresh', example: 'yotta-skills hub refresh <技能> --from <技能目录>', note: '手动同步非元阁技能(需 --from)' },
|
|
324
|
+
{ cmd: 'hub link', example: 'yotta-skills hub link --all', note: '全量分发到全部已核实宿主' },
|
|
323
325
|
{ cmd: 'hub link', example: 'yotta-skills hub link <技能> --agent codex', note: '针对指定智能体分发' },
|
|
324
326
|
{ cmd: 'hub unlink', example: 'yotta-skills hub unlink <技能> --agent codex', note: '只解除该智能体的链接,Hub 真源保留' },
|
|
325
|
-
{ cmd: 'hub unlink', example: 'yotta-skills hub unlink --
|
|
327
|
+
{ cmd: 'hub unlink', example: 'yotta-skills hub unlink --dir <技能目录>', note: '清理该目录全部指向当前 Hub 的链接(文件系统为真源;其它位置链接不动)' },
|
|
328
|
+
{ cmd: 'hub unlink', example: 'yotta-skills hub unlink --all', note: '解除全部已核实宿主的链接' },
|
|
326
329
|
{ cmd: 'hub remove', example: 'yotta-skills hub remove <技能> --dry-run', danger: true, note: '删除 Hub 技能:全宿主清链接(含死链)+ 目录入回收站(7 天)+ 清台账;先 --dry-run 看影响' },
|
|
327
|
-
{ cmd: 'hub status', example: 'yotta-skills hub status' },
|
|
328
|
-
{ cmd: 'hub doctor', example: 'yotta-skills hub doctor' },
|
|
330
|
+
{ cmd: 'hub status', example: 'yotta-skills hub status', note: '查看 Hub 技能 / 版本 / 链接 / 宿主' },
|
|
331
|
+
{ cmd: 'hub doctor', example: 'yotta-skills hub doctor', note: '检查断链 / 缺失 / slug 不一致' },
|
|
329
332
|
],
|
|
330
333
|
},
|
|
331
334
|
{
|
|
332
335
|
group: '使用记录与 Hook',
|
|
333
336
|
items: [
|
|
334
|
-
{ cmd: 'usage', example: 'yotta-skills usage status' },
|
|
335
|
-
{ cmd: 'usage status', example: 'yotta-skills usage status --json' },
|
|
336
|
-
{ cmd: 'usage enable', example: 'yotta-skills usage enable' },
|
|
337
|
-
{ cmd: 'usage disable', example: 'yotta-skills usage disable' },
|
|
338
|
-
{ cmd: 'usage mark', example: 'yotta-skills usage mark --skill yotta-memory --signal used' },
|
|
337
|
+
{ cmd: 'usage', example: 'yotta-skills usage status', note: '使用记录开关与计数总览' },
|
|
338
|
+
{ cmd: 'usage status', example: 'yotta-skills usage status --json', note: '查看开关与计数(JSON)' },
|
|
339
|
+
{ cmd: 'usage enable', example: 'yotta-skills usage enable', note: '开启本地使用记录' },
|
|
340
|
+
{ cmd: 'usage disable', example: 'yotta-skills usage disable', note: '关闭本地使用记录' },
|
|
341
|
+
{ cmd: 'usage mark', example: 'yotta-skills usage mark --skill yotta-memory --signal used', note: '记录一次使用信号' },
|
|
339
342
|
{ cmd: 'usage reset', example: 'yotta-skills usage reset --yes', danger: true, note: '清空本地使用记录,先确认不再需要计数' },
|
|
340
|
-
{ cmd: 'hook', example: 'yotta-skills hook capabilities' },
|
|
341
|
-
{ cmd: 'hook capabilities', example: 'yotta-skills hook capabilities --host codex --json' },
|
|
342
|
-
{ cmd: 'hook evaluate', example: 'yotta-skills hook evaluate --event before_install --manifest <file>' },
|
|
343
|
-
{ cmd: 'hook bind', example: 'yotta-skills hook bind --manifest <file>' },
|
|
344
|
-
{ cmd: 'hook unbind', example: 'yotta-skills hook unbind <binding-id>' },
|
|
343
|
+
{ cmd: 'hook', example: 'yotta-skills hook capabilities', note: 'hook 子命令总览' },
|
|
344
|
+
{ cmd: 'hook capabilities', example: 'yotta-skills hook capabilities --host codex --json', note: '查看宿主六事件能力矩阵' },
|
|
345
|
+
{ cmd: 'hook evaluate', example: 'yotta-skills hook evaluate --event before_install --manifest <file>', note: '评估 hook 声明并留证' },
|
|
346
|
+
{ cmd: 'hook bind', example: 'yotta-skills hook bind --manifest <file>', note: '注册 hook 声明(幂等)' },
|
|
347
|
+
{ cmd: 'hook unbind', example: 'yotta-skills hook unbind <binding-id>', note: '反注册 hook 声明' },
|
|
345
348
|
],
|
|
346
349
|
},
|
|
347
350
|
{
|
|
348
351
|
group: '面板与帮助',
|
|
349
352
|
items: [
|
|
350
|
-
{ cmd: 'view', example: 'yotta-skills view' },
|
|
351
|
-
{ cmd: 'view', example: 'yotta-skills view --port 8790' },
|
|
352
|
-
{ cmd: '--version', example: 'yotta-skills --version' },
|
|
353
|
-
{ cmd: '--help', example: 'yotta-skills --help' },
|
|
353
|
+
{ cmd: 'view', example: 'yotta-skills view', note: '打开本地控制面板(仅 127.0.0.1)' },
|
|
354
|
+
{ cmd: 'view', example: 'yotta-skills view --port 8790', note: '指定端口打开(默认 8789)' },
|
|
355
|
+
{ cmd: '--version', example: 'yotta-skills --version', note: '查看当前版本' },
|
|
356
|
+
{ cmd: '--help', example: 'yotta-skills --help', note: '查看终端帮助(与面板同一真源)' },
|
|
354
357
|
],
|
|
355
358
|
},
|
|
356
359
|
];
|
|
357
360
|
|
|
361
|
+
/**
|
|
362
|
+
* UX-10:速查引用解析(单一真源,服务端随 /api/help 下发)。
|
|
363
|
+
*
|
|
364
|
+
* 按「最深前缀」把速查条目的 cmd 解析到模型条目:
|
|
365
|
+
* - 'hub hosts add' → 'hub hosts add'(不是父级 'hub hosts')
|
|
366
|
+
* - 'hub hosts --state orphan' → 'hub hosts'(旗标 / 参数尾段容忍)
|
|
367
|
+
* - '--list' 等伪命令按全名精确匹配
|
|
368
|
+
*/
|
|
369
|
+
function resolveHelpEntry(groups, ref) {
|
|
370
|
+
const raw = String(ref || '').trim();
|
|
371
|
+
if (!raw) return null;
|
|
372
|
+
const entries = [];
|
|
373
|
+
for (const group of groups) {
|
|
374
|
+
for (const cmd of group.commands) {
|
|
375
|
+
entries.push({ path: cmd.name, entry: cmd });
|
|
376
|
+
for (const sub of cmd.subcommands || []) {
|
|
377
|
+
entries.push({ path: cmd.name + ' ' + sub.name, entry: sub });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
for (const item of entries) {
|
|
382
|
+
if (item.path === raw) return item.entry;
|
|
383
|
+
}
|
|
384
|
+
const tokens = [];
|
|
385
|
+
for (const token of raw.split(/\s+/)) {
|
|
386
|
+
if (token.startsWith('-') || token.startsWith('<') || token.startsWith('[')) break;
|
|
387
|
+
tokens.push(token);
|
|
388
|
+
}
|
|
389
|
+
if (!tokens.length) return null;
|
|
390
|
+
let best = null;
|
|
391
|
+
let bestLen = -1;
|
|
392
|
+
for (const item of entries) {
|
|
393
|
+
const pathTokens = item.path.split(' ');
|
|
394
|
+
if (pathTokens.length > tokens.length || pathTokens.length <= bestLen) continue;
|
|
395
|
+
if (pathTokens.every((token, index) => token === tokens[index])) {
|
|
396
|
+
best = item.entry;
|
|
397
|
+
bestLen = pathTokens.length;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return best;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** 速查数据 + 服务端解析结果(面板直接用 meta,不再自行解析)。 */
|
|
404
|
+
function quickWithMeta() {
|
|
405
|
+
return CLI_HELP_QUICK.map((group) => ({
|
|
406
|
+
group: group.group,
|
|
407
|
+
items: group.items.map((item) => Object.assign({}, item, {
|
|
408
|
+
meta: resolveHelpEntry(CLI_HELP_MODEL, item.cmd) || {},
|
|
409
|
+
})),
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
|
|
358
413
|
module.exports = {
|
|
359
414
|
CLI_HELP_MODEL,
|
|
360
415
|
CLI_HELP_QUICK,
|
|
361
416
|
CLI_GLOBAL_OPTIONS,
|
|
417
|
+
resolveHelpEntry,
|
|
418
|
+
quickWithMeta,
|
|
362
419
|
};
|
|
@@ -183,7 +183,7 @@ function markHost(opts, input) {
|
|
|
183
183
|
const options = opts || {};
|
|
184
184
|
const state = input && input.state ? String(input.state).toLowerCase() : null;
|
|
185
185
|
if (!MANUAL_STATES.includes(state)) {
|
|
186
|
-
return { ok: false, error: '未知手动状态:' + state + '(可用 available / 残留 orphan /
|
|
186
|
+
return { ok: false, error: '未知手动状态:' + state + '(可用 available / 残留 orphan / 隐藏标记 ignored)' };
|
|
187
187
|
}
|
|
188
188
|
const dir = input && input.dir ? path.resolve(input.dir) : null;
|
|
189
189
|
if (!dir) return { ok: false, error: '缺少目录' };
|
|
@@ -206,6 +206,33 @@ function markHost(opts, input) {
|
|
|
206
206
|
return { ok: true, entry, registry };
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
/** 一步改名:只改显示名 label,不动目录 / 手动状态 / 覆盖(0.30.0 UX-08)。 */
|
|
210
|
+
function renameHost(opts, input) {
|
|
211
|
+
const options = opts || {};
|
|
212
|
+
const label = String((input && input.label) || '').trim();
|
|
213
|
+
const labelError = validateLabel(label);
|
|
214
|
+
if (labelError) return { ok: false, error: labelError };
|
|
215
|
+
const dir = input && input.dir ? path.resolve(input.dir) : null;
|
|
216
|
+
const id = input && input.id ? String(input.id).trim().toLowerCase() : null;
|
|
217
|
+
if (!dir && !id) return { ok: false, error: '缺少目标:hub hosts rename <目录> --label <名称> 或 --id <agentId>' };
|
|
218
|
+
const registry = readHostsRegistry(options);
|
|
219
|
+
const entry = registry.hosts.find((item) =>
|
|
220
|
+
(dir && samePath(item.dir, dir)) ||
|
|
221
|
+
(id && String(item.agentId || '').toLowerCase() === id));
|
|
222
|
+
if (!entry) {
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
error: '未找到注册项' + (dir ? ':' + dir : ':' + id),
|
|
226
|
+
hint: '本命令只改自定义注册表(hub hosts list)里的显示名;未注册目录先 hub hosts add <目录> --label <名称>,内置映射改名用 hub hosts set <agentId> --label <名称>。',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const previous = entry.label;
|
|
230
|
+
entry.label = label;
|
|
231
|
+
entry.updatedAt = nowIso();
|
|
232
|
+
writeHostsRegistry(options, registry);
|
|
233
|
+
return { ok: true, entry, previous, registry };
|
|
234
|
+
}
|
|
235
|
+
|
|
209
236
|
/** 0.29.1 U2:不接管名单匹配(agentId 或目录);返回命中条目或 null。 */
|
|
210
237
|
function excludedEntryFor(registry, input) {
|
|
211
238
|
const agentId = input && input.agentId ? String(input.agentId).toLowerCase() : null;
|
|
@@ -331,6 +358,7 @@ module.exports = {
|
|
|
331
358
|
addHost,
|
|
332
359
|
removeHost,
|
|
333
360
|
markHost,
|
|
361
|
+
renameHost,
|
|
334
362
|
excludedEntryFor,
|
|
335
363
|
excludeHost,
|
|
336
364
|
includeHost,
|
|
@@ -314,7 +314,18 @@ function refreshFrom(options) {
|
|
|
314
314
|
}
|
|
315
315
|
scan = opts.scan(source, { slug });
|
|
316
316
|
if (!scan.ok) return { ok: false, slug, error: scan.error || '扫描失败' };
|
|
317
|
-
if (scan.block && !opts.allowUnverified)
|
|
317
|
+
if (scan.block && !opts.allowUnverified) {
|
|
318
|
+
return {
|
|
319
|
+
ok: false, slug, error: '元信 verdict: ' + scan.verdict, blocked: true,
|
|
320
|
+
// 0.30.0 OPT-05:附带扫描明细,供 CLI 输出「与上一已审版本」差异摘要。
|
|
321
|
+
scan: {
|
|
322
|
+
verdict: scan.verdict,
|
|
323
|
+
counts: scan.counts || null,
|
|
324
|
+
findings: scan.findings || null,
|
|
325
|
+
excluded: (scan.policy && scan.policy.excludedFindings) || [],
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
318
329
|
}
|
|
319
330
|
let backup = null;
|
|
320
331
|
let tmp = null;
|
|
@@ -204,6 +204,7 @@ function overviewPayload(ctx) {
|
|
|
204
204
|
configured: hubNow.configured,
|
|
205
205
|
configFile: hubNow.configFile,
|
|
206
206
|
effectiveNow: hubNow.dir,
|
|
207
|
+
defaultDir: skillsConfigLib.defaultHubDir({ homeDir: ctx.homeDir, env: ctx.env }),
|
|
207
208
|
restartRequired: !ctx.hubFlag && !sameDir(hubNow.dir, ctx.hubDir),
|
|
208
209
|
},
|
|
209
210
|
standard: hubLib.STANDARD_ID,
|
|
@@ -278,9 +279,25 @@ function hostsPayload(ctx) {
|
|
|
278
279
|
}));
|
|
279
280
|
const hosts = found.hosts.map((host) => {
|
|
280
281
|
const hostLinks = links.filter((link) => sameDir(link.dir, host.dir));
|
|
282
|
+
const ledgerOk = hostLinks.filter((link) => link.status === 'ok').length;
|
|
283
|
+
// 0.30.0 OPT-03:「指向当前 Hub」以文件系统为真源(台账辅助);指向其它
|
|
284
|
+
// 位置的链接单独计数(只统计能识别为「其它 Hub」的目标),用于配置错位预警。
|
|
285
|
+
let scan = null;
|
|
286
|
+
if (host.exists && !host.bridgeOnly) {
|
|
287
|
+
try {
|
|
288
|
+
scan = hubLib.scanDirForHubLinks(host.dir, ctx.hubDir);
|
|
289
|
+
} catch (_) {
|
|
290
|
+
scan = null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const otherHubLinks = scan ? scan.otherLinks.filter((item) => item.root) : [];
|
|
294
|
+
const otherHubRoots = [...new Set(otherHubLinks.map((item) => item.root))];
|
|
281
295
|
return {
|
|
282
296
|
...host,
|
|
283
|
-
hubLinks:
|
|
297
|
+
hubLinks: scan ? scan.hubSlugs.length : ledgerOk,
|
|
298
|
+
hubLinksLedger: ledgerOk,
|
|
299
|
+
hubLinksOther: otherHubLinks.length,
|
|
300
|
+
hubLinksOtherRoots: otherHubRoots.slice(0, 3),
|
|
284
301
|
brokenLinks: hostLinks.filter((link) => link.status !== 'ok').length,
|
|
285
302
|
};
|
|
286
303
|
});
|
|
@@ -309,6 +326,7 @@ function hostsPayload(ctx) {
|
|
|
309
326
|
overrides: (registry.overrides || []).length,
|
|
310
327
|
links: links.length,
|
|
311
328
|
brokenLinks: links.filter((link) => link.status !== 'ok').length,
|
|
329
|
+
otherHubLinks: hosts.reduce((sum, host) => sum + (host.hubLinksOther || 0), 0),
|
|
312
330
|
// 0.29.0 F3:宿主生命周期状态细分(面板分组 / 筛选)。
|
|
313
331
|
states: {
|
|
314
332
|
available: hosts.filter((host) => host.state === 'available').length,
|
|
@@ -664,6 +682,27 @@ function writeHostRemove(ctx, body) {
|
|
|
664
682
|
return { code: 200, payload: { action: 'hosts.remove', removed: result.removed } };
|
|
665
683
|
}
|
|
666
684
|
|
|
685
|
+
function writeHostRename(ctx, body) {
|
|
686
|
+
const dir = String(body.dir || '').trim();
|
|
687
|
+
const id = String(body.id || '').trim();
|
|
688
|
+
const label = String(body.label || '').trim();
|
|
689
|
+
if (!label) return { code: 400, payload: { error: '缺少新名称。' } };
|
|
690
|
+
if (!dir && !id) return { code: 400, payload: { error: '缺少目标目录或 agentId。' } };
|
|
691
|
+
const result = hostsRegistryLib.renameHost(
|
|
692
|
+
{ homeDir: ctx.homeDir, env: ctx.env },
|
|
693
|
+
{ dir, id, label },
|
|
694
|
+
);
|
|
695
|
+
if (!result.ok) return { code: 400, payload: { error: result.error } };
|
|
696
|
+
auditViewHostEvent(ctx, {
|
|
697
|
+
event: 'hosts.rename',
|
|
698
|
+
dir: result.entry.dir,
|
|
699
|
+
from: result.previous,
|
|
700
|
+
to: result.entry.label,
|
|
701
|
+
via: 'view',
|
|
702
|
+
});
|
|
703
|
+
return { code: 200, payload: { action: 'hosts.rename', entry: result.entry, previous: result.previous } };
|
|
704
|
+
}
|
|
705
|
+
|
|
667
706
|
function writeHostPurge(ctx, body) {
|
|
668
707
|
if (String(body.confirm || '') !== CONFIRM.purge) {
|
|
669
708
|
return { code: 400, payload: { error: '破坏性操作确认不匹配;请确认后再执行。' } };
|
|
@@ -1053,7 +1092,8 @@ function handleGet(ctx, pathname, url, res) {
|
|
|
1053
1092
|
return json(res, 200, {
|
|
1054
1093
|
version: ctx.version || '',
|
|
1055
1094
|
groups: cliHelpLib.CLI_HELP_MODEL,
|
|
1056
|
-
|
|
1095
|
+
// UX-10:速查解析在服务端完成(单一真源),面板直接用 item.meta。
|
|
1096
|
+
quick: cliHelpLib.quickWithMeta(),
|
|
1057
1097
|
options: cliHelpLib.CLI_GLOBAL_OPTIONS,
|
|
1058
1098
|
});
|
|
1059
1099
|
}
|
|
@@ -1117,6 +1157,7 @@ function handlePost(ctx, pathname, req, res) {
|
|
|
1117
1157
|
if (pathname === '/api/skills/remove') return writeRemove(ctx, body);
|
|
1118
1158
|
if (pathname === '/api/hosts/add') return writeHostAdd(ctx, body);
|
|
1119
1159
|
if (pathname === '/api/hosts/remove') return writeHostRemove(ctx, body);
|
|
1160
|
+
if (pathname === '/api/hosts/rename') return writeHostRename(ctx, body);
|
|
1120
1161
|
if (pathname === '/api/hosts/purge') return writeHostPurge(ctx, body);
|
|
1121
1162
|
if (pathname === '/api/hosts/mark') return writeHostMark(ctx, body);
|
|
1122
1163
|
if (pathname === '/api/hosts/set') return writeHostSet(ctx, body);
|
|
@@ -749,6 +749,47 @@ function classifyTarget(target, hubDir) {
|
|
|
749
749
|
return { kind: 'file' };
|
|
750
750
|
}
|
|
751
751
|
|
|
752
|
+
/**
|
|
753
|
+
* 0.30.0 UX-02 / OPT-01:以文件系统为真源扫描目标目录内的链接。
|
|
754
|
+
* - hubSlugs:指向当前 Hub 的链接(可安全清理)
|
|
755
|
+
* - otherLinks:指向其它位置的链接(fail-closed,不动;供错位警告)
|
|
756
|
+
*/
|
|
757
|
+
function scanDirForHubLinks(targetDir, hubDir) {
|
|
758
|
+
const hubSlugs = [];
|
|
759
|
+
const otherLinks = [];
|
|
760
|
+
let entries = [];
|
|
761
|
+
try {
|
|
762
|
+
entries = fs.readdirSync(targetDir, { withFileTypes: true });
|
|
763
|
+
} catch (_) {
|
|
764
|
+
return { hubSlugs, otherLinks, scanned: 0, readable: false };
|
|
765
|
+
}
|
|
766
|
+
for (const entry of entries) {
|
|
767
|
+
const target = path.join(targetDir, entry.name);
|
|
768
|
+
const current = classifyTarget(target, hubDir);
|
|
769
|
+
if (current.kind !== 'link') continue;
|
|
770
|
+
if (current.inHub) hubSlugs.push(entry.name);
|
|
771
|
+
else otherLinks.push({ slug: entry.name, target: current.target, root: foreignHubRoot(current.target) });
|
|
772
|
+
}
|
|
773
|
+
return { hubSlugs, otherLinks, scanned: entries.length, readable: true };
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* 解析链接目标所属的「其它 Hub」根目录:宿主链接通常指向 <hub>/<slug>,
|
|
778
|
+
* 因此先看目标本身、再看父目录是否含元阁 Hub 台账标记;都不是则返回 null。
|
|
779
|
+
*/
|
|
780
|
+
function foreignHubRoot(target) {
|
|
781
|
+
if (!target) return null;
|
|
782
|
+
const resolved = path.resolve(target);
|
|
783
|
+
for (const dir of [resolved, path.dirname(resolved)]) {
|
|
784
|
+
try {
|
|
785
|
+
if (fs.existsSync(path.join(dir, STATE_FILE))) return dir;
|
|
786
|
+
if (fs.existsSync(path.join(dir, LINKS_FILE))) return dir;
|
|
787
|
+
if (fs.existsSync(path.join(dir, AUDIT_FILE))) return dir;
|
|
788
|
+
} catch (_) { /* 单个候选失败继续 */ }
|
|
789
|
+
}
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
|
|
752
793
|
function linkSkills(options) {
|
|
753
794
|
const opts = options || {};
|
|
754
795
|
const hubDir = path.resolve(opts.hubDir);
|
|
@@ -1125,13 +1166,32 @@ function unlinkSkills(options) {
|
|
|
1125
1166
|
const hubDir = path.resolve(opts.hubDir);
|
|
1126
1167
|
const targetDir = path.resolve(opts.targetDir);
|
|
1127
1168
|
const dryRun = Boolean(opts.dryRun);
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1169
|
+
const linkState = readLinkState(hubDir);
|
|
1170
|
+
let slugs = null;
|
|
1171
|
+
let discovery = null;
|
|
1172
|
+
if (Array.isArray(opts.slugs) && opts.slugs.length > 0) {
|
|
1173
|
+
slugs = opts.slugs;
|
|
1174
|
+
} else {
|
|
1175
|
+
// 0.30.0 UX-02 / OPT-01:无 slug 时以文件系统为真源扫描目标目录
|
|
1176
|
+
// (台账仅作辅助),保证「指向当前 Hub 的链接」即使台账缺失也能被清理;
|
|
1177
|
+
// 同时记录指向其它位置的链接数,供调用方输出错位警告。
|
|
1178
|
+
const ledgerSlugs = linkState.links
|
|
1131
1179
|
.filter((item) => samePath(item.dir || '', targetDir))
|
|
1132
1180
|
.map((item) => item.slug);
|
|
1181
|
+
const scan = scanDirForHubLinks(targetDir, hubDir);
|
|
1182
|
+
slugs = [...new Set(ledgerSlugs.concat(scan.hubSlugs))];
|
|
1183
|
+
discovery = {
|
|
1184
|
+
source: 'filesystem+ledger',
|
|
1185
|
+
fromLedger: ledgerSlugs.length,
|
|
1186
|
+
fromFilesystem: scan.hubSlugs.length,
|
|
1187
|
+
scannedEntries: scan.scanned,
|
|
1188
|
+
dirReadable: scan.readable,
|
|
1189
|
+
otherLinks: scan.otherLinks.length,
|
|
1190
|
+
otherTargets: [...new Set(scan.otherLinks.map((item) => item.root || item.target))].slice(0, 5),
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1133
1193
|
const unique = [...new Set(slugs)];
|
|
1134
|
-
const links =
|
|
1194
|
+
const links = linkState.links.slice();
|
|
1135
1195
|
const results = [];
|
|
1136
1196
|
|
|
1137
1197
|
for (const slug of unique) {
|
|
@@ -1165,7 +1225,7 @@ function unlinkSkills(options) {
|
|
|
1165
1225
|
}
|
|
1166
1226
|
}
|
|
1167
1227
|
if (!dryRun) writeLinkState(hubDir, { links });
|
|
1168
|
-
return { hubDir, targetDir, dryRun, results };
|
|
1228
|
+
return { hubDir, targetDir, dryRun, results, discovery };
|
|
1169
1229
|
}
|
|
1170
1230
|
|
|
1171
1231
|
/**
|
|
@@ -1640,6 +1700,39 @@ function doctor(options) {
|
|
|
1640
1700
|
}
|
|
1641
1701
|
}
|
|
1642
1702
|
|
|
1703
|
+
// 0.30.0 UX-03:宿主链接目标 vs 当前 Hub 一致性(配置错位提前发现)。
|
|
1704
|
+
if (scopeDirs && scopeDirs.length > 0) {
|
|
1705
|
+
const foreign = new Map();
|
|
1706
|
+
for (const dir of scopeDirs) {
|
|
1707
|
+
let entries = [];
|
|
1708
|
+
try {
|
|
1709
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1710
|
+
} catch (_) {
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
for (const entry of entries) {
|
|
1714
|
+
const target = path.join(dir, entry.name);
|
|
1715
|
+
const current = classifyTarget(target, hubDir);
|
|
1716
|
+
if (current.kind !== 'link' || current.inHub) continue;
|
|
1717
|
+
const root = foreignHubRoot(current.target);
|
|
1718
|
+
if (!root) continue;
|
|
1719
|
+
if (!foreign.has(root)) {
|
|
1720
|
+
foreign.set(root, { count: 0, dirs: new Set(), slugs: new Set() });
|
|
1721
|
+
}
|
|
1722
|
+
const rec = foreign.get(root);
|
|
1723
|
+
rec.count += 1;
|
|
1724
|
+
rec.dirs.add(dir);
|
|
1725
|
+
rec.slugs.add(entry.name);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
for (const [target, rec] of foreign) {
|
|
1729
|
+
add('hub_mismatch:' + pathKey(target), false, 'warning',
|
|
1730
|
+
'发现 ' + rec.count + ' 条链接指向其它 Hub: ' + target + '(当前 Hub: ' + hubDir + ')',
|
|
1731
|
+
'核对位置来源: yotta-skills hub config get;恢复配置 = hub config set --hub "' + target +
|
|
1732
|
+
'",或重链到当前 Hub = hub link --all --force。');
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1643
1736
|
try {
|
|
1644
1737
|
singleSourceChecks(opts, add);
|
|
1645
1738
|
} catch (_) { /* 只读检查失败不影响 doctor 主流程 */ }
|
|
@@ -1711,6 +1804,7 @@ module.exports = {
|
|
|
1711
1804
|
createDirLink,
|
|
1712
1805
|
removeDirLink,
|
|
1713
1806
|
classifyTarget,
|
|
1807
|
+
scanDirForHubLinks,
|
|
1714
1808
|
linkSkills,
|
|
1715
1809
|
unlinkSkills,
|
|
1716
1810
|
removeSkills,
|