@yottameta/yotta-memory 0.19.0 → 0.20.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/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## v0.20.1 (2026-10-01)
2
+
3
+ - 安装器卫生批次:`bin/install.js` / `install.sh` 统一(未知参数报错 exit 2、`--help` / `--version`、残留清理白名单、嵌套载荷保留);由模板单一真源渲染,接入漂移门禁。
4
+
5
+ ## v0.20.0 (2026-09-30)
6
+
7
+ 上下文分页 P2.5:先修 `memory.hook` 的 PREF 驱逐空转,再新增 `context.paging` 预算调用点。
8
+
9
+ - 修复 `memory.hook` 对画像段落驱逐空转:hook / paging 决策实际生效时,画像段落按实时可读条目渲染并套用同一套过滤;自我接入档案不进候选、不可驱逐;无 provider 时仍读 `profile.md`,基线输出逐字节不变。
10
+ - 修复 `profile` 只读单层目录:`private/<owner>/{prefs,bounds,commits}/YYYY/MM/` 里的分层条目现在也会进入静态画像;`context` 的实时画像路径不受影响。
11
+ - 新增 `context.paging`:仅在用户显式传 `--budget` 时调用;`memory.hook` 先过滤,`context.paging` 再排序 / 分页。`drop` 始终安全应用;`order` 仅在 `complete:true` 且候选未超过 500 时应用;候选集外文件只记 note、不进入输出。
12
+ - `context --json` 新增 `paging` 块(`status` / `provider_id` / `applied` / `dropped` / `ordered` / `truncated` / `budget` / `used` / `note`);文本仅在 `context.paging` 实际被声明或触发时增加状态行;审计沿用 `provider-audit.jsonl`,不含记忆正文。
13
+ - provider 子进程透传 `YOTTA_LICENSE_HOME` / `YOTTA_LICENSE_KEYS_DIR` / `YOTTA_LICENSE_BASE_URL` / `YOTTA_LICENSE_SERVER_ID`,与路线 B 授权隔离环境一致。
14
+ - 协议文档升级 v1.1,新增 `context.paging` 请求 / 响应 / 回落说明。
15
+
1
16
  ## v0.19.0 (2026-09-28)
2
17
  - 新增扩展提供方(provider)装载点:`context` 可选调用本地 provider(capability `memory.hook`),在权限过滤后的候选集内驱逐条目;BOUND / COMMIT 与身份画像不可驱逐,预算 / 去重 / 宽限仍由引擎掌控。
3
18
  - 未配置 / 未授权 / 超时 / 非法输出一律 fail-open 回普通记忆;`provider.json` 非法只记状态、不阻断。
package/README.md CHANGED
@@ -107,7 +107,7 @@ Each agent has a globally unique agent ID: it is the ownership key for private m
107
107
  ### Profile & start-of-work context (v0.6.0 + v0.9.0 + v0.14.0)
108
108
 
109
109
  - **profile**: aggregates `private/<owner>/` PREF / BOUND / COMMIT verbatim, grouped by type + subject + tags, written to `profile.md`; the engine infers nothing — profile conclusions are formed internally by the AI per the "memory discipline", never pasted as labels.
110
- - **context**: one-shot start-of-work package — multi-agent integration rules + identity + long-term summaries first (`consolidate` output) + user profile digest + optional task-focused memory (`--focus`) + time-ordered recent corridor + deduplicated high-value backfill + boundary reminders + commitments/anchors + session loop contract; supports `--budget` for dynamic memory and `--explain` selection trace.
110
+ - **context**: one-shot start-of-work package — multi-agent integration rules + identity + long-term summaries first (`consolidate` output) + user profile digest + optional task-focused memory (`--focus`) + time-ordered recent corridor + deduplicated high-value backfill + boundary reminders + commitments/anchors + session loop contract; supports `--budget` for dynamic memory and `--explain` selection trace. Optional local providers can add `memory.hook` filtering and, when `--budget` is explicit, `context.paging` ordering/paging; `context --json` returns `hook` and `paging` blocks.
111
111
  - **Memory discipline**: SKILL.md embeds a rule layer (type red lines / proactive trigger capture / know-the-user three stages / psychological grounding & alignment / bottom lines / host isolation / anti-patterns).
