diff --git a/app/.gitignore b/app/.gitignore index aead979..f90ce55 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -3,5 +3,8 @@ src-tauri/target/ src-tauri/gen/ src-tauri/Cargo.lock.bak +# Sidecar payload — generated by scripts/bundle-sidecar.sh, ~150 MB tree +src-tauri/sidecar-payload/ + # OS .DS_Store diff --git a/app/README.md b/app/README.md index c36adf7..225db58 100644 --- a/app/README.md +++ b/app/README.md @@ -28,22 +28,35 @@ app/ ## Status -This PR ships the scaffold only. `cargo check` is green. What works: +`cargo check` and `cargo tauri build` are green. What works on a developer +Mac (the iteration-A target): - Window opens at `http://127.0.0.1:8787` (existing dashboard). -- Tray icon with Show/Hide + Quit. +- Tray icon with **Show/Hide**, **Open data dir**, **Check for updates**, + **Quit**. - Closing the window minimises to tray (does NOT quit). -- Sidecar spawn wired (`MYCELIUM_DATA_DIR`, `MYCELIUM_DASHBOARD_PORT`, - `MYCELIUM_WIRE_PORT` env vars passed through). +- Sidecar bundled into the .app — `scripts/bundle-sidecar.sh` packs + `mcp-server/dist` + `node_modules` + migrations into `sidecar-payload/`, + Tauri ships it as a Resources sub-tree. +- Sidecar inherits `SUPABASE_URL` / `SUPABASE_KEY` from the user's + launchd environment (`launchctl setenv …`) — works zero-config on the + developer machine; distribution to other users needs the + PGlite-as-primary refactor (separate ticket). +- Auto-updater wired to GitHub Releases (placeholder pubkey, see below). - Wave-3 forward-compat: `wire-cert/` directory created at first launch; capability set already lists shell-execute so the sidecar can later bind UDP for mDNS without re-signing. What does NOT work yet (intentional, follow-up tickets): -- The sidecar binary in `binaries/mycelium-mcp-aarch64-apple-darwin` is a - shell stub. Building the real bundled Node binary (Bun/pkg compile) is - sub-task 8 (CI matrix). +- **Self-contained Node runtime.** The launcher invokes `node` from + PATH. Bundling a portable Node so the .dmg works without a system + Node install is sub-task 8 (CI matrix / cross-platform). +- **PGlite as primary backend.** `mcp-server/src/index.ts` still + constructs every service against `SUPABASE_URL` / `SUPABASE_KEY`. A + refactor to honour `MYCELIUM_USE_PGLITE=1` and route all services + through the in-process adapter is the next big native-app PR + (separate ticket). - App icons are solid-purple placeholders. - The auto-updater is wired, but the `pubkey` in `tauri.conf.json` is a placeholder — see "Signing-key bootstrap" below. @@ -80,14 +93,19 @@ each platform artefact with the private key and ships ```bash # Prereqs (one-time) brew install rustup-init && rustup-init -y --default-toolchain stable --profile minimal +cargo install tauri-cli --version "^2" -# Type-check the scaffold +# Type-check cd app/src-tauri && cargo check -# Run with mcp-server already up at :8787 (`cd mcp-server && npm run dev`): -cd app/src-tauri && cargo tauri dev # requires `cargo install tauri-cli` -``` +# Bundle the sidecar payload into sidecar-payload/ (also runs as Tauri's +# beforeBuildCommand, so usually you don't need to call it directly): +bash scripts/bundle-sidecar.sh -`cargo tauri dev` will spawn the stub sidecar and the dashboard will fail -to load — until the bundling PR lands, run the mcp-server manually in a -separate terminal. +# Develop with hot-reload-ish (Tauri respawns sidecar on quit): +cd app/src-tauri && cargo tauri dev + +# Build the production .dmg: +cd app/src-tauri && cargo tauri build +# → app/src-tauri/target/release/bundle/dmg/mycelium_*.dmg +``` diff --git a/app/src-tauri/binaries/mycelium-mcp-aarch64-apple-darwin b/app/src-tauri/binaries/mycelium-mcp-aarch64-apple-darwin index 1eb551a..ee875f4 100755 --- a/app/src-tauri/binaries/mycelium-mcp-aarch64-apple-darwin +++ b/app/src-tauri/binaries/mycelium-mcp-aarch64-apple-darwin @@ -1,11 +1,48 @@ #!/bin/sh -# Stub sidecar — the real bundled Node binary is built by a follow-up -# release-pipeline ticket (#176 sub-task 8 — CI matrix). For development -# right now, run the mcp-server directly: +# Launcher for the Tauri-bundled mcp-server sidecar. # -# cd mcp-server && npm run build && node dist/index.js +# Locates the payload (compiled JS + node_modules) and runs it via the +# system-installed `node`. Two layouts are supported: # -# Tauri's `tauri dev` will spawn this stub instead and the dashboard will -# fail to load — that is expected until the bundling PR lands. -echo "[mycelium-mcp] stub sidecar — run mcp-server/dist/index.js manually for development" >&2 -exit 0 +# 1. dev mode: `cargo tauri dev` +# Layout: /app/src-tauri/binaries/mycelium-mcp-* (this file) +# /app/src-tauri/sidecar-payload/index.js +# +# 2. bundled .app: +# Layout: …/mycelium.app/Contents/MacOS/mycelium-mcp (this file) +# …/mycelium.app/Contents/Resources/sidecar-payload/index.js +# (Tauri copies bundle.resources into Contents/Resources/) +# +# IMPORTANT: requires `node` on PATH. Bundling a portable Node runtime so +# the .dmg is self-contained is follow-up sub-task 8 (CI matrix). + +set -e + +DIR="$(cd "$(dirname "$0")" && pwd)" + +if [ -d "$DIR/../sidecar-payload" ]; then + PAYLOAD="$DIR/../sidecar-payload" +elif [ -d "$DIR/../Resources/sidecar-payload" ]; then + PAYLOAD="$DIR/../Resources/sidecar-payload" +else + echo "mycelium-mcp launcher: sidecar-payload not found near $DIR" >&2 + echo " Run scripts/bundle-sidecar.sh from the repo root, or rebuild the .app." >&2 + exit 2 +fi + +if ! command -v node >/dev/null 2>&1; then + echo "mycelium-mcp launcher: 'node' not found on PATH." >&2 + echo " Install Node 20+ (brew install node) or run a self-contained build" >&2 + echo " (sub-task 8 — portable Node runtime bundling)." >&2 + exit 3 +fi + +# Iteration A: mcp-server still boots against Supabase by default +# (see #176 sub-task follow-up: PGlite-as-primary-backend refactor). +# SUPABASE_URL / SUPABASE_KEY come from the user-launchd environment +# (`launchctl setenv …`), which any GUI-launched app inherits — so the +# Tauri-bundled .app gets them automatically on the developer's machine. +# Distribution to other users will need PGlite-as-primary or an in-app +# onboarding flow that asks for the keys (separate ticket). + +exec node "$PAYLOAD/index.js" "$@" diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json index 03df0a6..2b06aa8 100644 --- a/app/src-tauri/tauri.conf.json +++ b/app/src-tauri/tauri.conf.json @@ -4,8 +4,8 @@ "version": "0.1.0", "identifier": "io.mycelium.app", "build": { - "beforeDevCommand": "", - "beforeBuildCommand": "", + "beforeDevCommand": "bash -c 'bash \"$(git rev-parse --show-toplevel)/scripts/bundle-sidecar.sh\"'", + "beforeBuildCommand": "bash -c 'bash \"$(git rev-parse --show-toplevel)/scripts/bundle-sidecar.sh\"'", "devUrl": "http://127.0.0.1:8787", "frontendDist": "../dist" }, @@ -47,6 +47,7 @@ "icons/icon.png" ], "externalBin": ["binaries/mycelium-mcp"], + "resources": ["sidecar-payload"], "category": "Productivity", "shortDescription": "Local-first cognitive memory for any LLM", "longDescription": "mycelium is a sovereign cognitive layer for LLM agents — persistent memory, neurochemistry, evolution, federation. Runs entirely on your machine, no cloud." diff --git a/scripts/bundle-sidecar.sh b/scripts/bundle-sidecar.sh new file mode 100755 index 0000000..b021927 --- /dev/null +++ b/scripts/bundle-sidecar.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Bundles the mcp-server (compiled JS + node_modules) into the Tauri app's +# sidecar-payload directory so `cargo tauri build` packs it as a Resources +# sub-tree. Called by `tauri.conf.json`'s beforeBuildCommand and runnable +# standalone for development. +# +# Output layout (under app/src-tauri/sidecar-payload/): +# index.js, *.map ← compiled mcp-server entry +# node_modules/ ← runtime deps (PGlite WASM, llama.cpp .node, …) +# package.json ← so node resolves the right "main"/"type" +# ...rest of dist/ +# +# IMPORTANT (current iteration, sub-task 3 ticket A): +# This requires a system-Node install on the target machine. The launcher +# script in app/src-tauri/binaries/mycelium-mcp-* invokes `node` from PATH. +# Bundling a portable Node runtime so the .dmg is self-contained is +# follow-up sub-task 8 (CI matrix / cross-platform compile). +# +# Usage: scripts/bundle-sidecar.sh +# Idempotent — safe to re-run. + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +MCP_SERVER="$REPO_ROOT/mcp-server" +PAYLOAD="$REPO_ROOT/app/src-tauri/sidecar-payload" + +echo "[bundle-sidecar] repo=$REPO_ROOT" +echo "[bundle-sidecar] payload=$PAYLOAD" + +# 1. Ensure node_modules are present (needed for runtime resolve AND for +# `tsc` in step 2 — must run before the build, not after). +if [ ! -d "$MCP_SERVER/node_modules" ]; then + echo "[bundle-sidecar] node_modules missing — running npm ci" + (cd "$MCP_SERVER" && npm ci) +fi + +# 2. Ensure mcp-server is built. +if [ ! -f "$MCP_SERVER/dist/index.js" ] || [ "$MCP_SERVER/src/index.ts" -nt "$MCP_SERVER/dist/index.js" ]; then + echo "[bundle-sidecar] mcp-server build needed — running npm run build" + (cd "$MCP_SERVER" && npm run build) +else + echo "[bundle-sidecar] mcp-server build is fresh" +fi + +# 3. Wipe and re-create payload (cheaper than rsync diff for ~150 MB tree). +rm -rf "$PAYLOAD" +mkdir -p "$PAYLOAD" + +# 4. Copy compiled dist (everything: index.js, services/, tools/, agents/, …). +cp -R "$MCP_SERVER/dist/." "$PAYLOAD/" + +# 5. Copy node_modules verbatim. PGlite ships WASM + .data, node-llama-cpp +# ships native .node bindings — both need their on-disk layout intact. +cp -R "$MCP_SERVER/node_modules" "$PAYLOAD/node_modules" + +# 6. package.json so Node honours "type": "module" / "exports". +cp "$MCP_SERVER/package.json" "$PAYLOAD/package.json" + +# 7. Migrations dir is read at boot by the migration runner — pack a copy. +mkdir -p "$PAYLOAD/supabase" +cp -R "$REPO_ROOT/supabase/migrations" "$PAYLOAD/supabase/migrations" + +SIZE=$(du -sh "$PAYLOAD" | cut -f1) +echo "[bundle-sidecar] payload built — $SIZE"