#!/usr/bin/env node import fs from "node:fs"; import path from "node:path"; const home = process.env.OPENCLAW_HOME || process.env.HOME || "/app"; const stateDir = process.env.OPENCLAW_STATE_DIR || path.join(home, ".openclaw"); const configPath = path.join(stateDir, "openclaw.json"); const runtimeConfigPath = path.join(stateDir, "worker-runtime.json"); const usageLogPath = path.join(stateDir, "worker-usage.jsonl"); function splitCsv(val) { return String(val || "") .split(",") .map((s) => s.trim()) .filter(Boolean); } function firstNonEmpty(...vals) { for (const v of vals) { const s = String(v || "").trim(); if (s) return s; } return ""; } function ensureFile(pathname, content) { if (!fs.existsSync(pathname)) { fs.writeFileSync(pathname, content, "utf8"); } } const gatewayToken = firstNonEmpty( process.env.OPENCLAW_GATEWAY_TOKEN, process.env.OPENCLAW_TOKEN ); const gatewayPassword = firstNonEmpty(process.env.OPENCLAW_GATEWAY_PASSWORD); const defaultModel = firstNonEmpty(process.env.OPENCLAW_HF_DEFAULT_MODEL) || "huggingface/Qwen/Qwen2.5-Coder-32B-Instruct"; const inferredOrigin = process.env.SPACE_HOST ? `https://${process.env.SPACE_HOST}` : ""; const allowedOrigins = splitCsv( firstNonEmpty( process.env.OPENCLAW_CONTROL_UI_ALLOWED_ORIGINS, inferredOrigin ) ); const trustedProxies = splitCsv( firstNonEmpty( process.env.OPENCLAW_GATEWAY_TRUSTED_PROXIES, "127.0.0.1,::1" ) ); const defaultProvider = firstNonEmpty( process.env.OPENCLAW_DEFAULT_PROVIDER, "openclaw" ); const defaultAgent = firstNonEmpty( process.env.OPENCLAW_WORKER_DEFAULT_AGENT, "main" ); const defaultSessionKey = firstNonEmpty( process.env.OPENCLAW_WORKER_DEFAULT_SESSION_KEY, "webchat:languageapp" ); const defaultTimeoutMs = Number( firstNonEmpty(process.env.OPENCLAW_WORKER_DEFAULT_TIMEOUT_MS, "120000") ); const defaultTemperature = Number( firstNonEmpty(process.env.OPENCLAW_WORKER_DEFAULT_TEMPERATURE, "0.2") ); const defaultMaxTokensRaw = firstNonEmpty( process.env.OPENCLAW_WORKER_DEFAULT_MAX_TOKENS, "" ); const defaultMaxTokens = defaultMaxTokensRaw ? Number(defaultMaxTokensRaw) : null; const openclawAgents = splitCsv( firstNonEmpty(process.env.OPENCLAW_OPENCLAW_AGENTS, "main") ); const groqModels = splitCsv( firstNonEmpty( process.env.OPENCLAW_GROQ_MODELS, "openai/gpt-oss-120b,llama-3.3-70b-versatile" ) ); const huggingFaceModels = splitCsv( firstNonEmpty( process.env.OPENCLAW_HUGGINGFACE_MODELS, "Qwen/Qwen2.5-Coder-32B-Instruct,Qwen/Qwen3-235B-A22B-Instruct-2507" ) ); let config = {}; if (fs.existsSync(configPath)) { try { config = JSON.parse(fs.readFileSync(configPath, "utf8")); } catch { config = {}; } } config.gateway ??= {}; config.gateway.mode = "local"; config.gateway.port = Number(process.env.OPENCLAW_INTERNAL_PORT || 18789); config.gateway.bind = "loopback"; config.gateway.auth ??= {}; if (gatewayToken) { config.gateway.auth.mode = "token"; config.gateway.auth.token = gatewayToken; } else if (gatewayPassword) { config.gateway.auth.mode = "password"; config.gateway.auth.password = gatewayPassword; } config.gateway.controlUi ??= {}; config.gateway.controlUi.enabled = true; config.gateway.controlUi.dangerouslyDisableDeviceAuth = true; if (allowedOrigins.length > 0) { config.gateway.controlUi.allowedOrigins = allowedOrigins; } config.gateway.trustedProxies = trustedProxies; config.gateway.http ??= {}; config.gateway.http.endpoints ??= {}; config.gateway.http.endpoints.chatCompletions ??= {}; config.gateway.http.endpoints.chatCompletions.enabled = true; // optional, falls du später zusätzlich /v1/responses nutzen willst config.gateway.http.endpoints.responses ??= {}; config.gateway.http.endpoints.responses.enabled = true; config.agents ??= {}; config.agents.defaults ??= {}; config.agents.defaults.model ??= {}; config.agents.defaults.model.primary = defaultModel; fs.mkdirSync(stateDir, { recursive: true }); fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8"); let runtimeConfig = {}; if (fs.existsSync(runtimeConfigPath)) { try { runtimeConfig = JSON.parse(fs.readFileSync(runtimeConfigPath, "utf8")); } catch { runtimeConfig = {}; } } runtimeConfig.version = 1; runtimeConfig.updatedAt = new Date().toISOString(); runtimeConfig.defaults = { provider: runtimeConfig.defaults?.provider || defaultProvider, agentId: runtimeConfig.defaults?.agentId || defaultAgent, model: runtimeConfig.defaults?.model || "", sessionKey: runtimeConfig.defaults?.sessionKey || defaultSessionKey, timeoutMs: Number(runtimeConfig.defaults?.timeoutMs) > 0 ? Number(runtimeConfig.defaults.timeoutMs) : defaultTimeoutMs, temperature: Number.isFinite(Number(runtimeConfig.defaults?.temperature)) ? Number(runtimeConfig.defaults.temperature) : defaultTemperature, maxTokens: runtimeConfig.defaults?.maxTokens === null || runtimeConfig.defaults?.maxTokens === undefined ? defaultMaxTokens : Number(runtimeConfig.defaults.maxTokens), }; runtimeConfig.providers = { openclaw: { enabled: true, label: "OpenClaw (internal)", defaultAgent: defaultAgent, models: openclawAgents.map((agentId) => ({ id: `openclaw:${agentId}`, provider: "openclaw", label: `OpenClaw Agent: ${agentId}`, agentId, })), }, groq: { enabled: Boolean(firstNonEmpty(process.env.GROQ_API_KEY)), label: "Groq", baseUrl: firstNonEmpty(process.env.OPENCLAW_GROQ_BASE_URL) || "https://api.groq.com/openai/v1/chat/completions", models: groqModels.map((model) => ({ id: model, provider: "groq", label: model, })), }, huggingface: { enabled: Boolean( firstNonEmpty( process.env.OPENCLAW_HUGGINGFACE_API_KEY, process.env.HF_TOKEN ) ), label: "Hugging Face", baseUrl: firstNonEmpty(process.env.OPENCLAW_HUGGINGFACE_BASE_URL) || "https://router.huggingface.co/v1/chat/completions", models: huggingFaceModels.map((model) => ({ id: model, provider: "huggingface", label: model, })), }, }; runtimeConfig.meta = { note: "Runtime config is container-local. Persist long-term source-of-truth in your Vercel app / database.", }; fs.writeFileSync(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2), "utf8"); ensureFile(usageLogPath, ""); console.log( `[openclaw-hf-setup] wrote ${configPath} allowedOrigins=${allowedOrigins.length} trustedProxies=${trustedProxies.length} chatCompletions=enabled runtimeConfig=ready` );