112
112
 
113
113
  ### Retrieval: semantic search (v0.8.0 + v0.9.0 embedding)
package/README.zh-CN.md CHANGED
@@ -22,7 +22,7 @@
22
22
  </p>
23
23
 
24
24
  > 📖 面向用户的操作手册见 [USER_GUIDE.md](USER_GUIDE.md)。
25
- > 🆕 **v0.19.0(扩展提供方装载点 + context JSON)**:`context` 可选调用用户显式配置的本地扩展提供方(capability `memory.hook`)参与「哪些记忆进上下文」——提供方只能驱逐本次候选集内条目,BOUND / COMMIT 与身份画像不可驱逐,预算 / 去重 / 宽限仍由引擎掌控;未配置 / 未授权 / 超时 / 非法输出一律回普通记忆。`context --json` 输出结构化 JSON(`schema` / `hook` / `text`)。配置与协议见 `references/provider-protocol.md`。
25
+ > 🆕 **v0.20.0(上下文分页 + 画像驱逐修复)**:`memory.hook` 的 PREF 驱逐空转已修复——hook / paging 决策生效时,画像段落按实时可读条目渲染并套用同一套过滤;自我接入档案不进候选、不可驱逐。新增 `context.paging`:仅在显式 `--budget` 时调用,hook 先过滤、paging 再排序与分页;`drop` 始终安全应用,`order` 仅在 `complete:true` 且未截断时应用。`context --json` 新增 `paging` 块,`--explain` 增加 `[paging]` 轨迹。配置与协议见 `references/provider-protocol.md`。
26
26
  > 🆕 **v0.18.1(蒸馏溯源 + 日期绝对化 + 巩固标记 + 规则晋升建议 + 写入纪律)**:`distill` 新增分类型提取清单(事件 / 教训 / 待办 / 成长 / 规则边界 / 其他)+ 逐条 `[溯源: 文件#L<a>-L<b>]` + 实测质量指标 + 跳过与边界,`--json` 输出结构化报告;`consolidate` 摘要按条目 `created` 把「昨天 / 上周 / 本月 / 今年」写成绝对日期,归档副本写入巩固标记(`--undo` 剥离还原);`maintain --rules` 只读给出「重复踩坑 → 建议升级为规则」(默认阈值 3,不自动写 BOUND);记忆守则新增冲突权威顺序 6 层与写入纪律 3 条。
27
27
 
28
28
  > 🆕 **v0.18.0(命中打点 + 容量水位 + consolidate 提案闸门 + 上下文压缩审计)**:`recall` / `explain` / `feedback --useful` / `context` 记录本地命中元数据(`hit_days` 默认保留 90 天;`hit_queries` 只存查询 8 位指纹、最多 12 槽,不存原文;随文件加密 / 备份 / 导出),`config set usage_enabled false` 或命令级 `--no-usage` 可关;只读命令与跨 owner 私密条目不写。`maintain --capacity` 只读报告容量水位、30 / 90 天活跃度、LRU / LFU 淘汰候选(冷却期 30 天 + immutable / BOUND / evergreen / pinned 豁免)与晋升建议(≥3 次命中且 ≥3 个不同查询,只出建议命令);`archive` 默认豁免冷却期与标签常青,`--force` 可显式覆盖。`consolidate` 默认等价 `--propose`(结构化报告 + `--json`),`--apply` 交互式需确认串、非交互必须 `--yes`;首次启用显示一次数据生命周期说明。`doctor` 规模体检新增 `scale_info_*` 与逐项 `ok / info / warning` 分级(`doctor.ok` 仍只看 critical)。新增 `context --audit [--from <文件|->] [--json] [--gate N]`:核对被压缩掉的内容是否已落盘,输出未落盘清单与 `remember` 建议命令,只读、不自动补写。`archive --dry-run` 只预览不改库(不动文件 / 不建事务快照 / 不写审计),无候选时不建整库快照,`archive --json` 输出结构化报告;MCP 侧只补只读 / 预演能力(`archive.dryRun`、`maintain.capacity`、`context.audit` + 内联 `auditText`、`consolidate` 只出 propose 报告),`archive --force` 与 `consolidate --apply / --undo` 仍只在命令行。上一版 v0.17.4:`rename` 消除平铺 / 分层同序号冲突(跨布局 fail-closed + 破坏性闸门 + 审计)。
