@yottameta/yotta-skills 0.23.2 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +4 -0
- package/README.zh-CN.md +4 -0
- package/SKILL.md +36 -1
- package/bin/yotta-skills.js +203 -14
- package/install.sh +215 -57
- package/lib/deps.js +173 -0
- package/lib/install-pipeline.js +5 -0
- package/lib/npm-pack.js +8 -2
- package/lib/registry-fetch-child.js +29 -0
- package/lib/registry-fetch.js +448 -0
- package/lib/untar.js +179 -0
- package/package.json +1 -1
- package/references/install-flow.md +37 -4
- package/references/skill-list.md +29 -29
- package/scan-policy.json +26 -26
- package/scripts/yotta-skills-mcp.py +1 -1
- package/skill-manifest.json +1 -1
- package/skills.json +55 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## v0.24.0 (2026-10-02)
|
|
2
|
+
|
|
3
|
+
- 接管能力补缺口(A 无 npm 拉包 / B 依赖人话提示 / C 范围控制 / D 行为回归):
|
|
4
|
+
- A 内置拉包 / 解包:新增 `lib/registry-fetch.js`(Node 内置 https 直连 registry:abbreviated packument + 版本解析 + tarball 下载 + `integrity`(sha512) / `shasum`(sha1) fail-closed 校验 + `HTTPS_PROXY` / `HTTP_PROXY` CONNECT 隧道)与 `lib/untar.js`(zlib + ustar / pax / GNU 长名解析;路径越界与链接条目 fail-closed);默认内置为主,npm 与系统 tar 仅作回退通道,`YOTTA_SKILLS_FETCH` / `YOTTA_SKILLS_EXTRACT` 可强制单通道;安装证据新增 `fetch_channel` / `extract_channel`。
|
|
5
|
+
- B 依赖统一提示:新增 `lib/deps.js` 人话模板(需要什么 / 为什么 / 一条修复命令 / 不影响使用),接入拉包失败、缺 Python、双通道解包失败与 Node 版本检查;`doctor` 新增 `dependencies` 自检块(text + `--json`,只告警不失败);顺手修 `--python` 旗标在扫描路径被忽略。
|
|
6
|
+
- C 范围控制:`skills.json` 每条技能新增 `domain`(对齐家族索引 9 类),`references/skill-list.md` 同步补列;新增 `--only` / `--domain` 与 `update --installed-only`。接管语义:只维护目标目录已安装的家族技能,`skills.json` 只作身份 / 版本参照;未装不动作、不新增、不报错;已管理且已最新跳过(退出码 0);无旗标时 install / update 语义不变。
|
|
7
|
+
- D 回归与文档:新增 `test/update-scope.test.js` / `domain-filter.test.js` / `fetch-builtin.test.js` / `deps-message.test.js` / `python-flag.test.js`(含 PATH 隔离的 Node-only 端到端);`references/install-flow.md` 补双通道流程、依赖矩阵与范围语义矩阵;`SKILL.md` / README 中英同步。
|
|
8
|
+
|
|
9
|
+
## v0.23.3 (2026-10-01)
|
|
10
|
+
|
|
11
|
+
- 安装器卫生批次:`install.sh` 统一(未知参数报错 exit 2、`--help` / `--version`、残留清理白名单);`skills.json` 清单 pin 同步 27 项。
|
|
12
|
+
|
|
1
13
|
## v0.23.2 (2026-10-01)
|
|
2
14
|
|
|
3
15
|
- 修复家族安装管线嵌套载荷丢失:复制原语的顶层跳过名单(`package.json` / `bin` / `node_modules` / `.git`)此前按条目名逐层生效,会把技能包内嵌套的同名载荷(如元造 `template/package.json`、`template/bin/install.js`)一并跳过——家族安装 / 更新元造后,其脚手架自检会失败(缺必需文件)。现改为仅顶层生效,与元造 0.1.3 的安装器修复对齐。
|
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ Installing the whole YottaMeta family used to mean running <code>npx</code> for
|
|
|
31
31
|
- **Install** — install everything (or a subset) into an agent's default user-level directory or any directory.
|
|
32
32
|
- **Update** — incremental update: add missing skills, upgrade version-skewed ones.
|
|
33
33
|
- **Update check / auto-update** — <code>update --check</code> compares installed skill versions against the npm registry and reports; <code>update --check --scheduled</code> is the weekly background entry with a local cache and jitter, so it stays offline until due; <code>update --auto</code> upgrades the installed YottaMeta family to the latest (non-family skills are never auto-updated).
|
|
34
|
+
- **Works without npm** — fetching and extraction default to built-in Node channels (npm / system tar are fallbacks only); <code>update --installed-only</code> adopts only skills already installed on the machine and never backfills or errors on missing ones.
|
|
34
35
|
- **Doctor / rollback** — <code>doctor</code> checks the installed skill directory, version, manifest, registry and custom doctor script without modifying it; <code>--dir</code> may point to a skills container or directly to a single skill package. <code>rollback</code> validates a snapshot and restores the latest install or update.
|
|
35
36
|
- **Runtime hook adapter** — manifests declare six lifecycle requirements; <code>hook capabilities / evaluate / bind / unbind</code> probe host support, apply deterministic decisions, record evidence, and never overstate audit-only hosts as enforced.
|
|
36
37
|
- **Idempotent** — a skill already at the manifest version is skipped; re-running is safe.
|
|
@@ -67,6 +68,9 @@ npx -y @yottameta/yotta-skills update --check --scheduled
|
|
|
67
68
|
# Check and auto-update the installed YottaMeta family (pre-install gate applies; non-family skills untouched)
|
|
68
69
|
npx -y @yottameta/yotta-skills update --auto
|
|
69
70
|
|
|
71
|
+
# Adopt only the family skills already installed on this machine (no backfill)
|
|
72
|
+
npx -y @yottameta/yotta-skills update --installed-only --dir ~/my-skills
|
|
73
|
+
|
|
70
74
|
# Read-only health check (optionally with --slug / --json)
|
|
71
75
|
npx -y @yottameta/yotta-skills doctor --dir ~/my-skills --slug yotta-memory
|
|
72
76
|
|
package/README.zh-CN.md
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
- **运行时 hook 适配层**——技能 manifest 只声明六个生命周期要求;<code>hook capabilities / evaluate / bind / unbind</code> 负责宿主能力探测、确定性决策、结构化证据和降级标注,不把 audit 能力夸大为强制。
|
|
39
39
|
- **幂等**——已在清单版本的技能跳过;重复运行安全。
|
|
40
40
|
- **装前门禁**——家族安装先读取包内 manifest,元信(yotta-verify)缺失时自动自举,再逐个扫描。`DO NOT INSTALL` 阻断;`CAUTION` / `REVIEW` 继续但显示风险。旧版本会先做快照再替换。
|
|
41
|
+
- **无 npm 也能接管**——拉包与解包默认走 Node 内置通道(npm / 系统 tar 仅作回退);`update --installed-only` 只接管机器内已安装的元技能,不补装、不新增、不报错。
|
|
41
42
|
- **盘点 / re-index**--扫描本机各智能体技能目录,维护本地注册表(<code>~/.yottaskills/registry.json</code>);可用 <code>YOTTA_SKILLS_REGISTRY_FILE</code> 为不同 agent 指定独立注册表;自包含,不需要任何其他技能。新装技能自动被发现:<code>install</code> / <code>update</code> 完成后自动重扫注册表,<code>--reindex</code> 可随时手动重扫(如会话开工)。可选 <code>yotta-skills</code> MCP(按需加载、不常驻)提供 <code>list_installed_skills</code> / <code>describe_skill</code> / <code>reindex</code> / <code>route_request</code> / <code>decide_memory</code> 五工具,配置见 <code>SKILL.md</code>。
|
|
42
43
|
|
|
43
44
|
边界:只做「下载 + 落位 + 门禁 + 汇总」——**不**开发技能内容、**不**内置任何技能本体、**不**用 <code>-g</code>
|
|
@@ -70,6 +71,9 @@ npx -y @yottameta/yotta-skills update --check --scheduled
|
|
|
70
71
|
# 检查后自动更新已装元阁家族(含装前门禁;非元阁家族技能不自动更新)
|
|
71
72
|
npx -y @yottameta/yotta-skills update --auto
|
|
72
73
|
|
|
74
|
+
# 只接管机器内已装的元技能(不补装缺失;清单只作身份 / 版本参照)
|
|
75
|
+
npx -y @yottameta/yotta-skills update --installed-only --dir ~/my-skills
|
|
76
|
+
|
|
73
77
|
# 只读检查已装技能(可加 --slug / --json)
|
|
74
78
|
npx -y @yottameta/yotta-skills doctor --dir ~/my-skills --slug yotta-memory
|
|
75
79
|
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yotta-skills
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.24.0
|
|
4
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 阻断,非元阁家族包不自动安装。
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
@@ -163,6 +163,7 @@ openclaw skills update @yottameta/yotta-skills
|
|
|
163
163
|
| `install --dir <path>` | 装全家到指定目录,每个技能落在 `<path>/<slug>` |
|
|
164
164
|
| `install <skill>... [--agent <name> \| --dir <path>]` | 只装指定的一个或多个技能 |
|
|
165
165
|
| `update [--agent <name> \| --dir <path>]` | 增量更新:补齐缺失技能、升级版本不一致的技能 |
|
|
166
|
+
| `update --installed-only [--agent <name> \| --dir <path>]` | 只维护目标目录**已安装**的家族技能(不补装缺失;无匹配时退出码 0)——接管现有元技能的标准配方 |
|
|
166
167
|
| `update --check [--agent <name> \| --dir <path>]` | 只读检查更新:联网对 npm 最新,不改动;退出码 0=全部最新 / 3=有更新 / 1=查失败 |
|
|
167
168
|
| `update --check --scheduled [--agent <name> \| --dir <path>]` | 后台周检入口:未到期不联网;到期只检查一次并写本地缓存;文本失败静默,`--json` 保留诊断;始终退出 0 |
|
|
168
169
|
| `update --auto [--agent <name> \| --dir <path>]` | 检查到家族更新后自动更新(仅 yotta-* 自家家族,含装前安全扫描) |
|
|
@@ -184,6 +185,9 @@ openclaw skills update @yottameta/yotta-skills
|
|
|
184
185
|
| `--pin` | 锁死清单精确版本(默认 range:跟随同 major 最新 patch) |
|
|
185
186
|
| `--force` | 已是最新也重新安装 |
|
|
186
187
|
| `--skip-scan` | 人工应急路径:跳过元信门禁并标记 `explicit-unverified`;不得用于 `update --auto` |
|
|
188
|
+
| `--only <a,b>` | install / update 只处理指定技能(与位置参数技能列表合并去重) |
|
|
189
|
+
| `--domain <name>` | install / update 只处理指定家族(security / quality / memory / writing / workflow / entry / compliance / education / distribution) |
|
|
190
|
+
| `--installed-only` | update 只维护目标目录已安装的技能(不补装缺失;无匹配时退出码 0) |
|
|
187
191
|
| `--npm <path>` | 指定 npm 可执行文件 |
|
|
188
192
|
| `--python <path>` | 指定 python 可执行文件(元信 scan 用) |
|
|
189
193
|
| `--verify <path>` | 指定 yotta_verify.py 路径 |
|
|
@@ -192,6 +196,22 @@ openclaw skills update @yottameta/yotta-skills
|
|
|
192
196
|
|
|
193
197
|
不带命令直接给技能名时,等价于 `install <skill>`。
|
|
194
198
|
|
|
199
|
+
### 范围控制(接多少管多少)
|
|
200
|
+
|
|
201
|
+
| 用法 | 范围 |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `install` / `update`(无旗标) | 全量清单:install 安装全部;update 补齐缺失 + 升级已装(现状语义不变) |
|
|
204
|
+
| `update --installed-only` | 只维护目标目录已安装的家族技能(不补装缺失;`skills.json` 只作身份 / 版本参照) |
|
|
205
|
+
| `--only <a,b>` / `--domain <name>` | 只处理指定技能 / 家族(可与 `--installed-only` 取交集) |
|
|
206
|
+
| `update --check` / `--auto` | 只检查 / 维护已装技能,不补装缺失 |
|
|
207
|
+
|
|
208
|
+
已管理且已最新 → 跳过(退出码 0);清单内但机器未装 → 不动作、不新增、不报错;
|
|
209
|
+
非家族 / 未收录目录 → 忽略不报错。标准接管配方:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npx -y @yottameta/yotta-skills update --installed-only --dir <技能目录>
|
|
213
|
+
```
|
|
214
|
+
|
|
195
215
|
## 运行时 hook 适配层
|
|
196
216
|
|
|
197
217
|
技能 manifest 只声明六个统一事件的要求,元阁负责能力探测、确定性评估、证据留痕和降级标注:
|
|
@@ -407,6 +427,18 @@ npx -y @yottameta/yotta-skills usage reset --yes
|
|
|
407
427
|
`workbuddy` `kiro` `trae` `trae-cn` `qwen` `comate` `codebuddy` `kimi` `agents`。
|
|
408
428
|
未收录的智能体请用 `--dir` 指定其技能目录(`.agents/skills` 不是通用目录)。
|
|
409
429
|
|
|
430
|
+
## 依赖与回退链
|
|
431
|
+
|
|
432
|
+
| 依赖 | 角色 | 缺失时 |
|
|
433
|
+
|---|---|---|
|
|
434
|
+
| Node.js 18+ | 必需(CLI 本体) | 明确提示 + 按平台给安装命令 |
|
|
435
|
+
| npm | 拉包**回退通道** | 不影响:内置拉包为主 |
|
|
436
|
+
| 系统 tar | 解包**回退通道** | 不影响:内置解包为主 |
|
|
437
|
+
| Python 3.8+ | 元信装前扫描 | 门禁阻断;优先用 `--python` / `YOTTA_SKILLS_PYTHON` 指向宿主自带 Python(如 YottaCode),确实没有时用 `--skip-scan` 应急(`explicit-unverified`) |
|
|
438
|
+
|
|
439
|
+
缺依赖时按需给出「需要什么 / 为什么 / 一条修复命令 / 不影响使用」人话提示,不常驻提醒;
|
|
440
|
+
`doctor` 的依赖自检块只告警不失败。
|
|
441
|
+
|
|
410
442
|
## 环境变量
|
|
411
443
|
|
|
412
444
|
| 变量 | 作用 |
|
|
@@ -414,6 +446,9 @@ npx -y @yottameta/yotta-skills usage reset --yes
|
|
|
414
446
|
| `YOTTA_SKILLS_NPM` | 指定 npm 可执行文件(同 `--npm`) |
|
|
415
447
|
| `YOTTA_SKILLS_NPM_FLAGS` | 追加传给 `npm pack` 的参数(按空白拆分,如 `--registry=...`) |
|
|
416
448
|
| `YOTTA_SKILLS_NO_FALLBACK` | 设为 `1` 时禁用「默认源 404 → 官方源重试」的自动回退 |
|
|
449
|
+
| `YOTTA_SKILLS_FETCH` | 拉包通道:`builtin`(仅内置)/ `npm`(仅 npm);缺省内置为主、失败回退 npm |
|
|
450
|
+
| `YOTTA_SKILLS_EXTRACT` | 解包通道:`builtin`(仅内置)/ `tar`(仅系统 tar);缺省内置为主、失败回退系统 tar |
|
|
451
|
+
| `YOTTA_SKILLS_REGISTRY` | registry 地址(默认 `https://registry.npmjs.org/`;内置拉包与更新检查共用;支持 `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`) |
|
|
417
452
|
| `YOTTA_SKILLS_PYTHON` | 指定 python 可执行文件(元信 scan 用,同 `--python`) |
|
|
418
453
|
| `YOTTA_SKILLS_VERIFY` | 指定 yotta_verify.py 路径(同 `--verify`) |
|
|
419
454
|
| `YOTTA_SKILLS_MANIFEST` | 指定技能清单 JSON 路径(默认随包 skills.json) |
|
package/bin/yotta-skills.js
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* npx -y @yottameta/yotta-skills --dry-run # 预览将安装清单(不联网、不改动)
|
|
17
17
|
*
|
|
18
18
|
* 版本策略:默认 `--pin` 锁死清单精确版本(可复现,不静默跟随浮动版本);`--range` 才跟随同 major 最新 patch。
|
|
19
|
-
* 依赖:Node.js 18
|
|
19
|
+
* 依赖:Node.js 18+(必需);npm / 系统 tar 仅作回退通道(内置拉包 / 内置解包为主);
|
|
20
|
+
* 元信 scan 需要 Python 3.8+(可 --python / YOTTA_SKILLS_PYTHON 指向宿主自带 Python)。
|
|
20
21
|
* 边界:只做「清单 + 下载 + 落位 + 汇总」;不内置任何技能本体;不 -g 污染。
|
|
21
22
|
*/
|
|
22
23
|
'use strict';
|
|
@@ -41,11 +42,14 @@ const usageJournalLib = require('../lib/usage-journal');
|
|
|
41
42
|
const m1FeaturesLib = require('../lib/m1-features');
|
|
42
43
|
const routeFeaturesLib = require('../lib/route-features');
|
|
43
44
|
const routeDynamicLib = require('../lib/route-dynamic');
|
|
45
|
+
const depsLib = require('../lib/deps');
|
|
46
|
+
const untarLib = require('../lib/untar');
|
|
47
|
+
const registryFetchLib = require('../lib/registry-fetch');
|
|
44
48
|
const { createInstaller, isSafeTarEntry } = require('../lib/install-pipeline');
|
|
45
49
|
const { COPY_SKIP, copyDir } = require('../lib/copy-tree');
|
|
46
50
|
|
|
47
51
|
const PKG_ROOT = path.join(__dirname, '..');
|
|
48
|
-
let VERSION = '0.
|
|
52
|
+
let VERSION = '0.24.0';
|
|
49
53
|
try { VERSION = require(path.join(PKG_ROOT, 'package.json')).version; } catch (_) { /* keep fallback */ }
|
|
50
54
|
|
|
51
55
|
function loadManifest() {
|
|
@@ -113,6 +117,12 @@ function out(s) { process.stdout.write(s + '\n'); }
|
|
|
113
117
|
|
|
114
118
|
function tarBin() { return 'tar'; }
|
|
115
119
|
|
|
120
|
+
/** 供依赖提示复制的「原命令」回显(尽力还原参数,仅作提示用)。 */
|
|
121
|
+
function currentCommand() {
|
|
122
|
+
const args = process.argv.slice(2).map((a) => (/\s/.test(a) ? '"' + a + '"' : a));
|
|
123
|
+
return 'yotta-skills ' + args.join(' ');
|
|
124
|
+
}
|
|
125
|
+
|
|
116
126
|
// 解析 npm 调用方式(Windows 的 .cmd 不能直接 spawn:EINVAL;cmd /c 引号脆弱)。
|
|
117
127
|
// 最优:定位 npm.cmd -> 读内容 -> 提取 node_modules/npm/bin/npm-cli.js -> 用 node 直接执行。
|
|
118
128
|
// 兼容:--npm / YOTTA_SKILLS_NPM 可指向 .js(node 执行)、.cmd(同样解析)、或可执行文件。
|
|
@@ -159,6 +169,7 @@ function parseArgs(argv) {
|
|
|
159
169
|
check: false, auto: false, scheduled: false, registry: null, slug: null,
|
|
160
170
|
host: null, event: null, manifest: null, context: null,
|
|
161
171
|
signal: null, yes: false, explain: false, promote: false,
|
|
172
|
+
only: [], domain: null, installedOnly: false,
|
|
162
173
|
};
|
|
163
174
|
const positionals = [];
|
|
164
175
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -199,6 +210,15 @@ function parseArgs(argv) {
|
|
|
199
210
|
else if (a === '--auto') opts.auto = true;
|
|
200
211
|
else if (a === '--scheduled') opts.scheduled = true;
|
|
201
212
|
else if (a === '--registry') opts.registry = take('--registry');
|
|
213
|
+
else if (a === '--only') {
|
|
214
|
+
const value = take('--only');
|
|
215
|
+
for (const item of value.split(',')) {
|
|
216
|
+
const slug = item.trim().toLowerCase();
|
|
217
|
+
if (slug) opts.only.push(slug);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else if (a === '--domain') opts.domain = take('--domain').toLowerCase();
|
|
221
|
+
else if (a === '--installed-only') opts.installedOnly = true;
|
|
202
222
|
else if (a === '--host') opts.host = take('--host').toLowerCase();
|
|
203
223
|
else if (a === '--event') opts.event = take('--event').toLowerCase();
|
|
204
224
|
else if (a === '--manifest') opts.manifest = take('--manifest');
|
|
@@ -227,6 +247,12 @@ function parseArgs(argv) {
|
|
|
227
247
|
if (opts.scheduled && (opts.command !== 'update' || !opts.check || opts.auto)) {
|
|
228
248
|
die('--scheduled 只能与 update --check 一起使用', 2, '请使用 update --check --scheduled;自动更新不使用后台调度入口。');
|
|
229
249
|
}
|
|
250
|
+
if (opts.installedOnly && opts.command !== 'update') {
|
|
251
|
+
die('--installed-only 只能与 update 一起使用', 2, '标准配方:yotta-skills update --installed-only --dir <技能目录>。');
|
|
252
|
+
}
|
|
253
|
+
if (opts.domain && !domainValues().includes(opts.domain)) {
|
|
254
|
+
die('未知 domain: ' + opts.domain + '。可用: ' + domainValues().join(', '), 2, 'domain 对齐家族索引 9 类;用 yotta-skills --list 查看技能清单。');
|
|
255
|
+
}
|
|
230
256
|
return opts;
|
|
231
257
|
}
|
|
232
258
|
|
|
@@ -240,15 +266,39 @@ function specOf(s, pin) {
|
|
|
240
266
|
function findSkill(slug) {
|
|
241
267
|
return MANIFEST.find(s => s.slug === slug || s.pkg === slug || s.pkg.replace('@yottameta/', '') === slug);
|
|
242
268
|
}
|
|
269
|
+
|
|
270
|
+
const KNOWN_DOMAINS = ['security', 'quality', 'memory', 'writing', 'workflow', 'entry', 'compliance', 'education', 'distribution'];
|
|
271
|
+
|
|
272
|
+
function domainValues() {
|
|
273
|
+
const values = new Set(KNOWN_DOMAINS);
|
|
274
|
+
for (const s of MANIFEST) if (s.domain) values.add(s.domain);
|
|
275
|
+
return [...values].sort();
|
|
276
|
+
}
|
|
277
|
+
|
|
243
278
|
function selectSkills(opts) {
|
|
244
|
-
if (opts.skills.length === 0) return MANIFEST;
|
|
245
279
|
const picked = [];
|
|
280
|
+
const seen = new Set();
|
|
281
|
+
const push = (s) => {
|
|
282
|
+
if (!seen.has(s.slug)) {
|
|
283
|
+
seen.add(s.slug);
|
|
284
|
+
picked.push(s);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
246
287
|
for (const slug of opts.skills) {
|
|
247
288
|
const s = findSkill(slug);
|
|
248
289
|
if (!s) die('未知技能: ' + slug + '(可用: yotta-skills --list)', 2, '请先运行 --list 查看技能名,或检查拼写。');
|
|
249
|
-
|
|
290
|
+
push(s);
|
|
291
|
+
}
|
|
292
|
+
for (const slug of opts.only || []) {
|
|
293
|
+
const s = findSkill(slug);
|
|
294
|
+
if (!s) die('未知技能(--only): ' + slug + '(可用: yotta-skills --list)', 2, '请先运行 --list 查看技能名,或检查拼写。');
|
|
295
|
+
push(s);
|
|
250
296
|
}
|
|
251
|
-
|
|
297
|
+
let list = picked.length > 0 ? picked : MANIFEST.slice();
|
|
298
|
+
if (opts.domain) {
|
|
299
|
+
list = list.filter((s) => s.domain === opts.domain);
|
|
300
|
+
}
|
|
301
|
+
return list;
|
|
252
302
|
}
|
|
253
303
|
|
|
254
304
|
function readInstalledVersion(dir) {
|
|
@@ -577,19 +627,24 @@ function findPython(opts) {
|
|
|
577
627
|
return null;
|
|
578
628
|
}
|
|
579
629
|
|
|
580
|
-
function runScan(engine, skillDir) {
|
|
581
|
-
const python = findPython({});
|
|
582
|
-
if (!python)
|
|
630
|
+
function runScan(engine, skillDir, opts) {
|
|
631
|
+
const python = findPython(opts || {});
|
|
632
|
+
if (!python) {
|
|
633
|
+
return {
|
|
634
|
+
ok: false,
|
|
635
|
+
error: depsLib.describe('python', { command: currentCommand(), missing: true }),
|
|
636
|
+
};
|
|
637
|
+
}
|
|
583
638
|
return gateLib.runVerifier(engine, skillDir, { python, spawnSync });
|
|
584
639
|
}
|
|
585
640
|
|
|
586
641
|
function scanTarget(engine, skillDir, context) {
|
|
587
|
-
const
|
|
642
|
+
const ctx = context || {};
|
|
643
|
+
const scan = runScan(engine, skillDir, ctx.opts);
|
|
588
644
|
if (!scan.ok) {
|
|
589
645
|
out(' 元信 scan: ' + scan.error);
|
|
590
646
|
return scan;
|
|
591
647
|
}
|
|
592
|
-
const ctx = context || {};
|
|
593
648
|
const reviewed = scanPolicyLib.applyScanPolicy(scan, {
|
|
594
649
|
slug: ctx.slug,
|
|
595
650
|
version: ctx.version,
|
|
@@ -612,18 +667,75 @@ function scanTarget(engine, skillDir, context) {
|
|
|
612
667
|
return reviewed;
|
|
613
668
|
}
|
|
614
669
|
|
|
670
|
+
/** doctor 依赖自检(只告警不失败):node / npm / python / tar 四项。 */
|
|
671
|
+
function probeDependencies(opts) {
|
|
672
|
+
const npm = resolveNpm(opts);
|
|
673
|
+
const npmProbe = spawnSync(npm.bin, [...npm.prefix, '--version'], {
|
|
674
|
+
encoding: 'utf8',
|
|
675
|
+
timeout: 15000,
|
|
676
|
+
shell: npm.shell,
|
|
677
|
+
});
|
|
678
|
+
const python = findPython(opts);
|
|
679
|
+
const pythonProbe = python
|
|
680
|
+
? spawnSync(python, ['--version'], { encoding: 'utf8', timeout: 15000 })
|
|
681
|
+
: null;
|
|
682
|
+
const tarProbe = spawnSync(tarBin(), ['--version'], { encoding: 'utf8', timeout: 15000 });
|
|
683
|
+
return depsLib.dependencyReport({
|
|
684
|
+
node: depsLib.nodeCheck(),
|
|
685
|
+
npm: { found: !!(npmProbe && npmProbe.status === 0), version: depsLib.versionLine(npmProbe) },
|
|
686
|
+
python: { found: !!python, version: depsLib.versionLine(pythonProbe), path: python || null },
|
|
687
|
+
tar: { found: !!(tarProbe && tarProbe.status === 0), version: depsLib.versionLine(tarProbe) },
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
615
691
|
// ── 安装 ───────────────────────────────────────────────────────────────────
|
|
616
692
|
const runNpmPackBase = npmPackLib.createPackRunner({ spawnSync, specOf, resolveNpm });
|
|
617
693
|
|
|
694
|
+
function fetchMode() {
|
|
695
|
+
const raw = String(process.env.YOTTA_SKILLS_FETCH || 'auto').trim().toLowerCase();
|
|
696
|
+
return raw === 'builtin' || raw === 'npm' ? raw : 'auto';
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function extractMode() {
|
|
700
|
+
const raw = String(process.env.YOTTA_SKILLS_EXTRACT || 'auto').trim().toLowerCase();
|
|
701
|
+
return raw === 'builtin' || raw === 'tar' ? raw : 'auto';
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* 拉包:默认内置通道(Node 内置 https,零 npm),失败回退 npm pack。
|
|
706
|
+
* YOTTA_SKILLS_FETCH=builtin 禁用回退;=npm 直接走 npm(显式选择 / 离线测试)。
|
|
707
|
+
*/
|
|
618
708
|
function runNpmPack(skill, opts, packDir) {
|
|
709
|
+
const mode = fetchMode();
|
|
710
|
+
let builtin = null;
|
|
711
|
+
if (mode !== 'npm') {
|
|
712
|
+
builtin = registryFetchLib.fetchPackageSync(skill, opts, packDir);
|
|
713
|
+
if (!builtin.error) return builtin;
|
|
714
|
+
}
|
|
715
|
+
if (mode === 'builtin') {
|
|
716
|
+
return { error: '内置拉包失败(YOTTA_SKILLS_FETCH=builtin 已禁用 npm 回退): ' + builtin.error };
|
|
717
|
+
}
|
|
619
718
|
const packed = runNpmPackBase(skill, opts, packDir);
|
|
719
|
+
if (packed.error) {
|
|
720
|
+
const detail = packed.detail || packed.error;
|
|
721
|
+
let message = '拉包失败(内置 + npm 双通道): 内置=' + (builtin ? builtin.error : '未启用') + ';npm=' + packed.error;
|
|
722
|
+
if (depsLib.isNpmMissing(detail)) {
|
|
723
|
+
message += '\n' + depsLib.describe('npm', { command: currentCommand(), missing: true });
|
|
724
|
+
}
|
|
725
|
+
return { error: message, detail };
|
|
726
|
+
}
|
|
727
|
+
packed.channel = 'npm';
|
|
728
|
+
if (builtin) {
|
|
729
|
+
packed.fallbackFrom = builtin.error;
|
|
730
|
+
out(' ↳ 拉包通道:内置失败(' + builtin.error + ')→ npm 回退成功');
|
|
731
|
+
}
|
|
620
732
|
if (packed.registryFallback) {
|
|
621
733
|
out(' ↳ npm 默认源 404,已用官方源重试成功(' + npmPackLib.OFFICIAL_REGISTRY + ')');
|
|
622
734
|
}
|
|
623
735
|
return packed;
|
|
624
736
|
}
|
|
625
737
|
|
|
626
|
-
function
|
|
738
|
+
function extractWithSystemTar(tarball, extractDir) {
|
|
627
739
|
const packDir = path.dirname(tarball);
|
|
628
740
|
const tarballName = path.basename(tarball);
|
|
629
741
|
fs.mkdirSync(extractDir, { recursive: true });
|
|
@@ -643,6 +755,34 @@ function extractTarball(tarball, extractDir) {
|
|
|
643
755
|
return { pkgDir };
|
|
644
756
|
}
|
|
645
757
|
|
|
758
|
+
/**
|
|
759
|
+
* 解包:默认内置(zlib + tar 解析),失败回退系统 tar;
|
|
760
|
+
* YOTTA_SKILLS_EXTRACT=builtin 禁用回退;=tar 直接走系统 tar。
|
|
761
|
+
*/
|
|
762
|
+
function extractTarball(tarball, extractDir) {
|
|
763
|
+
const mode = extractMode();
|
|
764
|
+
let builtin = null;
|
|
765
|
+
if (mode !== 'tar') {
|
|
766
|
+
builtin = untarLib.extractTarballBuiltin(tarball, extractDir);
|
|
767
|
+
if (!builtin.error) return builtin;
|
|
768
|
+
}
|
|
769
|
+
if (mode === 'builtin') {
|
|
770
|
+
return { error: '内置解包失败(YOTTA_SKILLS_EXTRACT=builtin 已禁用 tar 回退): ' + builtin.error };
|
|
771
|
+
}
|
|
772
|
+
const fallback = extractWithSystemTar(tarball, extractDir);
|
|
773
|
+
if (fallback.error) {
|
|
774
|
+
const message = '解包失败(内置 + 系统 tar 双通道): 内置=' + (builtin ? builtin.error : '未启用') + ';tar=' + fallback.error +
|
|
775
|
+
'\n' + depsLib.describe('tar', { command: currentCommand(), missing: true });
|
|
776
|
+
return { error: message };
|
|
777
|
+
}
|
|
778
|
+
if (builtin) {
|
|
779
|
+
out(' ↳ 解包通道:内置失败(' + builtin.error + ')→ 系统 tar 回退成功');
|
|
780
|
+
fallback.fallbackFrom = builtin.error;
|
|
781
|
+
}
|
|
782
|
+
fallback.channel = 'tar';
|
|
783
|
+
return fallback;
|
|
784
|
+
}
|
|
785
|
+
|
|
646
786
|
function ensureGate(context) {
|
|
647
787
|
const { skill, extracted, dest, opts } = context;
|
|
648
788
|
const current = findVerifyEngine(dest, opts);
|
|
@@ -707,6 +847,11 @@ const installOne = createInstaller({
|
|
|
707
847
|
|
|
708
848
|
function runInstall(opts, dest) {
|
|
709
849
|
const skills = selectSkills(opts);
|
|
850
|
+
if (skills.length === 0) {
|
|
851
|
+
out('过滤后没有匹配的技能(--only / --domain),未执行任何操作。');
|
|
852
|
+
process.exitCode = 2;
|
|
853
|
+
return { failed: 1, exitCode: 2 };
|
|
854
|
+
}
|
|
710
855
|
out('yotta-skills(元阁)v' + VERSION + ' —— 安装 ' + skills.length + ' 个技能 -> ' + dest);
|
|
711
856
|
out('版本策略: ' + (opts.pin ? 'pin(锁死清单精确版本,默认)' : 'range(' + skillRange(skills[0]) + ',跟随最新 patch)'));
|
|
712
857
|
const results = [];
|
|
@@ -738,8 +883,21 @@ function runInstall(opts, dest) {
|
|
|
738
883
|
}
|
|
739
884
|
|
|
740
885
|
function runUpdate(opts, dest) {
|
|
741
|
-
|
|
886
|
+
let skills = selectSkills(opts);
|
|
742
887
|
out('yotta-skills(元阁)v' + VERSION + ' —— 增量更新 -> ' + dest);
|
|
888
|
+
if (opts.installedOnly) {
|
|
889
|
+
const candidates = skills.length;
|
|
890
|
+
skills = skills.filter((s) => readInstalledVersion(path.join(dest, s.slug)) !== null);
|
|
891
|
+
out('范围: 仅已安装技能(--installed-only;候选 ' + candidates + ' / 已装 ' + skills.length + ')');
|
|
892
|
+
if (skills.length === 0) {
|
|
893
|
+
out('目标目录未发现已安装的元阁家族技能,无动作(退出码 0)。');
|
|
894
|
+
return { failed: 0, exitCode: 0 };
|
|
895
|
+
}
|
|
896
|
+
} else if (skills.length === 0) {
|
|
897
|
+
out('过滤后没有匹配的技能(--only / --domain),未执行任何操作。');
|
|
898
|
+
process.exitCode = 2;
|
|
899
|
+
return { failed: 1, exitCode: 2 };
|
|
900
|
+
}
|
|
743
901
|
const results = [];
|
|
744
902
|
let failed = 0;
|
|
745
903
|
let exitCode = 0;
|
|
@@ -920,6 +1078,7 @@ function runDoctor(opts, dest) {
|
|
|
920
1078
|
fixes.push.apply(fixes, result.fixes);
|
|
921
1079
|
}
|
|
922
1080
|
|
|
1081
|
+
const dependencies = probeDependencies(opts);
|
|
923
1082
|
const payload = {
|
|
924
1083
|
ok: errors.length === 0,
|
|
925
1084
|
dir: dest,
|
|
@@ -928,6 +1087,7 @@ function runDoctor(opts, dest) {
|
|
|
928
1087
|
errors,
|
|
929
1088
|
warnings,
|
|
930
1089
|
fixes: Array.from(new Set(fixes)),
|
|
1090
|
+
dependencies,
|
|
931
1091
|
};
|
|
932
1092
|
const code = doctorExitCode(payload);
|
|
933
1093
|
if (opts.json) {
|
|
@@ -942,6 +1102,15 @@ function runDoctor(opts, dest) {
|
|
|
942
1102
|
out(' ' + (check.ok ? '✔' : '✘') + ' ' + check.message + (check.hint ? '(修复: ' + check.hint + ')' : ''));
|
|
943
1103
|
}
|
|
944
1104
|
}
|
|
1105
|
+
out('');
|
|
1106
|
+
out('依赖自检(只告警不失败):');
|
|
1107
|
+
for (const dep of dependencies) {
|
|
1108
|
+
const mark = dep.ok ? '✔' : (dep.optional ? '△' : '✘');
|
|
1109
|
+
out(' ' + mark + ' ' + dep.name + (dep.version ? ' ' + dep.version : '') +
|
|
1110
|
+
'(需要 ' + dep.need + (dep.optional ? ';回退通道,可选' : '') + ')' +
|
|
1111
|
+
(dep.ok ? '' : ' —— 影响:' + dep.why));
|
|
1112
|
+
if (!dep.ok && dep.fix) out(' 修复:' + dep.fix);
|
|
1113
|
+
}
|
|
945
1114
|
for (const item of warnings) out('[警告] ' + item);
|
|
946
1115
|
for (const item of fixes) out('[建议] ' + item);
|
|
947
1116
|
if (!payload.ok) out('doctor 未通过:请按上面的修复建议处理后重试。');
|
|
@@ -1121,6 +1290,7 @@ function printHelp() {
|
|
|
1121
1290
|
out(' yotta-skills install --dir <path> 装全家到指定目录');
|
|
1122
1291
|
out(' yotta-skills install <skill> --dir <path> 装单个技能(可多个)');
|
|
1123
1292
|
out(' yotta-skills update --agent <name> 增量更新已装技能(补齐缺失 / 版本不一致)');
|
|
1293
|
+
out(' yotta-skills update --installed-only --dir <path> 只维护已装技能(接管现有元技能的标准配方)');
|
|
1124
1294
|
out(' yotta-skills update --check 只读联网检查更新(退出码 0/3/1)');
|
|
1125
1295
|
out(' yotta-skills update --check --scheduled 后台周检(未到期不联网;到期单次检查并写缓存)');
|
|
1126
1296
|
out(' yotta-skills doctor --dir <path> 只读自检技能目录(可加 --slug / --json)');
|
|
@@ -1144,6 +1314,9 @@ function printHelp() {
|
|
|
1144
1314
|
out(' --range 跟随同 major 最新 patch(非默认:显式指定后才浮动跟随)');
|
|
1145
1315
|
out(' --force 已是最新也重装');
|
|
1146
1316
|
out(' --skip-scan 跳过元信装前 scan(装了 yotta-verify 自动启用)');
|
|
1317
|
+
out(' --only <a,b> install / update 只处理指定技能(与位置参数技能列表合并去重)');
|
|
1318
|
+
out(' --domain <name> install / update 只处理指定家族(security / quality / memory / writing / workflow / entry / compliance / education / distribution)');
|
|
1319
|
+
out(' --installed-only update 只维护目标目录已安装的技能(不补装缺失;无匹配时退出码 0)');
|
|
1147
1320
|
out(' --npm <path> 指定 npm 可执行文件(默认 npm / npm.cmd)');
|
|
1148
1321
|
out(' --python <path> 指定 python 可执行文件(元信 scan 用)');
|
|
1149
1322
|
out(' --verify <path> 指定 yotta_verify.py 路径(默认找目标目录已装的元信)');
|
|
@@ -1169,7 +1342,8 @@ function printHelp() {
|
|
|
1169
1342
|
out(' -v, --version 版本');
|
|
1170
1343
|
out('');
|
|
1171
1344
|
out('支持智能体: ' + Object.keys(AGENT_DIRS).join(', '));
|
|
1172
|
-
out('依赖: Node.js 18
|
|
1345
|
+
out('依赖: Node.js 18+(必需);npm 与系统 tar 为回退通道(内置拉包 / 内置解包为主);元信 scan 需要 Python 3.8+(--python / YOTTA_SKILLS_PYTHON 可指向宿主自带 Python)。');
|
|
1346
|
+
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 可覆盖。');
|
|
1173
1347
|
}
|
|
1174
1348
|
|
|
1175
1349
|
|
|
@@ -1628,6 +1802,11 @@ function runHook(opts) {
|
|
|
1628
1802
|
|
|
1629
1803
|
// ── main ───────────────────────────────────────────────────────────────────
|
|
1630
1804
|
function main() {
|
|
1805
|
+
const node = depsLib.nodeCheck();
|
|
1806
|
+
if (!node.ok) {
|
|
1807
|
+
die('需要 Node 18+(用途:运行元阁 CLI 本体;当前 v' + node.version + ')', 4,
|
|
1808
|
+
depsLib.fixFor('node') + ';装好后重跑:' + currentCommand());
|
|
1809
|
+
}
|
|
1631
1810
|
const opts = parseArgs(process.argv.slice(2));
|
|
1632
1811
|
if (opts.help) { printHelp(); return; }
|
|
1633
1812
|
if (opts.version) { out('yotta-skills v' + VERSION); return; }
|
|
@@ -1660,7 +1839,17 @@ function main() {
|
|
|
1660
1839
|
}
|
|
1661
1840
|
|
|
1662
1841
|
if (opts.dryRun) {
|
|
1663
|
-
|
|
1842
|
+
let skills = selectSkills(opts);
|
|
1843
|
+
if (command === 'update' && opts.installedOnly && dest) {
|
|
1844
|
+
skills = skills.filter((s) => readInstalledVersion(path.join(dest, s.slug)) !== null);
|
|
1845
|
+
}
|
|
1846
|
+
if (skills.length === 0) {
|
|
1847
|
+
out(opts.installedOnly
|
|
1848
|
+
? 'dry-run:目标目录没有可维护的已安装家族技能(无动作)。'
|
|
1849
|
+
: 'dry-run:过滤后没有匹配的技能(--only / --domain)。');
|
|
1850
|
+
process.exitCode = opts.installedOnly ? 0 : 2;
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1664
1853
|
out('yotta-skills(元阁)v' + VERSION + ' —— dry-run(' + command + ',' + skills.length + ' 个技能)');
|
|
1665
1854
|
out('目标: ' + (dest || '未指定(将检测项目级目录)'));
|
|
1666
1855
|
out('版本策略: ' + (opts.pin ? 'pin(锁死)' : 'range(' + skillRange(skills[0]) + ')'));
|