1 line
46 KiB
JSON
1 line
46 KiB
JSON
{"repo": "bidewio/better-openclaw", "files": {"packages/core/src/frameworks/openclaw.ts": {"983125bfe00f076d9a2862ce0fbb9ad7b2cdfa65": {"t": 1785745580.992133, "n": 10259, "h": "33ec106a256a0f6e", "c": "import { randomBytes } from \"node:crypto\";\nimport { generateOpenClawConfig } from \"../generators/openclaw-json.js\";\nimport type { ResolverOutput } from \"../types.js\";\nimport type {\n\tAgentFrameworkDefinition,\n\tEnvLine,\n\tFrameworkComposeOptions,\n\tFrameworkConfigOptions,\n\tFrameworkConfigResult,\n\tFrameworkEnvOptions,\n\tGatewayBuildResult,\n} from \"./types.js\";\n\n// ─── Image Variants ─────────────────────────────────────────────────────────\n\nconst IMAGE_VARIANTS: Record<string, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\nfunction getOpenclawImage(variant: string, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant] ?? IMAGE_VARIANTS.official;\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n// ─── Provider API Keys ──────────────────────────────────────────────────────\n\nconst PROVIDER_ENV_KEYS = [\n\t\"OPENAI_API_KEY\",\n\t\"ANTHROPIC_API_KEY\",\n\t\"GOOGLE_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"OLLAMA_API_KEY\",\n\t\"NVIDIA_API_KEY\",\n];\n\n// ─── OpenClaw Framework Definition ──────────────────────────────────────────\n\nexport const openclawFramework: AgentFrameworkDefinition = {\n\tid: \"openclaw\",\n\tname: \"OpenClaw\",\n\ticon: \"🦀\",\n\tdescription: \"Full-featured AI agent gateway with CLI, bridge, and skill system\",\n\truntime: \"node\",\n\tcanBePrimary: true,\n\tcanBeCompanion: false,\n\tnetworkName: \"openclaw-network\",\n\timageVariants: IMAGE_VARIANTS,\n\tdefaultImageVariant: \"official\",\n\n\tbuildGatewayService(\n\t\tresolved: ResolverOutput,\n\t\toptions: FrameworkComposeOptions,\n\t\tdependsOn?: Record<string, { condition: string }>,\n\t): GatewayBuildResult {\n\t\tconst allVolumes = new Set<string>();\n\n\t\t// Gateway environment\n\t\tconst gatewayEnv: Record<string, string> = {\n\t\t\tHOME: \"/home/node\",\n\t\t\tTERM: \"xterm-256color\",\n\t\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t\t\t// Container user/group ID for bind-mount ownership\n\t\t\tPUID: \"${PUID:-1000}\",\n\t\t\tPGID: \"${PGID:-1000}\",\n\t\t};\n\n\t\t// Add AI provider API keys to gateway environment\n\t\tfor (const key of PROVIDER_ENV_KEYS) {\n\t\t\tgatewayEnv[key] = `\\${${key}}`;\n\t\t}\n\n\t\t// Add notification webhook env vars to gateway environment\n\t\tconst notifyEnvKeys = [\n\t\t\t\"NOTIFY_DISCORD\",\n\t\t\t\"NOTIFY_SLACK\",\n\t\t\t\"NOTIFY_TELEGRAM_TOKEN\",\n\t\t\t\"NOTIFY_TELEGRAM_CHAT\",\n\t\t\t\"NOTIFY_EMAIL_TO\",\n\t\t\t\"NOTIFY_EMAIL_FROM\",\n\t\t\t\"NOTIFY_EMAIL_SMTP\",\n\t\t\t\"NOTIFY_NTFY_TOPIC\",\n\t\t\t\"NOTIFY_PUSHOVER_TOKEN\",\n\t\t\t\"NOTIFY_PUSHOVER_USER\",\n\t\t\t\"NOTIFY_GOTIFY_URL\",\n\t\t\t\"NOTIFY_GOTIFY_TOKEN\",\n\t\t];\n\t\tfor (const key of notifyEnvKeys) {\n\t\t\tgatewayEnv[key] = `\\${${key}:-}`;\n\t\t}\n\n\t\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\t\tconst gatewayVolumes: string[] = [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t];\n\n\t\t// Collect env vars and volume mounts from companion services\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tfor (const env of def.openclawEnvVars) {\n\t\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t\t}\n\t\t\tif (def.openclawVolumeMounts) {\n\t\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\t\tconst isBindMount =\n\t\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\t\tif (!isBindMount) {\n\t\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t\t}\n\t\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Gateway service\n\t\tconst defaultImage = getOpenclawImage(\n\t\t\toptions.frameworkImageVariant ?? \"official\",\n\t\t\toptions.frameworkVersion,\n\t\t);\n\t\tconst gateway: Record<string, unknown> = {\n\t\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\t\tenvironment: gatewayEnv,\n\t\t\tvolumes: gatewayVolumes,\n\t\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\t\tnetworks: [this.networkName],\n\t\t\tinit: true,\n\t\t\trestart: \"unless-stopped\",\n\t\t\tcommand: [\n\t\t\t\t\"node\",\n\t\t\t\t\"dist/index.js\",\n\t\t\t\t\"gateway\",\n\t\t\t\t\"--bind\",\n\t\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\t\"--port\",\n\t\t\t\t\"18789\",\n\t\t\t\t\"--allow-unconfigured\",\n\t\t\t],\n\t\t\thealthcheck: {\n\t\t\t\ttest: [\n\t\t\t\t\t\"CMD\",\n\t\t\t\t\t\"node\",\n\t\t\t\t\t\"-e\",\n\t\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t\t],\n\t\t\t\tinterval: \"30s\",\n\t\t\t\ttimeout: \"5s\",\n\t\t\t\tretries: 5,\n\t\t\t\tstart_period: \"20s\",\n\t\t\t},\n\t\t};\n\n\t\t// Traefik labels for the gateway\n\t\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\t\tif (gwTraefikLabels) {\n\t\t\tgateway.labels = gwTraefikLabels;\n\t\t}\n\n\t\tif (options.bareMetalNativeHost) {\n\t\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t\t}\n\n\t\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\t\tgateway.depends_on = dependsOn;\n\t\t}\n\n\t\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t\tconst cliEnv: Record<string, string> = {\n\t\t\tHOME: \"/home/node\",\n\t\t\tTERM: \"xterm-256color\",\n\t\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t\tBROWSER: \"echo\",\n\t\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t\t};\n\n\t\tfor (const key of PROVIDER_ENV_KEYS) {\n\t\t\tcliEnv[key] = `\\${${key}}`;\n\t\t}\n\n\t\tconst cliService: Record<string, unknown> = {\n\t\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\t\tenvironment: cliEnv,\n\t\t\tvolumes: [\n\t\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t\t],\n\t\t\tstdin_open: true,\n\t\t\ttty: true,\n\t\t\tinit: true,\n\t\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\t\tdepends_on: [\"openclaw-gateway\"],\n\t\t};\n\n\t\treturn { gatewayService: gateway, cliService, allVolumes };\n\t},\n\n\tgenerateConfig(resolved: ResolverOutput, options: FrameworkConfigOptions): FrameworkConfigResult {\n\t\tconst content = generateOpenClawConfig(resolved, {\n\t\t\tdeploymentType: options.deploymentType as any,\n\t\t\tgatewayPort: options.gatewayPort,\n\t\t\topenclawVersion: options.frameworkVersion,\n\t\t\tnotificationProviders: options.notificationProviders,\n\t\t});\n\t\treturn {\n\t\t\tpath: \"openclaw/config/openclaw.json\",\n\t\t\tcontent,\n\t\t};\n\t},\n\n\tgetBaseEnvVars(options: FrameworkEnvOptions): EnvLine[] {\n\t\tconst version = options.frameworkVersion ?? \"latest\";\n\t\tconst gatewayToken = options.generateSecrets ? randomBytes(24).toString(\"hex\") : \"\";\n\n\t\tconst imageVariantMap: Record<string, string> = {\n\t\t\tofficial: \"\",\n\t\t\tcoolify: \"coollabsio/openclaw:latest\",\n\t\t\talpine: \"alpine/openclaw:latest\",\n\t\t};\n\t\tconst imageValue = imageVariantMap[options.frameworkImageVariant ?? \"official\"] ?? \"\";\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tcomment: \"OpenClaw version to deploy\",\n\t\t\t\tkey: \"OPENCLAW_VERSION\",\n\t\t\t\texampleValue: version,\n\t\t\t\tactualValue: version,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Authentication token for the OpenClaw gateway API\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_TOKEN\",\n\t\t\t\texampleValue: \"your_gateway_token_here\",\n\t\t\t\tactualValue: gatewayToken,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Port the OpenClaw gateway listens on\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_PORT\",\n\t\t\t\texampleValue: \"18789\",\n\t\t\t\tactualValue: \"18789\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Port for the OpenClaw ACP bridge (WebSocket)\",\n\t\t\t\tkey: \"OPENCLAW_BRIDGE_PORT\",\n\t\t\t\texampleValue: \"18790\",\n\t\t\t\tactualValue: \"18790\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment:\n\t\t\t\t\t\"Gateway network bind mode: 'lan' (all interfaces, required for Docker). Use 'loopback' only for native (non-Docker) installs with Tailscale serve/funnel\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_BIND\",\n\t\t\t\texampleValue: \"lan\",\n\t\t\t\tactualValue: \"lan\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Host path to OpenClaw configuration directory\",\n\t\t\t\tkey: \"OPENCLAW_CONFIG_DIR\",\n\t\t\t\texampleValue: \"./openclaw/config\",\n\t\t\t\tactualValue: \"./openclaw/config\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Host path to OpenClaw workspace directory\",\n\t\t\t\tkey: \"OPENCLAW_WORKSPACE_DIR\",\n\t\t\t\texampleValue: \"./openclaw/workspace\",\n\t\t\t\tactualValue: \"./openclaw/workspace\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: `OpenClaw Docker image variant: ${options.frameworkImageVariant ?? \"official\"} (official, coolify, alpine)`,\n\t\t\t\tkey: \"OPENCLAW_IMAGE\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: imageValue,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment:\n\t\t\t\t\t\"Extra bind mounts for gateway and CLI containers (comma-separated, format: source:target[:options])\",\n\t\t\t\tkey: \"OPENCLAW_EXTRA_MOUNTS\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Named volume or host path for /home/node persistence across container restarts\",\n\t\t\t\tkey: \"OPENCLAW_HOME_VOLUME\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Extra apt packages to install during Docker image build (space-separated)\",\n\t\t\t\tkey: \"OPENCLAW_DOCKER_APT_PACKAGES\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Alternative auth: gateway password (use token OR password, not both)\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_PASSWORD\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Override state directory path (default: ~/.openclaw)\",\n\t\t\t\tkey: \"OPENCLAW_STATE_DIR\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Override config file path (default: ~/.openclaw/openclaw.json)\",\n\t\t\t\tkey: \"OPENCLAW_CONFIG_PATH\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Import missing keys from login shell profile (set to 1 to enable)\",\n\t\t\t\tkey: \"OPENCLAW_LOAD_SHELL_ENV\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t];\n\t},\n\n\tgetMandatoryServices(): string[] {\n\t\treturn [\"convex\", \"mission-control\", \"tailscale\"];\n\t},\n\n\tgetRecommendedServices(): string[] {\n\t\treturn [\"clawrouter\", \"bridge\"];\n\t},\n\n\tgetProviderEnvKeys(): string[] {\n\t\treturn [...PROVIDER_ENV_KEYS];\n\t},\n\n\tgetEnvSectionName(): string {\n\t\treturn \"OpenClaw Core\";\n\t},\n};\n"}, "39f532b33b416f34a8077308141367006513f475": {"t": 1785754476.9827263, "n": 9600, "h": "e97d8b91ba7e4770", "c": "import { randomBytes } from \"node:crypto\";\nimport { generateOpenClawConfig } from \"../generators/openclaw-json.js\";\nimport type { ResolverOutput } from \"../types.js\";\nimport type {\n\tAgentFrameworkDefinition,\n\tEnvLine,\n\tFrameworkComposeOptions,\n\tFrameworkConfigOptions,\n\tFrameworkConfigResult,\n\tFrameworkEnvOptions,\n\tGatewayBuildResult,\n} from \"./types.js\";\n\n// ─── Image Variants ─────────────────────────────────────────────────────────\n\nconst IMAGE_VARIANTS: Record<string, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\nfunction getOpenclawImage(variant: string, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant] ?? IMAGE_VARIANTS.official;\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n// ─── Provider API Keys ──────────────────────────────────────────────────────\n\nconst PROVIDER_ENV_KEYS = [\n\t\"OPENAI_API_KEY\",\n\t\"ANTHROPIC_API_KEY\",\n\t\"GOOGLE_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"OLLAMA_API_KEY\",\n];\n\n// ─── OpenClaw Framework Definition ──────────────────────────────────────────\n\nexport const openclawFramework: AgentFrameworkDefinition = {\n\tid: \"openclaw\",\n\tname: \"OpenClaw\",\n\ticon: \"🦀\",\n\tdescription: \"Full-featured AI agent gateway with CLI, bridge, and skill system\",\n\truntime: \"node\",\n\tcanBePrimary: true,\n\tcanBeCompanion: false,\n\tnetworkName: \"openclaw-network\",\n\timageVariants: IMAGE_VARIANTS,\n\tdefaultImageVariant: \"official\",\n\n\tbuildGatewayService(\n\t\tresolved: ResolverOutput,\n\t\toptions: FrameworkComposeOptions,\n\t\tdependsOn?: Record<string, { condition: string }>,\n\t): GatewayBuildResult {\n\t\tconst allVolumes = new Set<string>();\n\n\t\t// Gateway environment\n\t\tconst gatewayEnv: Record<string, string> = {\n\t\t\tHOME: \"/home/node\",\n\t\t\tTERM: \"xterm-256color\",\n\t\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t\t};\n\n\t\t// Add AI provider API keys to gateway environment\n\t\tfor (const key of PROVIDER_ENV_KEYS) {\n\t\t\tgatewayEnv[key] = `\\${${key}}`;\n\t\t}\n\n\t\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\t\tconst gatewayVolumes: string[] = [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t];\n\n\t\t// Collect env vars and volume mounts from companion services\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tfor (const env of def.openclawEnvVars) {\n\t\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t\t}\n\t\t\tif (def.openclawVolumeMounts) {\n\t\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\t\tconst isBindMount =\n\t\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\t\tif (!isBindMount) {\n\t\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t\t}\n\t\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Gateway service\n\t\tconst defaultImage = getOpenclawImage(\n\t\t\toptions.frameworkImageVariant ?? \"official\",\n\t\t\toptions.frameworkVersion,\n\t\t);\n\t\tconst gateway: Record<string, unknown> = {\n\t\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\t\tenvironment: gatewayEnv,\n\t\t\tvolumes: gatewayVolumes,\n\t\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\t\tnetworks: [this.networkName],\n\t\t\tinit: true,\n\t\t\trestart: \"unless-stopped\",\n\t\t\tcommand: [\n\t\t\t\t\"node\",\n\t\t\t\t\"dist/index.js\",\n\t\t\t\t\"gateway\",\n\t\t\t\t\"--bind\",\n\t\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\t\"--port\",\n\t\t\t\t\"18789\",\n\t\t\t\t\"--allow-unconfigured\",\n\t\t\t],\n\t\t\thealthcheck: {\n\t\t\t\ttest: [\n\t\t\t\t\t\"CMD\",\n\t\t\t\t\t\"node\",\n\t\t\t\t\t\"-e\",\n\t\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t\t],\n\t\t\t\tinterval: \"30s\",\n\t\t\t\ttimeout: \"5s\",\n\t\t\t\tretries: 5,\n\t\t\t\tstart_period: \"20s\",\n\t\t\t},\n\t\t};\n\n\t\t// Traefik labels for the gateway\n\t\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\t\tif (gwTraefikLabels) {\n\t\t\tgateway.labels = gwTraefikLabels;\n\t\t}\n\n\t\tif (options.bareMetalNativeHost) {\n\t\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t\t}\n\n\t\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\t\tgateway.depends_on = dependsOn;\n\t\t}\n\n\t\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t\tconst cliEnv: Record<string, string> = {\n\t\t\tHOME: \"/home/node\",\n\t\t\tTERM: \"xterm-256color\",\n\t\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t\tBROWSER: \"echo\",\n\t\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t\t};\n\n\t\tfor (const key of PROVIDER_ENV_KEYS) {\n\t\t\tcliEnv[key] = `\\${${key}}`;\n\t\t}\n\n\t\tconst cliService: Record<string, unknown> = {\n\t\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\t\tenvironment: cliEnv,\n\t\t\tvolumes: [\n\t\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t\t],\n\t\t\tstdin_open: true,\n\t\t\ttty: true,\n\t\t\tinit: true,\n\t\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\t\tdepends_on: [\"openclaw-gateway\"],\n\t\t};\n\n\t\treturn { gatewayService: gateway, cliService, allVolumes };\n\t},\n\n\tgenerateConfig(resolved: ResolverOutput, options: FrameworkConfigOptions): FrameworkConfigResult {\n\t\tconst content = generateOpenClawConfig(resolved, {\n\t\t\tdeploymentType: options.deploymentType as any,\n\t\t\tgatewayPort: options.gatewayPort,\n\t\t\topenclawVersion: options.frameworkVersion,\n\t\t});\n\t\treturn {\n\t\t\tpath: \"openclaw/config/openclaw.json\",\n\t\t\tcontent,\n\t\t};\n\t},\n\n\tgetBaseEnvVars(options: FrameworkEnvOptions): EnvLine[] {\n\t\tconst version = options.frameworkVersion ?? \"latest\";\n\t\tconst gatewayToken = options.generateSecrets ? randomBytes(24).toString(\"hex\") : \"\";\n\n\t\tconst imageVariantMap: Record<string, string> = {\n\t\t\tofficial: \"\",\n\t\t\tcoolify: \"coollabsio/openclaw:latest\",\n\t\t\talpine: \"alpine/openclaw:latest\",\n\t\t};\n\t\tconst imageValue = imageVariantMap[options.frameworkImageVariant ?? \"official\"] ?? \"\";\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tcomment: \"OpenClaw version to deploy\",\n\t\t\t\tkey: \"OPENCLAW_VERSION\",\n\t\t\t\texampleValue: version,\n\t\t\t\tactualValue: version,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Authentication token for the OpenClaw gateway API\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_TOKEN\",\n\t\t\t\texampleValue: \"your_gateway_token_here\",\n\t\t\t\tactualValue: gatewayToken,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Port the OpenClaw gateway listens on\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_PORT\",\n\t\t\t\texampleValue: \"18789\",\n\t\t\t\tactualValue: \"18789\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Port for the OpenClaw ACP bridge (WebSocket)\",\n\t\t\t\tkey: \"OPENCLAW_BRIDGE_PORT\",\n\t\t\t\texampleValue: \"18790\",\n\t\t\t\tactualValue: \"18790\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment:\n\t\t\t\t\t\"Gateway network bind mode: 'lan' (all interfaces, required for Docker). Use 'loopback' only for native (non-Docker) installs with Tailscale serve/funnel\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_BIND\",\n\t\t\t\texampleValue: \"lan\",\n\t\t\t\tactualValue: \"lan\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Host path to OpenClaw configuration directory\",\n\t\t\t\tkey: \"OPENCLAW_CONFIG_DIR\",\n\t\t\t\texampleValue: \"./openclaw/config\",\n\t\t\t\tactualValue: \"./openclaw/config\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Host path to OpenClaw workspace directory\",\n\t\t\t\tkey: \"OPENCLAW_WORKSPACE_DIR\",\n\t\t\t\texampleValue: \"./openclaw/workspace\",\n\t\t\t\tactualValue: \"./openclaw/workspace\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: `OpenClaw Docker image variant: ${options.frameworkImageVariant ?? \"official\"} (official, coolify, alpine)`,\n\t\t\t\tkey: \"OPENCLAW_IMAGE\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: imageValue,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment:\n\t\t\t\t\t\"Extra bind mounts for gateway and CLI containers (comma-separated, format: source:target[:options])\",\n\t\t\t\tkey: \"OPENCLAW_EXTRA_MOUNTS\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Named volume or host path for /home/node persistence across container restarts\",\n\t\t\t\tkey: \"OPENCLAW_HOME_VOLUME\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Extra apt packages to install during Docker image build (space-separated)\",\n\t\t\t\tkey: \"OPENCLAW_DOCKER_APT_PACKAGES\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Alternative auth: gateway password (use token OR password, not both)\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_PASSWORD\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Override state directory path (default: ~/.openclaw)\",\n\t\t\t\tkey: \"OPENCLAW_STATE_DIR\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Override config file path (default: ~/.openclaw/openclaw.json)\",\n\t\t\t\tkey: \"OPENCLAW_CONFIG_PATH\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Import missing keys from login shell profile (set to 1 to enable)\",\n\t\t\t\tkey: \"OPENCLAW_LOAD_SHELL_ENV\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t];\n\t},\n\n\tgetMandatoryServices(): string[] {\n\t\treturn [\"convex\", \"mission-control\", \"tailscale\"];\n\t},\n\n\tgetRecommendedServices(): string[] {\n\t\treturn [\"clawrouter\"];\n\t},\n\n\tgetProviderEnvKeys(): string[] {\n\t\treturn [...PROVIDER_ENV_KEYS];\n\t},\n\n\tgetEnvSectionName(): string {\n\t\treturn \"OpenClaw Core\";\n\t},\n};\n"}, "ff7d9865238110b5ca87d3c68e56d3e9464c9d5e": {"t": 1785754479.3921685, "n": 9608, "h": "00c84aa0dec9ff69", "c": "import { randomBytes } from \"node:crypto\";\nimport { generateOpenClawConfig } from \"../generators/openclaw-json.js\";\nimport type { ResolverOutput } from \"../types.js\";\nimport type {\n\tAgentFrameworkDefinition,\n\tEnvLine,\n\tFrameworkComposeOptions,\n\tFrameworkConfigOptions,\n\tFrameworkConfigResult,\n\tFrameworkEnvOptions,\n\tGatewayBuildResult,\n} from \"./types.js\";\n\n// ─── Image Variants ─────────────────────────────────────────────────────────\n\nconst IMAGE_VARIANTS: Record<string, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\nfunction getOpenclawImage(variant: string, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant] ?? IMAGE_VARIANTS.official;\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n// ─── Provider API Keys ──────────────────────────────────────────────────────\n\nconst PROVIDER_ENV_KEYS = [\n\t\"OPENAI_API_KEY\",\n\t\"ANTHROPIC_API_KEY\",\n\t\"GOOGLE_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"OLLAMA_API_KEY\",\n];\n\n// ─── OpenClaw Framework Definition ──────────────────────────────────────────\n\nexport const openclawFramework: AgentFrameworkDefinition = {\n\tid: \"openclaw\",\n\tname: \"OpenClaw\",\n\ticon: \"🦀\",\n\tdescription: \"Full-featured AI agent gateway with CLI, bridge, and skill system\",\n\truntime: \"node\",\n\tcanBePrimary: true,\n\tcanBeCompanion: false,\n\tnetworkName: \"openclaw-network\",\n\timageVariants: IMAGE_VARIANTS,\n\tdefaultImageVariant: \"official\",\n\n\tbuildGatewayService(\n\t\tresolved: ResolverOutput,\n\t\toptions: FrameworkComposeOptions,\n\t\tdependsOn?: Record<string, { condition: string }>,\n\t): GatewayBuildResult {\n\t\tconst allVolumes = new Set<string>();\n\n\t\t// Gateway environment\n\t\tconst gatewayEnv: Record<string, string> = {\n\t\t\tHOME: \"/home/node\",\n\t\t\tTERM: \"xterm-256color\",\n\t\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t\t};\n\n\t\t// Add AI provider API keys to gateway environment\n\t\tfor (const key of PROVIDER_ENV_KEYS) {\n\t\t\tgatewayEnv[key] = `\\${${key}}`;\n\t\t}\n\n\t\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\t\tconst gatewayVolumes: string[] = [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t];\n\n\t\t// Collect env vars and volume mounts from companion services\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tfor (const env of def.openclawEnvVars) {\n\t\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t\t}\n\t\t\tif (def.openclawVolumeMounts) {\n\t\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\t\tconst isBindMount =\n\t\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\t\tif (!isBindMount) {\n\t\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t\t}\n\t\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Gateway service\n\t\tconst defaultImage = getOpenclawImage(\n\t\t\toptions.frameworkImageVariant ?? \"official\",\n\t\t\toptions.frameworkVersion,\n\t\t);\n\t\tconst gateway: Record<string, unknown> = {\n\t\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\t\tenvironment: gatewayEnv,\n\t\t\tvolumes: gatewayVolumes,\n\t\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\t\tnetworks: [this.networkName],\n\t\t\tinit: true,\n\t\t\trestart: \"unless-stopped\",\n\t\t\tcommand: [\n\t\t\t\t\"node\",\n\t\t\t\t\"dist/index.js\",\n\t\t\t\t\"gateway\",\n\t\t\t\t\"--bind\",\n\t\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\t\"--port\",\n\t\t\t\t\"18789\",\n\t\t\t\t\"--allow-unconfigured\",\n\t\t\t],\n\t\t\thealthcheck: {\n\t\t\t\ttest: [\n\t\t\t\t\t\"CMD\",\n\t\t\t\t\t\"node\",\n\t\t\t\t\t\"-e\",\n\t\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t\t],\n\t\t\t\tinterval: \"30s\",\n\t\t\t\ttimeout: \"5s\",\n\t\t\t\tretries: 5,\n\t\t\t\tstart_period: \"20s\",\n\t\t\t},\n\t\t};\n\n\t\t// Traefik labels for the gateway\n\t\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\t\tif (gwTraefikLabels) {\n\t\t\tgateway.labels = gwTraefikLabels;\n\t\t}\n\n\t\tif (options.bareMetalNativeHost) {\n\t\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t\t}\n\n\t\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\t\tgateway.depends_on = dependsOn;\n\t\t}\n\n\t\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t\tconst cliEnv: Record<string, string> = {\n\t\t\tHOME: \"/home/node\",\n\t\t\tTERM: \"xterm-256color\",\n\t\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t\tBROWSER: \"echo\",\n\t\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t\t};\n\n\t\tfor (const key of PROVIDER_ENV_KEYS) {\n\t\t\tcliEnv[key] = `\\${${key}}`;\n\t\t}\n\n\t\tconst cliService: Record<string, unknown> = {\n\t\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\t\tenvironment: cliEnv,\n\t\t\tvolumes: [\n\t\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t\t],\n\t\t\tstdin_open: true,\n\t\t\ttty: true,\n\t\t\tinit: true,\n\t\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\t\tdepends_on: [\"openclaw-gateway\"],\n\t\t};\n\n\t\treturn { gatewayService: gateway, cliService, allVolumes };\n\t},\n\n\tgenerateConfig(\n\t\tresolved: ResolverOutput,\n\t\toptions: FrameworkConfigOptions,\n\t): FrameworkConfigResult {\n\t\tconst content = generateOpenClawConfig(resolved, {\n\t\t\tdeploymentType: options.deploymentType as any,\n\t\t\tgatewayPort: options.gatewayPort,\n\t\t\topenclawVersion: options.frameworkVersion,\n\t\t});\n\t\treturn {\n\t\t\tpath: \"openclaw/config/openclaw.json\",\n\t\t\tcontent,\n\t\t};\n\t},\n\n\tgetBaseEnvVars(options: FrameworkEnvOptions): EnvLine[] {\n\t\tconst version = options.frameworkVersion ?? \"latest\";\n\t\tconst gatewayToken = options.generateSecrets ? randomBytes(24).toString(\"hex\") : \"\";\n\n\t\tconst imageVariantMap: Record<string, string> = {\n\t\t\tofficial: \"\",\n\t\t\tcoolify: \"coollabsio/openclaw:latest\",\n\t\t\talpine: \"alpine/openclaw:latest\",\n\t\t};\n\t\tconst imageValue = imageVariantMap[options.frameworkImageVariant ?? \"official\"] ?? \"\";\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tcomment: \"OpenClaw version to deploy\",\n\t\t\t\tkey: \"OPENCLAW_VERSION\",\n\t\t\t\texampleValue: version,\n\t\t\t\tactualValue: version,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Authentication token for the OpenClaw gateway API\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_TOKEN\",\n\t\t\t\texampleValue: \"your_gateway_token_here\",\n\t\t\t\tactualValue: gatewayToken,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Port the OpenClaw gateway listens on\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_PORT\",\n\t\t\t\texampleValue: \"18789\",\n\t\t\t\tactualValue: \"18789\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Port for the OpenClaw ACP bridge (WebSocket)\",\n\t\t\t\tkey: \"OPENCLAW_BRIDGE_PORT\",\n\t\t\t\texampleValue: \"18790\",\n\t\t\t\tactualValue: \"18790\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment:\n\t\t\t\t\t\"Gateway network bind mode: 'lan' (all interfaces, required for Docker). Use 'loopback' only for native (non-Docker) installs with Tailscale serve/funnel\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_BIND\",\n\t\t\t\texampleValue: \"lan\",\n\t\t\t\tactualValue: \"lan\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Host path to OpenClaw configuration directory\",\n\t\t\t\tkey: \"OPENCLAW_CONFIG_DIR\",\n\t\t\t\texampleValue: \"./openclaw/config\",\n\t\t\t\tactualValue: \"./openclaw/config\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Host path to OpenClaw workspace directory\",\n\t\t\t\tkey: \"OPENCLAW_WORKSPACE_DIR\",\n\t\t\t\texampleValue: \"./openclaw/workspace\",\n\t\t\t\tactualValue: \"./openclaw/workspace\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: `OpenClaw Docker image variant: ${options.frameworkImageVariant ?? \"official\"} (official, coolify, alpine)`,\n\t\t\t\tkey: \"OPENCLAW_IMAGE\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: imageValue,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment:\n\t\t\t\t\t\"Extra bind mounts for gateway and CLI containers (comma-separated, format: source:target[:options])\",\n\t\t\t\tkey: \"OPENCLAW_EXTRA_MOUNTS\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Named volume or host path for /home/node persistence across container restarts\",\n\t\t\t\tkey: \"OPENCLAW_HOME_VOLUME\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Extra apt packages to install during Docker image build (space-separated)\",\n\t\t\t\tkey: \"OPENCLAW_DOCKER_APT_PACKAGES\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Alternative auth: gateway password (use token OR password, not both)\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_PASSWORD\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Override state directory path (default: ~/.openclaw)\",\n\t\t\t\tkey: \"OPENCLAW_STATE_DIR\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Override config file path (default: ~/.openclaw/openclaw.json)\",\n\t\t\t\tkey: \"OPENCLAW_CONFIG_PATH\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Import missing keys from login shell profile (set to 1 to enable)\",\n\t\t\t\tkey: \"OPENCLAW_LOAD_SHELL_ENV\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t];\n\t},\n\n\tgetMandatoryServices(): string[] {\n\t\treturn [\"convex\", \"mission-control\", \"tailscale\"];\n\t},\n\n\tgetRecommendedServices(): string[] {\n\t\treturn [\"clawrouter\"];\n\t},\n\n\tgetProviderEnvKeys(): string[] {\n\t\treturn [...PROVIDER_ENV_KEYS];\n\t},\n\n\tgetEnvSectionName(): string {\n\t\treturn \"OpenClaw Core\";\n\t},\n};\n"}, "adc7e0c7b9db791d300f3973042e5ec94e0a08fb": {"t": 1785754494.5125635, "n": 9596, "h": "fae98ca3b2fdc582", "c": "import { randomBytes } from \"node:crypto\";\nimport { generateOpenClawConfig } from \"../generators/openclaw-json.js\";\nimport type { ResolverOutput } from \"../types.js\";\nimport type {\n\tAgentFrameworkDefinition,\n\tEnvLine,\n\tFrameworkComposeOptions,\n\tFrameworkConfigOptions,\n\tFrameworkConfigResult,\n\tFrameworkEnvOptions,\n\tGatewayBuildResult,\n} from \"./types.js\";\n\n// ─── Image Variants ─────────────────────────────────────────────────────────\n\nconst IMAGE_VARIANTS: Record<string, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\nfunction getOpenclawImage(variant: string, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant] ?? IMAGE_VARIANTS.official;\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n// ─── Provider API Keys ──────────────────────────────────────────────────────\n\nconst PROVIDER_ENV_KEYS = [\n\t\"OPENAI_API_KEY\",\n\t\"ANTHROPIC_API_KEY\",\n\t\"GOOGLE_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"OLLAMA_API_KEY\",\n];\n\n// ─── OpenClaw Framework Definition ──────────────────────────────────────────\n\nexport const openclawFramework: AgentFrameworkDefinition = {\n\tid: \"openclaw\",\n\tname: \"OpenClaw\",\n\ticon: \"🦀\",\n\tdescription: \"Full-featured AI agent gateway with CLI, bridge, and skill system\",\n\truntime: \"node\",\n\tcanBePrimary: true,\n\tcanBeCompanion: false,\n\tnetworkName: \"openclaw-network\",\n\timageVariants: IMAGE_VARIANTS,\n\tdefaultImageVariant: \"official\",\n\n\tbuildGatewayService(\n\t\tresolved: ResolverOutput,\n\t\toptions: FrameworkComposeOptions,\n\t\tdependsOn?: Record<string, { condition: string }>,\n\t): GatewayBuildResult {\n\t\tconst allVolumes = new Set<string>();\n\n\t\t// Gateway environment\n\t\tconst gatewayEnv: Record<string, string> = {\n\t\t\tHOME: \"/home/node\",\n\t\t\tTERM: \"xterm-256color\",\n\t\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t\t};\n\n\t\t// Add AI provider API keys to gateway environment\n\t\tfor (const key of PROVIDER_ENV_KEYS) {\n\t\t\tgatewayEnv[key] = `\\${${key}}`;\n\t\t}\n\n\t\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\t\tconst gatewayVolumes: string[] = [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t];\n\n\t\t// Collect env vars and volume mounts from companion services\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tfor (const env of def.openclawEnvVars) {\n\t\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t\t}\n\t\t\tif (def.openclawVolumeMounts) {\n\t\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\t\tconst isBindMount =\n\t\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\t\tif (!isBindMount) {\n\t\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t\t}\n\t\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Gateway service\n\t\tconst defaultImage = getOpenclawImage(\n\t\t\toptions.frameworkImageVariant ?? \"official\",\n\t\t\toptions.frameworkVersion,\n\t\t);\n\t\tconst gateway: Record<string, unknown> = {\n\t\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\t\tenvironment: gatewayEnv,\n\t\t\tvolumes: gatewayVolumes,\n\t\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\t\tnetworks: [this.networkName],\n\t\t\tinit: true,\n\t\t\trestart: \"unless-stopped\",\n\t\t\tcommand: [\n\t\t\t\t\"node\",\n\t\t\t\t\"dist/index.js\",\n\t\t\t\t\"gateway\",\n\t\t\t\t\"--bind\",\n\t\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\t\"--port\",\n\t\t\t\t\"18789\",\n\t\t\t\t\"--allow-unconfigured\",\n\t\t\t],\n\t\t\thealthcheck: {\n\t\t\t\ttest: [\n\t\t\t\t\t\"CMD\",\n\t\t\t\t\t\"node\",\n\t\t\t\t\t\"-e\",\n\t\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t\t],\n\t\t\t\tinterval: \"30s\",\n\t\t\t\ttimeout: \"5s\",\n\t\t\t\tretries: 5,\n\t\t\t\tstart_period: \"20s\",\n\t\t\t},\n\t\t};\n\n\t\t// Traefik labels for the gateway\n\t\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\t\tif (gwTraefikLabels) {\n\t\t\tgateway.labels = gwTraefikLabels;\n\t\t}\n\n\t\tif (options.bareMetalNativeHost) {\n\t\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t\t}\n\n\t\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\t\tgateway.depends_on = dependsOn;\n\t\t}\n\n\t\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t\tconst cliEnv: Record<string, string> = {\n\t\t\tHOME: \"/home/node\",\n\t\t\tTERM: \"xterm-256color\",\n\t\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t\tBROWSER: \"echo\",\n\t\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t\t};\n\n\t\tfor (const key of PROVIDER_ENV_KEYS) {\n\t\t\tcliEnv[key] = `\\${${key}}`;\n\t\t}\n\n\t\tconst cliService: Record<string, unknown> = {\n\t\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\t\tenvironment: cliEnv,\n\t\t\tvolumes: [\n\t\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t\t],\n\t\t\tstdin_open: true,\n\t\t\ttty: true,\n\t\t\tinit: true,\n\t\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\t\tdepends_on: [\"openclaw-gateway\"],\n\t\t};\n\n\t\treturn { gatewayService: gateway, cliService, allVolumes };\n\t},\n\n\tgenerateConfig(\n\t\tresolved: ResolverOutput,\n\t\toptions: FrameworkConfigOptions,\n\t): FrameworkConfigResult {\n\t\tconst content = generateOpenClawConfig(resolved, {\n\t\t\tdeploymentType: options.deploymentType as any,\n\t\t\tgatewayPort: options.gatewayPort,\n\t\t\topenclawVersion: options.frameworkVersion,\n\t\t});\n\t\treturn {\n\t\t\tpath: \"openclaw/config/openclaw.json\",\n\t\t\tcontent,\n\t\t};\n\t},\n\n\tgetBaseEnvVars(options: FrameworkEnvOptions): EnvLine[] {\n\t\tconst version = options.frameworkVersion ?? \"latest\";\n\t\tconst gatewayToken = options.generateSecrets ? randomBytes(24).toString(\"hex\") : \"\";\n\n\t\tconst imageVariantMap: Record<string, string> = {\n\t\t\tofficial: \"\",\n\t\t\tcoolify: \"coollabsio/openclaw:latest\",\n\t\t\talpine: \"alpine/openclaw:latest\",\n\t\t};\n\t\tconst imageValue = imageVariantMap[options.frameworkImageVariant ?? \"official\"] ?? \"\";\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tcomment: \"OpenClaw version to deploy\",\n\t\t\t\tkey: \"OPENCLAW_VERSION\",\n\t\t\t\texampleValue: version,\n\t\t\t\tactualValue: version,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Authentication token for the OpenClaw gateway API\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_TOKEN\",\n\t\t\t\texampleValue: \"your_gateway_token_here\",\n\t\t\t\tactualValue: gatewayToken,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Port the OpenClaw gateway listens on\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_PORT\",\n\t\t\t\texampleValue: \"18789\",\n\t\t\t\tactualValue: \"18789\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Port for the OpenClaw ACP bridge (WebSocket)\",\n\t\t\t\tkey: \"OPENCLAW_BRIDGE_PORT\",\n\t\t\t\texampleValue: \"18790\",\n\t\t\t\tactualValue: \"18790\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment:\n\t\t\t\t\t\"Gateway network bind mode: 'lan' (all interfaces, required for Docker). Use 'loopback' only for native (non-Docker) installs with Tailscale serve/funnel\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_BIND\",\n\t\t\t\texampleValue: \"lan\",\n\t\t\t\tactualValue: \"lan\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Host path to OpenClaw configuration directory\",\n\t\t\t\tkey: \"OPENCLAW_CONFIG_DIR\",\n\t\t\t\texampleValue: \"./openclaw/config\",\n\t\t\t\tactualValue: \"./openclaw/config\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Host path to OpenClaw workspace directory\",\n\t\t\t\tkey: \"OPENCLAW_WORKSPACE_DIR\",\n\t\t\t\texampleValue: \"./openclaw/workspace\",\n\t\t\t\tactualValue: \"./openclaw/workspace\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: `OpenClaw Docker image variant: ${options.frameworkImageVariant ?? \"official\"} (official, coolify, alpine)`,\n\t\t\t\tkey: \"OPENCLAW_IMAGE\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: imageValue,\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment:\n\t\t\t\t\t\"Extra bind mounts for gateway and CLI containers (comma-separated, format: source:target[:options])\",\n\t\t\t\tkey: \"OPENCLAW_EXTRA_MOUNTS\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Named volume or host path for /home/node persistence across container restarts\",\n\t\t\t\tkey: \"OPENCLAW_HOME_VOLUME\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Extra apt packages to install during Docker image build (space-separated)\",\n\t\t\t\tkey: \"OPENCLAW_DOCKER_APT_PACKAGES\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Alternative auth: gateway password (use token OR password, not both)\",\n\t\t\t\tkey: \"OPENCLAW_GATEWAY_PASSWORD\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Override state directory path (default: ~/.openclaw)\",\n\t\t\t\tkey: \"OPENCLAW_STATE_DIR\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Override config file path (default: ~/.openclaw/openclaw.json)\",\n\t\t\t\tkey: \"OPENCLAW_CONFIG_PATH\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tcomment: \"Import missing keys from login shell profile (set to 1 to enable)\",\n\t\t\t\tkey: \"OPENCLAW_LOAD_SHELL_ENV\",\n\t\t\t\texampleValue: \"\",\n\t\t\t\tactualValue: \"\",\n\t\t\t},\n\t\t];\n\t},\n\n\tgetMandatoryServices(): string[] {\n\t\treturn [\"convex\", \"mission-control\", \"tailscale\"];\n\t},\n\n\tgetRecommendedServices(): string[] {\n\t\treturn [];\n\t},\n\n\tgetProviderEnvKeys(): string[] {\n\t\treturn [...PROVIDER_ENV_KEYS];\n\t},\n\n\tgetEnvSectionName(): string {\n\t\treturn \"OpenClaw Core\";\n\t},\n};\n"}}}} |