一个 macOS 专用的命令行工具,把 .app bundle 克隆成一个独立可运行的第二个实例,具备新的 bundle identifier 和本地 ad-hoc 重签名。
当前定位:公开 beta。doppel 面向需要本地多实例、隔离配置、测试克隆 app 的 macOS 用户;它不是万能的 app 多开器,也不会绕过厂商完整性校验、App Store 限制、SIP 或 notarization。
一份二进制,两种模式:
- TUI(默认):
doppel— 全屏交互式 app 选择器 → 配置表单 → 实时进度 → 结果页 - CLI:
doppel <inspect|clone|verify|doctor> …— 可脚本化,支持--json输出
brew install tt-a1i/tap/doppelmacOS 首次启动时可能会拦截未签名二进制。若被拦:
xattr -dr com.apple.quarantine "$(brew --prefix)/bin/doppel"go install github.com/tt-a1i/doppel/cmd/doppel@latestgit clone https://github.com/tt-a1i/doppel
cd doppel
make build # 产出 ./doppel
make install # 拷到 $GOPATH/bin(可选)仅支持 macOS(Intel 或 Apple Silicon)。源码 / go install 路径需要 Go 1.26+。运行时 doppel 通过系统自带的 /usr/bin/ditto、/usr/bin/codesign、/usr/sbin/spctl 完成实际操作。
普通用户建议按这个顺序走:
# 1. 先诊断兼容性
doppel doctor /Applications/cmux.app
# 2. 试跑,确认目标路径和自动生成的 bundle ID
doppel clone /Applications/cmux.app --name cmux2 --dry-run
# 3. 真实克隆,并做启动存活测试
doppel clone /Applications/cmux.app \
--name cmux2 \
--launch-testclone 默认写入 ~/Applications/<Name>.app,避免普通用户碰到 /Applications 权限问题。也可以通过 --target /Applications/cmux2.app 显式指定系统 Applications 目录。
--bundle-id 可以省略。省略时 doppel 会根据源 app 的 bundle ID 和 --name 自动生成一个新 ID;需要固定身份时再手动传 --bundle-id com.example.cmux2。
clone 默认会先跑一次 preflight。若发现 error 级问题(例如源 app 自身 codesign --strict 不通过),会在写入磁盘前停止。只有你明确知道风险时才使用 --skip-doctor。
doctor 会输出一个兼容性结论:ready、caution 或 blocked。普通用户只需要看这行结论;需要排障时再看后面的 finding 详情。
doppel启动全屏 picker,扫描 /Applications、/Applications/Utilities、~/Applications。挑一个 app,填入新名字;bundle ID 会自动生成,也可以手动覆盖。TUI 默认会在签名后做一次短启动测试。若目标 app 不在列表里,按 p 手动输入 .app 路径。
# 列出可克隆的 app(agent-friendly)
doppel list
doppel list --json
# 检查指定 bundle
doppel inspect /Applications/cmux.app
doppel inspect /Applications/cmux.app --json
# 试跑(只推演计划,不动任何文件)
doppel clone /Applications/cmux.app --name cmux2 --dry-run
# 真实克隆
doppel clone /Applications/cmux.app \
--name cmux2 \
--launch-test
# 需要固定身份时,手动指定 bundle ID
doppel clone /Applications/cmux.app --name cmux2 --bundle-id com.example.cmux2 --dry-run
# 覆盖已存在的 .app 目标(先删后建;目标必须以 .app 结尾)
doppel clone /Applications/cmux.app --name cmux2 --force
doppel verify ~/Applications/cmux2.app
doppel doctor /Applications/cmux.app全局 flag:--json(结构化输出)、--verbose。
运行 doppel <cmd> --help 查看每个子命令的完整 flag 列表。
| 码值 | 含义 |
|---|---|
| 0 | OK |
| 1 | 一般错误 |
| 2 | 无效输入(路径非法、bundle id 非法、目标已存在但未加 --force 等) |
| 3 | 不支持的环境(非 macOS) |
| 4–9 | 分阶段失败:copy / plist / sign / verify / launch-test / inspect |
克隆流水线分 6 个阶段,每个阶段通过 stage event 推送给 TUI / CLI 渲染:
- copy —
/usr/bin/ditto保留cp无法处理的 xattrs、ACLs、resource fork - plist — 改写
CFBundleIdentifier、CFBundleName、CFBundleDisplayName;同时改写那些嵌入父 ID 的 helper bundle ID(Electron 模式) - entitlements — 从源 app 提取,剥离和身份绑定的 key(
application-identifier、keychain-access-groups、team-identifier 等),否则 ad-hoc 重签后会挂 - discover — 走遍
Contents/Frameworks、XPCServices、PlugIns、Helpers、LoginItems找嵌套可签名项,按深度倒排 - resign — ad-hoc(
codesign --sign -)按深度倒序:最深的先、外层 bundle 最后 - verify —
codesign --verify --deep --strict+ 可选的spctl --assess
已知权衡:
- Ad-hoc 签名本机可运行,但不具备厂商信任。
spctl会拒绝克隆;Gatekeeper 首次启动会弹窗。这是正常的——本地 ad-hoc 签名就是 Apple 对本地开发构建的预期,不是分发场景。 - 永不修改源 bundle。 源 app 全程只读。
- 默认写入
~/Applications。 这样不需要管理员权限;需要放到系统/Applications时请显式传--target。 --force支持已有.app目标。 doppel 可以删掉已有克隆目标再建,但仍然拒绝危险的非.app路径。- preflight 默认开启。
clone会先跑 doctor 规则;error 级 finding 会阻断克隆,warn/info 会作为提示继续显示。
具体每个 app 的结果见 docs/support-matrix.md,doctor 规则目录见 docs/failure-modes.md。
速览:Swift/Rust/原生 app 克隆得非常干净。Electron 支持因应用而异:doppel 会改写 Contents/Frameworks/*.app 下的 helper bundle ID、保留 helper 依赖的 bundle name、必要时改写 Electron app.asar 的包身份。Cherry Studio 已验证可作为独立第二实例与源 app 同时运行;Claude 仍会在启动时卡在自己的 app.asar 完整性校验。Sparkle 自更新 app 可以克隆但更新器会坏。沙盒 app 可以克隆,但容器目录是空的。源盘上带有 codesign --strict 问题的 app(例如 Chrome 的 FinderInfo xattrs)会在克隆开始前被标记。
发布前真实 app 回归流程见 docs/smoke-testing.md。Developer ID 签名和 notarization 路线见 docs/release-signing.md。
- 仅 macOS(启动时强制检查)
- 公开 beta——不是所有 app 都能干净克隆;
doctor会点出常见失败模式 - App Store 应用不在 v1 范围
- 不保留厂商公证、更新渠道或与原 team 绑定的 entitlement 身份
- 不保证强完整性自检 app 能启动;这种情况请用
--launch-test验证