2025-12-09 17:51:05 +00:00
---
2026-01-30 03:15:10 +01:00
summary: "OpenClaw macOS companion app (menu bar + gateway broker)"
2025-12-09 17:51:05 +00:00
read_when:
- Implementing macOS app features
2025-12-20 02:08:04 +00:00
- Changing gateway lifecycle or node bridging on macOS
2026-01-31 16:04:03 -05:00
title: "macOS App"
2025-12-09 17:51:05 +00:00
---
2026-01-31 21:13:13 +09:00
2026-01-30 03:15:10 +01:00
# OpenClaw macOS Companion (menu bar + gateway broker)
2025-12-05 23:13:53 +01:00
2026-01-30 03:15:10 +01:00
The macOS app is the **menu‑ bar companion** for OpenClaw. It owns permissions,
2026-01-18 16:29:33 +00:00
manages/attaches to the Gateway locally (launchd or manual), and exposes macOS
capabilities to the agent as a node.
2025-12-05 23:13:53 +01:00
2026-01-08 23:06:56 +01:00
## What it does
2026-01-07 21:37:05 +01:00
2026-01-08 23:06:56 +01:00
- Shows native notifications and status in the menu bar.
- Owns TCC prompts (Notifications, Accessibility, Screen Recording, Microphone,
Speech Recognition, Automation/AppleScript).
- Runs or connects to the Gateway (local or remote).
- Exposes macOS‑ only tools (Canvas, Camera, Screen Recording, `system.run` ).
2026-01-18 22:49:58 +00:00
- Starts the local node host service in **remote** mode (launchd), and stops it in **local** mode.
2026-01-08 23:06:56 +01:00
- Optionally hosts **PeekabooBridge** for UI automation.
2026-01-30 03:15:10 +01:00
- Installs the global CLI (`openclaw` ) via npm/pnpm on request (bun not recommended for the Gateway runtime).
2026-01-08 23:06:56 +01:00
## Local vs remote mode
2026-01-18 16:29:33 +00:00
- **Local** (default): the app attaches to a running local Gateway if present;
2026-01-30 03:15:10 +01:00
otherwise it enables the launchd service via `openclaw gateway install` .
2026-01-08 23:06:56 +01:00
- **Remote**: the app connects to a Gateway over SSH/Tailscale and never starts
a local process.
2026-01-18 22:49:58 +00:00
The app starts the local **node host service** so the remote Gateway can reach this Mac.
2026-01-31 21:13:13 +09:00
The app does not spawn the Gateway as a child process.
2026-01-08 23:06:56 +01:00
## Launchd control
2026-01-27 14:46:27 -06:00
The app manages a per‑ user LaunchAgent labeled `bot.molt.gateway`
2026-01-30 03:15:10 +01:00
(or `bot.molt.<profile>` when using `--profile` /`OPENCLAW_PROFILE` ; legacy `com.openclaw.*` still unloads).
2026-01-07 21:37:05 +01:00
```bash
2026-01-27 14:46:27 -06:00
launchctl kickstart -k gui/$UID/bot.molt.gateway
launchctl bootout gui/$UID/bot.molt.gateway
2026-01-07 21:37:05 +01:00
```
2026-01-27 14:46:27 -06:00
Replace the label with `bot.molt.<profile>` when running a named profile.
2026-01-11 02:17:10 +01:00
2026-01-08 23:06:56 +01:00
If the LaunchAgent isn’ t installed, enable it from the app or run
2026-01-30 03:15:10 +01:00
`openclaw gateway install` .
2025-12-20 02:08:04 +00:00
2026-01-08 23:06:56 +01:00
## Node capabilities (mac)
2025-12-20 02:08:04 +00:00
2026-01-08 23:06:56 +01:00
The macOS app presents itself as a node. Common commands:
2025-12-20 02:08:04 +00:00
2026-01-08 23:06:56 +01:00
- Canvas: `canvas.present` , `canvas.navigate` , `canvas.eval` , `canvas.snapshot` , `canvas.a2ui.*`
- Camera: `camera.snap` , `camera.clip`
- Screen: `screen.record`
- System: `system.run` , `system.notify`
2025-12-05 23:13:53 +01:00
2026-01-08 23:06:56 +01:00
The node reports a `permissions` map so agents can decide what’ s allowed.
2025-12-12 23:22:40 +00:00
2026-01-18 22:49:58 +00:00
Node service + app IPC:
2026-01-31 21:13:13 +09:00
2026-01-21 16:48:31 +00:00
- When the headless node host service is running (remote mode), it connects to the Gateway WS as a node.
2026-01-18 22:49:58 +00:00
- `system.run` executes in the macOS app (UI/TCC context) over a local Unix socket; prompts + output stay in-app.
2026-01-18 16:20:48 +00:00
Diagram (SCI):
2026-01-31 21:13:13 +09:00
2026-01-18 16:20:48 +00:00
```
2026-01-22 23:07:58 +00:00
Gateway -> Node Service (WS)
2026-01-18 16:20:48 +00:00
| IPC (UDS + token + HMAC + TTL)
v
Mac App (UI + TCC + system.run)
```
2026-01-18 07:44:28 +00:00
## Exec approvals (system.run)
2026-01-16 03:45:03 +00:00
2026-01-18 07:44:28 +00:00
`system.run` is controlled by **Exec approvals** in the macOS app (Settings → Exec approvals).
Security + ask + allowlist are stored locally on the Mac in:
2026-01-16 03:45:03 +00:00
```
2026-01-30 03:15:10 +01:00
~/.openclaw/exec-approvals.json
2026-01-16 03:45:03 +00:00
```
2026-01-18 07:44:28 +00:00
Example:
2026-01-16 03:45:03 +00:00
```json
{
2026-01-18 07:44:28 +00:00
"version": 1,
"defaults": {
"security": "deny",
"ask": "on-miss"
},
"agents": {
"main": {
"security": "allowlist",
"ask": "on-miss",
2026-01-31 21:13:13 +09:00
"allowlist": [{ "pattern": "/opt/homebrew/bin/rg" }]
2026-01-18 07:44:28 +00:00
}
2026-01-16 03:45:03 +00:00
}
}
```
Notes:
2026-01-31 21:13:13 +09:00
2026-01-22 23:07:58 +00:00
- `allowlist` entries are glob patterns for resolved binary paths.
2026-02-21 19:16:15 +01:00
- Raw shell command text that contains shell control or expansion syntax (`&&` , `||` , `;` , `|` , `` ` ``, ` $`, ` < `, ` >`, ` (`, ` )`) is treated as an allowlist miss and requires explicit approval (or allowlisting the shell binary).
2026-01-16 03:45:03 +00:00
- Choosing “Always Allow” in the prompt adds that command to the allowlist.
2026-02-22 12:46:55 +01:00
- `system.run` environment overrides are filtered (drops `PATH` , `DYLD_*` , `LD_*` , `NODE_OPTIONS` , `PYTHON*` , `PERL*` , `RUBYOPT` , `SHELLOPTS` , `PS4` ) and then merged with the app’ s environment.
- For shell wrappers (`bash|sh|zsh ... -c/-lc` ), request-scoped environment overrides are reduced to a small explicit allowlist (`TERM` , `LANG` , `LC_*` , `COLORTERM` , `NO_COLOR` , `FORCE_COLOR` ).
2026-02-22 22:54:21 +01:00
- For allow-always decisions in allowlist mode, known dispatch wrappers (`env` , `nice` , `nohup` , `stdbuf` , `timeout` ) persist inner executable paths instead of wrapper paths. If unwrapping is not safe, no allowlist entry is persisted automatically.
2026-01-16 03:45:03 +00:00
2026-01-08 23:06:56 +01:00
## Deep links
2025-12-12 23:22:40 +00:00
2026-01-30 03:15:10 +01:00
The app registers the `openclaw://` URL scheme for local actions.
2025-12-12 23:22:40 +00:00
2026-01-30 03:15:10 +01:00
### `openclaw://agent`
2025-12-12 23:22:40 +00:00
2026-01-08 23:06:56 +01:00
Triggers a Gateway `agent` request.
2025-12-12 23:22:40 +00:00
```bash
2026-01-30 03:15:10 +01:00
open 'openclaw://agent?message=Hello%20from%20deep%20link'
2025-12-12 23:22:40 +00:00
```
Query parameters:
2026-01-31 21:13:13 +09:00
2026-01-08 23:06:56 +01:00
- `message` (required)
- `sessionKey` (optional)
- `thinking` (optional)
2026-01-13 07:15:57 +00:00
- `deliver` / `to` / `channel` (optional)
2026-01-08 23:06:56 +01:00
- `timeoutSeconds` (optional)
- `key` (optional unattended mode key)
Safety:
2026-01-31 21:13:13 +09:00
2026-01-08 23:06:56 +01:00
- Without `key` , the app prompts for confirmation.
2026-02-14 14:53:20 +01:00
- Without `key` , the app enforces a short message limit for the confirmation prompt and ignores `deliver` / `to` / `channel` .
2026-01-08 23:06:56 +01:00
- With a valid `key` , the run is unattended (intended for personal automations).
## Onboarding flow (typical)
2026-01-31 21:13:13 +09:00
1. Install and launch **OpenClaw.app** .
2. Complete the permissions checklist (TCC prompts).
3. Ensure **Local** mode is active and the Gateway is running.
4. Install the CLI if you want terminal access.
2025-12-12 23:22:40 +00:00
2025-12-05 23:13:53 +01:00
## Build & dev workflow (native)
2026-01-08 23:06:56 +01:00
- `cd apps/macos && swift build`
2026-01-30 03:15:10 +01:00
- `swift run OpenClaw` (or Xcode)
2026-01-11 10:15:37 +00:00
- Package app: `scripts/package-mac-app.sh`
2026-01-08 23:06:56 +01:00
2026-01-20 19:17:18 +00:00
## Debug gateway connectivity (macOS CLI)
2026-01-10 23:39:14 +01:00
2026-01-20 19:17:18 +00:00
Use the debug CLI to exercise the same Gateway WebSocket handshake and discovery
logic that the macOS app uses, without launching the app.
2026-01-10 23:39:14 +01:00
```bash
cd apps/macos
2026-01-30 03:15:10 +01:00
swift run openclaw-mac connect --json
swift run openclaw-mac discover --timeout 3000 --json
2026-01-10 23:39:14 +01:00
```
2026-01-20 19:17:18 +00:00
Connect options:
2026-01-31 21:13:13 +09:00
2026-01-20 19:17:18 +00:00
- `--url <ws://host:port>` : override config
- `--mode <local|remote>` : resolve from config (default: config or local)
- `--probe` : force a fresh health probe
- `--timeout <ms>` : request timeout (default: `15000` )
- `--json` : structured output for diffing
Discovery options:
2026-01-31 21:13:13 +09:00
2026-01-10 23:39:14 +01:00
- `--include-local` : include gateways that would be filtered as “local”
2026-01-20 19:17:18 +00:00
- `--timeout <ms>` : overall discovery window (default: `2000` )
2026-01-10 23:39:14 +01:00
- `--json` : structured output for diffing
2026-01-30 03:15:10 +01:00
Tip: compare against `openclaw gateway discover --json` to see whether the
2026-01-10 23:39:14 +01:00
macOS app’ s discovery pipeline (NWBrowser + tailnet DNS‑ SD fallback) differs from
the Node CLI’ s `dns-sd` based discovery.
2026-01-16 01:59:14 +00:00
## Remote connection plumbing (SSH tunnels)
2026-01-19 08:54:21 +00:00
When the macOS app runs in **Remote** mode, it opens an SSH tunnel so local UI
components can talk to a remote Gateway as if it were on localhost.
2026-01-16 01:59:14 +00:00
2026-01-19 08:54:21 +00:00
### Control tunnel (Gateway WebSocket port)
2026-01-31 21:13:13 +09:00
2026-01-16 01:59:14 +00:00
- **Purpose:** health checks, status, Web Chat, config, and other control-plane calls.
- **Local port:** the Gateway port (default `18789` ), always stable.
- **Remote port:** the same Gateway port on the remote host.
- **Behavior:** no random local port; the app reuses an existing healthy tunnel
or restarts it if needed.
- **SSH shape:** `ssh -N -L <local>:127.0.0.1:<remote>` with BatchMode +
ExitOnForwardFailure + keepalive options.
2026-01-25 00:39:50 +00:00
- **IP reporting:** the SSH tunnel uses loopback, so the gateway will see the node
IP as `127.0.0.1` . Use **Direct (ws/wss)** transport if you want the real client
IP to appear (see [macOS remote access ](/platforms/mac/remote )).
2026-01-16 01:59:14 +00:00
For setup steps, see [macOS remote access ](/platforms/mac/remote ). For protocol
2026-01-19 08:54:21 +00:00
details, see [Gateway protocol ](/gateway/protocol ).
2026-01-16 01:59:14 +00:00
2026-01-08 23:06:56 +01:00
## Related docs
- [Gateway runbook ](/gateway )
2026-01-11 10:15:37 +00:00
- [Gateway (macOS) ](/platforms/mac/bundled-gateway )
2026-01-08 23:06:56 +01:00
- [macOS permissions ](/platforms/mac/permissions )
- [Canvas ](/platforms/mac/canvas )