 wuguanganandClaude Opus 5
|
bae4f9af86
|
feat: 零配置装 hook;改名 Vibe Foreman Free
## 零配置
装法从三步变两步:拖进应用程序 → 打开。中间那步「双击安装 hook.command」
没有任何用户能做的决定,漏点它的后果却是**静默的** —— app 开着、
界面正常、一条事件都收不到,看起来像"装了但没用"。
HookInstaller 在启动时自己装。四条边界,都有测试钉着:
- 只碰自己的条目:只删命令里含 `hubctl hook` / `hub-hook-` 的(我们的旧版),
用户手写的 hook 和 permissions / env 等其它键一律原样保留
- 幂等:内容没变一个字节都不写。`.sortedKeys` 是这条的前提 ——
不排序时 JSON 键序跟着哈希走,"没变就不写"的判断会随机失效
- 坏 JSON 不覆盖:用户可能只是手改时漏了个逗号,覆盖等于删了他的配置
- 失败不阻断启动:hook 没装好的 app 仍能显示会话,起不来的 app 什么都不是
hubctl 每次比内容再决定要不要拷 —— 升级 app 后 hook 还指着旧二进制的话,
协议一变整条链路静默失效。设置页显示状态 + 一键修复。
dmg 里那个 command 文件改名成「手工修复 hook(通常用不到)」,留作兜底。
实测:本机 settings.json 装前装后逐键对比,顶层键无增无减、
permissions 等非 hooks 值全部原样、9 类 hook 各 1 条无重复;
第二次启动 sha256 未变。
## 改名
显示名 Claude Hub → Vibe Foreman Free。
**bundle id、签名证书名、可执行文件名 ClaudeHub 全部不动** ——
换任何一个都会让已授予的 TCC 权限(控制 iTerm 的 Automation、通知)失效,
而 `pgrep -x ClaudeHub` 那条判活约束也依赖可执行名(见 NOTES.md)。
安装时清掉旧名字那份,避免 /Applications 里同时躺着两个共用 bundle id 的 app。
530 个测试全过。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-08-14 22:27:28 +08:00 |
|
 
|
b6d90cac2b
|
feat: 重写为原生 Swift 灵动岛,修掉通知跳转与主窗口数据链路 (#4)
删掉 Tauri 版(app/),全部重写为 SwiftUI + SwiftPM(HubKit/,8.2k 行源码 +
2k 行测试,131 个测试)。核心动机是旧版"点通知跳不到对应终端"——
根因是身份链路全程用会漂移的字符串:项目名 → tmux 窗口名 → pane_title →
AppleScript 子串匹配。实测 9 个 tmux 窗口只有 2 个能和 projects.yaml 对上,
其中一个窗口名是 `2.1.173`(tmux automatic-rename 把它改成了 claude 的版本号)。
主要变化
- 会话状态直接读 ~/.claude/sessions/<PID>.json(Claude Code 自己维护的权威源),
不再用 spinner 字符猜 busy;死进程的残留 json 用 kill(pid,0) 过滤
- 跳转改用 iTerm jobPid + 进程祖先链求交,全程只有整数 PID 和 UUID;
bg 会话不在任何终端里,单独用稳定 job 名兜底。实测 9/9 绑定成功
- hook 从 bash 换成 Swift 的 hubctl,走 Unix socket;Hook 没运行时立即放行
- 高风险操作在岛上审批,六重防误点;拒绝是超时和异常的默认值
- 主窗口:会话 / 项目 / 用量 / 审批日志 / 设置
修掉的真实缺陷(详见 HubKit/NOTES.md)
- 超时阶梯是反的:ApprovalCoordinator 和 hubctl 读超时都是 60s,同时到期时
hubctl 按 fail-open 放行了一条已被服务端判定拒绝的 git push --force origin main。
改成 60/65/75/90 严格递增,并区分「连不上」(放行)与「连上但没应答」(拒绝)
- 点击全部被吞:app 是 .accessory + panel 是 .nonactivatingPanel,
NSHostingView.acceptsFirstMouse 默认 false,每次点击都算 first mouse 被丢弃
- hitTest 假设 NSHostingView 恒为 flipped,而 isFlipped 是 final 的可读写属性
- 形态切换靠替换 rootView,导致动画不可能发生且 @State 每次归零
- 全局 leftMouseDown 监听会收到点在自己身上的点击,展开态一点就收起
- hook 进程死后审批卡不消失,且会挡住后面的请求(按 clientPid 扫描清理)
- app 读的 projects.yaml 不是脚本维护的那份,且那份被写坏成
`projects: []` + 117 条内容,解析器静默返回 0 —— 三个页面因此全空
- 用量对 945MB/341 个 jsonl 整份读进内存,跑不完;改流式 + 增量缓存后
7 天首扫 3.6s、命中缓存 0.22s、全量 4s
- 辅助二进制放 Contents/MacOS/ 且共用 bundle id 会被 exec 瞬间 SIGKILL
- 同一 bundle id 对应多个 .app 让存活判断和 TCC 授权互相打架
刻意的取舍
- 放弃 Liquid Glass 做岛的基底:它会跟着背景亮度双向补偿,而刘海恒为纯黑,
两者物理上互斥。改用固定 86% 暗色基底 + 背景模糊 + 边缘高光
- 用 SwiftPM + 打包脚本而不是 .xcodeproj:手写 xcodeproj 的 XML 无法 review,
且 SwiftPM 保住了 swift test
Co-authored-by: wuguangan <anjaihd0206@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-07-29 19:20:12 +08:00 |
|