diff --git a/.github/workflows/agent-tarballs.yml b/.github/workflows/agent-tarballs.yml index 31d4ea98..47d0d7e2 100644 --- a/.github/workflows/agent-tarballs.yml +++ b/.github/workflows/agent-tarballs.yml @@ -99,7 +99,7 @@ jobs: echo "==> Installing agent..." # Allowed domains for curl/wget downloads (official agent vendor domains) - ALLOWED_DOMAINS="claude.ai|opencode.ai|raw.githubusercontent.com|registry.npmjs.org|crates.io|github.com" + ALLOWED_DOMAINS="claude.ai|opencode.ai|raw.githubusercontent.com|registry.npmjs.org|crates.io|github.com|dl.google.com" CMD_COUNT=$(jq -r --arg a "${AGENT_NAME}" '.[$a].install | length' packer/agents.json) i=0 diff --git a/packages/cli/package.json b/packages/cli/package.json index c0e5e604..adf35d87 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "0.15.24", + "version": "0.15.25", "type": "module", "bin": { "spawn": "cli.js" diff --git a/packages/cli/src/commands/shared.ts b/packages/cli/src/commands/shared.ts index 270293ff..8a2248d0 100644 --- a/packages/cli/src/commands/shared.ts +++ b/packages/cli/src/commands/shared.ts @@ -545,17 +545,6 @@ export function getCredentialGuidance(cloud: string, onlyOpenRouter: boolean): s return `Run ${pc.cyan(`spawn ${cloud}`)} for setup instructions.`; } -export async function confirmContinueWithMissingCreds(onlyOpenRouter: boolean): Promise { - const confirmMsg = onlyOpenRouter - ? "Continue? You'll authenticate via browser." - : "Continue anyway? The script will prompt for missing credentials."; - const shouldContinue = await p.confirm({ - message: confirmMsg, - initialValue: true, - }); - return !p.isCancel(shouldContinue) && shouldContinue; -} - export async function preflightCredentialCheck(manifest: Manifest, cloud: string): Promise { const cloudAuth = manifest.clouds[cloud].auth; if (cloudAuth.toLowerCase() === "none") { @@ -574,12 +563,8 @@ export async function preflightCredentialCheck(manifest: Manifest, cloud: string const onlyOpenRouter = missing.length === 1 && missing[0] === "OPENROUTER_API_KEY"; p.log.info(getCredentialGuidance(cloud, onlyOpenRouter)); - if (isInteractiveTTY()) { - const shouldContinue = await confirmContinueWithMissingCreds(onlyOpenRouter); - if (!shouldContinue) { - handleCancel(); - } - } + // No confirmation needed — the warning + guidance above is sufficient. + // The orchestration pipeline will prompt for credentials as needed. } /** Build auth hint string from cloud auth field for error messages */ diff --git a/packages/cli/src/shared/agent-setup.ts b/packages/cli/src/shared/agent-setup.ts index 05eb772f..b212b62f 100644 --- a/packages/cli/src/shared/agent-setup.ts +++ b/packages/cli/src/shared/agent-setup.ts @@ -315,10 +315,33 @@ wire_api = "responses" // ─── OpenClaw Config ───────────────────────────────────────────────────────── +async function installChromeBrowser(runner: CloudRunner): Promise { + // Install Google Chrome for OpenClaw's browser tool (recommended by OpenClaw docs). + // Snap Chromium on Ubuntu 24.04 fails — AppArmor confinement blocks CDP control. + // Google Chrome .deb bypasses snap entirely and lands at /usr/bin/google-chrome. + logStep("Installing Google Chrome for browser tool..."); + try { + await runner.runServer( + "{ command -v google-chrome-stable >/dev/null 2>&1 || command -v google-chrome >/dev/null 2>&1; } && { echo 'Chrome already installed'; exit 0; }; " + + "wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -O /tmp/google-chrome.deb && " + + "sudo dpkg -i /tmp/google-chrome.deb 2>/dev/null; sudo apt-get install -f -y -qq 2>/dev/null; " + + "rm -f /tmp/google-chrome.deb", + 120, + ); + logInfo("Google Chrome installed"); + } catch { + logWarn("Google Chrome install failed (browser tool will be unavailable)"); + } +} + async function setupOpenclawConfig(runner: CloudRunner, apiKey: string, modelId: string): Promise { logStep("Configuring openclaw..."); await runner.runServer("mkdir -p ~/.openclaw"); + // Chrome must be installed before config is written (config references its path). + // This runs in configure() — not install() — so it works even with tarball installs. + await installChromeBrowser(runner); + const gatewayToken = crypto.randomUUID().replace(/-/g, ""); const escapedKey = jsonEscape(apiKey); const escapedToken = jsonEscape(gatewayToken); @@ -343,6 +366,19 @@ async function setupOpenclawConfig(runner: CloudRunner, apiKey: string, modelId: } }`; await uploadConfigFile(runner, config, "$HOME/.openclaw/openclaw.json"); + + // Configure browser via CLI (openclaw config set) — the supported way to set + // browser options. Writing JSON directly may not be picked up by all versions. + try { + await runner.runServer( + "export PATH=$HOME/.npm-global/bin:$HOME/.bun/bin:$HOME/.local/bin:$PATH; " + + "openclaw config set browser.executablePath /usr/bin/google-chrome-stable; " + + "openclaw config set browser.noSandbox true; " + + "openclaw config set browser.headless true", + ); + } catch { + logWarn("Browser config setup failed (non-fatal)"); + } } export async function startGateway(runner: CloudRunner): Promise { @@ -577,12 +613,6 @@ function createAgents(runner: CloudRunner): Record { "{ grep -qF '.npm-global/bin' ~/.bashrc 2>/dev/null || echo 'export PATH=\"$HOME/.npm-global/bin:$PATH\"' >> ~/.bashrc; } && " + "{ [ ! -f ~/.zshrc ] || grep -qF '.npm-global/bin' ~/.zshrc 2>/dev/null || echo 'export PATH=\"$HOME/.npm-global/bin:$PATH\"' >> ~/.zshrc; }", ); - // Install Playwright Chromium for OpenClaw's browser tool (headless, no snap dependency) - try { - await runner.runServer("npx playwright install chromium --with-deps 2>/dev/null", 300); - } catch { - logWarn("Playwright Chromium install failed (browser tool will be unavailable)"); - } }, envVars: (apiKey) => [ `OPENROUTER_API_KEY=${apiKey}`, diff --git a/packages/cli/src/shared/oauth.ts b/packages/cli/src/shared/oauth.ts index 138bd410..18dd22a8 100644 --- a/packages/cli/src/shared/oauth.ts +++ b/packages/cli/src/shared/oauth.ts @@ -316,20 +316,9 @@ export async function getOrPromptApiKey(agentSlug?: string, cloudSlug?: string): return key; } - // OAuth failed, offer manual entry + // OAuth failed — fall through to manual entry process.stderr.write("\n"); - logWarn("Browser-based OAuth login was not completed."); - logInfo("You can paste an API key instead. Create one at: https://openrouter.ai/settings/keys"); - process.stderr.write("\n"); - - const choice = await prompt("Paste your API key manually? (Y/n): "); - if (/^[Nn]$/.test(choice)) { - logError("Authentication cancelled. An OpenRouter API key is required."); - throw new Error("No API key"); - } - - process.stderr.write("\n"); - logInfo("Manual API Key Entry"); + logWarn("Browser-based login was not completed."); logInfo("Get your API key from: https://openrouter.ai/settings/keys"); process.stderr.write("\n"); diff --git a/packer/agents.json b/packer/agents.json index e53f003e..878276b9 100644 --- a/packer/agents.json +++ b/packer/agents.json @@ -14,7 +14,8 @@ "openclaw": { "tier": "full", "install": [ - "mkdir -p ~/.npm-global/bin && npm install -g --prefix ~/.npm-global openclaw" + "mkdir -p ~/.npm-global/bin && npm install -g --prefix ~/.npm-global openclaw", + "wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -O /tmp/google-chrome.deb && apt-get install -y -qq /tmp/google-chrome.deb && rm -f /tmp/google-chrome.deb" ] }, "opencode": { diff --git a/packer/scripts/capture-agent.sh b/packer/scripts/capture-agent.sh index a0cbba43..e8176892 100644 --- a/packer/scripts/capture-agent.sh +++ b/packer/scripts/capture-agent.sh @@ -25,7 +25,13 @@ PATHS_FILE="/tmp/spawn-tarball-paths.txt" # Map agent -> filesystem paths to capture (all relative to /) case "${AGENT_NAME}" in - openclaw|codex|kilocode) + openclaw) + echo "/root/.npm-global/" >> "${PATHS_FILE}" + # Google Chrome for OpenClaw's browser tool (CDP automation) + echo "/usr/bin/google-chrome" >> "${PATHS_FILE}" + echo "/opt/google/chrome/" >> "${PATHS_FILE}" + ;; + codex|kilocode) echo "/root/.npm-global/" >> "${PATHS_FILE}" ;; claude)