# Mahana Devices — Consolidation Proposal
*From 4-5 loose systems + Browser + Terminal to one coherent product. 2026-04-22.*

---

## The one product name

**Mahana Devices.**

Not "daemon", not "sidecar", not "substrate", not "sessions". Those words describe what developers built. **Devices** describes what users get: a set of things that plug into the world and let an agent see, hear, speak, and act on your behalf.

Inside Mahana Devices there are four "devices" today, and users never have to know which is which:

1. **Mahana Daemon** — the always-on brain
2. **Mahana Terminal** — the hands that type
3. **Mahana Capture** — the ears + eyes
4. **Mahana Mobile** — the same product in your pocket

All four are **already built** (per INVENTORY.md). The consolidation work is: packaging, first-run, and vocabulary.

---

## Two distribution flows

### Flow A — macOS `.dmg`

```
Mahana Devices.dmg
└── Mahana Devices.app
    ├── Contents/MacOS/
    │   ├── mahana-daemon      (Node, from src/main headless build)
    │   ├── mahana-capture     (Rust binary from mahana-capture/)
    │   ├── mahana-terminal    (Swift app from mahana-terminal/)
    │   └── mahana-updater     (sparkle-rs or electron-updater stub)
    ├── Contents/Resources/
    │   ├── launchd/
    │   │   ├── cc.mahana.daemon.plist
    │   │   ├── cc.mahana.substrate.plist
    │   │   └── cc.mahana.capture.plist
    │   └── web/               (pre-built web apps for offline)
    └── Contents/Info.plist    (TCC declarations: mic, camera, screen, keyboard)
```

**Build pipeline** (already partially in place):
- `pnpm build` → compiles `src/main` via electron-vite
- `cd mahana-capture && cargo build --release --target aarch64-apple-darwin` → Rust binary
- `cd mahana-terminal && swift build -c release` → Swift app
- `electron-builder --mac` → DMG (use `buildResources` to include the three binaries)
- Notarize + staple via `notarytool` (pipeline exists per `.claude/rules/build-vs-product.md` reference to commit `837f7a2ad merge: feat/electron-dist-pipeline — DMG notarize/auto-update`)

**On install:**
1. Drag to Applications → .app runs first launch
2. .app installer script copies plists to `~/Library/LaunchAgents/` and loads them
3. TCC prompts appear **inline, in the council room** (see First-Run below) — NOT as a scary bootstrap wall
4. Auto-update via sparkle-rs (checks `releases.mahana.ai/devices.json`)

### Flow B — Linux / other `curl | sh`

```
$ curl -fsSL https://get.mahana.ai | sh
```

The `sh` script does:

1. Detect OS/arch (`uname -sm`)
2. Download signed tarball `mahana-devices-linux-amd64-v6.x.x.tar.gz` from GitHub Releases / R2 bucket
3. Verify signature (`cosign verify --key=mahana-fleet.pub`)
4. Unpack to `~/.local/share/mahana-devices/`:
   - `bin/mahana-daemon` (Node compiled bundle + `node_modules`)
   - `bin/mahana-capture` (Rust binary, Linux-native — no ScreenCaptureKit, falls back to X11/Wayland grab)
   - `bin/mahana` (shell alias → curls localhost:9878)
5. Install `systemd --user` units: `mahana-daemon.service`, `mahana-capture.service`, `mahana-substrate.service`
6. `systemctl --user enable --now mahana-daemon.service`
7. Print **one line**: `Mahana is ready. Open http://localhost:9878/council to begin.`

That's it. No config file, no onboarding form, no ANTHROPIC_API_KEY prompt. The council room is the front door.

**Self-update:** `mahana update` → re-runs installer. Daemon is versioned and idempotent.

---

## First-run experience — the council IS the onboarding

**Principle**: no config UI, no terms-of-service wall, no "select your AI provider" dropdown. The user is dropped into a **room with someone in it**, and that someone asks what they're here to do.

### macOS first-run

