Skip to content

Latest commit

 

History

History
208 lines (158 loc) · 7.97 KB

File metadata and controls

208 lines (158 loc) · 7.97 KB

Desktop development setup

This page walks through standing up the OpenSail desktop client (Tauri v2 + PyInstaller-frozen FastAPI sidecar) from a clean checkout.

If you already have the backend and frontend running in cloud mode, the desktop client reuses both — there's no separate React or FastAPI codebase; only the Tauri shell and the PyInstaller packaging are unique to desktop.

Toolchain

Tool Why Install
Rust (stable) Tauri host + cargo build curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh then source $HOME/.cargo/env
cargo-tauri CLI cargo tauri dev / cargo tauri build entry points cargo install tauri-cli --version '^2.0' --locked
uv Manages the sidecar .venv and installs PyInstaller + orchestrator deps brew install uv (macOS) or curl -LsSf https://astral.sh/uv/install.sh | sh
Python 3.12 Sidecar runtime — pinned because every native dep PyInstaller bundles (asyncpg, grpcio, hiredis, PyNaCl) ships wheels for it uv will fetch this on first uv venv
pnpm React frontend corepack enable && corepack prepare pnpm@latest --activate (or npm i -g pnpm if corepack is off)
Node 20+ pnpm runtime nvm install 20 or system package

Linux-only system deps (Tauri WebKitGTK + tray-icon sysdeps):

sudo apt update
sudo apt install -y \
  libwebkit2gtk-4.1-dev libssl-dev libgtk-3-dev \
  libayatana-appindicator3-dev librsvg2-dev libsoup-3.0-dev \
  pkg-config build-essential

WSL2 works — WebKit renders under a software GL fallback (MESA/EGL warnings on startup are harmless).

One-time orchestrator install

desktop/scripts/dev.sh and desktop/scripts/build-all.sh bootstrap a .venv/ at the repo root via uv on first run and install PyInstaller + the editable orchestrator deps automatically — there's nothing manual to do for a fresh checkout. If you want to prep the venv ahead of time (or run scripts directly against the frozen Python):

uv venv --python 3.12 .venv
uv pip install pyinstaller \
  -e packages/tesslate-agent \
  -e orchestrator

The .venv/ directory is gitignored. Override the Python pin with OPENSAIL_PY_VERSION=3.x if you have a specific reason — wheels for the native deps must exist for that version.

Build the sidecar

The sidecar is a single --onefile PyInstaller executable that Tauri spawns as externalBin. dev.sh rebuilds it on demand; if you want to freeze it manually use the venv Python:

.venv/bin/python desktop/sidecar/build_sidecar.py

Output lands at desktop/src-tauri/binaries/tesslate-studio-orchestrator-<target-triple>. On first build this takes 2–3 minutes; subsequent builds reuse PyInstaller caches and finish in under a minute.

desktop/scripts/dev.sh rebuilds the sidecar on demand when it's missing or older than the entrypoint / shared spec.

Run the desktop client

Fastest path — cargo tauri dev starts the React dev server for you, spawns the sidecar, and opens the window:

cd desktop/src-tauri
cargo tauri dev

Or use the repo-level wrapper that handles sidecar freshness and toolchain checks:

./desktop/scripts/dev.sh

Do not run cargo run directly — it skips the beforeDevCommand that starts vite, so the window will load http://localhost:5173 against nothing and show a connection-refused error.

If you prefer manual control (useful for frontend-only iteration against a previously-built sidecar):

# terminal 1
pnpm --dir app dev
# terminal 2 — once vite reports "Local: http://localhost:5173"
cd desktop/src-tauri && cargo run

Verify the sidecar end-to-end

Run the frozen binary by itself to confirm the Python side boots independently of Tauri. Useful when debugging migration, seeding, or auth issues:

# clean slate
rm -rf /tmp/tesslate-studio-test
mkdir -p /tmp/tesslate-studio-test

OPENSAIL_HOME=/tmp/tesslate-studio-test \
  desktop/src-tauri/binaries/tesslate-studio-orchestrator-x86_64-unknown-linux-gnu

