diff --git a/docs.json b/docs.json index 1a813472..0ebb722e 100644 --- a/docs.json +++ b/docs.json @@ -56,6 +56,15 @@ "docs/agents/amp", "docs/agents/claude-code", "docs/agents/codex", + { + "group": "OpenClaw", + "icon": "/images/icons/openclaw.svg", + "pages": [ + "docs/agents/openclaw-gateway", + "docs/agents/openclaw-telegram", + "docs/agents/openclaw-slack" + ] + }, "docs/agents/opencode" ] }, diff --git a/docs/agents/openclaw-environment.mdx b/docs/agents/openclaw-environment.mdx new file mode 100644 index 00000000..2ecc9aca --- /dev/null +++ b/docs/agents/openclaw-environment.mdx @@ -0,0 +1,331 @@ +--- +title: "OpenClaw Environment" +description: "Change models, rotate tokens, update channels, and reset OpenClaw state in a running E2B sandbox." +icon: "sliders" +--- + +Use this guide for post-deploy changes in a running OpenClaw sandbox using SDK calls. + +## What you can change without rebuilding + +| Setting | Rebuild required? | Notes | +|---|---|---| +| Default model (`agents.defaults.model.primary`) | No | Applies to new runs | +| Gateway token (`--token`) | No | Restart gateway process with new token | +| Telegram bot token | No | Re-run `channels add`, then restart gateway | +| OpenAI / Anthropic API keys | No | Update runtime env used by OpenClaw processes | +| OpenAI Codex OAuth profile | No | Clear auth profile, then relink | + +## Connect to your sandbox + + +```python Python +from e2b import Sandbox + +sandbox = Sandbox.connect("", timeout=60 * 30) +``` +```typescript JavaScript & TypeScript +import { Sandbox } from 'e2b' + +const sandbox = await Sandbox.connect('', { + timeoutMs: 60 * 30 * 1000, +}) +``` + + +## Change default model + + +```python Python +sandbox.commands.run("openclaw config set agents.defaults.model.primary openai/gpt-5.2") +print(sandbox.commands.run("openclaw models status --json").stdout) +``` +```typescript JavaScript & TypeScript +await sandbox.commands.run('openclaw config set agents.defaults.model.primary openai/gpt-5.2') +const models = await sandbox.commands.run('openclaw models status --json') +console.log(models.stdout) +``` + + +## Rotate gateway token (no sandbox restart) + +After rotation, old URLs with the previous `?token=` stop working. + + +```python Python +NEW_TOKEN = "replace-me" +PORT = 18789 + +rotate = sandbox.commands.run( + """bash -lc ' +set -euo pipefail +pkill -f "openclaw gateway" >/dev/null 2>&1 || true +nohup openclaw gateway --allow-unconfigured --bind lan --auth token --token "$NEW_TOKEN" --port "$PORT" \ + > /home/user/openclaw_gateway.log 2>&1 & +sleep 1 +ss -ltn | grep -q ":$PORT " && echo ready || echo waiting +'""", + envs={"NEW_TOKEN": NEW_TOKEN, "PORT": str(PORT)}, +) +print(rotate.stdout) +``` +```typescript JavaScript & TypeScript +const NEW_TOKEN = 'replace-me' +const PORT = 18789 + +const rotate = await sandbox.commands.run( + `bash -lc ' +set -euo pipefail +pkill -f "openclaw gateway" >/dev/null 2>&1 || true +nohup openclaw gateway --allow-unconfigured --bind lan --auth token --token "$NEW_TOKEN" --port "$PORT" \ + > /home/user/openclaw_gateway.log 2>&1 & +sleep 1 +ss -ltn | grep -q ":$PORT " && echo ready || echo waiting +'`, + { envs: { NEW_TOKEN, PORT: String(PORT) } } +) +console.log(rotate.stdout) +``` + + +## Update provider keys after deploy + +### One-off command + + +```python Python +one_off = sandbox.commands.run( + 'openclaw agent --local --message "hello"', + envs={"OPENAI_API_KEY": "sk-..."}, +) +print(one_off.stdout) +``` +```typescript JavaScript & TypeScript +const oneOff = await sandbox.commands.run( + 'openclaw agent --local --message "hello"', + { envs: { OPENAI_API_KEY: 'sk-...' } } +) +console.log(oneOff.stdout) +``` + + +### Persistent runtime env + + +```python Python +persist = sandbox.commands.run( + """bash -lc ' +set -euo pipefail +cat > /home/user/.openclaw-runtime.env < /home/user/.openclaw-runtime.env < + +## Update Telegram bot token + + +```python Python +telegram = sandbox.commands.run( + """bash -lc ' +set -euo pipefail +openclaw config set plugins.entries.telegram.enabled true +openclaw channels add --channel telegram --token "$NEW_TELEGRAM_BOT_TOKEN" +'""", + envs={"NEW_TELEGRAM_BOT_TOKEN": "123456:abc"}, +) +print(telegram.stdout) +``` +```typescript JavaScript & TypeScript +const telegram = await sandbox.commands.run( + `bash -lc ' +set -euo pipefail +openclaw config set plugins.entries.telegram.enabled true +openclaw channels add --channel telegram --token "$NEW_TELEGRAM_BOT_TOKEN" +'`, + { envs: { NEW_TELEGRAM_BOT_TOKEN: '123456:abc' } } +) +console.log(telegram.stdout) +``` + + +## Reset Telegram channel + pairing state + + +```python Python +sandbox.commands.run("openclaw channels remove --channel telegram --delete") +sandbox.commands.run( + 'openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"', + envs={"TELEGRAM_BOT_TOKEN": "123456:abc"}, +) +print(sandbox.commands.run("openclaw pairing list --json --channel telegram").stdout) +``` +```typescript JavaScript & TypeScript +await sandbox.commands.run('openclaw channels remove --channel telegram --delete') +await sandbox.commands.run( + 'openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"', + { envs: { TELEGRAM_BOT_TOKEN: '123456:abc' } } +) +const pairing = await sandbox.commands.run('openclaw pairing list --json --channel telegram') +console.log(pairing.stdout) +``` + + +Approve a pending pairing code: + + +```python Python +PAIRING_CODE = "ABCDEFGH" +sandbox.commands.run(f"openclaw pairing approve --channel telegram {PAIRING_CODE}") +``` +```typescript JavaScript & TypeScript +const PAIRING_CODE = 'ABCDEFGH' +await sandbox.commands.run(`openclaw pairing approve --channel telegram ${PAIRING_CODE}`) +``` + + +## Reset OpenAI Codex OAuth profile + + +```python Python +reset = sandbox.commands.run( + """python3 - <<'PY' +import json +from pathlib import Path + +p = Path('/home/user/.openclaw/agents/main/agent/auth-profiles.json') +raw = json.loads(p.read_text()) if p.exists() else {} +profiles = raw.get('profiles', {}) if isinstance(raw, dict) else {} +raw['profiles'] = { + k: v for k, v in profiles.items() + if not (isinstance(v, dict) and v.get('provider') == 'openai-codex') +} +p.parent.mkdir(parents=True, exist_ok=True) +p.write_text(json.dumps(raw, indent=2) + '\\n') +print('cleared openai-codex oauth profiles') +PY""" +) +print(reset.stdout) +``` +```typescript JavaScript & TypeScript +const reset = await sandbox.commands.run(`python3 - <<'PY' +import json +from pathlib import Path + +p = Path('/home/user/.openclaw/agents/main/agent/auth-profiles.json') +raw = json.loads(p.read_text()) if p.exists() else {} +profiles = raw.get('profiles', {}) if isinstance(raw, dict) else {} +raw['profiles'] = { + k: v for k, v in profiles.items() + if not (isinstance(v, dict) and v.get('provider') == 'openai-codex') +} +p.parent.mkdir(parents=True, exist_ok=True) +p.write_text(json.dumps(raw, indent=2) + '\\n') +print('cleared openai-codex oauth profiles') +PY`) +console.log(reset.stdout) +``` + + +Then relink OAuth using the OAuth guide. + +## Verify current state + + +```python Python +print(sandbox.commands.run("openclaw channels list --json").stdout) +print(sandbox.commands.run("openclaw channels status --json --probe").stdout) +print(sandbox.commands.run("openclaw pairing list --json --channel telegram").stdout) +print(sandbox.commands.run("openclaw models status --json").stdout) +``` +```typescript JavaScript & TypeScript +console.log((await sandbox.commands.run('openclaw channels list --json')).stdout) +console.log((await sandbox.commands.run('openclaw channels status --json --probe')).stdout) +console.log((await sandbox.commands.run('openclaw pairing list --json --channel telegram')).stdout) +console.log((await sandbox.commands.run('openclaw models status --json')).stdout) +``` + + +## Interactive tasks with PTY (optional) + +Use PTY when the command expects interactive input (for example OAuth onboarding). + + +```python Python +from e2b.sandbox.commands.command_handle import PtySize + +terminal = sandbox.pty.create(PtySize(cols=140, rows=40), timeout=0) +sandbox.pty.send_stdin(terminal.pid, b"openclaw onboard --accept-risk --flow quickstart --mode local --auth-choice openai-codex\\r") +``` +```typescript JavaScript & TypeScript +const terminal = await sandbox.pty.create({ cols: 140, rows: 40, timeoutMs: 10 * 60_000, onData: (d) => process.stdout.write(new TextDecoder().decode(d)) }) +await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode('openclaw onboard --accept-risk --flow quickstart --mode local --auth-choice openai-codex\\r')) +``` + + +## Cleanup + + +```python Python +sandbox.kill() +``` +```typescript JavaScript & TypeScript +await sandbox.kill() +``` + + +## Troubleshooting + +- `Unknown channel: telegram` + - Telegram channel is not configured yet. + - Run `openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"`, then retry. +- `No API key found for provider ...` + - Model/provider mismatch. + - `openai/...` needs `OPENAI_API_KEY`; `anthropic/...` needs `ANTHROPIC_API_KEY`. +- Pairing list is empty + - Send a fresh message to the bot first. + +## Related + + + + Run OpenClaw headless in a sandbox + + + Run OpenClaw's web/channel runtime + + + Connect and pair Telegram users + + + Link OpenAI Codex OAuth in a sandbox + + diff --git a/docs/agents/openclaw-gateway.mdx b/docs/agents/openclaw-gateway.mdx new file mode 100644 index 00000000..f04d65c6 --- /dev/null +++ b/docs/agents/openclaw-gateway.mdx @@ -0,0 +1,239 @@ +--- +title: "Deploy OpenClaw" +description: "Start the OpenClaw gateway in an E2B sandbox, connect your browser, and auto-approve device pairing." +icon: "globe" +--- + +The OpenClaw [gateway](https://docs.openclaw.ai/gateway) serves a web UI for chatting with agents. When exposed over a network it requires **token auth** and **device pairing** — your browser must be approved as a trusted device before it can connect. + +This guide starts the gateway inside an E2B sandbox, prints the URL, and programmatically approves your browser. + +## Quick start + + +```typescript JavaScript & TypeScript +import { Sandbox } from 'e2b' + +const TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-gateway-token' +const PORT = 18789 + +// 1. Create sandbox +const sandbox = await Sandbox.create('openclaw', { + envs: { OPENAI_API_KEY: process.env.OPENAI_API_KEY }, + timeoutMs: 3600_000, +}) + +// 2. Set the default model +await sandbox.commands.run('openclaw config set agents.defaults.model.primary openai/gpt-5.2') + +// 3. Start the gateway with token auth +sandbox.commands.run( + `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${TOKEN} --port ${PORT}`, + { background: true } +) + +// 4. Wait for the gateway to start listening +for (let i = 0; i < 45; i++) { + const probe = await sandbox.commands.run( + `bash -lc 'ss -ltn | grep -q ":${PORT} " && echo ready || echo waiting'` + ) + if (probe.stdout.trim() === 'ready') break + await new Promise((r) => setTimeout(r, 1000)) +} + +const url = `https://${sandbox.getHost(PORT)}/?token=${TOKEN}` +console.log(`Gateway: ${url}`) +// Open the URL in your browser before running the next step. +``` +```python Python +import os, time +from e2b import Sandbox + +TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-gateway-token") +PORT = 18789 + +# 1. Create sandbox +sandbox = Sandbox.create("openclaw", envs={ + "OPENAI_API_KEY": os.environ["OPENAI_API_KEY"], +}, timeout=3600) + +# 2. Set the default model +sandbox.commands.run("openclaw config set agents.defaults.model.primary openai/gpt-5.2") + +# 3. Start the gateway with token auth +sandbox.commands.run( + f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {TOKEN} --port {PORT}", + background=True, +) + +# 4. Wait for the gateway to start listening +for _ in range(45): + probe = sandbox.commands.run( + f'bash -lc \'ss -ltn | grep -q ":{PORT} " && echo ready || echo waiting\'' + ) + if probe.stdout.strip() == "ready": + break + time.sleep(1) + +url = f"https://{sandbox.get_host(PORT)}/?token={TOKEN}" +print(f"Gateway: {url}") +# Open the URL in your browser before running the next step. +``` + + + +Open the URL in your browser. It will show **"Pairing required"** — that's expected. Run the next step to approve your device. + + +## Approve device pairing + +The gateway requires each browser to be approved as a paired device. Run this after opening the URL — it polls for pending requests and approves the first one. + + +```typescript JavaScript & TypeScript +// Make sure you've opened the gateway URL in your browser before running this. + +// 4. Poll for the browser's pending device request and approve it +for (let i = 0; i < 30; i++) { + try { + const res = await sandbox.commands.run( + `openclaw devices list --json --url ws://127.0.0.1:${PORT} --token ${TOKEN}` + ) + const data = JSON.parse(res.stdout) + if (data.pending?.length) { + const rid = data.pending[0].requestId + await sandbox.commands.run( + `openclaw devices approve ${rid} --token ${TOKEN} --url ws://127.0.0.1:${PORT}` + ) + console.log(`Device approved: ${rid}`) + break + } + } catch {} + await new Promise((r) => setTimeout(r, 2000)) +} +``` +```python Python +import json + +# Make sure you've opened the gateway URL in your browser before running this. + +# 4. Poll for the browser's pending device request and approve it +for _ in range(30): + try: + res = sandbox.commands.run( + f"openclaw devices list --json --url ws://127.0.0.1:{PORT} --token {TOKEN}" + ) + data = json.loads(res.stdout) + if data.get("pending"): + rid = data["pending"][0]["requestId"] + sandbox.commands.run( + f"openclaw devices approve {rid} --token {TOKEN} --url ws://127.0.0.1:{PORT}" + ) + print(f"Device approved: {rid}") + break + except Exception: + pass + time.sleep(2) +``` + + +Once approved, the browser connects and the gateway UI loads. + +## How it works + +| Step | What happens | +|------|-------------| +| `--bind lan` | Gateway listens on `0.0.0.0` so E2B can proxy it | +| `--auth token` | Requires `?token=` on the URL for HTTP and WebSocket auth | +| Browser opens URL | Gateway serves the UI, browser opens a WebSocket | +| `code=1008 pairing required` | Gateway closes the WebSocket until the device is approved | +| `devices approve` | Approves the browser's device fingerprint | +| Browser reconnects | WebSocket connects successfully, UI is live | + +## Gateway flags reference + +| Flag | Purpose | +|------|---------| +| `--allow-unconfigured` | Start without a full config file | +| `--bind lan` | Bind to `0.0.0.0` (required for E2B port proxying) | +| `--auth token` | Enable token-based authentication | +| `--token ` | The auth token (passed as `?token=` in the URL) | +| `--port ` | Gateway listen port (default: `18789`) | + +## How to restart the gateway + +Use this when the gateway is already running and you want a clean restart (for example, after changing model or env settings). + + +We can't use the `openclaw gateway restart` command here. Some SDK environments cannot target a specific Unix user in `commands.run`. The commands below use the default command user context. + + + +```typescript JavaScript & TypeScript +const TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-gateway-token' +const PORT = 18789 + +// 1) Kill existing gateway processes if present +await sandbox.commands.run( + `bash -lc 'for p in "[o]penclaw gateway" "[o]penclaw-gateway"; do for pid in $(pgrep -f "$p" || true); do kill "$pid" >/dev/null 2>&1 || true; done; done'` +) +await new Promise((r) => setTimeout(r, 1000)) + +// 2) Start gateway again +await sandbox.commands.run( + `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${TOKEN} --port ${PORT}`, + { background: true } +) + +// 3) Wait for listening socket +for (let i = 0; i < 45; i++) { + const probe = await sandbox.commands.run( + `bash -lc 'ss -ltn | grep -q ":${PORT} " && echo ready || echo waiting'` + ) + if (probe.stdout.trim() === 'ready') break + await new Promise((r) => setTimeout(r, 1000)) +} +``` +```python Python +import os, time + +TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-gateway-token") +PORT = 18789 + +# 1) Kill existing gateway processes if present +sandbox.commands.run( + """bash -lc 'for p in "[o]penclaw gateway" "[o]penclaw-gateway"; do +for pid in $(pgrep -f "$p" || true); do + kill "$pid" >/dev/null 2>&1 || true +done +done'""" +) +time.sleep(1) + +# 2) Start gateway again +sandbox.commands.run( + f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {TOKEN} --port {PORT}", + background=True, +) + +# 3) Wait for listening socket +for _ in range(45): + probe = sandbox.commands.run( + f'bash -lc \'ss -ltn | grep -q ":{PORT} " && echo ready || echo waiting\'' + ) + if probe.stdout.strip() == "ready": + break + time.sleep(1) +``` + + +## Related + + + + Connect OpenClaw to Telegram and approve pairing + + + Connect OpenClaw to Slack and approve pairing + + diff --git a/docs/agents/openclaw-oauth.mdx b/docs/agents/openclaw-oauth.mdx new file mode 100644 index 00000000..6fdcd991 --- /dev/null +++ b/docs/agents/openclaw-oauth.mdx @@ -0,0 +1,315 @@ +--- +title: "OpenClaw Codex OAuth" +description: "Set up OpenAI Codex OAuth for OpenClaw using interactive PTY in an E2B sandbox." +icon: "key" +--- + +OpenClaw can authenticate with OpenAI Codex via OAuth instead of an API key. + +This page uses a PTY-first flow, which works on raw OpenClaw sandboxes. + +## Prerequisites + +- App started from setup. +- `E2B_API_KEY` in your local environment. +- OpenAI account with Codex access. + +## End-to-end PTY flow + +### Connect to your sandbox + + +```python Python +import json +import re +import threading +import time +from e2b import Sandbox +from e2b.sandbox.commands.command_handle import CommandExitException, PtySize + +sandbox = Sandbox.connect("", timeout=60 * 30) + +pty_chunks = [] +terminal = None +thread = None + +def on_pty(data: bytes): + text = data.decode("utf-8", errors="replace") + print(text, end="") + pty_chunks.append(text) + +def pty_send_line(text: str): + # For OpenClaw TUI prompts, carriage return is more reliable than newline. + sandbox.pty.send_stdin(terminal.pid, (text + "\r").encode()) +``` +```typescript JavaScript & TypeScript +import { Sandbox } from 'e2b' +import * as readline from 'node:readline/promises' +import { stdin as input, stdout as output } from 'node:process' + +const sandbox = await Sandbox.connect('', { + timeoutMs: 60 * 30 * 1000, +}) + +let ptyOutput = '' +const decoder = new TextDecoder() +const encoder = new TextEncoder() +let terminal +let waitPromise + +const rl = readline.createInterface({ input, output }) +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) + +async function sendLine(line) { + await sandbox.pty.sendInput(terminal.pid, encoder.encode(`${line}\r`)) +} +``` + + +### Start onboarding in PTY + + +```python Python +terminal = sandbox.pty.create( + PtySize(cols=140, rows=40), + timeout=0, # keep PTY open while you complete browser login +) + +thread = threading.Thread( + target=lambda: terminal.wait(on_pty=on_pty), + daemon=True, +) +thread.start() + +onboard_cmd = ( + "openclaw onboard --accept-risk --flow quickstart --mode local " + "--auth-choice openai-codex --skip-ui --skip-channels --skip-skills --skip-health" +) +pty_send_line(onboard_cmd) +``` +```typescript JavaScript & TypeScript +terminal = await sandbox.pty.create({ + cols: 140, + rows: 40, + timeoutMs: 10 * 60_000, + onData: (data) => { + const text = decoder.decode(data) + process.stdout.write(text) + ptyOutput += text + }, +}) + +waitPromise = terminal.wait() + +await sendLine( + 'openclaw onboard --accept-risk --flow quickstart --mode local --auth-choice openai-codex --skip-ui --skip-channels --skip-skills --skip-health' +) +``` + + +### Extract auth URL and open it in your local browser + + +```python Python +ansi_re = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]") +oauth_re = re.compile(r"https://auth\.openai\.com/oauth/authorize[^\s\"']+") + +auth_url = None +for _ in range(60): + cleaned = ansi_re.sub("", "".join(pty_chunks).replace("\r", "\n")) + match = oauth_re.search(cleaned) + if match: + auth_url = match.group(0) + break + time.sleep(1) + +if not auth_url: + raise RuntimeError("OAuth URL not found in PTY output") + +print("Open this URL in your LOCAL browser:\n") +print(auth_url) +``` +```typescript JavaScript & TypeScript +const ansiRe = /\x1b\[[0-9;?]*[ -/]*[@-~]/g +const oauthRe = /https:\/\/auth\.openai\.com\/oauth\/authorize[^\s"']+/ + +let authUrl = null +for (let i = 0; i < 60; i++) { + const cleaned = ptyOutput.replace(/\r/g, '\n').replace(ansiRe, '') + const match = cleaned.match(oauthRe) + if (match) { + authUrl = match[0] + break + } + await sleep(1000) +} + +if (!authUrl) throw new Error('OAuth URL not found in PTY output') + +console.log('\nOpen this URL in your LOCAL browser:\n') +console.log(authUrl) +``` + + +After browser sign-in, copy the localhost callback URL: + +`http://localhost:1455/auth/callback?code=...&state=...` + +### Submit callback back into PTY + + +```python Python +callback_url = input("\nPaste localhost callback URL: ").strip() +pty_send_line(callback_url) + +# Wait briefly so OpenClaw can process the callback, then exit PTY shell. +time.sleep(2) +pty_send_line("exit") + +try: + thread.join(timeout=30) +except CommandExitException: + pass +``` +```typescript JavaScript & TypeScript +const callbackUrl = (await rl.question('\nPaste localhost callback URL: ')).trim() +await sendLine(callbackUrl) + +await sleep(2000) +await sendLine('exit') + +await Promise.race([ + waitPromise, + new Promise((_, reject) => + setTimeout(() => reject(new Error('PTY wait timeout after 30s')), 30_000) + ), +]).catch((e) => console.warn(String(e))) + +rl.close() +``` + + +### Verify OAuth link and run a smoke command + + +```python Python +auth_check = sandbox.commands.run( + """python3 - <<'PY' +import json +from pathlib import Path +p = Path('/home/user/.openclaw/agents/main/agent/auth-profiles.json') +raw = json.loads(p.read_text()) if p.exists() else {} +profiles = raw.get('profiles', {}) if isinstance(raw, dict) else {} +ok = any( + isinstance(v, dict) and v.get('provider') == 'openai-codex' and v.get('refresh') + for v in profiles.values() +) +print('openai_codex_oauth_linked=' + ('true' if ok else 'false')) +PY""" +) +print(auth_check.stdout) + +sandbox.commands.run( + "openclaw config set agents.defaults.model.primary openai-codex/gpt-5.2-codex" +) + +smoke = sandbox.commands.run( + 'openclaw agent --session-id main --thinking low --message "Say exactly: oauth-ok"' +) +print(smoke.stdout) +``` +```typescript JavaScript & TypeScript +const authRaw = await sandbox.commands.run( + "cat /home/user/.openclaw/agents/main/agent/auth-profiles.json 2>/dev/null || echo '{}'" +) +const authStore = JSON.parse(authRaw.stdout || '{}') +const profiles = authStore?.profiles ?? {} +const linked = Object.values(profiles).some( + (p) => p?.provider === 'openai-codex' && p?.refresh +) +console.log({ openai_codex_oauth_linked: linked }) +if (!linked) throw new Error('openai-codex OAuth profile not linked') + +await sandbox.commands.run( + 'openclaw config set agents.defaults.model.primary openai-codex/gpt-5.2-codex' +) + +const smoke = await sandbox.commands.run( + 'openclaw agent --session-id main --thinking low --message "Say exactly: oauth-ok"' +) +console.log(smoke.stdout) +``` + + +## Cleanup + + +```python Python +sandbox.kill() +``` +```typescript JavaScript & TypeScript +await sandbox.kill() +``` + + +## Optional: reset and relink OAuth + + +```python Python +reset = sandbox.commands.run( + """python3 - <<'PY' +import json +from pathlib import Path + +p = Path('/home/user/.openclaw/agents/main/agent/auth-profiles.json') +raw = json.loads(p.read_text()) if p.exists() else {} +profiles = raw.get('profiles', {}) if isinstance(raw, dict) else {} +raw['profiles'] = { + k: v for k, v in profiles.items() + if not (isinstance(v, dict) and v.get('provider') == 'openai-codex') +} +p.parent.mkdir(parents=True, exist_ok=True) +p.write_text(json.dumps(raw, indent=2) + '\\n') +print('cleared openai-codex oauth profiles') +PY""" +) +print(reset.stdout) +``` +```typescript JavaScript & TypeScript +const reset = await sandbox.commands.run(`python3 - <<'PY' +import json +from pathlib import Path + +p = Path('/home/user/.openclaw/agents/main/agent/auth-profiles.json') +raw = json.loads(p.read_text()) if p.exists() else {} +profiles = raw.get('profiles', {}) if isinstance(raw, dict) else {} +raw['profiles'] = { + k: v for k, v in profiles.items() + if not (isinstance(v, dict) and v.get('provider') == 'openai-codex') +} +p.parent.mkdir(parents=True, exist_ok=True) +p.write_text(json.dumps(raw, indent=2) + '\\n') +print('cleared openai-codex oauth profiles') +PY`) +console.log(reset.stdout) +``` + + +After reset, rerun onboarding and verification. + +## Related + + + + Run OpenClaw headless in a sandbox + + + Run OpenClaw's web gateway with token auth + + + Connect OpenClaw to Telegram and approve pairing + + + Change models, tokens, and channels after deploy + + diff --git a/docs/agents/openclaw-slack.mdx b/docs/agents/openclaw-slack.mdx new file mode 100644 index 00000000..8b564a8c --- /dev/null +++ b/docs/agents/openclaw-slack.mdx @@ -0,0 +1,182 @@ +--- +title: "OpenClaw Slack" +description: "Connect OpenClaw to Slack in an E2B sandbox, approve pairing, and chat through your Slack app." +icon: "message-circle" +--- + +OpenClaw supports Slack as a chat channel. Using E2B you can run OpenClaw in a sandbox, attach Slack tokens, and approve user pairing from the terminal. + +Official OpenClaw Slack channel docs: [docs.openclaw.ai/channels/slack](https://docs.openclaw.ai/channels/slack) + +## Prerequisites + +- Slack app tokens from the same Slack app: + - `SLACK_APP_TOKEN` (`xapp-...`, Socket Mode) + - `SLACK_BOT_TOKEN` (`xoxb-...`) +- `OPENAI_API_KEY` (this guide uses `openai/gpt-5.2`) +- E2B API key configured locally + +## Quick start + + +```typescript JavaScript & TypeScript +import { Sandbox } from 'e2b' + +const GATEWAY_PORT = 18789 +const GATEWAY_TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-openclaw-token' + +const sandbox = await Sandbox.create('openclaw', { + envs: { + OPENAI_API_KEY: process.env.OPENAI_API_KEY, + SLACK_APP_TOKEN: process.env.SLACK_APP_TOKEN, + SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN, + }, + timeoutMs: 3600_000, +}) + +await sandbox.commands.run('openclaw config set agents.defaults.model.primary openai/gpt-5.2') +await sandbox.commands.run('openclaw config set plugins.entries.slack.enabled true') +await sandbox.commands.run( + 'openclaw channels add --channel slack --app-token "$SLACK_APP_TOKEN" --bot-token "$SLACK_BOT_TOKEN"' +) + +await sandbox.commands.run( + `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${GATEWAY_TOKEN} --port ${GATEWAY_PORT}`, + { background: true } +) + +for (let i = 0; i < 45; i++) { + const probe = await sandbox.commands.run( + `bash -lc 'ss -ltn | grep -q ":${GATEWAY_PORT} " && echo ready || echo waiting'` + ) + if (probe.stdout.trim() === 'ready') break + await new Promise((r) => setTimeout(r, 1000)) +} +``` +```python Python +import os +import time +from e2b import Sandbox + +GATEWAY_PORT = 18789 +GATEWAY_TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-openclaw-token") + +sandbox = Sandbox.create("openclaw", envs={ + "OPENAI_API_KEY": os.environ["OPENAI_API_KEY"], + "SLACK_APP_TOKEN": os.environ["SLACK_APP_TOKEN"], + "SLACK_BOT_TOKEN": os.environ["SLACK_BOT_TOKEN"], +}, timeout=3600) + +sandbox.commands.run("openclaw config set agents.defaults.model.primary openai/gpt-5.2") +sandbox.commands.run("openclaw config set plugins.entries.slack.enabled true") +sandbox.commands.run( + 'openclaw channels add --channel slack --app-token "$SLACK_APP_TOKEN" --bot-token "$SLACK_BOT_TOKEN"' +) + +sandbox.commands.run( + f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {GATEWAY_TOKEN} --port {GATEWAY_PORT}", + background=True, +) + +for _ in range(45): + probe = sandbox.commands.run( + f"bash -lc 'ss -ltn | grep -q \":{GATEWAY_PORT} \" && echo ready || echo waiting'" + ) + if probe.stdout.strip() == "ready": + break + time.sleep(1) +``` + + + +For Slack setup, you do **not** need to open the gateway URL in a browser. The gateway process is used as a long-running channel runtime. + + +## Configure your Slack app + +Before testing messages, make sure Slack app settings are complete: + +- Socket Mode enabled (`xapp-...` token with `connections:write`) +- Bot scopes include: + - `app_mentions:read` + - `chat:write` + - `channels:history` + - `im:history` (for DMs) +- Event Subscriptions enabled with bot events: + - `app_mention` + - `message.im` (for DMs) +- App reinstalled to workspace after scope/event changes +- Bot invited to target channel (`/invite @your-bot`) + +## Pair your Slack user + +On first message, Slack may return: + +```text +OpenClaw: access not configured. +Your Slack user id: ... +Pairing code: XXXXXXXX +Ask the bot owner to approve with: +openclaw pairing approve slack XXXXXXXX +``` + +Approve that code: + + +```typescript JavaScript & TypeScript +const PAIRING_CODE = 'XXXXXXXX' // from Slack +await sandbox.commands.run(`openclaw pairing approve slack ${PAIRING_CODE}`) +``` +```python Python +PAIRING_CODE = "XXXXXXXX" # from Slack +sandbox.commands.run(f"openclaw pairing approve slack {PAIRING_CODE}") +``` + + +## Verify channel status + + +```typescript JavaScript & TypeScript +const channels = await sandbox.commands.run('openclaw channels list --json') +const status = await sandbox.commands.run('openclaw channels status --json --probe') +const logs = await sandbox.commands.run('openclaw channels logs --channel slack --lines 200') + +console.log(channels.stdout) +console.log(status.stdout) // may print text instead of JSON when gateway is unreachable +console.log(logs.stdout) +``` +```python Python +channels = sandbox.commands.run("openclaw channels list --json") +status = sandbox.commands.run("openclaw channels status --json --probe") +logs = sandbox.commands.run("openclaw channels logs --channel slack --lines 200") + +print(channels.stdout) +print(status.stdout) # may print text instead of JSON when gateway is unreachable +print(logs.stdout) +``` + + +Healthy Slack logs should show a connected provider, then incoming events and replies after you message the bot. + +## Troubleshooting + +- Slack logs show only `slack socket mode connected` and nothing else + - Slack is connected but events are not being delivered. + - Recheck Event Subscriptions (`app_mention`, `message.im`), reinstall app, and invite bot to channel. +- `OpenClaw: access not configured` + - Pairing is required for this Slack user. Approve the pairing code. +- `No API key found for provider ...` + - This guide uses `openai/gpt-5.2`. Set `OPENAI_API_KEY` in sandbox envs. +- `Sending messages to this app has been turned off` + - In Slack App Home, enable users sending messages to the app, then reinstall. + +## Related + + + + Run OpenClaw's web gateway with token auth + + + Connect OpenClaw to Telegram and approve pairing + + diff --git a/docs/agents/openclaw-telegram.mdx b/docs/agents/openclaw-telegram.mdx new file mode 100644 index 00000000..b40999b5 --- /dev/null +++ b/docs/agents/openclaw-telegram.mdx @@ -0,0 +1,195 @@ +--- +title: "OpenClaw Telegram" +description: "Connect OpenClaw to Telegram in an E2B sandbox, approve pairing, and chat through your bot." +icon: "paper-plane" +--- + +OpenClaw supports Telegram as a chat channel. In E2B you can run OpenClaw in a sandbox, attach your bot token, and approve user pairing from the terminal. + +This guide covers the working flow we used: + +1. Start OpenClaw in a sandbox. +2. Enable the Telegram plugin. +3. Add Telegram channel credentials. +4. Start the channel runtime in background. +5. Approve Telegram pairing. + +## Prerequisites + +- A Telegram bot token from [@BotFather](https://t.me/BotFather). +- An OpenAI API key for the OpenClaw model. +- E2B API key configured locally. + +## Quick start + + +```typescript JavaScript & TypeScript +import { Sandbox } from 'e2b' + +const GATEWAY_PORT = 18789 +const GATEWAY_TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-openclaw-token' + +const sandbox = await Sandbox.create('openclaw', { + envs: { + OPENAI_API_KEY: process.env.OPENAI_API_KEY, + TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN, + }, + timeoutMs: 3600_000, +}) + +await sandbox.commands.run('openclaw config set agents.defaults.model.primary openai/gpt-5.2') + +// Enable the Telegram plugin (required before adding the channel) +await sandbox.commands.run('openclaw config set plugins.entries.telegram.enabled true') +await sandbox.commands.run('openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"') + +await sandbox.commands.run( + `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${GATEWAY_TOKEN} --port ${GATEWAY_PORT}`, + { background: true } +) + +for (let i = 0; i < 45; i++) { + const probe = await sandbox.commands.run( + `bash -lc 'ss -ltn | grep -q ":${GATEWAY_PORT} " && echo ready || echo waiting'` + ) + if (probe.stdout.trim() === 'ready') break + await new Promise((r) => setTimeout(r, 1000)) +} +``` +```python Python +import os +import time +from e2b import Sandbox + +GATEWAY_PORT = 18789 +GATEWAY_TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-openclaw-token") + +sandbox = Sandbox.create("openclaw", envs={ + "OPENAI_API_KEY": os.environ["OPENAI_API_KEY"], + "TELEGRAM_BOT_TOKEN": os.environ["TELEGRAM_BOT_TOKEN"], +}, timeout=3600) + +sandbox.commands.run("openclaw config set agents.defaults.model.primary openai/gpt-5.2") + +# Enable the Telegram plugin (required before adding the channel) +sandbox.commands.run("openclaw config set plugins.entries.telegram.enabled true") +sandbox.commands.run('openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"') + +sandbox.commands.run( + f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {GATEWAY_TOKEN} --port {GATEWAY_PORT}", + background=True, +) + +for _ in range(45): + probe = sandbox.commands.run( + f"bash -lc 'ss -ltn | grep -q \":{GATEWAY_PORT} \" && echo ready || echo waiting'" + ) + if probe.stdout.strip() == "ready": + break + time.sleep(1) +``` + + + +For Telegram setup, you do **not** need to open the gateway URL in a browser. The gateway process is used here as a long-running channel runtime. + + +## Pair your Telegram user + +1. Open your bot in Telegram and send a message (for example: `hi`). +2. Telegram will return a pairing prompt similar to: + +```text +OpenClaw: access not configured. + +Your Telegram user id: ... +Pairing code: XXXXXXXX + +Ask the bot owner to approve with: +openclaw pairing approve telegram XXXXXXXX +``` + +3. Approve that pairing code via `sandbox.commands.run(...)`: + + +```typescript JavaScript & TypeScript +const PAIRING_CODE = 'XXXXXXXX' // from Telegram + +await sandbox.commands.run( + `openclaw pairing approve --channel telegram ${PAIRING_CODE}` +) +``` +```python Python +PAIRING_CODE = "XXXXXXXX" # from Telegram + +sandbox.commands.run( + f"openclaw pairing approve --channel telegram {PAIRING_CODE}" +) +``` + + +`openclaw pairing approve telegram ` also works if you prefer that form. + +## Verify channel status + + +```typescript JavaScript & TypeScript +const channels = await sandbox.commands.run('openclaw channels list --json') +const status = await sandbox.commands.run('openclaw channels status --json --probe') +const pairing = await sandbox.commands.run('openclaw pairing list --json --channel telegram') + +console.log(JSON.parse(channels.stdout)) +console.log(JSON.parse(status.stdout)) +console.log(JSON.parse(pairing.stdout)) +``` +```python Python +import json + +channels = sandbox.commands.run("openclaw channels list --json") +status = sandbox.commands.run("openclaw channels status --json --probe") +pairing = sandbox.commands.run("openclaw pairing list --json --channel telegram") + +print(json.loads(channels.stdout)) +print(json.loads(status.stdout)) +print(json.loads(pairing.stdout)) +``` + + +If you need logs from channel handlers: + + +```typescript JavaScript & TypeScript +const logs = await sandbox.commands.run( + 'openclaw channels logs --channel telegram --lines 200' +) +console.log(logs.stdout) +``` +```python Python +logs = sandbox.commands.run( + "openclaw channels logs --channel telegram --lines 200" +) +print(logs.stdout) +``` + + +## Troubleshooting + +- `Unknown channel: telegram` + - The Telegram plugin is not enabled. Run `openclaw config set plugins.entries.telegram.enabled true` before adding the channel. +- `OpenClaw: access not configured` + - Pairing has not been approved yet. Run `openclaw pairing approve ...`. +- `No API key found for provider ...` + - This guide uses `openai/gpt-5.2`. Set `OPENAI_API_KEY` in sandbox envs. +- No pending pairing requests from `pairing list` + - Send a fresh message to the bot first, then retry `pairing list --channel telegram`. + +## Related + + + + Run OpenClaw's web gateway with token auth + + + Connect OpenClaw to Slack and approve pairing + + diff --git a/docs/agents/openclaw.mdx b/docs/agents/openclaw.mdx index 374a4509..b55e1c4f 100644 --- a/docs/agents/openclaw.mdx +++ b/docs/agents/openclaw.mdx @@ -1,7 +1,7 @@ --- -title: "OpenClaw" +title: "Customize OpenClaw" description: "Run OpenClaw in a secure E2B sandbox with full filesystem, terminal, and git access." -icon: "/images/icons/openclaw.svg" +icon: "wand-magic-sparkles" --- [OpenClaw](https://openclaw.ai) is an open-source personal AI assistant that supports multiple LLM providers and messaging channels. E2B provides a pre-built `openclaw` template with OpenClaw already installed. @@ -382,50 +382,6 @@ sandbox.kill() ``` -## Launch the gateway - -OpenClaw has a built-in [web UI and chat interface](https://openclaw.ai) served by its gateway. Start it inside a sandbox and connect from your browser. - - -This sandbox is created with a 10-minute timeout and auto-pause enabled — after 10 minutes of inactivity it pauses and can be resumed later. See [Sandbox Persistence](/docs/sandbox/persistence) and [Sandbox Lifecycle](/docs/sandbox) for more details. - - - -```typescript JavaScript & TypeScript -import { Sandbox } from 'e2b' - -const sandbox = await Sandbox.betaCreate('openclaw', { - envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, - autoPause: true, - timeoutMs: 10 * 60 * 1000, -}) - -// Start the gateway -sandbox.commands.run('openclaw gateway --port 18789 --verbose') - -const host = sandbox.getHost(18789) -const url = `https://${host}` -console.log(`OpenClaw Gateway: ${url}`) -console.log(`Sandbox ID: ${sandbox.sandboxId}`) -``` -```python Python -import os -from e2b import Sandbox - -sandbox = Sandbox.beta_create("openclaw", envs={ - "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], -}, auto_pause=True, timeout=10 * 60) - -# Start the gateway -sandbox.commands.run("openclaw gateway --port 18789 --verbose") - -host = sandbox.get_host(18789) -url = f"https://{host}" -print(f"OpenClaw Gateway: {url}") -print(f"Sandbox ID: {sandbox.sandbox_id}") -``` - - ## Build a custom template If you need to customize the environment (e.g. pre-install dependencies, add config files), build your own template on top of the pre-built `openclaw` template. @@ -495,16 +451,22 @@ python build.py ``` -## Related guides - - - - Auto-pause, resume, and manage sandbox lifecycle - - - Clone repos, manage branches, and push changes - - - Connect to the sandbox via SSH for interactive sessions - - +## Related + + + + Run OpenClaw's web gateway with token auth + + + Connect OpenClaw to Telegram and approve pairing + + + Connect OpenClaw to Slack and approve pairing + + + Link OpenAI Codex OAuth in a sandbox + + + Change models, tokens, and channel settings after deploy + +