{
  "title": "Terminal B: export the gateway token and connect through the tunnel",
  "content": "export OPENCLAW_GATEWAY_TOKEN=\"<gateway-token>\"\nopenclaw node run --host 127.0.0.1 --port 18790 --display-name \"Build Node\"\nbash theme={null}\nopenclaw node install --host <gateway-host> --port 18789 --display-name \"Build Node\"\nopenclaw node restart\nbash theme={null}\nopenclaw nodes pending\nopenclaw nodes approve <requestId>\nopenclaw nodes list\nbash theme={null}\nopenclaw approvals allowlist add --node <id|name|ip> \"/usr/bin/uname\"\nopenclaw approvals allowlist add --node <id|name|ip> \"/usr/bin/sw_vers\"\nbash theme={null}\nopenclaw config set tools.exec.host node\nopenclaw config set tools.exec.security allowlist\nopenclaw config set tools.exec.node \"<id-or-name>\"\n\n/exec host=node security=allowlist node=<id-or-name>\nbash theme={null}\nopenclaw nodes invoke --node <idOrNameOrIp> --command canvas.eval --params '{\"javaScript\":\"location.href\"}'\nbash theme={null}\nopenclaw nodes canvas snapshot --node <idOrNameOrIp> --format png\nopenclaw nodes canvas snapshot --node <idOrNameOrIp> --format jpg --max-width 1200 --quality 0.9\nbash theme={null}\nopenclaw nodes canvas present --node <idOrNameOrIp> --target https://example.com\nopenclaw nodes canvas hide --node <idOrNameOrIp>\nopenclaw nodes canvas navigate https://example.com --node <idOrNameOrIp>\nopenclaw nodes canvas eval --node <idOrNameOrIp> --js \"document.title\"\nbash theme={null}\nopenclaw nodes canvas a2ui push --node <idOrNameOrIp> --text \"Hello\"\nopenclaw nodes canvas a2ui push --node <idOrNameOrIp> --jsonl ./payload.jsonl\nopenclaw nodes canvas a2ui reset --node <idOrNameOrIp>\nbash theme={null}\nopenclaw nodes camera list --node <idOrNameOrIp>\nopenclaw nodes camera snap --node <idOrNameOrIp>            # default: both facings (2 MEDIA lines)\nopenclaw nodes camera snap --node <idOrNameOrIp> --facing front\nbash theme={null}\nopenclaw nodes camera clip --node <idOrNameOrIp> --duration 10s\nopenclaw nodes camera clip --node <idOrNameOrIp> --duration 3000 --no-audio\nbash theme={null}\nopenclaw nodes screen record --node <idOrNameOrIp> --duration 10s --fps 10\nopenclaw nodes screen record --node <idOrNameOrIp> --duration 10s --fps 10 --no-audio\nbash theme={null}\nopenclaw nodes location get --node <idOrNameOrIp>\nopenclaw nodes location get --node <idOrNameOrIp> --accuracy precise --max-age 15000 --location-timeout 10000\nbash theme={null}\nopenclaw nodes invoke --node <idOrNameOrIp> --command sms.send --params '{\"to\":\"+15555550123\",\"message\":\"Hello from OpenClaw\"}'\nbash theme={null}\nopenclaw nodes run --node <idOrNameOrIp> -- echo \"Hello from mac node\"\nopenclaw nodes notify --node <idOrNameOrIp> --title \"Ping\" --body \"Gateway ready\"\nbash theme={null}\nopenclaw config set tools.exec.node \"node-id-or-name\"\nbash theme={null}\nopenclaw config get agents.list\nopenclaw config set agents.list[0].tools.exec.node \"node-id-or-name\"\nbash theme={null}\nopenclaw config unset tools.exec.node\nopenclaw config unset agents.list[0].tools.exec.node\nbash theme={null}\nopenclaw node run --host <gateway-host> --port 18789\n```\n\n* Pairing is still required (the Gateway will show a node approval prompt).\n* The node host stores its node id, token, display name, and gateway connection info in `~/.openclaw/node.json`.\n* Exec approvals are enforced locally via `~/.openclaw/exec-approvals.json`\n  (see [Exec approvals](/tools/exec-approvals)).\n* On macOS, the headless node host prefers the companion app exec host when reachable and falls\n  back to local execution if the app is unavailable. Set `OPENCLAW_NODE_EXEC_HOST=app` to require\n  the app, or `OPENCLAW_NODE_EXEC_FALLBACK=0` to disable fallback.\n* Add `--tls` / `--tls-fingerprint` when the Gateway WS uses TLS.\n\n* The macOS menubar app connects to the Gateway WS server as a node (so `openclaw nodes …` works against this Mac).\n* In remote mode, the app opens an SSH tunnel for the Gateway port and connects to `localhost`.",
  "code_samples": [
    {
      "code": "Notes:\n\n* The token is `gateway.auth.token` from the gateway config (`~/.openclaw/openclaw.json` on the gateway host).\n* `openclaw node run` reads `OPENCLAW_GATEWAY_TOKEN` for auth.\n\n### Start a node host (service)",
      "language": "unknown"
    },
    {
      "code": "### Pair + name\n\nOn the gateway host:",
      "language": "unknown"
    },
    {
      "code": "Naming options:\n\n* `--display-name` on `openclaw node run` / `openclaw node install` (persists in `~/.openclaw/node.json` on the node).\n* `openclaw nodes rename --node <id|name|ip> --name \"Build Node\"` (gateway override).\n\n### Allowlist the commands\n\nExec approvals are **per node host**. Add allowlist entries from the gateway:",
      "language": "unknown"
    },
    {
      "code": "Approvals live on the node host at `~/.openclaw/exec-approvals.json`.\n\n### Point exec at the node\n\nConfigure defaults (gateway config):",
      "language": "unknown"
    },
    {
      "code": "Or per session:",
      "language": "unknown"
    },
    {
      "code": "Once set, any `exec` call with `host=node` runs on the node host (subject to the\nnode allowlist/approvals).\n\nRelated:\n\n* [Node host CLI](/cli/node)\n* [Exec tool](/tools/exec)\n* [Exec approvals](/tools/exec-approvals)\n\n## Invoking commands\n\nLow-level (raw RPC):",
      "language": "unknown"
    },
    {
      "code": "Higher-level helpers exist for the common “give the agent a MEDIA attachment” workflows.\n\n## Screenshots (canvas snapshots)\n\nIf the node is showing the Canvas (WebView), `canvas.snapshot` returns `{ format, base64 }`.\n\nCLI helper (writes to a temp file and prints `MEDIA:<path>`):",
      "language": "unknown"
    },
    {
      "code": "### Canvas controls",
      "language": "unknown"
    },
    {
      "code": "Notes:\n\n* `canvas present` accepts URLs or local file paths (`--target`), plus optional `--x/--y/--width/--height` for positioning.\n* `canvas eval` accepts inline JS (`--js`) or a positional arg.\n\n### A2UI (Canvas)",
      "language": "unknown"
    },
    {
      "code": "Notes:\n\n* Only A2UI v0.8 JSONL is supported (v0.9/createSurface is rejected).\n\n## Photos + videos (node camera)\n\nPhotos (`jpg`):",
      "language": "unknown"
    },
    {
      "code": "Video clips (`mp4`):",
      "language": "unknown"
    },
    {
      "code": "Notes:\n\n* The node must be **foregrounded** for `canvas.*` and `camera.*` (background calls return `NODE_BACKGROUND_UNAVAILABLE`).\n* Clip duration is clamped (currently `<= 60s`) to avoid oversized base64 payloads.\n* Android will prompt for `CAMERA`/`RECORD_AUDIO` permissions when possible; denied permissions fail with `*_PERMISSION_REQUIRED`.\n\n## Screen recordings (nodes)\n\nNodes expose `screen.record` (mp4). Example:",
      "language": "unknown"
    },
    {
      "code": "Notes:\n\n* `screen.record` requires the node app to be foregrounded.\n* Android will show the system screen-capture prompt before recording.\n* Screen recordings are clamped to `<= 60s`.\n* `--no-audio` disables microphone capture (supported on iOS/Android; macOS uses system capture audio).\n* Use `--screen <index>` to select a display when multiple screens are available.\n\n## Location (nodes)\n\nNodes expose `location.get` when Location is enabled in settings.\n\nCLI helper:",
      "language": "unknown"
    },
    {
      "code": "Notes:\n\n* Location is **off by default**.\n* “Always” requires system permission; background fetch is best-effort.\n* The response includes lat/lon, accuracy (meters), and timestamp.\n\n## SMS (Android nodes)\n\nAndroid nodes can expose `sms.send` when the user grants **SMS** permission and the device supports telephony.\n\nLow-level invoke:",
      "language": "unknown"
    },
    {
      "code": "Notes:\n\n* The permission prompt must be accepted on the Android device before the capability is advertised.\n* Wi-Fi-only devices without telephony will not advertise `sms.send`.\n\n## System commands (node host / mac node)\n\nThe macOS node exposes `system.run`, `system.notify`, and `system.execApprovals.get/set`.\nThe headless node host exposes `system.run`, `system.which`, and `system.execApprovals.get/set`.\n\nExamples:",
      "language": "unknown"
    },
    {
      "code": "Notes:\n\n* `system.run` returns stdout/stderr/exit code in the payload.\n* `system.notify` respects notification permission state on the macOS app.\n* `system.run` supports `--cwd`, `--env KEY=VAL`, `--command-timeout`, and `--needs-screen-recording`.\n* `system.notify` supports `--priority <passive|active|timeSensitive>` and `--delivery <system|overlay|auto>`.\n* macOS nodes drop `PATH` overrides; headless node hosts only accept `PATH` when it prepends the node host PATH.\n* On macOS node mode, `system.run` is gated by exec approvals in the macOS app (Settings → Exec approvals).\n  Ask/allowlist/full behave the same as the headless node host; denied prompts return `SYSTEM_RUN_DENIED`.\n* On headless node host, `system.run` is gated by exec approvals (`~/.openclaw/exec-approvals.json`).\n\n## Exec node binding\n\nWhen multiple nodes are available, you can bind exec to a specific node.\nThis sets the default node for `exec host=node` (and can be overridden per agent).\n\nGlobal default:",
      "language": "unknown"
    },
    {
      "code": "Per-agent override:",
      "language": "unknown"
    },
    {
      "code": "Unset to allow any node:",
      "language": "unknown"
    },
    {
      "code": "## Permissions map\n\nNodes may include a `permissions` map in `node.list` / `node.describe`, keyed by permission name (e.g. `screenRecording`, `accessibility`) with boolean values (`true` = granted).\n\n## Headless node host (cross-platform)\n\nOpenClaw can run a **headless node host** (no UI) that connects to the Gateway\nWebSocket and exposes `system.run` / `system.which`. This is useful on Linux/Windows\nor for running a minimal node alongside a server.\n\nStart it:",
      "language": "unknown"
    }
  ],
  "headings": [
    {
      "level": "h3",
      "text": "Start a node host (service)",
      "id": "start-a-node-host-(service)"
    },
    {
      "level": "h3",
      "text": "Pair + name",
      "id": "pair-+-name"
    },
    {
      "level": "h3",
      "text": "Allowlist the commands",
      "id": "allowlist-the-commands"
    },
    {
      "level": "h3",
      "text": "Point exec at the node",
      "id": "point-exec-at-the-node"
    },
    {
      "level": "h2",
      "text": "Invoking commands",
      "id": "invoking-commands"
    },
    {
      "level": "h2",
      "text": "Screenshots (canvas snapshots)",
      "id": "screenshots-(canvas-snapshots)"
    },
    {
      "level": "h3",
      "text": "Canvas controls",
      "id": "canvas-controls"
    },
    {
      "level": "h3",
      "text": "A2UI (Canvas)",
      "id": "a2ui-(canvas)"
    },
    {
      "level": "h2",
      "text": "Photos + videos (node camera)",
      "id": "photos-+-videos-(node-camera)"
    },
    {
      "level": "h2",
      "text": "Screen recordings (nodes)",
      "id": "screen-recordings-(nodes)"
    },
    {
      "level": "h2",
      "text": "Location (nodes)",
      "id": "location-(nodes)"
    },
    {
      "level": "h2",
      "text": "SMS (Android nodes)",
      "id": "sms-(android-nodes)"
    },
    {
      "level": "h2",
      "text": "System commands (node host / mac node)",
      "id": "system-commands-(node-host-/-mac-node)"
    },
    {
      "level": "h2",
      "text": "Exec node binding",
      "id": "exec-node-binding"
    },
    {
      "level": "h2",
      "text": "Permissions map",
      "id": "permissions-map"
    },
    {
      "level": "h2",
      "text": "Headless node host (cross-platform)",
      "id": "headless-node-host-(cross-platform)"
    },
    {
      "level": "h2",
      "text": "Mac node mode",
      "id": "mac-node-mode"
    }
  ],
  "url": "llms-txt#terminal-b:-export-the-gateway-token-and-connect-through-the-tunnel",
  "links": []
}