Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
46 changes: 32 additions & 14 deletions app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
```
53 changes: 45 additions & 8 deletions app/src-tauri/binaries/mycelium-mcp-aarch64-apple-darwin
Original file line number Diff line number Diff line change
@@ -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: <repo>/app/src-tauri/binaries/mycelium-mcp-* (this file)
# <repo>/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" "$@"
5 changes: 3 additions & 2 deletions app/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down Expand Up @@ -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."
Expand Down
65 changes: 65 additions & 0 deletions scripts/bundle-sidecar.sh
Original file line number Diff line number Diff line change
@@ -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"