public beta · macOS only公开 beta · 仅 macOS

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 应对其他一切。

$ brew install tt-a1i/tap/doppel
See how it works看看它怎么工作
Pipeline流水线 6 stages6 个阶段
Modes模式 TUI · CLI
Runtime运行环境 macOS
License许可证 Open source开源
doppel — clone any macOS app pipeline diagram

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 实时渲染。悬停任意节点查看详情。

cmux.app source · read-only源 · 只读 com.cmux.app
cmux2.app clone · ad-hoc signed克隆 · ad-hoc 已签名 com.cmux.app.cmux2
Stage 01 · copy

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。挑一个、起个名、看它跑。

~/Applications — doppel — 96×30
doppel · pick an app to clone · ↑↓ navigate · enter select · p paste path · q quitdoppel · 选一个 app 来克隆 · ↑↓ 移动 · 回车 选中 · p 粘贴路径 · q 退出
Alacritty
org.alacritty
8.2 MB
Cherry Studio
com.kangfenmao.CherryStudio
218.4 MB
▸ cmux
com.cmux.app
42.1 MB
Ghostty
com.mitchellh.ghostty
66.7 MB
LocalSend
org.localsend.localsend_app
36.0 MB
SELECTED · cmux.app已选中 · cmux.app
name名称 cmux2
id com.cmux.app.cmux2 (auto)(自动)
launch-test启动测试 on开启
[enter] clone →[回车] 开始克隆 →

doppel clone

Scriptable CLI可脚本化的 CLI

Same pipeline, structured output. Pass --json and pipe it anywhere. 同一条流水线,结构化输出。加 --json,往哪接都行。

~ — zsh — 96×30
$ doppel doctor /Applications/cmux.app
→ 8 rules · 0 errors · 1 info
✓ ready · Swift + Sparkle · 4 signables
 
$ doppel clone /Applications/cmux.app \
    --name cmux2 \
    --launch-test
 
✓ copy · 2,847 entries · 214.3 MB
✓ plist · CFBundleIdentifier → com.cmux.app.cmux2
✓ ents · stripped 3 identity-bound keys
✓ discover · 4 signables · deepest-first
✓ resign · ad-hoc · post-order
✓ verify · codesign --deep --strict
✓ launch · survived 3.0s startup
 
→ ~/Applications/cmux2.app
bundle id com.cmux.app.cmux2
signed ad-hoc (-)
elapsed 4.2s
 
$

Six stages, each does one thing well. 六个阶段,每个都只把一件事做好。

1copy

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

2plist

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. 改写 CFBundleIdentifierCFBundleName,以及嵌入父 ID 的 Electron helper ID —— 这就是 Electron 克隆能和原版并存运行的关键。

3entitlements

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 签名下会挂。

4discover

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. 爬过 FrameworksXPCServicesPlugInsHelpersLoginItems。所有可签名项按深度倒排 —— 这是下一阶段 codesign 唯一接受的顺序。

5resign

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 用的是同一种签名。

6verify

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 会自己拒绝自己。

App应用
Type类型
Cloned克隆
Launches启动
Notes备注
Alacritty
Rust · single-binaryRust · 单二进制
pass通过
pass通过
Cleanest case. No frameworks, no helpers.最干净的情况。无框架、无 helper。
Ghostty
Swift + C · SparkleSwift + C · Sparkle
pass通过
pass通过
1 entitlement change. Sparkle present.1 处 entitlement 改动。带 Sparkle。
cmux
Swift + SparkleSwift + Sparkle
pass通过
pass通过
Dock-tile plugin bundle ID rewritten.Dock-tile 插件的 bundle ID 已改写。
Cherry Studio
Electron · 9 signablesElectron · 9 个可签项
pass通过
pass通过
Strips ElectronAsarIntegrity. Independent app-support dir.剥离 ElectronAsarIntegrity。使用独立 app-support 目录。
BetterDisplay
Swift + helpersSwift + helpers
pass通过
!caution注意
Launches, but menu-bar privileges may need re-grant.可启动,但菜单栏权限可能需要重新授权。
LocalSend
Flutter · 28 signablesFlutter · 28 个可签项
pass通过
fails失败
Exits silently at ~2.5s. Sandbox + login-item findings.约 2.5 秒静默退出。有沙盒和登录项 finding。
Claude
Electron · asar integrityElectron · asar 完整性
pass通过
fails失败
Self-rejects on Resources/app.asar integrity.在 Resources/app.asar 完整性校验处自我拒绝。
Google Chrome
Chrome + framework helpersChrome + framework helpers
blocked阻断
n/a
Source fails 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 面向本地多实例、隔离配置、在自己机器上测试克隆这些场景。下面这份清单不是路线图,是边界线。

01
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 会如实告知,而不是粉饰。

02
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 的预期。

03
Never modifies the source. 永不修改源 bundle。

The source bundle is treated strictly read-only across all six stages. Worst case, you delete the clone. 源 bundle 在六个阶段中全程严格只读。最坏情况就是删掉克隆。

04
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 的目标。

05
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 可以正常克隆,但更新渠道不认新身份。把克隆当成固定快照。

Make a doppelgänger. 造一个分身。

$ brew install tt-a1i/tap/doppel
$ go install github.com/tt-a1i/doppel/cmd/doppel@latest