Expected output on a healthy boot:

  • TESSLATE_READY <port> <bearer> on the first stdout line.
  • Alembic migrations running to head.
  • Marketplace seeds landing (≈10 "Created base:" / "Created agent:" lines).
  • Application startup complete.
  • Uvicorn running on http://127.0.0.1:<port>.

Smoke-test the API with the bearer from the ready line:

# read the bearer out of stdout or grep it from your log
curl -H "Authorization: Bearer $BEARER" "http://127.0.0.1:$PORT/api/desktop/tray-state"

A {"runtimes":{...},"running_projects":[],"running_agents":[]} response confirms the sidecar-bearer loopback auth is wired correctly.

Build installers

For a local installer that isn't signed — useful for smoke-testing packaging:

./desktop/scripts/build-all.sh --release

Artifacts land under desktop/src-tauri/target/release/bundle/{deb,rpm,appimage,dmg,msi}/ depending on your host OS. Each OS's full bundle only runs on that OS (PyInstaller freezes a host-native Python, and the Tauri installer formats are OS-specific) — cross-compiling is out of scope for this repo; CI matrix builds are the intended path.

Signing + notarization (macOS Developer ID, Windows Authenticode, AppImage GPG) is run by the release pipeline, not this script.

Configuration

Environment variables the desktop sidecar reads at boot:

Variable Default Purpose
OPENSAIL_HOME per-OS default (macOS Application Support / Windows AppData / XDG) Root for projects, SQLite DB, cache, marketplace installs
DEPLOYMENT_MODE desktop (set by entrypoint) Orchestrator mode selector
DATABASE_URL sqlite+aiosqlite:///$OPENSAIL_HOME/opensail.db Override to point at Postgres for debugging
REDIS_URL "" (empty) Set to a real URL to opt back into Redis-backed pubsub + ARQ
TESSLATE_DESKTOP_BEARER minted per launch Loopback isolation token; Tauri host reads it from the handshake
TESSLATE_CLOUD_TOKEN Override cloud bearer without going through the pairing deep-link
TESSLATE_DESKTOP_HOST 127.0.0.1 Override the bind host (don't)
TESSLATE_DESKTOP_PORT ephemeral Pin the port (don't — the Tauri host reads whichever the sidecar chose)

See /orchestrator/app/config.py for the full set and /orchestrator/app/services/desktop_paths.py for the per-OS $OPENSAIL_HOME resolution.

Troubleshooting

"Could not connect to localhost" in the window — you ran cargo run directly. Use cargo tauri dev or ./desktop/scripts/dev.sh so vite actually starts.

"cannot open shared object file: libpython3.12.so" — the sidecar binary was built with --onedir (sibling _internal/ dir). Force a --onefile rebuild: rm -rf desktop/sidecar/dist desktop/src-tauri/binaries && .venv/bin/python desktop/sidecar/build_sidecar.py.

/tray-state returns 401 — the tray poll is sending the wrong bearer, or TESSLATE_DESKTOP_BEARER isn't set in the sidecar's environment. Check the sidecar stdout for the TESSLATE_READY line — the second field on that line is the bearer both the Tauri host and the backend compare against.

Alembic fails with "unknown function: now()" — you're running against a SQLite DB without the connect-time UDF. The orchestrator registers it in app/database.py automatically when DATABASE_URL starts with sqlite; if you're hitting this the sidecar is mis-wiring the URL.

PyInstaller warning about app.routers.public — expected. Those modules import at collect-time and need Settings populated; the warnings are suppressed but submodule collection skips affected packages. The orchestrator still runs because we collect_submodules each top-level package explicitly.

Related

  • /desktop/CLAUDE.md — desktop package entry point
  • /desktop/src-tauri/CLAUDE.md — Rust host internals
  • /desktop/sidecar/CLAUDE.md — PyInstaller packaging
  • /desktop/scripts/CLAUDE.md — dev + build helpers
  • /docs/desktop/runtimes.md — runtime probe + tray-state contract
  • /docs/desktop/cloud.md — pairing + CloudClient