package/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yotta-memory
3
3
  description: 元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/可回滚:让任何 AI 智能体活过会话,开工 recall 恢复上下文、重要信息 remember 落盘、收工归档。类型体系 FACT(公共共享)/ PREF / BOUND / COMMIT(私密隔离)。触发:记住、别忘了、记一笔、记忆、remember、recall、跨会话、上次说到、续测、交接、归档、记忆盘、共享记忆、局域网记忆、画像、开工上下文、长期理解摘要、近期走廊、会话闭环、记忆守则、profile、context、越用越懂、语义检索、反馈、维护、蒸馏、feedback、maintain、distill、explain、自我学习、自我进化、自我提升、查看平台分页、recall 候选预过滤、任务相关记忆、--focus、--embedding、压缩遗忘、consolidate、周期摘要、自动合并、分类型衰减、回滚、备份、backup、防误删、doctor、事务快照、基线探针、恢复演练、记忆库安全扫描、scan、命中打点、容量水位、--capacity、--propose、上下文压缩审计、context --audit
4
- version: 0.19.0
4
+ version: 0.20.1
5
5
  license: MIT
6
6
  ---
7
7
 
@@ -36,7 +36,7 @@ license: MIT
36
36
  - **可靠性基线(v0.12.0)**:`init` 对非空记忆库默认拒绝覆盖(`--attach` 接入现有库);`forget` 先移入 `.trash/` 并写审计;新增 `backup volumes / setup / status / ensure-daily / schedule / drill`(用户确认真实独立卷后默认每日自动备份)与 `backup create / list / doctor / restore`。
37
37
  - **可靠性收口(v0.12.2)**:新增 `yotta-memory doctor` 开工检查(根目录 / 密钥库 / 索引 / 身份 / 最近备份);`maintain --apply`、`consolidate --apply`、`merge`、`archive`、`--purge` 在写入前自动创建事务快照,快照失败或严重检查异常时拒绝写入。
38
38
  - **运行时 hook 声明(v0.13.0)**:manifest 声明 `after_milestone` / `remember_commit`;里程碑记忆必须有真实文件路径证据才标 verified,缺证据时输出 `explicit-unverified` + 一次纠偏。
39
- - **扩展提供方装载点(v0.19.0)**:`context` 支持可选的本地扩展提供方(capability `memory.hook`)参与「哪些记忆进上下文」:提供方只能驱逐本次候选集内的条目;身份画像、BOUND / COMMIT、预算 / 去重 / 宽限仍由引擎掌控。未配置 / 未授权 / 超时 / 非法输出一律走普通记忆,输出不带扩展行;`context --json` 输出 `schema` / `hook` / `text`。配置与协议见 `references/provider-protocol.md`。
39
+ - **扩展提供方装载点(v0.20.0)**:`context` 支持可选的本地扩展提供方参与「哪些记忆进上下文」:`memory.hook` 先按候选集驱逐 / 白名单过滤,`context.paging` 在显式 `--budget` 时再排序与分页。身份段落、自我接入档案、BOUND / COMMIT、预算 / 去重 / 宽限仍由引擎掌控;未配置 / 未授权 / 超时 / 非法输出一律走普通记忆。`context --json` 输出 `schema` / `hook` / `paging` / `text`,`--explain` 增加 `[paging]` 轨迹。配置与协议见 `references/provider-protocol.md`。
40
40
 
41
41
  - **蒸馏溯源与分类型提取(v0.18.1)**:`distill` 输出新增「分类型提取清单(事件 / 教训 / 待办 / 成长 / 规则边界 / 其他)+ 质量指标 + 跳过与边界」;每条提取项带 `[溯源: 文件#L<a>-L<b>]`,报告只给实测压缩比 / 条目覆盖 / 溯源覆盖 / 要素提取率,不承诺压缩倍数或语义保真。`distill --json` 输出结构化报告;帮助补 `--owner` / `--out`。
