Files
wuguanganandClaude Opus 5 e3b9153b66 feat: 拦住 AI 把密钥搬出去;README 补 AI 时代的隐私那一节
## 密钥泄漏闸

把活交给 agent 之后,泄密的形态变了。以前是人手滑,现在是**每一步都很合理的
一串操作**:git add . 顺手把 .env 提了、把真实 token 内联进配置文件当"示例"、
为了确认配置 cat 一下密钥(值就进了 transcript,0644、永久、每轮重发给 API)。

**没有一条能靠「这条命令危不危险」判断出来。** 它们都不危险。唯一可靠的信号是
这次调用的内容里有没有出现真实的密钥值。

所以加了一道闸,默认开:参数里出现库里的真实值 → 拒;git add/commit 会把
.env 一类文件或含真实值的文件带进仓库 → 拒。拒绝理由里写明正确做法
(用 $KEY、先加 .gitignore),否则模型只会换个写法再试一次。

**它跑在 hubctl 本地,不依赖 app 在不在跑。** 仓库其它拦截都是「app 没开就放行」
(一个监控工具不该让你的命令跑不起来),这一条反过来 —— 密钥漏出去不可逆。

几条守住的边界,判宽了比判窄了危险:值短于 8 字符不参与匹配(abc/true/test
会命中一切);.example/.template/.sample 结尾放行(模板文件本来就该提交,
误拦一次用户就把整个功能关掉了);只认 EnvRenderer 写出来的格式,不做通用
dotenv 解析;不是 git add/commit 就一个子进程都不起。

拒了之后事件照发给 app(不等应答),进审批日志 —— 一道看不见的闸,
用户第一次被拦时只会觉得 Claude 抽风。开关在密钥页底部,
落成 ~/.vibe-foreman/guard.json,且**只在关掉时才写**:
默认开,「文件不存在 = 开着」,这样没打开过密钥页的人也受保护。

明确写了它挡不住什么:python -c 读、软链、base64 转一道、库外手写的值。
它是一道减少手滑的闸,不是沙箱。

## 截图

主窗口现在也能自动截了:HUB_WINDOW_SECTION 钉住打开哪一页,
配 DemoFixtures.seed 造的假密钥和假账号(刻意编得一眼假 —— 一个像真的
token 混进去,读者会去试)。

两个坑:find-window 按面积取第一个会截到岛(它比主窗口先出现一瞬),
改成按宽度 ≥900 筛;.accessory 的进程从后台 shell 起来抢不到焦点,
而非活跃窗口里的系统控件会被去饱和 —— 截出来开关全是灰的,
正好把要展示的东西说反了,所以截图模式下改用 .regular。

顺带:进密钥页自动选中第一组。有组却什么都不选,右边一大片空白,
那是"还没建组"的样子而不是"有四组等你挑"的样子。

646 个测试全过(原 625)。真 hubctl 二进制端到端验过四种情况:
写入真值被拒、约定的 source 用法放行、git add . 带 .env 被拒、普通命令放行。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 10:46:46 +08:00

4.1 KiB
Raw Permalink Blame History

参与共创

先说结论:欢迎,而且有几件事是真的缺人做(往下翻到「现在最缺什么」)。

跑起来

git clone https://github.com/gulugulu-cn/vibe-foreman.git ~/Documents/code/vibe-foreman
cd ~/Documents/code/vibe-foreman
bash scripts/setup.sh          # 编译 + 安装到 /Applications
cd HubKit && swift test        # 646 个测试,约 45 秒

需要 macOS 26 SDK(Xcode 26) —— 灵动岛依赖 Liquid Glass API,这是硬门槛。 零第三方依赖,swift build 之外什么都不用装。

改完代码重新装:

bash scripts/build-swift-app.sh     # 几秒,只编当前架构
bash scripts/release.sh             # 打 universal + dmg,几分钟

这个仓库的写法

代码风格没什么特别的,但注释和测试的写法是有讲究的,PR 会按这个看。

注释写「为什么」,不写「是什么」

// ✗ 不要:检查目录是否存在
// ✓ 要:
// tmux 对不存在的 `-c` 目录**返回 0 并静默回落到家目录** ——
// 实测 exit=0,pane_current_path 变成 /Users/xxx。`open /不存在` 则什么都不做。
// 两者都不报错,于是一条坏数据表现成"右键菜单坏了"。

代码本身已经说清楚"是什么"了。值得写下来的是下一个人(包括三个月后的你)会踩的那个坑: 为什么不用那个看起来更简单的写法、这个魔数是怎么来的、改成另一种会怎样。

HubKit/NOTES.md 里攒着所有实机撞出来的坑,改动碰到相关的地方请顺手补一条。

测试钉的是边界,不是覆盖率

优先测写错了不会报错、只会静默失灵的那些判断。这个仓库里几乎每个测试 都对应一次真实事故,测试的文档注释里写着那次事故是什么样的:

/// **绑不到 pane 的不算 detached。**
///
/// 那是「根本不在 tmux 里」——VS Code 扩展、直接开的终端、后台任务。
/// 它们有没有终端连着这里判断不了,报成 detached 就是拿"不知道"当结论,
/// 会让一堆正常会话集体显示成"终端已关"。

中文注释

现有代码全是中文注释,继续用中文即可。代码标识符用英文。

提 PR 之前

cd HubKit && swift test        # 必须全过

commit message 用中文,写清楚为什么这么改,不只是改了什么。 碰到用户可见的行为变化,请一并更新 README。

现在最缺什么

按优先级,都是真实的缺口,不是造出来的任务:

1. 终端支持(最缺)

现在只有 iTerm2 能精准跳转到具体 tab,Terminal.app 只能前置。 Ghostty / Warp / Kitty / WezTerm 的用户点「跳转终端」完全没反应。

这是目前最硬的门槛。跳转的核心算法(进程树祖先链求交,见 HubJump/JumpEngine.swift)是终端无关的,缺的是每个终端的「枚举会话 + 前置指定 tab」适配层 —— 参考 HubJump/ITermLocator.swift,一个终端一个文件。

只做到"把终端拉到前面"也是巨大的改进,不必一步到位做到精准 tab。

2. 支持别的 AI CLI

目前只认 Claude Code(读它的 ~/.claude/sessions/<PID>.json)。 Codex 的 ~/.codex/sessions/ 调研过、结构接得上, 难点在"在跑还是在等"这一层要另走一条路 —— Claude Code 有权威的 status 字段, 别家不一定有。

3. 公证(Notarization)

现在是自签名,别人下载后要绕 Gatekeeper。这需要 Apple Developer Program 账号,不是纯代码问题,但如果你有经验,scripts/release.sh 里加一段 notarytool 就能让"双击即用"成立。

4. 外接屏上的胶囊形态

岛跟随光标换屏做完了,但没在 4K 屏上逐项核对过。 有外接屏的话,帮忙看一眼尺寸和位置对不对就很有价值。

有想法但不确定要不要做?

直接开 issue 聊。这个工具是从一个人的真实工作流里长出来的, 很多设计取舍(比如「盯梢默认全关」「审批只拦不可逆那一档」)都有具体理由, 先聊一下能省掉双方的返工。