@yottameta/yotta-skills-plugin 0.28.2 → 0.28.3

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.
@@ -0,0 +1,313 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * yotta-skills(元阁)CLI 帮助单一真源。
5
+ *
6
+ * - CLI_HELP_MODEL:分组 → 命令 → usage / what / when / options(引用全局选项)
7
+ * - CLI_HELP_QUICK:全量速查(每个命令与子命令至少一条:示例 + 危险标记 + 注意)
8
+ * - CLI_GLOBAL_OPTIONS:全局选项明细(--help 选项区 / 面板完整参数 / 官网导出)
9
+ *
10
+ * 面板 /api/help、CLI --help、官网结构化导出都从这里取;新增命令必须同步本文件,
11
+ * 否则 test/cli-help-coverage.test.js 会 FAIL(防漂移)。
12
+ */
13
+
14
+ const CLI_GLOBAL_OPTIONS = [
15
+ { flag: '--agent', arg: '<name>', what: '指定智能体(用它的默认用户级技能目录)', when: '给某个智能体装 / 链接 / 解除时', caution: '未收录的智能体请改用 --dir' },
16
+ { flag: '--dir', arg: '<path>', what: '指定技能目录(技能落到 <path>/<slug>)', when: '给任意宿主或自定义目录安装 / 分发时', caution: '' },
17
+ { flag: '--pin', arg: '', what: '锁死清单精确版本(默认)', when: '需要可复现安装时(默认行为)', caution: '' },
18
+ { flag: '--range', arg: '', what: '跟随同 major 最新 patch(非默认:显式指定后才浮动跟随)', when: '想主动跟随 patch 更新时', caution: '浮动版本每次安装结果可能不同' },
19
+ { flag: '--force', arg: '', what: '已是最新也重装;hub link 时替换同名真目录 / 外部链接', when: '确认要覆盖现有内容时', caution: '可能替换目标目录;先确认目标与备份' },
20
+ { flag: '--skip-scan', arg: '', what: '跳过元信装前扫描(装了元信时自动启用)', when: '已确认来源可信、需要离线安装时', caution: '跳过后风险自负;安装记录会标记 explicit-unverified' },
21
+ { flag: '--only', arg: '<a,b>', what: 'install / update 只处理指定技能', when: '只维护几个技能时', caution: '' },
22
+ { flag: '--domain', arg: '<name>', what: 'install / update 只处理指定家族', when: '按领域批量维护时', caution: '可用值见 --list 的家族列' },
23
+ { flag: '--installed-only', arg: '', what: 'update 只维护目标目录已安装的技能(不补装缺失)', when: '接管现有技能、不想新增时', caution: '无匹配时退出码 0(无动作)' },
24
+ { flag: '--npm', arg: '<path>', what: '指定 npm 可执行文件', when: 'npm 不在 PATH 或需要指定版本时', caution: '' },
25
+ { flag: '--python', arg: '<path>', what: '指定 python 可执行文件(元信扫描用)', when: 'Python 不在 PATH 时', caution: '' },
26
+ { flag: '--verify', arg: '<path>', what: '指定 yotta_verify.py 路径', when: '使用自带的元信引擎时', caution: '' },
27
+ { flag: '--json', arg: '', what: '输出 JSON(inventory / reindex / route / doctor / rollback / hub 等)', when: '脚本或自动化读取结果时', caution: '' },
28
+ { flag: '--slug', arg: '<slug>', what: 'doctor / rollback 只处理指定技能', when: '只检查 / 回滚一个技能时', caution: '' },
29
+ { flag: '--route', arg: '<需求>', what: '静态编排路由:组合 / 顺序 / 依据 / 缺失技能建议', when: '想知道该用哪些技能时', caution: '只建议不自动安装' },
30
+ { flag: '--check', arg: '', what: 'update 时只读检查更新(对 npm 最新版本,不改动)', when: '定期检查或发布前核对时', caution: '退出码 0=最新 / 3=有更新 / 1=检查失败' },
31
+ { flag: '--scheduled', arg: '', what: '与 update --check 合用:后台周检(未到期不联网)', when: '挂后台定时检查时', caution: '只与 update --check 一起使用' },
32
+ { flag: '--auto', arg: '', what: 'update 检查到家族更新后自动更新(仅 yotta-* 家族)', when: '想自动跟进家族更新时', caution: '自动更新含装前扫描;不会自动安装缺失技能' },
33
+ { flag: '--registry', arg: '<url>', what: 'npm registry 地址(默认官方源)', when: '需要走镜像或私有源时', caution: '' },
34
+ { flag: '--host', arg: '<name>', what: 'hook 适配宿主名(默认 generic)', when: '评估 / 绑定宿主 hook 时', caution: '当前已实测 codex' },
35
+ { flag: '--event', arg: '<event>', what: 'hook 六事件之一', when: 'evaluate / 排查 hook 时', caution: '六事件:before_start / before_tool / before_install / before_publish / after_milestone / before_send' },
36
+ { flag: '--manifest', arg: '<file>', what: 'hook evaluate / bind 使用的 skill-manifest.json', when: '按技能声明评估 hook 时', caution: '' },
37
+ { flag: '--context', arg: '<json>', what: 'hook evaluate 的检查结果 JSON', when: '外部传入 checks / wrapperRegistered / evidence 时', caution: '' },
38
+ { flag: '--skill', arg: '<slug>', what: 'usage mark 的技能 slug', when: '记录技能使用信号时', caution: '' },
39
+ { flag: '--signal', arg: '<name>', what: 'usage mark 的信号:used / named / accepted', when: '记录技能使用信号时', caution: '' },
40
+ { flag: '--yes', arg: '', what: 'usage reset 的确认开关', when: '确认清空本地使用记录时', caution: '会清空记录文件' },
41
+ { flag: '--explain', arg: '', what: 'decide-memory 文本报告追加信号明细', when: '想看裁决依据时', caution: '' },
42
+ { flag: '--promote', arg: '', what: 'decide-memory 只写本地建议文件(不写元忆)', when: '想留下裁决建议文件时', caution: '不会写元忆、不会删除任何内容' },
43
+ { flag: '--hub', arg: '<path>', what: 'Hub 目录(默认 ~/.yottaskills/hub)', when: '使用自定义 Hub 位置时', caution: 'YOTTA_SKILLS_HUB 环境变量同样可覆盖' },
44
+ { flag: '--port', arg: '<n>', what: 'view 面板端口(默认 8789)', when: '端口冲突或想固定端口时', caution: '仅 127.0.0.1 监听' },
45
+ { flag: '--all', arg: '', what: 'hub link / unlink 作用于全部已核实宿主', when: '全量分发或解除时', caution: '' },
46
+ { flag: '--include-discovered', arg: '', what: 'hub link / unlink / remove 时纳入自动发现目录', when: '确认要扩围到未核实目录时', caution: '默认只链已核实宿主;桥接目录永不链接' },
47
+ { flag: '--scan', arg: '', what: 'hub adopt 只读预演(默认行为)', when: '先看收编候选与冲突时', caution: '' },
48
+ { flag: '--apply', arg: '', what: 'hub adopt 执行收编', when: '确认候选后执行时', caution: '执行时逐项运行元信扫描;high / critical 默认阻断' },
49
+ { flag: '--in-place', arg: '', what: 'hub adopt 原地登记,不复制', when: '想让 Hub 直接指向原目录时', caution: '原目录删除即断链;默认复制保真更安全' },
50
+ { flag: '--allow-unverified', arg: '', what: '显式允许未扫描导入(默认 high / critical 阻断)', when: '已人工确认来源可信时', caution: '会放宽装前扫描门禁,谨慎使用' },
51
+ { flag: '--from', arg: '<path>', what: 'hub refresh 的来源目录', when: '手动同步非元阁技能时', caution: '无统一更新源,需显式指定来源' },
52
+ { flag: '--include', arg: '<a,b>', what: 'hub adopt --apply 只收编指定技能', when: '从候选中挑几个收编时', caution: '' },
53
+ { flag: '--project', arg: '', what: 'inventory / reindex 附加扫描当前项目 .agents/skills / .codex/skills', when: '盘点项目级技能时', caution: '' },
54
+ { flag: '--no-reindex', arg: '', what: '安装 / 更新后不自动重扫注册表', when: '批量操作后再统一重扫时', caution: '' },
55
+ { flag: '--dry-run', arg: '', what: '只预览不写入(install / update / hub link / hub unlink / hub remove)', when: '执行前先看影响时', caution: '预演只读,不改动任何文件' },
56
+ { flag: '--list', arg: '', what: '列出全家技能 + 版本 + 说明', when: '查看清单或核对版本时', caution: '' },
57
+ { flag: '--inventory', arg: '', what: '盘点本机已装技能(自研扫描)', when: '接手机器或排查技能分布时', caution: '' },
58
+ { flag: '--reindex', arg: '', what: '重扫本地技能注册表', when: '手工安装技能后刷新注册表时', caution: '' },
59
+ { flag: '-h, --help', arg: '', what: '显示帮助', when: '忘了命令怎么用时', caution: '' },
60
+ { flag: '-v, --version', arg: '', what: '显示版本', when: '确认安装版本时', caution: '' },
61
+ ];
62
+
63
+ const CLI_HELP_MODEL = [
64
+ {
65
+ group: '安装与更新',
66
+ commands: [
67
+ {
68
+ name: 'install',
69
+ usage: 'install [skill...] [--agent <name> | --dir <path>] [--only <a,b> | --domain <name>] [--dry-run]',
70
+ what: '装全家或指定技能到目标目录',
71
+ when: '新机器铺齐、补装单个技能、按领域安装时',
72
+ options: ['--agent', '--dir', '--only', '--domain', '--pin', '--range', '--dry-run', '--force', '--skip-scan', '--no-reindex'],
73
+ },
74
+ {
75
+ name: 'update',
76
+ usage: 'update [--agent <name> | --dir <path>] [--installed-only] [--check [--scheduled] | --auto] [--only <a,b> | --domain <name>]',
77
+ what: '增量更新已装技能(补齐缺失 / 升级版本不一致)',
78
+ when: '日常维护、只读检查更新、后台周检、自动跟进时',
79
+ options: ['--agent', '--dir', '--installed-only', '--check', '--scheduled', '--auto', '--only', '--domain', '--dry-run', '--skip-scan'],
80
+ },
81
+ {
82
+ name: '--dry-run',
83
+ usage: '--dry-run',
84
+ what: '只预览将安装 / 更新 / 链接 / 删除的清单,不联网不改动(与 install / update / hub link / hub unlink / hub remove 合用)',
85
+ when: '执行前先确认影响面时',
86
+ options: ['--dry-run'],
87
+ },
88
+ ],
89
+ },
90
+ {
91
+ group: '体检与回滚',
92
+ commands: [
93
+ {
94
+ name: 'doctor',
95
+ usage: 'doctor [--agent <name> | --dir <path>] [--slug <slug>] [--json]',
96
+ what: '只读自检技能目录(SKILL / 版本 / manifest / 注册表)',
97
+ when: '安装后、异常后、升级后确认状态时',
98
+ options: ['--agent', '--dir', '--slug', '--json'],
99
+ },
100
+ {
101
+ name: 'rollback',
102
+ usage: 'rollback [--agent <name> | --dir <path>] [--slug <slug>] [--list] [--json]',
103
+ what: '回滚最近一次技能安装或更新',
104
+ when: '新版本有问题、想回到快照时',
105
+ options: ['--agent', '--dir', '--slug', '--list', '--json'],
106
+ },
107
+ ],
108
+ },
109
+ {
110
+ group: '盘点与路由',
111
+ commands: [
112
+ {
113
+ name: '--list',
114
+ usage: '--list',
115
+ what: '列出全家技能 + 版本 + 说明',
116
+ when: '查看清单、核对版本、确认技能名时',
117
+ options: ['--list'],
118
+ },
119
+ {
120
+ name: '--inventory',
121
+ usage: '--inventory [--project] [--json]',
122
+ what: '盘点本机已装技能(自研扫描,不依赖任何元技能)',
123
+ when: '接手机器、排查技能分布、找同名多副本时',
124
+ options: ['--inventory', '--project', '--json'],
125
+ },
126
+ {
127
+ name: '--reindex',
128
+ usage: '--reindex [--project]',
129
+ what: '重扫本地技能注册表(install / update 后也会自动重扫)',
130
+ when: '手工安装技能后刷新注册表时',
131
+ options: ['--reindex', '--project'],
132
+ },
133
+ {
134
+ name: '--route',
135
+ usage: '--route "<需求摘要>" [--json]',
136
+ what: '按需求给出技能组合、调用顺序、缺失技能安装建议',
137
+ when: '不知道该用哪些技能、想按场景组合时',
138
+ options: ['--route', '--json'],
139
+ },
140
+ {
141
+ name: 'decide-memory',
142
+ usage: 'decide-memory [--dry-run | --promote] [--explain] [--json]',
143
+ what: 'M1 记忆裁决:输出 promote / hold / demote 只读建议',
144
+ when: '判断哪些技能值得长期记忆时',
145
+ options: ['--dry-run', '--promote', '--explain', '--json'],
146
+ },
147
+ ],
148
+ },
149
+ {
150
+ group: '技能 Hub',
151
+ commands: [
152
+ {
153
+ name: 'hub',
154
+ usage: 'hub <子命令> [选项]',
155
+ what: 'Hub 单点真源:发现宿主、安装 / 收编、链接分发、体检、删除',
156
+ when: '多智能体共享技能、统一管理技能目录时',
157
+ options: ['--hub', '--all', '--include-discovered', '--dry-run', '--json'],
158
+ subcommands: [
159
+ { name: 'hosts', usage: 'hub hosts [--json]', what: '发现本机已装智能体与技能目录(已核实 / 自动发现 / 桥接)', when: '先看这台机器有哪些宿主目录时', options: ['--json'] },
160
+ { name: 'install', usage: 'hub install [skill...]', what: '把元技能装进 Hub 真源(清单 27 + 特殊家族 5)', when: '初始化 Hub 或补装技能时', options: ['--hub'] },
161
+ { name: 'update', usage: 'hub update', what: '更新 Hub 内的元技能(非元技能不参与)', when: '日常维护 Hub 真源时', options: ['--hub'] },
162
+ { 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'] },
163
+ { name: 'refresh', usage: 'hub refresh <slug> --from <path>', what: '手动同步非元阁技能(无统一更新源)', when: '外部技能更新后刷新 Hub 副本时', options: ['--from'] },
164
+ { 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'] },
165
+ { name: 'unlink', usage: 'hub unlink [skill...] [--all | --agent <id> | --dir <path>] [--dry-run]', what: '只删除链接,不动 Hub 真源(fail-closed)', when: '解除某个 / 全部宿主的链接时', options: ['--all', '--agent', '--dir', '--include-discovered', '--dry-run'] },
166
+ { name: 'remove', usage: 'hub remove <slug> [--dry-run] [--include-discovered]', what: '删除 Hub 技能:全宿主清链接(含死链)+ 目录入回收站 + 清台账 + 审计', when: '彻底删除某个 Hub 技能时', options: ['--dry-run', '--include-discovered', '--json'] },
167
+ { name: 'status', usage: 'hub status [--json]', what: '查看 Hub 技能 / 来源 / 版本 / 链接 / 宿主', when: '核对 Hub 现状时', options: ['--json'] },
168
+ { name: 'doctor', usage: 'hub doctor [--json]', what: '检查断链 / 目标缺失 / slug 不一致 / 单一真源 / 链接范围', when: 'Hub 异常后排查时', options: ['--json'] },
169
+ ],
170
+ },
171
+ ],
172
+ },
173
+ {
174
+ group: '使用记录与 Hook',
175
+ commands: [
176
+ {
177
+ name: 'usage',
178
+ usage: 'usage <子命令>',
179
+ what: '本地技能使用记录(默认关闭;只记结构化计数)',
180
+ when: '想看技能使用信号、给路由 / 裁决提供依据时',
181
+ options: ['--json'],
182
+ subcommands: [
183
+ { name: 'status', usage: 'usage status [--json]', what: '查看开关与计数', when: '核对记录状态时', options: ['--json'] },
184
+ { name: 'enable', usage: 'usage enable', what: '开启本地使用记录', when: '想开始积累使用信号时', options: [] },
185
+ { name: 'disable', usage: 'usage disable', what: '关闭本地使用记录', when: '不想再记录时', options: [] },
186
+ { name: 'mark', usage: 'usage mark --skill <slug> --signal used|named|accepted', what: '记录一次使用信号', when: '技能被实际使用 / 点名 / 接受时', options: ['--skill', '--signal'] },
187
+ { name: 'reset', usage: 'usage reset --yes', what: '清空本地使用记录', when: '确认要重置计数时', options: ['--yes'] },
188
+ ],
189
+ },
190
+ {
191
+ name: 'hook',
192
+ usage: 'hook <子命令> [--host <name>]',
193
+ what: '宿主 hook 适配:能力矩阵 / 评估 / 绑定 / 反注册',
194
+ when: '接入宿主六事件、排查 hook 时',
195
+ options: ['--host', '--event', '--manifest', '--context', '--json'],
196
+ subcommands: [
197
+ { name: 'capabilities', usage: 'hook capabilities [--host <name>] [--json]', what: '查看宿主六事件能力矩阵', when: '确认宿主支持哪些 hook 时', options: ['--host', '--json'] },
198
+ { name: 'evaluate', usage: 'hook evaluate --event <event> --manifest <file> [--context <json>]', what: '评估 hook 声明并留证', when: '上线前验证 hook 行为时', options: ['--host', '--event', '--manifest', '--context', '--json'] },
199
+ { name: 'bind', usage: 'hook bind --manifest <file> [--host <name>]', what: '注册 hook 声明(幂等)', when: '给宿主登记 hook 时', options: ['--host', '--manifest', '--json'] },
200
+ { name: 'unbind', usage: 'hook unbind <binding-id> [--host <name>]', what: '反注册 hook 声明', when: '下线 hook 时', options: ['--host', '--json'] },
201
+ ],
202
+ },
203
+ ],
204
+ },
205
+ {
206
+ group: '面板与帮助',
207
+ commands: [
208
+ {
209
+ name: 'view',
210
+ usage: 'view [--port <n>]',
211
+ what: '启动本机技能枢纽面板(127.0.0.1:8789)',
212
+ when: '想图形化管理 Hub、看宿主矩阵 / 体检 / 路由时',
213
+ options: ['--port'],
214
+ },
215
+ {
216
+ name: '--version',
217
+ usage: '--version;短写法 -v',
218
+ what: '显示当前版本',
219
+ when: '确认安装的是哪个版本时',
220
+ options: [],
221
+ },
222
+ {
223
+ name: '--help',
224
+ usage: '--help;短写法 -h',
225
+ what: '显示命令与选项帮助',
226
+ when: '忘了命令怎么用时',
227
+ options: [],
228
+ },
229
+ ],
230
+ },
231
+ ];
232
+
233
+ const CLI_HELP_QUICK = [
234
+ {
235
+ group: '安装与更新',
236
+ items: [
237
+ { cmd: 'install', example: 'yotta-skills install --agent codex' },
238
+ { cmd: 'install', example: 'yotta-skills install yotta-memory --dir <技能目录>' },
239
+ { cmd: 'update', example: 'yotta-skills update --installed-only --dir <技能目录>' },
240
+ { cmd: 'update', example: 'yotta-skills update --check' },
241
+ { cmd: 'update', example: 'yotta-skills update --auto' },
242
+ { cmd: '--dry-run', example: 'yotta-skills install --dry-run --agent codex' },
243
+ ],
244
+ },
245
+ {
246
+ group: '体检与回滚',
247
+ items: [
248
+ { cmd: 'doctor', example: 'yotta-skills doctor --dir <技能目录>' },
249
+ { cmd: 'rollback', example: 'yotta-skills rollback --list' },
250
+ { cmd: 'rollback', example: 'yotta-skills rollback --slug <技能> --dir <技能目录>' },
251
+ ],
252
+ },
253
+ {
254
+ group: '盘点与路由',
255
+ items: [
256
+ { cmd: '--list', example: 'yotta-skills --list' },
257
+ { cmd: '--inventory', example: 'yotta-skills --inventory --json' },
258
+ { cmd: '--reindex', example: 'yotta-skills --reindex' },
259
+ { cmd: '--route', example: 'yotta-skills --route "帮我做发布前质量检查"' },
260
+ { cmd: 'decide-memory', example: 'yotta-skills decide-memory --dry-run' },
261
+ ],
262
+ },
263
+ {
264
+ group: '技能 Hub',
265
+ items: [
266
+ { cmd: 'hub', example: 'yotta-skills hub' },
267
+ { cmd: 'hub hosts', example: 'yotta-skills hub hosts' },
268
+ { cmd: 'hub install', example: 'yotta-skills hub install' },
269
+ { cmd: 'hub update', example: 'yotta-skills hub update' },
270
+ { cmd: 'hub adopt', example: 'yotta-skills hub adopt --scan' },
271
+ { cmd: 'hub adopt', example: 'yotta-skills hub adopt --apply --include <技能>' },
272
+ { cmd: 'hub refresh', example: 'yotta-skills hub refresh <技能> --from <技能目录>' },
273
+ { cmd: 'hub link', example: 'yotta-skills hub link --all' },
274
+ { cmd: 'hub link', example: 'yotta-skills hub link <技能> --agent codex', note: '针对指定智能体分发' },
275
+ { cmd: 'hub unlink', example: 'yotta-skills hub unlink <技能> --agent codex', note: '只解除该智能体的链接,Hub 真源保留' },
276
+ { cmd: 'hub unlink', example: 'yotta-skills hub unlink --all' },
277
+ { cmd: 'hub remove', example: 'yotta-skills hub remove <技能> --dry-run', danger: true, note: '删除 Hub 技能:全宿主清链接(含死链)+ 目录入回收站(7 天)+ 清台账;先 --dry-run 看影响' },
278
+ { cmd: 'hub status', example: 'yotta-skills hub status' },
279
+ { cmd: 'hub doctor', example: 'yotta-skills hub doctor' },
280
+ ],
281
+ },
282
+ {
283
+ group: '使用记录与 Hook',
284
+ items: [
285
+ { cmd: 'usage', example: 'yotta-skills usage status' },
286
+ { cmd: 'usage status', example: 'yotta-skills usage status --json' },
287
+ { cmd: 'usage enable', example: 'yotta-skills usage enable' },
288
+ { cmd: 'usage disable', example: 'yotta-skills usage disable' },
289
+ { cmd: 'usage mark', example: 'yotta-skills usage mark --skill yotta-memory --signal used' },
290
+ { cmd: 'usage reset', example: 'yotta-skills usage reset --yes', danger: true, note: '清空本地使用记录,先确认不再需要计数' },
291
+ { cmd: 'hook', example: 'yotta-skills hook capabilities' },
292
+ { cmd: 'hook capabilities', example: 'yotta-skills hook capabilities --host codex --json' },
293
+ { cmd: 'hook evaluate', example: 'yotta-skills hook evaluate --event before_install --manifest <file>' },
294
+ { cmd: 'hook bind', example: 'yotta-skills hook bind --manifest <file>' },
295
+ { cmd: 'hook unbind', example: 'yotta-skills hook unbind <binding-id>' },
296
+ ],
297
+ },
298
+ {
299
+ group: '面板与帮助',
300
+ items: [
301
+ { cmd: 'view', example: 'yotta-skills view' },
302
+ { cmd: 'view', example: 'yotta-skills view --port 8790' },
303
+ { cmd: '--version', example: 'yotta-skills --version' },
304
+ { cmd: '--help', example: 'yotta-skills --help' },
305
+ ],
306
+ },
307
+ ];
308
+
309
+ module.exports = {
310
+ CLI_HELP_MODEL,
311
+ CLI_HELP_QUICK,
312
+ CLI_GLOBAL_OPTIONS,
313
+ };
@@ -32,6 +32,7 @@ const routeFeaturesLib = require('./route-features');
32
32
  const routeDynamicLib = require('./route-dynamic');
33
33
  const providerLib = require('./provider');
34
34
  const usageLib = require('./usage-journal');
35
+ const cliHelpLib = require('./cli-help');
35
36
 
36
37
  const DEFAULT_HOST = '127.0.0.1';
37
38
  const DEFAULT_PORT = 8789;
@@ -41,7 +42,7 @@ const DEFAULT_LIMIT = 100;
41
42
  const MAX_LIMIT = 500;
42
43
  const TOKEN_HEADER = 'x-yotta-view-token';
43
44
  const TOKEN_PLACEHOLDER = '__YOTTA_VIEW_TOKEN__';
44
- const CONFIRM = { unlink: 'unlink', rollback: 'rollback' };
45
+ const CONFIRM = { unlink: 'unlink', rollback: 'rollback', remove: 'remove' };
45
46
  const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
46
47
 
47
48
  function nowIso() {
@@ -372,6 +373,65 @@ function restoreFromSnapshot(ctx, slug, snapshotPath) {
372
373
  };
373
374
  }
374
375
 
376
+ /**
377
+ * 面板删除技能的目标范围 = 已核实宿主 ∪ 本技能链接台账(与 CLI 默认范围一致)。
378
+ */
379
+ function removeTargets(ctx, slug) {
380
+ const found = discovery(ctx);
381
+ const targets = [];
382
+ const seen = new Set();
383
+ const add = (dir, label, agentId) => {
384
+ if (!dir) return;
385
+ const resolved = path.resolve(dir);
386
+ const key = normalizeDir(resolved);
387
+ if (seen.has(key)) return;
388
+ seen.add(key);
389
+ targets.push({ dir: resolved, label: label || '指定目录', agentId: agentId || null });
390
+ };
391
+ for (const host of found.hosts || []) {
392
+ if (!host.exists || host.bridgeOnly || !host.verified) continue;
393
+ add(host.dir, host.label, host.agentId);
394
+ }
395
+ for (const item of hubLib.readLinkState(ctx.hubDir).links.filter((link) => link.slug === slug)) {
396
+ add(item.dir, item.label || '台账记录目录', item.agent);
397
+ }
398
+ return targets;
399
+ }
400
+
401
+ function removePlanPayload(ctx, slug) {
402
+ return hubLib.removeSkills({
403
+ hubDir: ctx.hubDir,
404
+ slug,
405
+ targets: removeTargets(ctx, slug),
406
+ dryRun: true,
407
+ manifest: ctx.manifest,
408
+ homeDir: ctx.homeDir,
409
+ env: ctx.env,
410
+ });
411
+ }
412
+
413
+ function writeRemove(ctx, body) {
414
+ if (String(body.confirm || '') !== CONFIRM.remove) {
415
+ return { code: 400, payload: { error: '破坏性操作确认不匹配;请确认后再执行。' } };
416
+ }
417
+ const slug = String(body.slug || '').trim().toLowerCase();
418
+ if (!SLUG_RE.test(slug)) return { code: 400, payload: { error: '技能 slug 非法。' } };
419
+ if (String(body.confirmSlug || '') !== slug) {
420
+ return { code: 400, payload: { error: '技能名确认不匹配;请输入完整 slug。' } };
421
+ }
422
+ const payload = hubLib.removeSkills({
423
+ hubDir: ctx.hubDir,
424
+ slug,
425
+ targets: removeTargets(ctx, slug),
426
+ dryRun: false,
427
+ manifest: ctx.manifest,
428
+ homeDir: ctx.homeDir,
429
+ env: ctx.env,
430
+ });
431
+ const code = payload.verdict === 'failed' ? 500 : payload.verdict === 'not-found' ? 404 : 200;
432
+ return { code, payload };
433
+ }
434
+
375
435
  function writeAdopt(ctx, body) {
376
436
  if (body.skipScan || body.allowUnverified) {
377
437
  return {
@@ -558,6 +618,19 @@ function handleGet(ctx, pathname, url, res) {
558
618
  });
559
619
  }
560
620
  if (pathname === '/api/overview') return json(res, 200, overviewPayload(ctx));
621
+ if (pathname === '/api/help') {
622
+ return json(res, 200, {
623
+ version: ctx.version || '',
624
+ groups: cliHelpLib.CLI_HELP_MODEL,
625
+ quick: cliHelpLib.CLI_HELP_QUICK,
626
+ options: cliHelpLib.CLI_GLOBAL_OPTIONS,
627
+ });
628
+ }
629
+ if (pathname === '/api/skills/remove-plan') {
630
+ const slug = String(url.searchParams.get('slug') || '').trim().toLowerCase();
631
+ if (!SLUG_RE.test(slug)) return json(res, 400, { error: '技能 slug 非法。' });
632
+ return json(res, 200, removePlanPayload(ctx, slug));
633
+ }
561
634
  if (pathname === '/api/hosts') return json(res, 200, hostsPayload(ctx));
562
635
  if (pathname === '/api/adopt/scan') return json(res, 200, adoptScanPayload(ctx));
563
636
  if (pathname === '/api/links') return json(res, 200, linksPayload(ctx));
@@ -590,6 +663,7 @@ function handlePost(ctx, pathname, req, res) {
590
663
  if (pathname === '/api/links/apply') return writeLink(ctx, body);
591
664
  if (pathname === '/api/links/remove') return writeUnlink(ctx, body);
592
665
  if (pathname === '/api/rollback/apply') return writeRollback(ctx, body);
666
+ if (pathname === '/api/skills/remove') return writeRemove(ctx, body);
593
667
  return { code: 404, payload: { error: 'not found' } };
594
668
  };
595
669
  ctx.queue(run)