Desktop App Example
Tauri desktop shell + Bun sidecar backend + Next.js UI for running and inspecting Cline chat sessions.
Dev Commands
From apps/examples/desktop-app/:
bun run dev:web- Next.js UI only (http://localhost:3125)bun run dev:sidecar- sidecar backend onlybun run dev- Tauri desktop devbun run build- build web assetsbun run build:sidecar- build the Bun sidecar bundlebun run build:sidecar:bin- compile the Bun sidecar into a local binarybun run build:binary- build desktop binarybun run package:desktop- package the current OS desktop app intodist/desktop/bun run typecheck- TypeScript check
Shareable Desktop Packages
Tauri desktop bundles are OS-specific, so build each package on the target OS:
- macOS:
bun run package:desktop:mac - Windows:
bun run package:desktop:windows - Linux:
bun run package:desktop:linux
The macOS package script refuses to create a shareable package unless Developer ID signing and notarization credentials are configured. This prevents the common Gatekeeper failure where a downloaded unsigned build appears damaged on a teammate's Mac.
Set either APPLE_CERTIFICATE or APPLE_SIGNING_IDENTITY, plus one notarization credential set before packaging macOS:
APPLE_ID,APPLE_PASSWORD,APPLE_TEAM_IDAPPLE_API_KEYorAPPLE_API_KEY_PATH,APPLE_API_KEY_ID,APPLE_API_ISSUER
For local-only macOS testing, use bun run package:desktop:mac --allow-unsigned-mac. That ad-hoc signs the .app and strips quarantine attributes, but it is not suitable for a downloaded build shared with teammates.
macOS signing & notarization, step by step
One-time keychain setup:
- Get the Developer ID Application identity from your team admin. A
.ceralone is not enough — you need the private key. If the admin generated the CSR, have them export the identity from Keychain Access as a.p12and import it:security import BeeCertificates.p12 -k ~/Library/Keychains/login.keychain-db -T /usr/bin/codesign -T /usr/bin/security - If
security find-identity -v -p codesigningstill reports0 valid identities, the Apple intermediate CA is missing. Install it:curl -O https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer && security import DeveloperIDG2CA.cer -k ~/Library/Keychains/login.keychain-db - Re-run
security find-identity -v -p codesigning— it should now listDeveloper ID Application: <Team Name> (<TEAMID>). That exact quoted string is yourAPPLE_SIGNING_IDENTITY. - Get an App Store Connect API key from the admin: the
AuthKey_<KEYID>.p8file, the Key ID, and the Issuer ID (a UUID from App Store Connect → Users and Access → Integrations). This is used for notarization only — nothing is published.
Per-build:
export APPLE_SIGNING_IDENTITY="Developer ID Application: <Team Name> (<TEAMID>)"
export APPLE_API_KEY="<KEYID>" # Tauri reads APPLE_API_KEY (the Key ID); APPLE_API_KEY_ID alone silently skips notarization
export APPLE_API_KEY_PATH="/path/to/AuthKey_<KEYID>.p8"
export APPLE_API_ISSUER="<issuer UUID>"
bun run package:desktop:mac
The first signing run pops a keychain dialog — enter your macOS login password and click Always Allow. Notarization uploads the app to Apple's automated malware scan (typically 2–10 minutes) and staples the ticket. Artifacts land in dist/desktop/; share the .dmg. The DMG name takes its version from src-tauri/tauri.conf.json, the zip name from package.json — bump both.
Do not remove src-tauri/entitlements.plist or the bundle.macOS.entitlements reference in tauri.conf.json: notarization requires the hardened runtime, which breaks the Bun-compiled sidecar (SharedArrayBuffer is not defined, surfacing in-app as "desktop backend endpoint not ready") unless the JIT entitlements are present.
Runtime Overview
Startup flow:
- Tauri starts a persistent local desktop backend and keeps only native window/file-picker/open-path responsibilities.
- The desktop backend starts the Bun sidecar and exposes one websocket transport (
/transport) for commands, queries, and pushed events. - The React app uses
lib/desktop-client.tsand no longer imports@tauri-apps/api/coredirectly in feature code. - Tool approval updates are pushed from the backend instead of polled from the UI.
- Session process context resolves
workspaceRootfrom git root and uses that same path as defaultcwdfor chat runtime and git operations unless explicitly overridden.
Desktop transport envelope:
- Request:
{ "type": "command", "id": string, "command": string, "args"?: object } - Response:
{ "type": "response", "id": string, "ok": boolean, "result"?: unknown, "error"?: string } - Event:
{ "type": "event", "event": { "name": string, "payload": unknown } }
Settings: Routine
- The Settings sidebar includes a
Routineview for hub-backed automations. Routinelists all RPC schedules and shows status (enabled,nextRunAt, active execution).- From the UI you can open a create form and add, pause/resume, trigger-now, and delete schedules.
- The view is wired to the same scheduler APIs used by
cline schedulethrough Tauri commands andscripts/routine-schedules.ts.
Key Files
src-tauri/src/main.rs- Tauri shell lifecycle, backend launch, and native-only commandssidecar/index.ts- persistent Bun sidecar backendsidecar/chat-session.ts- in-process chat session runtimewebview/lib/desktop-client.ts- typed desktop websocket clientwebview/hooks/use-chat-session.ts- UI chat session state + backend subscriptionswebview/lib/chat-schema.ts- chat message schema used by the UIwebview/components/views/settings/routine-view.tsx- Routine schedules UI
Data + Storage
- Session artifacts are written under
~/.cline/data/sessions/<sessionId>/(orCLINE_SESSION_DATA_DIR). - Canonical replay/export artifact:
<sessionId>.messages.json. <sessionId>.messages.jsonis expected to contain ordered messages plus assistantmodelInfoandmetrics(including cache token fields when provided by the model runtime).<sessionId>.hooks.jsonlis observability/debug telemetry and should not be required for normal history replay/export flows.- Full v1 schema for the persisted messages file, including failure/retry semantics and golden fixtures, is documented in
packages/core/docs/messages-contract-v1.md.
Troubleshooting
- If live updates stall, verify the desktop backend websocket is connected and
chat_eventmessages are arriving. - Tauri restarts the desktop backend if the sidecar process exits and kills it on app teardown.
- Chat sends now preflight provider credentials. If a provider that requires API-key auth is selected without a key, the UI blocks the turn with a clear error message instead of starting a hanging session.
- If a turn completes with
finishReason=errorbefore any assistant content is produced, the UI now adds an explicit error chat message so failed turns are visible in the transcript. - If package changes are not reflected, rebuild SDK packages (
bun run build:sdk). The nextcline rpc ensurecall should attach to the current build's sidecar automatically. - Provider settings updates are patch-style: only fields you edit are changed. Unset fields are preserved instead of being cleared.