1. User double-clicks Mahana Devices.app after install
2. .app launches daemon + substrate + capture via launchctl kickstart
3. .app focuses the default browser at `http://localhost:9878/council?first-run=true`
4. The `/council` route (new; proposed in NEXT-PHASE) spawns a **single default-council agent** using:
   - Supabase anon auth (no login needed — agent has anon JWT, writes to its own session)
   - Grok 4.1 Fast as the opener model (cheap, fast, non-PRC)
   - First message hardcoded: **"I'm here. What do you want to do?"**
5. The council room UI is a plain grey chat (per the `Chat UI: GREY ONLY` rule in CLAUDE.md)
6. When the user says "I need my microphone to work" / "help me write an email" / "edit a video", the council agent:
   - Detects the capability needed (mic / keyboard / screen / camera)
   - Triggers the TCC grant flow **in-context** by POSTing to `/devices/request` with `{capability: 'microphone'}`
   - The daemon (via `window.ts:65-70 systemPreferences.askForMediaAccess('microphone')`) pops the system dialog
   - Council agent: "Granted. Okay — can you hear me now? [plays test audio]"

### Linux/headless first-run

Same, minus the TCC grants — Linux has no TCC. Instead:

- Capture capabilities are advertised by `mahana-capture` on register (`POST /substrate/register` at `substrate.js:~300`, adds capability strings)
- Council room says: "I can see you started me headless. What do you want me to do? (I can control your terminal, run code, help with writing. Say 'camera' if you want to wire up a webcam.)"
- Camera/mic on Linux require user to install `v4l-utils` / `pulseaudio` — council agent walks through it in chat.

### Key architectural commitment

**The council agent is the `/council` route. It is not an onboarding wizard.** It is literally a Mahana agent with the same API surface as any other agent, configured with a "first-contact" system prompt and given permission to call `/devices/request` to trigger hardware grants. The same council-room primitive is used every time the user wants a new "device" added — there is no second "settings UI."

---

## Hardware plug-in model — rooms as capability sets

A **room** is a chat thread with one or more agents in it. Each room has a **capability set** — what hardware / APIs that room's agents are allowed to touch.

| Room capability | macOS grant | Linux grant | Fallback if denied |
|---|---|---|---|
| Microphone | TCC mic prompt | PulseAudio / PipeWire | Type-only council; no voice |
| Camera | TCC camera prompt | V4L2 device access | Screenshot-only; vision from screen stream |
| Screen | TCC screen-recording | X11 `xwd` / Wayland `grim` | Manual screenshot paste |
| Keyboard (typing) | TCC accessibility (for pointer-brain) | `xdotool` / `ydotool` | HTTP-only (no physical typing) |
| Speaker (TTS) | No grant needed | ALSA | Text-only output |

**Hardware is advertised by `mahana-capture` at register-time** (`substrate.js POST /substrate/register` — file has been audited clean per project memory). A room's spawn payload includes a `capabilities: ['mic', 'screen']` array; the daemon's `/agent/spawn` refuses to allocate that room if a required capability isn't available or granted.

**TCC-denied fallback path** (macOS example):

- User denies mic in TCC prompt
- Council agent receives `{capability: 'microphone', granted: false}`
- Council reply: "No mic — that's fine. I can still do everything by typing. Would you like to try again later from Settings → Privacy → Microphone? (Here's a one-click link.)"
- Room state persists — user can re-grant later without redoing onboarding

**Linux denied fallback**: same shape, but the "grant" is installing a package or joining a group. Council walks through it.

---

## Cross-device rooms

The user on iPhone (via `mahana-unified` / Capacitor webview) sees the **same council room** the Mac is running, because:

- Rooms live as rows in Supabase `neuro_items` (or a new `rooms` table — schema TBD in NEXT-PHASE)
- Room state is subscribed via Supabase Realtime (pattern proven in `cloudConnect()`, `project_cloud_state_2026-04-14.md`)
- The phone is a display client that reads `SELECT * FROM rooms WHERE user_id = ?` and subscribes to inserts on the `messages` child table
- When a room needs a capability the phone has (camera, say), the phone's capture process advertises it and the council agent can use it from the phone instead of the Mac

