# Installing Maxy Code on macOS

End-to-end install for a fresh macOS account on Apple Silicon (M-series). Every command is copy-pasteable and uses auto-yes flags so nothing prompts interactively.

The doc is brand-aware. Examples use the default brand `maxy-code`; substitute `realagent-code` (or any other brand under `maxy-code/brands/`) wherever you want a parallel install. Each brand is fully isolated — its own persist directory, its own LaunchAgent, its own admin UI port, its own `CLAUDE_CONFIG_DIR`.

> Pi install: see [pi.md](pi.md) for the Raspberry Pi flow.
> Other hosts and engineering detail: see [index.md](index.md) and [../deployment.md](../deployment.md).

## Requirements

- macOS 14 (Sonoma) or newer. The installer refuses to run on 13 and below; you will see `[create-maxy] platform=darwin macos=… — refusing: macOS 14+ required`.
- Apple Silicon (M1/M2/M3/M4). Intel Macs are not part of the supported matrix — the installer pins `node@22` from Homebrew's Apple Silicon cellar (`/opt/homebrew`) and other paths assume that prefix.
- Admin (sudo) account. The installer asks for your password once when it sets the system hostname via `scutil`; everything else runs unprivileged.
- A working internet connection — Homebrew, npm, and Cloudflare endpoints are all reached during install.

## 1. Install Node 22 via Homebrew

```bash
# Homebrew (skip if already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Node 22 — pinned formula
brew install node@22
brew link --overwrite --force node@22

# Verify (must be 22.6 or newer)
node --version
```

Node from the system PATH must resolve to `/opt/homebrew/opt/node@22/bin/node`. If `which node` points anywhere else, fix the PATH before continuing — the installer reads node from `PATH` and a 20.x binary will trip the engines check.

## 2. Run the installer

The default brand is `maxy-code`. Run from any directory; the installer creates and writes everything under `$HOME/.maxy-code`.

```bash
npx -y @rubytech/create-maxy-code@latest
```

That command:
- creates the persist directory `$HOME/.maxy-code/` (logs, config, plugin state, the `.claude/` config tree, browser profile);
- exports `CLAUDE_CONFIG_DIR=$HOME/.maxy-code/.claude` for every Claude Code invocation it spawns (default `~/.claude` is the wrong tree on a multi-brand machine);
- builds the platform payload bundled in the npm tarball;
- writes a launchd LaunchAgent at `~/Library/LaunchAgents/com.rubytech.maxy-code.plist` and loads it with `launchctl bootstrap gui/$UID`;
- prints the admin UI URL when the supervisor reports the server is listening.

The full install log lands at `$HOME/.maxy-code/logs/create-maxy-<timestamp>.log`. Every phase line is prefixed `[create-maxy] phase=… brand=… platform=darwin` — that's the canonical signal if you want to attach an install log to a support request.

### Optional: `--hostname`

By default the installer leaves your existing macOS hostname alone and serves the admin UI at `http://<your-existing-LocalHostName>.local:<port>`. If you want a dedicated name on the LAN, pass `--hostname`:

```bash
npx -y @rubytech/create-maxy-code@latest --hostname maxy
```

That triggers three `sudo scutil --set` calls — `HostName`, `LocalHostName`, `ComputerName` — and the admin UI then resolves at `http://maxy.local:<port>` from any device on the same Bonjour/mDNS network. The flag is the only path that mutates system hostname state; omitting it preserves whatever you had.

### Installing a second brand

To run, for example, `realagent-code` alongside the default install, repeat step 2 with that brand's package:

```bash
npx -y @rubytech/create-realagent-code@latest
```

The persist directory becomes `$HOME/.realagent-code`, the LaunchAgent becomes `com.rubytech.realagent-code`, and the admin URL switches to `http://realagent-code.local:<port>`. Every brand has its own isolated tree — there is no shared state, and `CLAUDE_CONFIG_DIR` is always `$HOME/.<brand>/.claude` for that brand, never the default `~/.claude`.

## 3. Confirm the LaunchAgent is up

```bash
launchctl print gui/$(id -u)/com.rubytech.maxy-code | head -20
```

You should see `state = running`. If the state is `not running` or the command fails, inspect the plist and the supervised stdout/stderr files referenced inside it:

```bash
cat ~/Library/LaunchAgents/com.rubytech.maxy-code.plist
```

The plist points at the wrapper script the installer wrote and at log files under `$HOME/.maxy-code/logs/`. `launchctl bootstrap`'s exit code is recorded in the install log as `[create-maxy] launchd-plist=… loaded=true|false`.

## 4. Open the admin UI

The install log's final block prints the URL. For the default brand on a default install:

```
================================================================

  Open in your browser: http://<hostname>.local:<port>

================================================================
```

Open that URL in any browser. The admin UI loads, the operator account is provisioned on first visit, and the platform's chat surface is ready.

## 5. Verify reboot persistence

Reboot the Mac. After login, the LaunchAgent reattaches automatically because the plist sets `RunAtLoad=true` and `KeepAlive=true`. Re-open the admin URL — it should respond within a few seconds without you doing anything.

If the admin UI does not respond after reboot:
- Re-check `launchctl print gui/$(id -u)/com.rubytech.maxy-code` for `state = running`.
- Tail the supervised log under `$HOME/.maxy-code/logs/`.
- The wrapper script reloads `$HOME/.maxy-code/.env` before exec'ing the platform binary; if you edited that file by hand and broke a quoted value, the supervisor will respawn on a fast loop and the URL never becomes reachable.

## Uninstall

```bash
npx @rubytech/create-maxy-code --uninstall
```

This unloads the LaunchAgent (`launchctl bootout gui/$UID/com.rubytech.maxy-code`), removes the plist, removes the Homebrew formula state the installer added, and removes the persist directory `$HOME/.maxy-code/`. After it completes the brand leaves no trace.

To uninstall a non-default brand, point at its package — for example:

```bash
npx @rubytech/create-realagent-code --uninstall
```

## What this install does not do

macOS is the lightweight surface. Compared with the Pi install, the macOS path deliberately skips:

- **No cgroup / resource decoupling.** Pi installs decouple Claude Code's cgroup from systemd's session scope so a closed VNC viewer cannot reap the long-running agent. macOS uses launchd, which is already per-user and does not have the same cleanup pathology, so the work is unnecessary.
- **No VNC.** The admin UI is the surface. You drive it from a browser on the same machine or any device on the LAN; there is no display server to bootstrap.
- **No `cloudflared` tunnel by default.** Pi installs ship a tunnel because the device is typically headless and on a residential network. On a Mac the LAN URL is usually enough; if you want a public URL, install `cloudflared` separately and run `cloudflared tunnel login` from the terminal. Cloudflare API tokens are never used — only the CLI's interactive `tunnel login` flow.

## Smoke checklist

The full operator-side fresh-Mac smoke is tracked separately (see `.tasks/339-macos-installer-smoke-task-297.md`). The headline pass criteria:

1. Install on a clean account with no prior Maxy footprint completes and prints an admin URL.
2. The admin UI opens at that URL and the chat surface is interactive.
3. Reboot — the URL is reachable again after login without any manual action.
4. Run `--hostname <name>` on a second install path; the URL switches to `<name>.local`.
5. Uninstall removes the LaunchAgent, the plist, and the persist directory.

If any step fails, attach `$HOME/.<brand>/logs/create-maxy-<timestamp>.log` to the report.