42
42
  - **日期绝对化与巩固标记(v0.18.1)**:`consolidate` 生成摘要正文时,按条目 `created`(缺则 `updated`)把「昨天 / 上周 / 本月 / 今年」等写成绝对日期(如 `昨天(2026-01-09)`);「最近 / 前几天 / 刚才」等模糊词不归一。归档副本末尾追加一行 `<!-- yotta-memory: consolidated to … -->` 巩固标记;`--undo` 剥离标记并还原原始内容;标记失败不影响批次,只在报告与审计里计数。
package/bin/install.js CHANGED
@@ -1,12 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * yotta-memory 跨平台安装器
3
+ * yotta-memory 跨平台安装器(YottaSkills)
4
+ *
4
5
  * 用法:
5
- * npx -y --package @yottameta/yotta-memory yotta-memory-install --agent <name> # 按智能体默认用户级目录安装(推荐)
6
- * npx -y --package @yottameta/yotta-memory yotta-memory-install --dir PATH # 装到指定目录(用户改了目录的智能体)
7
- * npx -y --package @yottameta/yotta-memory yotta-memory-install -g # 安装到全部已知智能体用户级目录
8
- * npx -y --package @yottameta/yotta-memory yotta-memory-install # 安装到检测到的项目级目录
9
- * npx -y --package @yottameta/yotta-memory yotta-memory-install --list # 列出智能体 -> 默认目录
6
+ * npx -y --package @yottameta/yotta-memory yotta-memory-install --agent <name> # 按智能体默认用户级目录安装(推荐)
7
+ * npx -y --package @yottameta/yotta-memory yotta-memory-install --dir <path> # 装到指定目录(用户改了目录的智能体)
8
+ * npx -y --package @yottameta/yotta-memory yotta-memory-install -g # 安装到全部已知智能体用户级目录
9
+ * npx -y --package @yottameta/yotta-memory yotta-memory-install # 安装到检测到的项目级目录
10
+ * npx -y --package @yottameta/yotta-memory yotta-memory-install --list # 列出智能体 -> 默认目录
11
+ * npx -y --package @yottameta/yotta-memory yotta-memory-install --dry-run # 只显示将写入的目标,不写文件
12
+ * npx -y --package @yottameta/yotta-memory yotta-memory-install --version # 显示版本
13
+ * npx -y --package @yottameta/yotta-memory yotta-memory-install --help # 显示帮助
14
+ *
15
+ * 退出码: 0 成功 / 1 安装失败 / 2 用法错误 / 4 目标错误
10
16
  */
11
17
  'use strict';
12
18
  const fs = require('fs');
@@ -16,6 +22,21 @@ const os = require('os');
16
22
  const SKILL_NAME = 'yotta-memory';
17
23
  const PKG_ROOT = path.join(__dirname, '..');
18
24
 
25
+ // 顶层开发文件 / 目录:只在安装包顶层跳过;技能包内嵌套同名载荷必须保留。
26
+ // 与 install.sh 同口径:开发件与运行时不相关目录一律不落目标。
27
+ const TOP_SKIP = [
28
+ 'package.json', 'package-lock.json', 'bin', 'lib', 'test',
29
+ '.github', '.git', '.gitignore', '.npmignore', '.gitattributes',
30
+ '.yotta', '.tmp', 'install.sh', 'node_modules',
31
+ ];
32
+ const TOP_SKIP_SET = new Set(TOP_SKIP);
33
+ // 缓存 / 编译产物:任意层级跳过,安装时同样清理。
34
+ const CACHE_DIRS = new Set(['__pycache__', '.pytest_cache', '.mypy_cache']);
35
+
36
+ class UsageError extends Error {}
37
+ class TargetError extends Error {}
38
+ class InstallError extends Error {}
39
+
19
40
  // 智能体 -> 用户级默认技能目录(dirs 按优先级排列;--agent 装到第一个)
20
41
  // 依据官方文档:.agents/skills 并非通用目录,被 OpenCode / Cursor / Cline / Amp /
21
42
  // Kimi / Gemini CLI / GitHub Copilot 等读取;Claude Code 与 Codex 默认不读 .agents。
