diff --git a/setup.mjs b/setup.mjs index d3e0a19..dd5e3d0 100755 --- a/setup.mjs +++ b/setup.mjs @@ -12,7 +12,7 @@ * 4. Creates start.sh for easy launch * 5. Optionally starts the proxy */ -import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from "node:fs"; +import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, readdirSync } from "node:fs"; import { execSync } from "node:child_process"; import { join, dirname } from "node:path"; import { homedir } from "node:os"; @@ -107,106 +107,112 @@ try { warn("Make sure you're logged in: claude login"); } -// Check openclaw config -if (!existsSync(CONFIG_PATH)) fail(`OpenClaw config not found at ${CONFIG_PATH}`); -log(`OpenClaw config: ${CONFIG_PATH}`); +// Check openclaw config (optional — OCP runs standalone without OpenClaw) +const OPENCLAW_PRESENT = existsSync(CONFIG_PATH); +if (OPENCLAW_PRESENT) { + log(`OpenClaw config: ${CONFIG_PATH}`); +} else { + warn(`OpenClaw not detected at ${CONFIG_PATH} — skipping OpenClaw integration.`); + warn(`To register OCP with OpenClaw later, install OpenClaw and re-run \`node setup.mjs\`,`); + warn(`or run \`ocp update\` if OpenClaw is installed afterward.`); +} // ── Step 2: Patch openclaw.json ───────────────────────────────────────── -console.log("\n📝 Configuring OpenClaw...\n"); +if (OPENCLAW_PRESENT) { + console.log("\n📝 Configuring OpenClaw...\n"); -const config = readJSON(CONFIG_PATH); + const config = readJSON(CONFIG_PATH); -// Ensure models.providers exists -if (!config.models) config.models = {}; -if (!config.models.providers) config.models.providers = {}; + // Ensure models.providers exists + if (!config.models) config.models = {}; + if (!config.models.providers) config.models.providers = {}; -// Add/update claude-local provider -config.models.providers[PROVIDER_NAME] = { - baseUrl: `http://127.0.0.1:${PORT}/v1`, - api: "openai-completions", - authHeader: false, - models: MODELS, -}; -log(`Provider "${PROVIDER_NAME}" → http://127.0.0.1:${PORT}/v1`); + // Add/update claude-local provider + config.models.providers[PROVIDER_NAME] = { + baseUrl: `http://127.0.0.1:${PORT}/v1`, + api: "openai-completions", + authHeader: false, + models: MODELS, + }; + log(`Provider "${PROVIDER_NAME}" → http://127.0.0.1:${PORT}/v1`); -// Ensure auth profile in config -if (!config.auth) config.auth = {}; -if (!config.auth.profiles) config.auth.profiles = {}; -config.auth.profiles[`${PROVIDER_NAME}:default`] = { - provider: PROVIDER_NAME, - mode: "api_key", -}; -log(`Auth profile "${PROVIDER_NAME}:default" registered`); + // Ensure auth profile in config + if (!config.auth) config.auth = {}; + if (!config.auth.profiles) config.auth.profiles = {}; + config.auth.profiles[`${PROVIDER_NAME}:default`] = { + provider: PROVIDER_NAME, + mode: "api_key", + }; + log(`Auth profile "${PROVIDER_NAME}:default" registered`); -// Add models to agents.defaults.models -if (!config.agents) config.agents = {}; -if (!config.agents.defaults) config.agents.defaults = {}; -if (!config.agents.defaults.models) config.agents.defaults.models = {}; -for (const [key, val] of Object.entries(MODEL_ALIASES)) { - config.agents.defaults.models[key] = val; -} -log(`Model aliases added to agents.defaults.models`); - -// Set idleTimeoutSeconds to 0 — critical for Claude tool-use. -// When Claude calls tools (Bash, Read, etc.), the token stream pauses for 30-120s. -// OpenClaw's default idleTimeoutSeconds (60s) kills the connection mid-tool-call, -// causing exit 143 (SIGTERM) and stuck sessions. Setting to 0 disables the idle timer. -if (!config.agents.defaults.llm) config.agents.defaults.llm = {}; -if (config.agents.defaults.llm.idleTimeoutSeconds === undefined || - config.agents.defaults.llm.idleTimeoutSeconds > 0) { - config.agents.defaults.llm.idleTimeoutSeconds = 0; - log(`Set agents.defaults.llm.idleTimeoutSeconds = 0 (prevents tool-call timeouts)`); -} else { - log(`idleTimeoutSeconds already configured: ${config.agents.defaults.llm.idleTimeoutSeconds}`); -} - -writeJSON(CONFIG_PATH, config); -log(`Config saved`); - -// ── Step 3: Patch auth-profiles.json ──────────────────────────────────── -console.log("\n🔑 Configuring auth profiles...\n"); - -// Find all agent auth-profiles.json files -const agentsDir = join(OPENCLAW_DIR, "agents"); -const agentDirs = existsSync(agentsDir) - ? readdirSync(agentsDir).filter((d) => { - const ap = join(agentsDir, d, "agent", "auth-profiles.json"); - return existsSync(ap); - }) - : []; - -import { readdirSync } from "node:fs"; - -for (const agentId of agentDirs) { - const apPath = join(agentsDir, agentId, "agent", "auth-profiles.json"); - try { - const ap = readJSON(apPath); - if (!ap.profiles) ap.profiles = {}; - - // Add claude-local profile if missing - if (!ap.profiles[`${PROVIDER_NAME}:default`]) { - ap.profiles[`${PROVIDER_NAME}:default`] = { - type: "api_key", - provider: PROVIDER_NAME, - key: "local-proxy-no-auth", - }; - } - - // Add to lastGood if missing - if (!ap.lastGood) ap.lastGood = {}; - if (!ap.lastGood[PROVIDER_NAME]) { - ap.lastGood[PROVIDER_NAME] = `${PROVIDER_NAME}:default`; - } - - writeJSON(apPath, ap); - log(`Agent "${agentId}" auth profile updated`); - } catch (e) { - warn(`Skipped agent "${agentId}": ${e.message}`); + // Add models to agents.defaults.models + if (!config.agents) config.agents = {}; + if (!config.agents.defaults) config.agents.defaults = {}; + if (!config.agents.defaults.models) config.agents.defaults.models = {}; + for (const [key, val] of Object.entries(MODEL_ALIASES)) { + config.agents.defaults.models[key] = val; } -} + log(`Model aliases added to agents.defaults.models`); -if (agentDirs.length === 0) { - warn("No agent auth-profiles.json found — you may need to restart the gateway first"); + // Set idleTimeoutSeconds to 0 — critical for Claude tool-use. + // When Claude calls tools (Bash, Read, etc.), the token stream pauses for 30-120s. + // OpenClaw's default idleTimeoutSeconds (60s) kills the connection mid-tool-call, + // causing exit 143 (SIGTERM) and stuck sessions. Setting to 0 disables the idle timer. + if (!config.agents.defaults.llm) config.agents.defaults.llm = {}; + if (config.agents.defaults.llm.idleTimeoutSeconds === undefined || + config.agents.defaults.llm.idleTimeoutSeconds > 0) { + config.agents.defaults.llm.idleTimeoutSeconds = 0; + log(`Set agents.defaults.llm.idleTimeoutSeconds = 0 (prevents tool-call timeouts)`); + } else { + log(`idleTimeoutSeconds already configured: ${config.agents.defaults.llm.idleTimeoutSeconds}`); + } + + writeJSON(CONFIG_PATH, config); + log(`Config saved`); + + // ── Step 3: Patch auth-profiles.json ──────────────────────────────────── + console.log("\n🔑 Configuring auth profiles...\n"); + + // Find all agent auth-profiles.json files + const agentsDir = join(OPENCLAW_DIR, "agents"); + const agentDirs = existsSync(agentsDir) + ? readdirSync(agentsDir).filter((d) => { + const ap = join(agentsDir, d, "agent", "auth-profiles.json"); + return existsSync(ap); + }) + : []; + + for (const agentId of agentDirs) { + const apPath = join(agentsDir, agentId, "agent", "auth-profiles.json"); + try { + const ap = readJSON(apPath); + if (!ap.profiles) ap.profiles = {}; + + // Add claude-local profile if missing + if (!ap.profiles[`${PROVIDER_NAME}:default`]) { + ap.profiles[`${PROVIDER_NAME}:default`] = { + type: "api_key", + provider: PROVIDER_NAME, + key: "local-proxy-no-auth", + }; + } + + // Add to lastGood if missing + if (!ap.lastGood) ap.lastGood = {}; + if (!ap.lastGood[PROVIDER_NAME]) { + ap.lastGood[PROVIDER_NAME] = `${PROVIDER_NAME}:default`; + } + + writeJSON(apPath, ap); + log(`Agent "${agentId}" auth profile updated`); + } catch (e) { + warn(`Skipped agent "${agentId}": ${e.message}`); + } + } + + if (agentDirs.length === 0) { + warn("No agent auth-profiles.json found — you may need to restart the gateway first"); + } } // ── Step 4: Create start.sh ───────────────────────────────────────────── @@ -238,31 +244,46 @@ if (!DRY_RUN) { log(`Launcher: ${startPath}`); // ── Step 5: Summary ───────────────────────────────────────────────────── -console.log(` -╔══════════════════════════════════════════════════════════════╗ -║ Setup complete! ║ -╠══════════════════════════════════════════════════════════════╣ -║ ║ -║ Provider: ${PROVIDER_NAME.padEnd(44)}║ -║ Port: ${String(PORT).padEnd(44)}║ -║ Models: ${`see models.json (${MODELS.length} available)`.padEnd(44)}║ -║ Default: ${DEFAULT_MODEL_ID.padEnd(44)}║ -║ ║ -║ Start proxy: ║ -║ bash ${startPath.replace(HOME, "~").padEnd(50)}║ -║ ║ -║ Or directly: ║ -║ node ${serverPath.replace(HOME, "~").padEnd(49)}║ -║ ║ -║ Set as default model in openclaw.json: ║ -║ agents.defaults.model.primary = ║ -║ "${PROVIDER_NAME}/${DEFAULT_MODEL_ID}"${" ".repeat(Math.max(0, 30 - PROVIDER_NAME.length - DEFAULT_MODEL_ID.length))}║ -║ ║ -║ Then restart gateway: ║ -║ openclaw gateway restart ║ -║ ║ -╚══════════════════════════════════════════════════════════════╝ -`); +const banner = [ + `╔══════════════════════════════════════════════════════════════╗`, + `║ Setup complete! ║`, + `╠══════════════════════════════════════════════════════════════╣`, + `║ ║`, + `║ Provider: ${PROVIDER_NAME.padEnd(44)}║`, + `║ Port: ${String(PORT).padEnd(44)}║`, + `║ Models: ${`see models.json (${MODELS.length} available)`.padEnd(44)}║`, + `║ Default: ${DEFAULT_MODEL_ID.padEnd(44)}║`, + `║ ║`, + `║ Start proxy: ║`, + `║ bash ${startPath.replace(HOME, "~").padEnd(50)}║`, + `║ ║`, + `║ Or directly: ║`, + `║ node ${serverPath.replace(HOME, "~").padEnd(49)}║`, + `║ ║`, +]; +if (OPENCLAW_PRESENT) { + banner.push( + `║ Set as default model in openclaw.json: ║`, + `║ agents.defaults.model.primary = ║`, + `║ "${PROVIDER_NAME}/${DEFAULT_MODEL_ID}"${" ".repeat(Math.max(0, 30 - PROVIDER_NAME.length - DEFAULT_MODEL_ID.length))}║`, + `║ ║`, + `║ Then restart gateway: ║`, + `║ openclaw gateway restart ║`, + `║ ║`, + ); +} else { + banner.push( + `║ OpenClaw not detected — running in standalone mode. ║`, + `║ Point your IDE (Cline / Cursor / Continue / OpenCode / ║`, + `║ Aider / OpenClaw) at: ║`, + `║ http://${BIND_ADDRESS}:${String(PORT)}/v1${" ".repeat(Math.max(0, 47 - BIND_ADDRESS.length - String(PORT).length))}║`, + `║ ║`, + `║ See README § "Client Setup" for per-IDE instructions. ║`, + `║ ║`, + ); +} +banner.push(`╚══════════════════════════════════════════════════════════════╝`); +console.log("\n" + banner.join("\n") + "\n"); // ── Step 6: Optionally start ──────────────────────────────────────────── if (!SKIP_START && !DRY_RUN) {