import { resolveGatewayLaunchAgentLabel, resolveGatewaySystemdServiceName, resolveGatewayWindowsTaskName, } from "../../daemon/constants.js"; import { formatRuntimeStatus } from "../../daemon/runtime-format.js"; import { buildPlatformRuntimeLogHints, buildPlatformServiceStartHints, } from "../../daemon/runtime-hints.js"; import { getResolvedLoggerSettings } from "../../logging.js"; import { colorize, isRich, theme } from "../../terminal/theme.js"; import { formatCliCommand } from "../command-format.js"; import { parsePort } from "../shared/parse-port.js"; export { formatRuntimeStatus }; export { parsePort }; export function createCliStatusTextStyles() { const rich = isRich(); return { rich, label: (value: string) => colorize(rich, theme.muted, value), accent: (value: string) => colorize(rich, theme.accent, value), infoText: (value: string) => colorize(rich, theme.info, value), okText: (value: string) => colorize(rich, theme.success, value), warnText: (value: string) => colorize(rich, theme.warn, value), errorText: (value: string) => colorize(rich, theme.error, value), }; } export function resolveRuntimeStatusColor(status: string | undefined): (value: string) => string { const runtimeStatus = status ?? "unknown"; return runtimeStatus === "running" ? theme.success : runtimeStatus === "stopped" ? theme.error : runtimeStatus === "unknown" ? theme.muted : theme.warn; } 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"; } if (bindMode === "lan") { // Same as call.ts: self-connections should always target loopback. // bind=lan controls which interfaces the server listens on (0.0.0.0), // but co-located CLI probes should connect via 127.0.0.1. return "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 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}`); } hints.push( ...buildPlatformRuntimeLogHints({ env, systemdServiceName: resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE), windowsTaskName: resolveGatewayWindowsTaskName(env.OPENCLAW_PROFILE), }), ); } return hints; } export function renderGatewayServiceStartHints(env: NodeJS.ProcessEnv = process.env): string[] { const profile = env.OPENCLAW_PROFILE; return buildPlatformServiceStartHints({ installCommand: formatCliCommand("openclaw gateway install", env), startCommand: formatCliCommand("openclaw gateway", env), launchAgentPlistPath: `~/Library/LaunchAgents/${resolveGatewayLaunchAgentLabel(profile)}.plist`, systemdServiceName: resolveGatewaySystemdServiceName(profile), windowsTaskName: resolveGatewayWindowsTaskName(profile), }); }