diff --git a/Dockerfile b/Dockerfile index 47a495b..6ca15c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN apt-get update \ unzip \ && rm -rf /var/lib/apt/lists/* -RUN npm install -g openclaw@2026.5.27 +RUN npm install -g openclaw@2026.6.1 RUN npm install -g clawhub@latest WORKDIR /app diff --git a/README.md b/README.md index 5c3d28b..a764b10 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ - The container runs a wrapper web server. - The wrapper protects `/setup` with `SETUP_PASSWORD`. -- During setup, the wrapper runs `openclaw onboard ...` inside the container, writes state to the volume, and then starts the gateway. API-key providers use non-interactive setup; ChatGPT/Codex device pairing uses OpenClaw's interactive device-code flow so the login URL and code can be streamed to `/setup`. +- During setup, the wrapper runs `openclaw onboard ...` inside the container, writes state to the volume, and then starts the gateway. API-key providers use non-interactive setup; ChatGPT/Codex and xAI/Grok device pairing use OpenClaw's interactive device-code flows so the login URL and code can be streamed to `/setup`. - After setup, **`/` is OpenClaw**. The wrapper reverse-proxies all traffic (including WebSockets) to the local gateway process. ## Getting chat tokens (so you don't have to scramble) @@ -28,6 +28,12 @@ In `/setup`, choose **OpenAI → OpenAI Codex device pairing**. Setup will strea This uses OpenClaw's `openai-codex-device-code` onboarding flow, so you do not need to paste an OpenAI API key. +### Grok / xAI subscription login + +In `/setup`, choose **xAI (Grok) → xAI device code**. Setup will stream an xAI URL and a short device code; open the URL in your browser, enter the code, and keep the setup page open until OpenClaw finishes saving the OAuth profile. + +This uses OpenClaw's `xai-device-code` onboarding flow, so you can use an eligible SuperGrok or X Premium subscription without pasting an xAI API key. + ### Telegram bot token 1. Open Telegram and message **@BotFather** diff --git a/src/public/setup.html b/src/public/setup.html index 0420774..bf90239 100644 --- a/src/public/setup.html +++ b/src/public/setup.html @@ -31,6 +31,7 @@ log: '', loginUrl: '', loginCode: '', + loginLabel: 'Device Login', setupError: '', setupStreamTail: '', loading: false, @@ -62,6 +63,7 @@ return ![ 'openai-codex', 'openai-codex-device-code', + 'xai-device-code', 'google-gemini-cli', 'github-copilot', 'ollama', @@ -74,6 +76,9 @@ if (this.selectedAuth === 'openai-codex-device-code') { return 'Run setup, then open the ChatGPT/Codex URL and enter the device code shown in the log.'; } + if (this.selectedAuth === 'xai-device-code') { + return 'Run setup, then open the xAI URL and enter the device code shown in the log. Use an eligible SuperGrok or X Premium account.'; + } if (this.selectedAuth === 'openai-codex') { return 'Browser login needs an interactive terminal to paste the redirect URL. Device pairing is recommended here.'; } @@ -136,6 +141,7 @@ async runSetup() { this.loginUrl = ''; this.loginCode = ''; + this.loginLabel = this.loginLabelForAuth(this.selectedAuth); this.setupError = ''; this.setupStreamTail = ''; const payload = { @@ -201,14 +207,23 @@ }, extractLoginDetails(text) { - const urlMatch = text.match(/https:\/\/auth\.openai\.com\/codex\/device[^\s)]*/); + const urlMatch = text.match(/https:\/\/auth\.openai\.com\/codex\/device[^\s)]*/) + || text.match(/\bURL:\s*(https:\/\/(?:auth|accounts)\.x\.ai\/[^\s)]*)/) + || text.match(/\bOpen(?: manually)?:\s*(https:\/\/(?:auth|accounts)\.x\.ai\/[^\s)]*)/); if (urlMatch) this.loginUrl = urlMatch[0]; + if (urlMatch && urlMatch[1]) this.loginUrl = urlMatch[1]; const codeMatches = [...text.matchAll(/\bCode:\s*([A-Z0-9][A-Z0-9-]{3,})\b/g)]; const visibleCode = codeMatches.map(m => m[1]).find(code => code !== 'shown'); if (visibleCode) this.loginCode = visibleCode; }, + loginLabelForAuth(auth) { + if (auth === 'xai-device-code') return 'xAI Login'; + if (auth === 'openai-codex-device-code') return 'ChatGPT Login'; + return 'Device Login'; + }, + extractSetupFailure(text) { if (text.includes('[setup] Internal error')) { this.setupError = 'Setup hit an internal error. Review the log below.'; @@ -743,7 +758,7 @@
Format: provider/model-name