@yottameta/yotta-skills-plugin 0.29.10 → 0.30.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/.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 +95 -19
- package/skills/yotta-skills/bin/yotta-skills.js +210 -7
- package/skills/yotta-skills/lib/cli-help.js +13 -10
- 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 +41 -1
- 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
|
],
|
|
@@ -302,6 +303,7 @@ const CLI_HELP_QUICK = [
|
|
|
302
303
|
{ cmd: 'hub hosts', example: 'yotta-skills hub hosts' },
|
|
303
304
|
{ cmd: 'hub hosts', example: 'yotta-skills hub hosts --state orphan' },
|
|
304
305
|
{ cmd: 'hub hosts add', example: 'yotta-skills hub hosts add <技能目录>' },
|
|
306
|
+
{ cmd: 'hub hosts rename', example: 'yotta-skills hub hosts rename <技能目录> --label <新名称>', note: '只改显示名;目录与手动状态保持' },
|
|
305
307
|
{ cmd: 'hub hosts list', example: 'yotta-skills hub hosts list' },
|
|
306
308
|
{ cmd: 'hub hosts mark', example: 'yotta-skills hub hosts mark <技能目录> --state orphan' },
|
|
307
309
|
{ cmd: 'hub hosts exclude', example: 'yotta-skills hub hosts exclude <agentId|目录>' },
|
|
@@ -310,9 +312,9 @@ const CLI_HELP_QUICK = [
|
|
|
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
319
|
{ cmd: 'hub install', example: 'yotta-skills hub install' },
|
|
318
320
|
{ cmd: 'hub update', example: 'yotta-skills hub update' },
|
|
@@ -322,6 +324,7 @@ const CLI_HELP_QUICK = [
|
|
|
322
324
|
{ cmd: 'hub link', example: 'yotta-skills hub link --all' },
|
|
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 真源保留' },
|
|
327
|
+
{ cmd: 'hub unlink', example: 'yotta-skills hub unlink --dir <技能目录>', note: '清理该目录全部指向当前 Hub 的链接(文件系统为真源;其它位置链接不动)' },
|
|
325
328
|
{ cmd: 'hub unlink', example: 'yotta-skills hub unlink --all' },
|
|
326
329
|
{ cmd: 'hub remove', example: 'yotta-skills hub remove <技能> --dry-run', danger: true, note: '删除 Hub 技能:全宿主清链接(含死链)+ 目录入回收站(7 天)+ 清台账;先 --dry-run 看影响' },
|
|
327
330
|
{ cmd: 'hub status', example: 'yotta-skills hub status' },
|
|
@@ -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: '破坏性操作确认不匹配;请确认后再执行。' } };
|
|
@@ -1117,6 +1156,7 @@ function handlePost(ctx, pathname, req, res) {
|
|
|
1117
1156
|
if (pathname === '/api/skills/remove') return writeRemove(ctx, body);
|
|
1118
1157
|
if (pathname === '/api/hosts/add') return writeHostAdd(ctx, body);
|
|
1119
1158
|
if (pathname === '/api/hosts/remove') return writeHostRemove(ctx, body);
|
|
1159
|
+
if (pathname === '/api/hosts/rename') return writeHostRename(ctx, body);
|
|
1120
1160
|
if (pathname === '/api/hosts/purge') return writeHostPurge(ctx, body);
|
|
1121
1161
|
if (pathname === '/api/hosts/mark') return writeHostMark(ctx, body);
|
|
1122
1162
|
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,
|
|
@@ -24,7 +24,7 @@ npx -y @yottameta/yotta-skills hub config set --hub "D:\my-hub" # 只换
|
|
|
24
24
|
npx -y @yottameta/yotta-skills hub config set --hub "D:\my-hub" --move # 迁移:复制校验 → 重链 → 旧 Hub 入回收站
|
|
25
25
|
npx -y @yottameta/yotta-skills hub config rollback # 回滚预览(上次迁移前的原位置)
|
|
26
26
|
npx -y @yottameta/yotta-skills hub config rollback --yes # 执行回滚(位置回退,用当前内容)
|
|
27
|
-
npx -y @yottameta/yotta-skills hub config clear #
|
|
27
|
+
npx -y @yottameta/yotta-skills hub config clear # 清除覆盖(有技能时默认阻断,确认影响后加 --yes)
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
- 配置文件:`<YOTTA_SKILLS_HOME>/config.json`(默认 `~/.yottaskills/config.json`;schema v1,原子写)。
|
|
@@ -34,6 +34,7 @@ npx -y @yottameta/yotta-skills hub config clear # 清除覆盖,
|
|
|
34
34
|
- 迁移记录:`config.json` 的 `lastMigration` 记录时间 / from→to / 校验技能数 / 重链目录数 / 回收站与剩余天数;`hub config get` 展示「最近一次迁移」块与回滚命令。
|
|
35
35
|
- 回滚语义:`hub config rollback` 默认预览、`--yes` 执行 = 位置回退(把当前 Hub 内容复制校验回原位置,再重链、当前 Hub 入回收站 7 天);**不是**恢复旧快照。目标非空 fail-closed(不合并)。
|
|
36
36
|
- 仅切换指针(不带 `--move`)时,若旧 Hub 非空会记录「未迁移」并在面板提示「旧 Hub 还有 N 个技能未迁移」+ 一键迁移;执行迁移 / 回滚后该提示自动清除。
|
|
37
|
+
- `hub config clear` 影响面防护(0.30.0):当前配置 Hub 仍有技能时默认阻断(fail-closed),输出影响面(回落默认位置、真源与链接不移动)、替代路径(`hub config set --hub <新路径> --move`)与 `--yes` 指引;确认后加 `--yes` 执行。`hub config get` 若检测到运行中的 `view` 面板仍用旧位置,会提示重启面板生效。
|
|
37
38
|
- 面板「Hub 位置」区显示来源 / 配置覆盖 / 重启提示,并提供「设置新位置(迁移向导:迁移 / 仅切换,默认迁移)/ 最近一次迁移 + 一键回滚 / 清除覆盖」(写操作需页面令牌 + `hub-config` 确认串);已在运行的 `view` 需重启后生效。
|
|
38
39
|
- 误用防护:`--hub` 只允许与 `hub` / `view` / `where` 一起使用;其它命令(含裸 `--hub`)fail-closed 报错,不再静默忽略。
|
|
39
40
|
|
|
@@ -99,6 +100,7 @@ Hub 真源对清单声明了 `runtimePayload` 的技能(元忆 / 元阁:`bin
|
|
|
99
100
|
可显式清理;文案统一「实体未确认」,不判定「已卸载」;
|
|
100
101
|
- **未创建**:目录不存在且无实体证据 —— 安装宿主后自动出现;
|
|
101
102
|
- **仅标记**:已装实体、技能目录未创建 —— `hub link --agent <id>` 会创建目录并链接。
|
|
103
|
+
- **隐藏标记**(`ignored`):只影响列表显示,不拦截任何行为;真正拦截发现 / 显示 / 链接的是「不接管」(`hosts exclude`)。
|
|
102
104
|
|
|
103
105
|
`hub doctor` 的 `host_state:*` 检查对残留 / 仅标记给 info 提示,不判 fail。
|
|
104
106
|
自定义宿主目录可用 `hub hosts add` 注册进发现 / 分发 / 收编范围(只注册,
|
|
@@ -107,10 +109,11 @@ Hub 真源对清单声明了 `runtimePayload` 的技能(元忆 / 元阁:`bin
|
|
|
107
109
|
```bash
|
|
108
110
|
# 注册 / 查看 / 移除自定义宿主(realpath 去重;Hub / 桥接目录拒绝注册)
|
|
109
111
|
npx -y @yottameta/yotta-skills hub hosts add ~/my-agent/skills --label "我的宿主"
|
|
112
|
+
npx -y @yottameta/yotta-skills hub hosts rename ~/my-agent/skills --label "新名字" # 一步改名(只改显示名)
|
|
110
113
|
npx -y @yottameta/yotta-skills hub hosts list
|
|
111
114
|
npx -y @yottameta/yotta-skills hub hosts remove ~/my-agent/skills
|
|
112
115
|
|
|
113
|
-
# 手动标记状态(实体证据不足时):available / orphan / ignored
|
|
116
|
+
# 手动标记状态(实体证据不足时):available / orphan / ignored(隐藏标记,只影响显示)
|
|
114
117
|
npx -y @yottameta/yotta-skills hub hosts mark ~/my-agent/skills --state orphan
|
|
115
118
|
|
|
116
119
|
# 清理残留目录:默认预览;--yes 执行 —— 只删指向 Hub 的链接(含死链),
|
|
@@ -121,6 +124,7 @@ npx -y @yottameta/yotta-skills hub hosts remove ~/my-agent/skills --purge --yes
|
|
|
121
124
|
|
|
122
125
|
注册表 = `<root>/hosts.json`(root = `YOTTA_SKILLS_HOME` 或 `~/.yottaskills`,schema v1,
|
|
123
126
|
可 diff);面板「宿主矩阵 → 添加自定义目录」等价可用,写操作需页面令牌与确认串。
|
|
127
|
+
移除注册 / 不接管后若该目录仍有 Hub 链接,CLI 会提示剩余条数与 `hub unlink --dir` 清理指引(面板确认框同源显示)。
|
|
124
128
|
|
|
125
129
|
## 不接管与目录覆盖(0.29.1 起)
|
|
126
130
|
|
|
@@ -141,7 +145,7 @@ npx -y @yottameta/yotta-skills hub hosts set box --dir "D:\box-agent\skills"
|
|
|
141
145
|
npx -y @yottameta/yotta-skills hub hosts set box --clear # 恢复内置映射
|
|
142
146
|
```
|
|
143
147
|
|
|
144
|
-
面板「宿主矩阵」提供等价动作:行内「不接管 / 编辑目录 / 标记可用 /
|
|
148
|
+
面板「宿主矩阵」提供等价动作:行内「不接管 / 编辑目录 / 标记可用 / 隐藏标记 / 重命名(自定义注册)」,
|
|
145
149
|
底部「不接管名单」区可「恢复接管」;写操作均写 Hub 审计。
|
|
146
150
|
|
|
147
151
|
**YottaCode 不纳入接管**:YottaCode 自带三层技能管理(skill-inventory / skill /
|
|
@@ -184,7 +188,7 @@ npx -y @yottameta/yotta-skills hub link --all
|
|
|
184
188
|
# 显式把自动发现目录一并纳入(桥接目录永不链接)
|
|
185
189
|
npx -y @yottameta/yotta-skills hub link --all --include-discovered
|
|
186
190
|
|
|
187
|
-
# 只删除链接,不动 Hub
|
|
191
|
+
# 只删除链接,不动 Hub 真源;--dir 不带 slug 时以文件系统为真源清理该目录全部 Hub 链接
|
|
188
192
|
npx -y @yottameta/yotta-skills hub unlink --all
|
|
189
193
|
|
|
190
194
|
# 指定智能体分发 / 解除(只影响该宿主;Hub 真源保留)
|
|
@@ -257,7 +261,7 @@ install / update / refresh 只在高级 CLI 页给出可复制命令,不在网
|
|
|
257
261
|
|
|
258
262
|
## 安全边界
|
|
259
263
|
|
|
260
|
-
- `unlink` 只删除 lstat 确认为链接、且 readlink
|
|
264
|
+
- `unlink` 只删除 lstat 确认为链接、且 readlink 目标位于当前 Hub 内的路径;`--dir` 不带 slug 时以文件系统为真源枚举该目录(台账缺失也能清理),指向其它位置的链接 fail-closed 不动并给出错位警告。
|
|
261
265
|
- 真目录 / 非 Hub 链接一律拒绝删除;`--force` 对真目录把旧副本移入回收站(7 天)再替换;
|
|
262
266
|
替换失败会把旧副本移回原位(回收站路径随结果输出)。
|
|
263
267
|
- `remove` 只删指向 Hub 的链接与 Hub 内目录;链接清理报错即中止、不删 Hub;
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
| `yotta-anti-shallow` | 元谨 | `@yottameta/yotta-anti-shallow` | 1.4.2 | quality | 防 AI 敷衍规则引擎(深入分析/根因追溯时激活) |
|
|
9
9
|
| `yotta-code-quality` | 元质 | `@yottameta/yotta-code-quality` | 0.4.3 | quality | 结对式代码质量评审(十二类腐化风险 + 0-100 健康分) |
|
|
10
10
|
| `yotta-workflow` | 元序 | `@yottameta/yotta-workflow` | 0.4.3 | workflow | 跨会话/跨项目通用工作流协议(状态就近存) |
|
|
11
|
-
| `yotta-memory` | 元忆 | `@yottameta/yotta-memory` | 0.22.
|
|
12
|
-
| `yotta-learn` | 元习 | `@yottameta/yotta-learn` | 0.
|
|
11
|
+
| `yotta-memory` | 元忆 | `@yottameta/yotta-memory` | 0.22.8 | memory | 有权限边界的文件式智能体记忆(FACT/PREF/BOUND/COMMIT) |
|
|
12
|
+
| `yotta-learn` | 元习 | `@yottameta/yotta-learn` | 0.5.0 | memory | 学习闭环 + 知识库(CLI / MCP / 本地面板):经验沉淀为 .learnings/ 条目;知识分类索引 + 关键词查询 |
|
|
13
13
|
| `yotta-security-audit` | 元安 | `@yottameta/yotta-security-audit` | 0.3.1 | security | 安全扫描引擎:13 类检测器 + 系统安全基线 + 学生数据隐私(教育版) |
|
|
14
14
|
| `yotta-vetter` | 元审 | `@yottameta/yotta-vetter` | 0.2.7 | security | 安全审查协议:四阶段 review + SAFE TO INSTALL 判定 |
|
|
15
15
|
| `yotta-recon` | 元析 | `@yottameta/yotta-recon` | 0.1.8 | security | 跨智能体网络侦察:零依赖端口/服务/版本指纹探测 |
|
|
@@ -197,10 +197,10 @@
|
|
|
197
197
|
]
|
|
198
198
|
},
|
|
199
199
|
"yotta-memory": {
|
|
200
|
-
"version": "0.22.
|
|
201
|
-
"treeHash": "sha256:
|
|
200
|
+
"version": "0.22.8",
|
|
201
|
+
"treeHash": "sha256:809772e0b9a057c6099e5ae4b5b88bb3c1b907c7e4864d07265234d5ef5a2bab",
|
|
202
202
|
"files": 18,
|
|
203
|
-
"bytes":
|
|
203
|
+
"bytes": 1350968,
|
|
204
204
|
"mode": "strict",
|
|
205
205
|
"exceptions": [
|
|
206
206
|
{
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"slug": "yotta-memory",
|
|
32
32
|
"name": "元忆",
|
|
33
33
|
"pkg": "@yottameta/yotta-memory",
|
|
34
|
-
"version": "0.22.
|
|
34
|
+
"version": "0.22.8",
|
|
35
35
|
"domain": "memory",
|
|
36
36
|
"runtimePayload": ["bin"],
|
|
37
37
|
"desc": "有权限边界的文件式智能体记忆(FACT/PREF/BOUND/COMMIT)"
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"slug": "yotta-learn",
|
|
41
41
|
"name": "元习",
|
|
42
42
|
"pkg": "@yottameta/yotta-learn",
|
|
43
|
-
"version": "0.
|
|
43
|
+
"version": "0.5.0",
|
|
44
44
|
"domain": "memory",
|
|
45
45
|
"desc": "学习闭环 + 知识库(CLI / MCP / 本地面板):经验沉淀为 .learnings/ 条目;知识分类索引 + 关键词查询"
|
|
46
46
|
},
|