CheetahClaws Desktop (P1 MVP)
June 23, 2026 · View on GitHub
A thin native-window shell around CheetahClaws' existing, production-ready web
UI. It launches cheetahclaws --web --no-auth as a localhost-only sidecar
and points a webview at its /chat page — so the browser UI (WebSocket
streaming chat, xterm terminal, permission approval, themes) becomes the
desktop app, with nothing reimplemented.
┌─ Desktop shell (Electron) ─────────────────────────────┐
│ loading.html → spawn `cheetahclaws --web --no-auth` │
│ (127.0.0.1, auto-picked free port) │
│ ↓ parse the printed "Chat UI: …/chat" │
│ BrowserWindow.loadURL(http://127.0.0.1:<port>/chat) │
└─────────────────────────────────────────────────────────┘
The server binds to 127.0.0.1 only and runs as you, with your own API key —
no network exposure, no multi-tenancy. This is the local, bring-your-own-key
model; the hard SaaS problems (sandboxing, billing) are deliberately out of
scope for P1.
Status
- ✅ Runs end-to-end on macOS — the Electron shell launches the server, discovers its port, and loads the chat UI in a native window.
- ✅ Sidecar integration verified —
npm run smokelaunches the real server, discovers its port, and confirms/chat,/,/healthall serve. - ✅ Self-contained server verified —
scripts/build-server.shfreezes the server with PyInstaller into a ~95 MB standalone binary that serves the full web UI with no Python installed; the sidecar drives it identically. So a packaged installer needs neither Node nor Python on the user's machine. - ⛏️ Remaining for a shippable installer: code signing / notarization (see open items) — everything else is wired and tested.
Prerequisites
- Node.js 18+ and npm.
- The
cheetahclawsCLI on your PATH, with the web extra:
(Point at a different binary withpip install 'cheetahclaws[web]' cheetahclaws --version # should print a versionCHEETAHCLAWS_BIN=/path/to/cheetahclaws.)
Run
cd desktop
npm install # pulls Electron (~150 MB first time)
npm start # opens the window
Verify just the sidecar wiring (no GUI, no Electron needed):
npm run smoke # DEBUG=1 npm run smoke to echo server logs
Troubleshooting npm install / npm start
The Electron npm package only downloads its ~90 MB binary in a postinstall
script. On hardened-npm setups (allow-scripts), very new Node versions, or
slow/blocked networks, that step misbehaves. Symptoms and fixes:
Electron failed to install correctly— the binary didn't download orpath.txtwasn't written. First make sure the binary is present, using a China-friendly mirror:rm -rf node_modules/electron ~/Library/Caches/electron # (Linux: ~/.cache/electron) ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ \ npm install electron --foreground-scripts npm run fix-electron # writes the correct path.txt npm startspawn .../dist/dist/Electron.app/... ENOENT(doubledist) —path.txthas a straydist/prefix.npm run fix-electronrewrites it correctly (Electron expects the path relative todist/).- Extraction left a tiny/partial
dist/— download the full zip yourself from https://npmmirror.com/mirrors/electron/ (match your platform + arch, e.g.electron-vXX-darwin-arm64.zip), unzip intonode_modules/electron/dist/, thennpm run fix-electron. - macOS "Electron is damaged / cannot be verified" — clear the quarantine
flag on the dev binary:
xattr -dr com.apple.quarantine node_modules/electron/dist/Electron.app. - Node version — Electron 31 is happiest on an LTS Node (18/20). Bleeding-edge
Node has been seen to skip writing
path.txt;npm run fix-electronpapers over that, or usenvm use 20.
None of this affects end users — a packaged .dmg/.exe/.AppImage
(below) bundles Electron, so installers never hit the postinstall path.
Build a self-contained installer (.dmg / .exe / .AppImage)
This produces an installer that bundles both Electron and a PyInstaller-frozen copy of the server, so the end user needs neither Node nor Python — they just double-click.
cd desktop
bash scripts/build-app.sh
# → out/ contains the .dmg (macOS) / .exe (Windows) / .AppImage (Linux)
What it does, in order:
scripts/build-server.sh— freezescheetahclaws --webwith PyInstaller in a clean virtualenv (only the core +[web]deps, so the bundle is ~95 MB, not GBs — the trading/voice/research stacks aren't installed and the modular loaders skip them gracefully). Output:server/dist/cheetahclaws-server/.electron-builder(npm run dist) — packages the Electron shell and copies the frozen server into the app'sResources/server/(seebuild.extraResources). At runtimesrc/main.jsspawnsResources/server/cheetahclaws-serverwhen packaged, falling back to the globalcheetahclawsCLI in dev.
PyInstaller does not cross-compile — run
build-app.shon each target OS: a Mac to get the.dmg, Windows to get the.exe. (The whole server + sidecar pipeline is verified on Linux; only the per-OS packaging differs.)
Known open items (next steps)
- Code signing + notarization. macOS Gatekeeper / Windows SmartScreen will
block an unsigned build (users get "damaged / unverified developer"). Needs
an Apple Developer cert (
CSC_LINK/CSC_KEY_PASSWORD+notarizein electron-builder) and a Windows code-signing cert. Paid prerequisite for public distribution — the only thing betweenbuild-app.shand a shippable installer. - First-run onboarding. A GUI provider/API-key step (the CLI's setup wizard, as a screen) — the main lever for reaching non-CLI users.
- Auto-update. Wire electron-updater (or Tauri's updater) so users don't stay pinned to old builds.
- Slim the server further (optional). 95 MB is fine; if you want smaller,
strip/UPX the binary or prune more stdlib via the spec'sexcludes.
Why Electron here (and Tauri later)
This MVP is Electron because it only needs Node, which let the sidecar
integration be actually run and verified on the dev box (no Rust toolchain
was available). For the shipped product, Tauri is the better target — a
~5 MB Rust shell vs Electron's ~150 MB — and the sidecar logic in
src/sidecar.js is intentionally framework-agnostic so it
ports across with only the window code rewritten.