**Key**: the user never says "spawn this on my phone vs my Mac". They say "look at that" and the council agent picks whichever device has the camera grant.

This is the Nordvest "plug it in, it works" model at the protocol level.

---

## What decouples from Electron-main (and the work to do it)

From DEPENDENCIES.md: only three hard-Electron files exist (video-room-window.ts, window.ts, sovereign-gate.ts). The consolidation work is:

### Work estimates (honest, no optimism bias)

| Task | File(s) | Hours | Risk |
|---|---|---|---|
| Wrap `createVideoRoomWindow()` in `bridge.mode === 'electron'` check + `open http://…` fallback for daemon | `video-room-open.ts:19` + `video-room-window.ts` | 0.5 | Low — pure branching |
| Add `scripts/smoke-daemon-mode.sh` smoke test that curls every device route in `--daemon` mode and asserts graceful degradation | new | 3 | Low — test harness only |
| Document macOS TCC grant flow + Info.plist entries for Mahana Devices.app | new `docs/devices/tcc-grants.md` | 1 | Low — already working in Electron, just needs docs |
| Extend `headless.ts` with a `--council` flag that spawns the default first-run council room | `headless.ts` + new `routes/council-routes.ts` | 4 | Medium — new route surface |
| Linux `systemd --user` unit files + curl-sh installer script | `scripts/install-linux.sh` | 3 | Medium — needs per-distro testing |
| DMG bundling the three binaries (daemon + capture + terminal-Swift + updater) | `electron-builder.yml` + `scripts/build-devices-dmg.sh` | 6 | Medium — notarize is fiddly |
| First-run UX in `/council` (grey chat UI, in-context TCC triggers, auto-capability detection) | new | 8 | Medium-High — UX design matters more than code |
| Vocabulary swap (daemon→"service" in logs, session→"room" in UI surfaces, etc.) | many | 6 | Low — search + PR |

**Total: ~31 hours** to go from "monorepo that ships as Electron monolith" → "four-binary bundle + curl-sh installer + council-room first-run". Call it a 4-day sprint with testing.

### What stays Electron

- `notch-window.ts`, `island-window.ts`, `editor-window.ts` — the **Mahana Desktop** experience for power users. This is the current Electron app, renamed. It remains a separate product (still useful as a daemon-control dashboard for the superbuilder persona).
- `mahana-preview` — keep as the v5.x reference build.

**Mahana Desktop** (Electron) and **Mahana Devices** (daemon + capture + terminal + mobile) ship as **two products from the same monorepo.** Desktop depends on Devices; Devices does not depend on Desktop.

---

## What this buys

- **Stephen-Hawking persona**: lands in `/council`, council asks what they want, grants flow inline, no terminal, no keyboard required if mic is granted.
- **Tony-Stark persona**: launches Mahana Desktop (Electron), has notch + island + editor + every shell, but the daemon underneath is the same one a mic-only user is hitting.
- **Shipping discipline**: the four-binary split makes the per-product three-way-drift auditable. DMG build = one pipeline. Capture = Rust, cargo. Terminal = Swift, swift build. Daemon = electron-vite headless build.
- **No re-auth** across devices: Supabase session flows through Capacitor webview → same agent sees phone and Mac.

---

## What this doesn't solve (call out honestly)

- **Windows**: the plan above is macOS + Linux. Windows Scheduled Task pipeline exists per `scripts/install-daemon-windows.ps1` but `mahana-capture` Rust has no Windows target yet, and `mahana-terminal` Swift is macOS-only. A Windows consolidation is a separate sprint.
- **The notch**: 762L of `notch-window.ts` is macOS-Electron-specific. A future "notch for daemon mode" would require a tray-icon native app (sparkle-rs + macOS NSStatusItem). Out of scope for this pass.
- **`mahana-preview` reconciliation**: the `~/mahana-ecosystem/mahana-preview/` repo carries an older v5-era Electron app. It should either be archived or renamed to `mahana-desktop-v5-reference` to avoid confusion with the v6 Devices build.
- **Voice-plugin global site-wide mount hazard** (from the task brief): not this document's scope, but flagged in NEXT-PHASE § failure patterns.