@@ -39,6 +60,26 @@ const AGENT_DIRS = {
39
60
  agents: { label: '通用 AGENTS.md', dirs: ['.agents/skills'] },
40
61
  };
41
62
 
63
+ const PROJECT_DIRS = [
64
+ '.claude/skills',
65
+ '.cursor/skills',
66
+ '.codex/skills',
67
+ '.config/goose/skills',
68
+ '.config/agents/skills',
69
+ '.opencode/skills',
70
+ '.codeium/windsurf/skills',
71
+ '.workbuddy/skills',
72
+ '.kiro/skills',
73
+ '.traecli/skills',
74
+ '.gemini/skills',
75
+ '.trae-cn/skills',
76
+ '.qwen/skills',
77
+ '.comate/skills',
78
+ '.codebuddy/skills',
79
+ '.kimi/skills',
80
+ '.agents/skills',
81
+ ];
82
+
42
83
  // Codex 用户级目录特判:优先 $CODEX_HOME/skills,否则 ~/.codex/skills
43
84
  function codexUserDir() {
44
85
  const base = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
@@ -57,106 +98,238 @@ function resolveUserDir(rel) {
57
98
  return path.join(os.homedir(), rel);
58
99
  }
59
100
 
60
- function installTo(dest) {
61
- const target = path.join(dest, SKILL_NAME);
62
- fs.mkdirSync(target, { recursive: true });
63
- copyDir(PKG_ROOT, target, new Set(['package.json', 'bin', 'node_modules', '.git']));
64
- console.log('installed -> ' + target);
101
+ function displayDir(rel) {
102
+ if (process.platform === 'win32') return '%USERPROFILE%\\' + rel.replace(/\//g, '\\');
103
+ return '~/' + rel;
104
+ }
105
+
106
+ function skillVersion() {
107
+ try {
108
+ const data = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
109
+ return data.version || null;
110
+ } catch (_) {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ function usage() {
116
+ console.log(SKILL_NAME + ' 安装器(YottaSkills)');
117
+ console.log('');
118
+ console.log('用法:');
119
+ console.log(' node bin/install.js --agent <name> 按智能体默认用户级目录安装(推荐)');
120
+ console.log(' node bin/install.js --dir <path> 装到指定目录(用户改了目录的智能体)');
121
+ console.log(' node bin/install.js -g 安装到全部已知智能体用户级目录');
122
+ console.log(' node bin/install.js 安装到检测到的项目级目录');
123
+ console.log('');
124
+ console.log('参数:');
125
+ console.log(' --agent <name> 智能体键名,见 --list');
126
+ console.log(' --dir <path> 自定义技能目录');
127
+ console.log(' -g, --global 安装到全部已知用户级目录');
128
+ console.log(' --list, -l 列出支持的智能体目录');
129
+ console.log(' --dry-run 只显示将写入的目标,不写文件');
130
+ console.log(' --version, -v 显示版本');
131
+ console.log(' --help, -h 显示帮助');
132
+ console.log(' --yes, -y 兼容参数(-g 不再强制要求)');
133
+ console.log('');
134
+ console.log('退出码: 0 成功 / 1 安装失败 / 2 用法错误 / 4 目标错误');
135
+ }
136
+
137
+ function parseArgs(argv) {
138
+ const opts = { help: false, version: false, list: false, global: false, dryRun: false, yes: false, dir: null, agent: null };
139
+ for (let i = 0; i < argv.length; i++) {
140
+ const arg = argv[i];
141
+ if (arg === '--help' || arg === '-h') opts.help = true;
142
+ else if (arg === '--version' || arg === '-v') opts.version = true;
143
+ else if (arg === '--list' || arg === '-l') opts.list = true;
144
+ else if (arg === '--global' || arg === '-g') opts.global = true;
145
+ else if (arg === '--dry-run') opts.dryRun = true;
146
+ else if (arg === '--yes' || arg === '-y') opts.yes = true;
147
+ else if (arg === '--dir') {
148
+ const value = argv[++i];
149
+ if (!value) throw new UsageError('--dir 需要一个非空路径');
150
+ opts.dir = value;
151
+ } else if (arg === '--agent') {
152
+ const value = argv[++i];
153
+ if (!value) throw new UsageError('--agent 需要一个非空名称');
154
+ opts.agent = String(value).toLowerCase();
155
+ } else {
156
+ throw new UsageError('未知参数: ' + arg);
157
+ }
158
+ }
159
+ if (!opts.help && !opts.version) {
160
+ const selected = [opts.dir, opts.agent, opts.global].filter(Boolean).length;
161
+ if (selected > 1) throw new UsageError('--dir / --agent / -g 只能选一个');
162
+ }
163
+ return opts;
164
+ }
165
+
166
+ function printList() {
167
+ console.log('智能体 -> 默认技能目录(--agent <name> 装到第一个,用户级):');
168
+ for (const [key, value] of Object.entries(AGENT_DIRS)) {
169
+ const resolved = value.dirs.map(displayDir);
170
+ console.log(' ' + key.padEnd(10) + value.label.padEnd(18) + resolved.join('、'));
171
+ }
172
+ console.log('\n说明:Windows 用 %USERPROFILE%,Linux/macOS 用 ~;仅收录有官方默认目录的智能体。');
173
+ console.log('改了目录的请用 --dir <路径>,不要依赖默认位置;若设置了 CODEX_HOME / XDG_CONFIG_HOME,安装自动以该变量为准。');
174
+ }
175
+
176
+ function assertSafeTarget(target) {
177
+ const rel = path.relative(PKG_ROOT, target);
178
+ if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) {
179
+ throw new UsageError('目标目录不能在技能源目录内(防止自装自毁)');
180
+ }
65
181
  }
66
182
 
67
- function copyDir(src, dst, skip) {
183
+ function shouldSkipCache(name, isFile) {
184
+ if (CACHE_DIRS.has(name)) return true;
185
+ if (isFile && (name.endsWith('.pyc') || name.endsWith('.pyo'))) return true;
186
+ return false;
187
+ }
188
+
189
+ function copyDir(src, dst, topLevel) {
68
190
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
69
- if (skip.has(entry.name)) continue;
191
+ // 只跳过安装包顶层开发文件;template/ 等嵌套同名载荷必须保留。
192
+ if (topLevel && TOP_SKIP_SET.has(entry.name)) continue;
193
+ if (shouldSkipCache(entry.name, entry.isFile())) continue;
70
194
  const s = path.join(src, entry.name);
71
195
  const d = path.join(dst, entry.name);
72
196
  if (entry.isDirectory()) {
73
197
  fs.mkdirSync(d, { recursive: true });
74
- copyDir(s, d, skip);
198
+ copyDir(s, d, false);
75
199
  } else if (entry.isFile()) {
76
200
  fs.copyFileSync(s, d);
77
201
  }
78
202
  }
79
203
  }
80
204
 
81
- function displayDir(rel) {
82
- if (process.platform === 'win32') return '%USERPROFILE%\\' + rel.replace(/\//g, '\\');
83
- return '~/' + rel;
205
+ // 清理旧版安装残留(fail-closed 白名单):
206
+ // 仅在目标目录已存在且含 SKILL.md 时触发;只删顶层开发项 + 任意层级缓存;
207
+ // 不整目录删除、不跟随符号链接。
208
+ function cleanResidue(target) {
209
+ const removed = [];
210
+ let stat = null;
211
+ try { stat = fs.statSync(target); } catch (_) { return removed; }
212
+ if (!stat.isDirectory()) return removed;
213
+ if (!fs.existsSync(path.join(target, 'SKILL.md'))) return removed;
214
+ for (const name of TOP_SKIP) {
215
+ const p = path.join(target, name);
216
+ let entry = null;
217
+ try { entry = fs.lstatSync(p); } catch (_) { continue; }
218
+ if (entry.isSymbolicLink()) continue;
219
+ fs.rmSync(p, { recursive: true, force: true });
220
+ removed.push(name);
221
+ }
222
+ const walk = (dir) => {
223
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
224
+ const p = path.join(dir, entry.name);
225
+ if (entry.isSymbolicLink()) continue;
226
+ if (entry.isDirectory()) {
227
+ if (CACHE_DIRS.has(entry.name)) {
228
+ fs.rmSync(p, { recursive: true, force: true });
229
+ removed.push(path.relative(target, p));
230
+ } else {
231
+ walk(p);
232
+ }
233
+ } else if (entry.isFile() && (entry.name.endsWith('.pyc') || entry.name.endsWith('.pyo'))) {
234
+ fs.unlinkSync(p);
235
+ removed.push(path.relative(target, p));
236
+ }
237
+ }
238
+ };
239
+ walk(target);
240
+ return removed;
84
241
  }
85
242
 
86
- function main() {
87
- const args = process.argv.slice(2);
88
- const isGlobal = args.includes('-g') || args.includes('--global');
89
- const list = args.includes('--list') || args.includes('-l');
90
- let explicitDir = null;
91
- const di = args.indexOf('--dir');
92
- if (di !== -1 && args[di + 1]) explicitDir = args[di + 1];
93
- let agent = null;
94
- const ai = args.indexOf('--agent');
95
- if (ai !== -1 && args[ai + 1]) agent = String(args[ai + 1]).toLowerCase();
96
-
97
- if (list) {
98
- console.log('智能体 -> 默认技能目录(--agent <name> 装到第一个,用户级):');
99
- for (const [key, v] of Object.entries(AGENT_DIRS)) {
100
- const resolved = v.dirs.map(displayDir);
101
- console.log(' ' + key.padEnd(10) + v.label.padEnd(18) + resolved.join('、'));
243
+ function installTo(dest, opts) {
244
+ if (!dest || typeof dest !== 'string') throw new UsageError('目标目录不能为空');
245
+ const target = path.resolve(dest, SKILL_NAME);
246
+ assertSafeTarget(target);
247
+ if (opts.dryRun) {
248
+ console.log('[dry-run] 将安装到 -> ' + target);
249
+ return target;
250
+ }
251
+ try {
252
+ for (const rel of cleanResidue(target)) console.log('已清理残留: ' + rel);
253
+ fs.mkdirSync(target, { recursive: true });
254
+ copyDir(PKG_ROOT, target, true);
255
+ if (!fs.existsSync(path.join(target, 'SKILL.md'))) {
256
+ throw new InstallError('安装结果缺少 SKILL.md');
257
+ }
258
+ } catch (err) {
259
+ if (err instanceof UsageError || err instanceof TargetError || err instanceof InstallError) throw err;
260
+ throw new InstallError('安装到 ' + target + ' 失败: ' + err.message);
261
+ }
262
+ console.log('installed -> ' + target);
263
+ return target;
264
+ }
265
+
266
+ function run() {
267
+ const opts = parseArgs(process.argv.slice(2));
268
+ if (opts.help) { usage(); return; }
269
+ if (opts.version) {
270
+ const version = skillVersion();
271
+ if (!version) {
272
+ console.error(SKILL_NAME + ' 版本未知(未找到 package.json)');
273
+ process.exitCode = 1;
274
+ return;
102
275
  }
103
- console.log('\n说明:Windows 用 %USERPROFILE%,Linux/macOS 用 ~;仅收录有官方默认目录的智能体。');
104
- console.log('改了目录的请用 --dir <路径>,不要依赖默认位置;若设置了 CODEX_HOME / XDG_CONFIG_HOME,安装自动以该变量为准。');
276
+ console.log(SKILL_NAME + ' v' + version);
105
277
  return;
106
278
  }
279
+ if (opts.list) { printList(); return; }
107
280
 
108
- if (explicitDir) { installTo(explicitDir); return; }
281
+ if (opts.dir) { installTo(opts.dir, opts); return; }
109
282
 
110
- if (agent) {
111
- const info = AGENT_DIRS[agent];
283
+ if (opts.agent) {
284
+ const info = AGENT_DIRS[opts.agent];
112
285
  if (!info) {
113
- console.log('未收录智能体: ' + agent + '。请用 --dir <路径> 指定技能目录。');
114
- console.log('可用: ' + Object.keys(AGENT_DIRS).join(', '));
115
- return;
286
+ throw new UsageError('未收录智能体: ' + opts.agent + '。可用: ' + Object.keys(AGENT_DIRS).join(', ') + ';自定义目录请用 --dir <路径>。');
116
287
  }
117
- installTo(resolveUserDir(info.dirs[0]));
288
+ installTo(resolveUserDir(info.dirs[0]), opts);
118
289
  console.log('完成。');
119
290
  return;
120
291
  }
121
292
 
122
- if (isGlobal) {
293
+ if (opts.global) {
123
294
  const seen = new Set();
124
- for (const v of Object.values(AGENT_DIRS)) {
125
- for (const d of v.dirs) {
126
- if (seen.has(d)) continue;
127
- seen.add(d);
128
- installTo(resolveUserDir(d));
295
+ for (const value of Object.values(AGENT_DIRS)) {
296
+ for (const rel of value.dirs) {
297
+ if (seen.has(rel)) continue;
298
+ seen.add(rel);
299
+ installTo(resolveUserDir(rel), opts);
129
300
  }
130
301
  }
131
- console.log('完成。');
302
+ if (!opts.dryRun) console.log('完成。');
132
303
  return;
133
304
  }
134
305
 
135
- const PROJECT_DIRS = [
136
- '.claude/skills',
137
- '.cursor/skills',
138
- '.codex/skills',
139
- '.config/goose/skills',
140
- '.config/agents/skills',
141
- '.opencode/skills',
142
- '.codeium/windsurf/skills',
143
- '.workbuddy/skills',
144
- '.kiro/skills',
145
- '.traecli/skills',
146
- '.gemini/skills',
147
- '.trae-cn/skills',
148
- '.qwen/skills',
149
- '.comate/skills',
150
- '.codebuddy/skills',
151
- '.kimi/skills',
152
- '.agents/skills',
153
- ];
154
- let installedAny = false;
155
- for (const d of PROJECT_DIRS) {
156
- if (fs.existsSync(d)) { installTo(d); installedAny = true; }
306
+ const dirs = PROJECT_DIRS.filter((d) => fs.existsSync(d));
307
+ if (!dirs.length) {
308
+ throw new TargetError('未检测到项目级智能体目录。可用 --agent <name> 装到用户级,或用 --dir <路径> 指定目录。');
157
309
  }
158
- if (!installedAny) {
159
- console.log('未检测到项目级智能体目录。可手动复制,或用 --agent <name> / -g 装到用户级。');
310
+ for (const dir of dirs) installTo(dir, opts);
311
+ if (!opts.dryRun) console.log('完成。');
312
+ }
313
+
314
+ function main() {
315
+ try {
316
+ run();
317
+ } catch (err) {
318
+ if (err instanceof UsageError) {
319
+ console.error('用法错误: ' + err.message);
320
+ usage();
321
+ process.exitCode = 2;
322
+ } else if (err instanceof TargetError) {
323
+ console.error('目标错误: ' + err.message);
324
+ process.exitCode = 4;
325
+ } else if (err instanceof InstallError) {
326
+ console.error('安装失败: ' + err.message);
327
+ console.error('修复建议: 检查目录权限与磁盘空间后重试,或用 --dir 换一个目录。');
328
+ process.exitCode = 1;
329
+ } else {
330
+ console.error('未知错误: ' + (err && err.message ? err.message : String(err)));
331
+ process.exitCode = 1;
332
+ }
160
333
  }
161
334
  }
162
335
 
package/bin/provider.js CHANGED
@@ -94,6 +94,7 @@ function minimalEnv() {
94
94
  const keep = [
95
95
  'PATH', 'Path', 'PATHEXT', 'SystemRoot', 'windir', 'COMSPEC', 'ComSpec',
96
96
  'TEMP', 'TMP', 'HOME', 'USERPROFILE', 'LANG', 'LC_ALL',
97
+ 'YOTTA_LICENSE_HOME', 'YOTTA_LICENSE_KEYS_DIR', 'YOTTA_LICENSE_BASE_URL', 'YOTTA_LICENSE_SERVER_ID',
97
98
  ];
98
99
  const env = {};
99
100
  for (const key of keep) {