Desktop app — local build guide
Desktop 殼層(src-tauri/)把既有的 duduclaw gateway 與內嵌的 dashboard 包進原生視窗(Tauri 2)。它以 sidecar 子行程的方式執行 gateway,核心二進位檔本身不受影響(TODO §D)。
這個殼層刻意排除在 Rust workspace(root
Cargo.toml)之外。請用 Tauri CLI 在src-tauri/底下建置,不要用cargo build。
Prerequisites
Section titled “Prerequisites”# Tauri CLI — needs rustc >= 1.77. If `cargo install` errors with# "requires rustc 1.77.2 or newer", your rustup default toolchain is too old:# rustup default stable && rustup update stablecargo install tauri-cli --version "^2"# Node (for the web build) — already required by the dashboard# macOS: Xcode CLT; Linux: libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf先產生一次 app 圖示。品牌來源圖 web/public/paw-1024.png 已提交進版控;src-tauri/icons/ 底下產生的圖示集則被 gitignore(要重新產生,不要提交):
scripts/desktop/gen-icons.sh # cargo tauri icon, with a macOS sips fallback# or directly: cd src-tauri && cargo tauri icon ../web/public/paw-1024.pngDev (hot-reload UI)
Section titled “Dev (hot-reload UI)”# Stage the gateway sidecar FIRST — `tauri dev` resolves it next to the dev# binary (src-tauri/target/debug/), not from binaries/. Without this the app# can't spawn the gateway and the UI can't reach /api (ECONNREFUSED).cargo build --release -p duduclaw-cli --bin duduclaw # from the REPO ROOTscripts/desktop/stage-sidecar.sh # copies into binaries/ + target/{debug,release}/
cd src-tauri && cargo tauri dev在 dev 模式下,視窗會停留在 Vite dev server(127.0.0.1:5173)以取得即時 HMR;Vite 會把 /ws 與 /api 代理到 gateway。App 啟動時仍會照樣 spawn gateway sidecar,並等它就緒才顯示視窗。在 release 模式下,視窗改指向 gateway 內嵌的 dashboard(在 main.rs 用 #[cfg] 分支切換)。所以修改 web 端程式碼在 dev 模式會立刻看得到,但若要在內嵌那條路徑上看到效果,就必須重新建置 dist 並重新內嵌(gateway 透過 rust_embed 提供 crates/duduclaw-dashboard/dist,是在編譯期就烤進去的)。
Production build (unsigned, local)
Section titled “Production build (unsigned, local)”# 1. build the release gateway and stage it as the sidecarcargo build --release -p duduclaw-cli --bin duduclawscripts/desktop/stage-sidecar.sh
# 2. build the app bundlecd src-tauri && cargo tauri build產物會落在 src-tauri/target/release/bundle/(.app/.dmg、.msi/.exe、.AppImage/.deb)。
Lifecycle behavior (what the shell does)
Section titled “Lifecycle behavior (what the shell does)”- 單一實例:第二次啟動只會把已存在的視窗帶到前景(§D2.1)。
- 附掛或新啟動:若
DUDUCLAW_PORT(預設 18789)上已經有 gateway 在跑,就直接附掛上去,不會把它殺掉;否則就在18789..=18797裡挑第一個空的 port 來 spawn sidecar(§D1 / §D2.2)。 - PATH:sidecar 是以擴充過的 PATH 啟動(Homebrew、
.local/bin、Bun、Volta、npm-global、asdf、cargo),確保從 Finder / Dock 啟動時也找得到 Claude CLI / node / containers(§D2.6)。 - 資料目錄:與 CLI 共用
~/.duduclaw,兩邊看到的 agent/SQLite/wiki 都是同一份(§D2.7)。 - 關閉即縮到系統列:關閉視窗只是隱藏它,要真正結束要從系統列選單選擇離開(§D2.4)。
- 健康檢查與重啟:sidecar 意外結束時會觸發指數退避重啟(最多 5 次),仍失敗才顯示錯誤(§D2.5)。
Relationship to launchd
Section titled “Relationship to launchd”如果你已經用 launchd 執行 gateway,desktop app 會附掛上去(不會重複啟動)。若想改讓 app 自己接管 gateway,要先停掉 launchd job。單一實例鎖加上 pidfile(~/.duduclaw/desktop-sidecar.pid)可以防止兩個由 app 啟動的 sidecar 同時存在。
First-build gotchas (verified 2026-07 on macOS arm64)
Section titled “First-build gotchas (verified 2026-07 on macOS arm64)”大致依你遇到的順序排列。這些都已經在 repo 裡解決,這裡記的是「為什麼」,讓乾淨機器不用重新除錯一遍。
-
cargo install tauri-cli失敗,錯誤是「requires rustc 1.77.2 or newer」。 就算已經裝了更新的版本,rustup 的預設工具鏈可能還是舊的。跑rustup default stable && rustup update stable。(cargo/rustc在 PATH 上可能是另一份 Homebrew 的拷貝,真正出問題的其實是 rustup 的 shim。) -
gateway 相關的 cargo 指令要從 REPO ROOT 執行,不要在
src-tauri/底下。src-tauri是被排除在外的獨立 workspace,所以在那裡跑cargo build -p duduclaw-cli會出現「package ID … did not match any packages」的錯誤。只有cargo tauri dev/build才是在src-tauri/底下執行。 -
cargo metadata/tauri dev無法解析 manifest:找不到lib.rs。 Mobile template 帶的[lib]已被移除,src-tauri是純二進位 crate(src/main.rs)。不要在沒有對應src/lib.rs的情況下重新加回[lib]。 -
兩個 frontend hook 都是從 repo root 執行,不是從
src-tauri/(已驗證:build hook 的pwd就是 repo root)。所以兩者都是cd web && npm run …,不是cd ../web。(cargo tauri dev/build是在src-tauri/底下被呼叫,但 Tauri 執行 hook 時的工作目錄是 project root。) -
Vite 一直卡在「Waiting for frontend dev server …」。 Vite 必須綁定 IPv4 的
127.0.0.1(不是預設的localhost/::1),才會跟 Tauri 的輪詢器與 proxy target 對上,這點已在web/vite.config.ts釘死(host: '127.0.0.1'、strictPort,以及 gateway proxy 預設值http://127.0.0.1:18789)。 -
cookie 0.18.1出現編譯錯誤(Parsable::parsearity 不符)。time 0.3.52在 0.3.x 系列內破壞了 API,已在src-tauri/Cargo.toml中鎖在time = "=0.3.51"。等 tauri/wry 支援新版time之後就可以拿掉這個鎖定。 -
Build script 出現「Permission core:webview:allow-navigate not found」。 Tauri 2 裡根本不存在這個權限(
navigate()是 Rust API,不需要權限閘)。別把它放進capabilities/default.json。 -
登入畫面出現 ECONNREFUSED,或 dev 模式下 gateway 完全沒啟動。 sidecar 是相對於正在執行的可執行檔來解析路徑的;
tauri dev是從src-tauri/target/debug/執行,所以二進位檔必須先放到那裡;stage-sidecar.sh現在也會複製一份進target/{debug,release}/,不只是binaries/。跑過cargo clean之後要記得重新執行一次stage-sidecar.sh。 -
圖示出現白邊。 來源圖必須有乾淨的透明邊角(不要用
qlmanage把 SVG 轉點陣,它會把透明部分貼上白底)。重新產生的paw-1024.png是滿版的琥珀色正方形,帶超取樣過的圓角 alpha 遮罩。build.rs會發出rerun-if-changed=icons,所以重新產生的圖示集會在下一次建置時重新內嵌(否則舊圖示會一直被烤在裡面,且 macOS 也可能還快取著舊的:sudo rm -rf /Library/Caches/com.apple.iconservices.store && killall Dock)。 -
cargo tauri build最後出現「A public key has been found, but no private key」。.app/.dmg其實已經建置完成,只是 updater 產物的簽章步驟失敗了。在金鑰產生之前,自動更新是關閉的 (plugins.updater.active = false、bundle.createUpdaterArtifacts = false); desktop-unblock.md 關卡 E 會在跑過cargo tauri signer generate之後把兩者都打開。 -
DMG 裡出現一個
.VolumeIcon.icns檔案。 那是磁碟映像的磁碟區圖示(DMG 的外殼裝飾),是個隱藏檔,一般使用者用預設的 Finder 設定看不到它,只有開了「顯示隱藏檔案」 (defaults write com.apple.finder AppleShowAllFiles)才會看到。它不會被打包進DuDuClaw.app裡。
DMG 視窗本身的樣式是透過 bundle.macOS.dmg 設定的(自訂背景圖、視窗大小、圖示位置)。背景圖是 src-tauri/dmg/background.png,由
scripts/desktop/gen-dmg-background.py 產生(用 Pillow;因為不是每台 macOS 都有 PingFang,所以 zh-TW 文字用的是 Heiti TC)。要改就改這個腳本,不要直接改 PNG。
Verified working (2026-07, macOS arm64)
Section titled “Verified working (2026-07, macOS arm64)”cargo tauri build 在本機可以產生一個可用、未簽章的 DuDuClaw.app 與 .dmg。
簽章/公證/自動更新需要真正的 Apple 與 Windows 憑證,以及 updater 金鑰,參見
desktop-release.md 與
desktop-unblock.md。