用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/publieople/hermes-config-kit --skill bootkeeper命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | bootkeeper |
| description | BootKeeper Windows 启动项管理工具 — 架构约束、已知坑、CI/发布模式 |
| trigger | bootkeeper|启动项|startup item|Windows 启动|注册表 Run|BootKeeper |
| tags | ["windows","rust","tauri","startup","bootkeeper"] |
| category | devops |
core::windows::launcher::run_helper()。CLI/GUI/MCP 都走它,不各自实现 ShellExecuteW。%ProgramData%\BootKeeper\snapshots,不是 %APPDATA%。原因:helper 提权后跑在管理员 profile,%APPDATA% 指向不同目录 → GUI 写的快照 helper 看不到。launcher::data_root() 统一。location 存真实路径,永远别用显示缩写。
// ❌ 会导致所有写操作失败
let key_path = format!("HKCU\\...\\Run");
// ✅ 正确
let key_path = format!("HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run");
原因:写操作 parse_hive_and_key() 用 location 决定打开哪个注册表键。HKCU\...\Run 被解析成 HKCU\Run → 值查找 NotFound → 静默失败。v0.1.0 的"点禁用无效"就是这个 bug。
教训:
release.yml三步:tag vX.Y.Z → build + 版本注入 → publish。
版本注入(自动同步 tauri.conf.json + Cargo.toml):
- name: Set version from git tag
shell: bash
working-directory: .
env:
VER: ${{ github.ref_name }}
run: |
VER="${VER#v}" # v0.1.1 -> 0.1.1
export VER
python3 -c "
import os, re, json
v = os.environ['VER']
c = open('Cargo.toml').read()
c = re.sub(r'(?m)^version\s*=\s*\"[^\"]+\"', f'version = \"{v}\"', c, count=1)
open('Cargo.toml', 'w').write(c)
d = json.load(open('gui/src-tauri/tauri.conf.json'))
d['version'] = v
open('gui/src-tauri/tauri.conf.json', 'w').write(json.dumps(d, indent=2, ensure_ascii=False) + '\n')
"
产物上传用通配符 *.exe *.msi(不硬编码版本号)→ publish 用 find installer -type f 收集。
CSS 自定义属性必须挂在 :root(document.documentElement),不能挂在子 <div> 上。
❌ 错误:Root.vue 的 wrapper <div :style="cssVars"> 注入变量 → html/body 在外层,读不到
✅ 正确:watch(cssVars, (v) => { for (const [k,val] of Object.entries(v)) document.documentElement.style.setProperty(k, val) }, { immediate: true })
NConfigProvider theme 只控制组件面,不控制 raw CSS 全局色watch + immediate: true 是注入全局 CSS 变量的最简方式当 CI 集成测试通过(真机注册表 disable→enable 往返 OK)但用户安装后报告条目消失时:
{{ items.length }} 实时条目数 → 看前后变化fetchItems 打印 total + .disabled 后缀条目数适用于任何"CI 绿但真机异常"的 GUI 交互类 bug。
现象:对启动文件夹条目点禁用,数字 24→23,条目消失。注册表禁用没问题。
根因:枚举器 enumerate_startup_folders 按扩展名筛选 .lnk/.exe/.cmd/.bat。禁用把 xxx.lnk rename 成 xxx.lnk.disabled,Path::extension() 返回的是 "disabled" 而不是 "lnk" → 筛选器踢掉 → 条目从列表中消失。
修复(一行核心逻辑):
// 在扩展名检查前,先剥掉 .disabled 后缀
let base = if name.ends_with(".disabled") {
&name[..name.len() - 9]
} else {
&name
};
let ext = PathBuf::from(base).extension().unwrap_or_default();
模式泛化:任何对文件名加后缀后查 Path::extension() 的地方都会命中此坑——extension() 返回最后一个 . 之后的部分,不是"真正的扩展名"。在 Windows 文件操作中(尤其是 rename/add suffix 后重新枚举),必须先 strip 人工后缀再查扩展名。
集成测试验证:
.bat 测试文件.bat.disabled → enable → 重枚举确认恢复 → 清理BootKeeper 的 enabled 判断有两个来源,不是只看 .disabled 后缀:
.disabled 后缀 → !name.ends_with(".disabled")StartupApproved 注册表键 → 必须额外检测任务管理器不在文件/注册表值名上加后缀,而是在这里标记:
HKCU/HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\
├── Run\ ← 注册表 Run 条目,value name = 注册表值名
└── StartupFolder\ ← 启动文件夹条目,value name = 文件完整路径
每个值是二进制 blob(≥4 bytes),首 4 字节是 little-endian u32 状态码:
0x01 → 启用(bit 0 = 1)0x02 / 0x03 → 用户禁用(bit 0 = 0)0x06 / 0x07 → 系统禁用检测逻辑(startup_approved.rs::is_entry_approved_disabled):
let state = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
(state & 1) == 0 // bit 0 = 0 → disabled
enrich() 里的最终 enabled 字段:
enabled: !raw.name.ends_with(".disabled") && !is_entry_approved_disabled(raw)
两个条件都要满足才算"启用"。
踩坑注意:
raw.command 而不是 raw.nameis_entry_approved_disabled 在 enrich() 里调用(enrich 不受 cfg(windows) 限制),所以模块声明不能包在 #[cfg(windows)] 里——函数内部已处理非 Windows 返回 falseTauri v2 的 MSI 目标不接受 semver 预发布标签:
0.2.2-pre → ❌ optional pre-release identifier must be numeric-only0.2.2 → ✅ 直接用纯数字 patch bumptauri.conf.json 的 beforeBuildCommand 构建 CLI+helper 到根 workspace target:
"beforeBuildCommand": "pnpm build && cargo build --release --manifest-path ../Cargo.toml -p bootkeeper-cli -p bootkeeper-helper && node scripts/prepare-resources.mjs"
prepare-resources.mjs 从 ../../target/release 复制 exe 到 src-tauri/resources。
CI gui job 跑 cargo check 前也必须先构建 CLI+helper + 跑 prepare-resources(否则 check 失败:resource doesn't exist)。
serve(stdio()).await? 返回 RunningService,必须 running.waiting().await? 才保活——否则 init 握手后就退出#[tool(aggr)] 语法无效,直接用 Parameters<SchemaType> 作为参数json + '\n'),不是 Content-Length 帧