A second self
for any macOS app.
为任何 macOS 应用
造一个分身。
doppel duplicates an .app bundle into a separately-launchable instance — with a new bundle identifier and a local ad-hoc re-sign. One binary, two modes: a full-screen TUI for picking, a scriptable CLI for everything else. doppel 把 .app bundle 克隆成一个可独立启动的第二实例 —— 拥有新的 bundle identifier,并完成本地 ad-hoc 重签名。 一份二进制,两种模式:交互式的 TUI 用来挑选,可脚本化的 CLI 应对其他一切。
From bundle to doppelgänger, in six precise steps. 从源 bundle 到分身,六个精确步骤。
Each stage emits a structured event the TUI and CLI render in real time. Hover any node to inspect what happens. 每个阶段都会发出一个结构化事件,TUI 和 CLI 实时渲染。悬停任意节点查看详情。
Faithful, byte-for-byte.
Sit in the chair, or pipe it through. 坐进驾驶座,或者接进管道。
A full-screen interactive TUI for humans. A structured, --json-emitting CLI for everyone else.
给人用的全屏交互 TUI。给其他一切用的、能输出 --json 的 CLI。
doppel
Interactive TUI交互式 TUI
Scans /Applications, /Applications/Utilities and ~/Applications. Pick, name, watch it run.
扫描 /Applications、/Applications/Utilities 和 ~/Applications。挑一个、起个名、看它跑。
doppel clone
Scriptable CLI可脚本化的 CLI
Same pipeline, structured output. Pass --json and pipe it anywhere.
同一条流水线,结构化输出。加 --json,往哪接都行。
Six stages, each does one thing well. 六个阶段,每个都只把一件事做好。
Bit-perfect ditto ditto · 比特级精确
Uses /usr/bin/ditto — the only macOS copy primitive that preserves xattrs, ACLs, and resource forks. cp silently loses them and the resulting bundle won't pass codesign --strict.
用 /usr/bin/ditto —— macOS 上唯一能完整保留 xattrs、ACL 和 resource fork 的复制原语。cp 会悄悄丢掉它们,结果 bundle 过不了 codesign --strict。
New identity 新的身份
Rewrites CFBundleIdentifier, CFBundleName, and the Electron helper IDs that embed the parent's bundle ID — the trick that lets Electron clones run side-by-side.
改写 CFBundleIdentifier、CFBundleName,以及嵌入父 ID 的 Electron helper ID —— 这就是 Electron 克隆能和原版并存运行的关键。
Trim the team-bound 剪掉绑身份的 key
Extracts the source app's entitlements and strips identity-bound keys (application-identifier, keychain access groups, team-id) that would fail under an ad-hoc signature.
提取源 app 的 entitlements,剥离身份绑定的 key(application-identifier、keychain access groups、team-id)—— 它们在 ad-hoc 签名下会挂。
Walk the tree 遍历整棵树
Crawls Frameworks, XPCServices, PlugIns, Helpers, LoginItems. Sorts every signable deepest-first so the next stage can sign in the only order codesign accepts.
爬过 Frameworks、XPCServices、PlugIns、Helpers、LoginItems。所有可签名项按深度倒排 —— 这是下一阶段 codesign 唯一接受的顺序。
Ad-hoc, post-order Ad-hoc · 后序签名
Re-signs every signable with codesign --sign -. Deepest nested first, outermost bundle last. Local-launchable; not vendor-trust-valid — the same signature shape Xcode uses for dev builds.
用 codesign --sign - 对每个可签名项重签。最深的嵌套先签,最外层 bundle 最后。本地可启动;不具备厂商信任 —— 和 Xcode 给 dev build 用的是同一种签名。
Prove it works 证明它能跑
Runs codesign --verify --deep --strict, optional spctl --assess, and an optional 3-second launch test that confirms the clone survives its startup window.
跑 codesign --verify --deep --strict、可选的 spctl --assess,再加一次 3 秒启动测试 —— 确认克隆挺得过启动窗口。
What clones cleanly, what doesn't. 哪些能干净克隆,哪些不能。
Re-verified with --launch-test. Swift and Rust apps are nearly trivial; Electron is per-app; self-integrity-checking apps will reject themselves.
已用 --launch-test 复测。Swift 和 Rust app 几乎一发即中;Electron 看具体应用;带强自检的 app 会自己拒绝自己。
codesign --strict (FinderInfo xattrs). Doctor blocks it.源 app 过不了 codesign --strict(FinderInfo xattrs)。Doctor 直接阻断。Honest about the edges. 诚实面对边界。
doppel is for local multi-instance, isolated configuration, and clone testing on your own machine. The list below is not a roadmap; it's a perimeter. doppel 面向本地多实例、隔离配置、在自己机器上测试克隆这些场景。下面这份清单不是路线图,是边界线。
Not a universal app multi-opener. 不是万能的 app 多开器。
Some apps self-check integrity and will refuse to launch the clone. doppel surfaces this honestly rather than papering over it. 部分 app 会自检完整性,拒绝启动克隆。doppel 会如实告知,而不是粉饰。
Does not bypass Gatekeeper. 不绕过 Gatekeeper。
Ad-hoc signatures are local-launchable, not vendor-trust-valid. spctl will reject the clone — exactly as Apple expects for dev builds.
Ad-hoc 签名只是本地可启动,不具备厂商信任。spctl 会拒绝克隆 —— 这正是 Apple 对本地 dev build 的预期。
Never modifies the source. 永不修改源 bundle。
The source bundle is treated strictly read-only across all six stages. Worst case, you delete the clone. 源 bundle 在六个阶段中全程严格只读。最坏情况就是删掉克隆。
App Store apps are out of scope. App Store 应用不在范围内。
Receipt validation, FairPlay protection, and team-bound entitlements make App Store apps a non-target for v1. Receipt 校验、FairPlay 保护、绑 team 的 entitlements —— App Store app 不是 v1 的目标。
Updaters will likely break. 自动更新通常会坏。
Sparkle-updated apps clone fine but the updater channel won't recognize the new identity. Treat clones as fixed snapshots. 用 Sparkle 自更新的 app 可以正常克隆,但更新渠道不认新身份。把克隆当成固定快照。