import { resolveGatewayLaunchAgentLabel, resolveGatewaySystemdServiceName, resolveGatewayWindowsTaskName, } from "../../daemon/constants.js"; import { resolveGatewayLogPaths } from "../../daemon/launchd.js"; import { getResolvedLoggerSettings } from "../../logging.js"; import { formatCliCommand } from "../command-format.js"; export function parsePort(raw: unknown): number | null { if (raw === undefined || raw === null) { return null; } const value = typeof raw === "string" ? raw : typeof raw === "number" || typeof raw === "bigint" ? raw.toString() : null; if (value === null) { return null; } const parsed = Number.parseInt(value, 10); if (!Number.isFinite(parsed) || parsed <= 0) { return null; } return parsed; } export function parsePortFromArgs(programArguments: string[] | undefined): number | null { if (!programArguments?.length) { return null; } for (let i = 0; i < programArguments.length; i += 1) { const arg = programArguments[i]; if (arg === "--port") { const next = programArguments[i + 1]; const parsed = parsePort(next); if (parsed) { return parsed; } } if (arg?.startsWith("--port=")) { const parsed = parsePort(arg.split("=", 2)[1]); if (parsed) { return parsed; } } } return null; } export function pickProbeHostForBind( bindMode: string, tailnetIPv4: string | undefined, customBindHost?: string, ) { if (bindMode === "custom" && customBindHost?.trim()) { return customBindHost.trim(); } if (bindMode === "tailnet") { return tailnetIPv4 ?? "127.0.0.1"; } return "127.0.0.1"; } const SAFE_DAEMON_ENV_KEYS = [ "OPENCLAW_PROFILE", "OPENCLAW_STATE_DIR", "OPENCLAW_CONFIG_PATH", "OPENCLAW_GATEWAY_PORT", "OPENCLAW_NIX_MODE", ]; export function filterDaemonEnv(env: Record | undefined): Record { if (!env) { return {}; } const filtered: Record = {}; for (const key of SAFE_DAEMON_ENV_KEYS) { const value = env[key]; if (!value?.trim()) { continue; } filtered[key] = value.trim(); } return filtered; } export function safeDaemonEnv(env: Record | undefined): string[] { const filtered = filterDaemonEnv(env); return Object.entries(filtered).map(([key, value]) => `${key}=${value}`); } export function normalizeListenerAddress(raw: string): string { let value = raw.trim(); if (!value) { return value; } value = value.replace(/^TCP\s+/i, ""); value = value.replace(/\s+\(LISTEN\)\s*$/i, ""); return value.trim(); } export function formatRuntimeStatus( runtime: | { status?: string; state?: string; subState?: string; pid?: number; lastExitStatus?: number; lastExitReason?: string; lastRunResult?: string; lastRunTime?: string; detail?: string; } | undefined, ) { if (!runtime) { return null; } const status = runtime.status ?? "unknown"; const details: string[] = []; if (runtime.pid) { details.push(`pid ${runtime.pid}`); } if (runtime.state && runtime.state.toLowerCase() !== status) { details.push(`state ${runtime.state}`); } if (runtime.subState) { details.push(`sub ${runtime.subState}`); } if (runtime.lastExitStatus !== undefined) { details.push(`last exit ${runtime.lastExitStatus}`); } if (runtime.lastExitReason) { details.push(`reason ${runtime.lastExitReason}`); } if (runtime.lastRunResult) { details.push(`last run ${runtime.lastRunResult}`); } if (runtime.lastRunTime) { details.push(`last run time ${runtime.lastRunTime}`); } if (runtime.detail) { details.push(runtime.detail); } return details.length > 0 ? `${status} (${details.join(", ")})` : status; } export function renderRuntimeHints( runtime: { missingUnit?: boolean; status?: string } | undefined, env: NodeJS.ProcessEnv = process.env, ): string[] { if (!runtime) { return []; } const hints: string[] = []; const fileLog = (() => { try { return getResolvedLoggerSettings().file; } catch { return null; } })(); if (runtime.missingUnit) { hints.push(`Service not installed. Run: ${formatCliCommand("openclaw gateway install", env)}`); if (fileLog) { hints.push(`File logs: ${fileLog}`); } return hints; } if (runtime.status === "stopped") { if (fileLog) { hints.push(`File logs: ${fileLog}`); } if (process.platform === "darwin") { const logs = resolveGatewayLogPaths(env); hints.push(`Launchd stdout (if installed): ${logs.stdoutPath}`); hints.push(`Launchd stderr (if installed): ${logs.stderrPath}`); } else if (process.platform === "linux") { const unit = resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE); hints.push(`Logs: journalctl --user -u ${unit}.service -n 200 --no-pager`); } else if (process.platform === "win32") { const task = resolveGatewayWindowsTaskName(env.OPENCLAW_PROFILE); hints.push(`Logs: schtasks /Query /TN "${task}" /V /FO LIST`); } } return hints; } export function renderGatewayServiceStartHints(env: NodeJS.ProcessEnv = process.env): string[] { const base = [ formatCliCommand("openclaw gateway install", env), formatCliCommand("openclaw gateway", env), ]; const profile = env.OPENCLAW_PROFILE; switch (process.platform) { case "darwin": { const label = resolveGatewayLaunchAgentLabel(profile); return [...base, `launchctl bootstrap gui/$UID ~/Library/LaunchAgents/${label}.plist`]; } case "linux": { const unit = resolveGatewaySystemdServiceName(profile); return [...base, `systemctl --user start ${unit}.service`]; } case "win32": { const task = resolveGatewayWindowsTaskName(profile); return [...base, `schtasks /Run /TN "${task}"`]; } default: return base; } }