| import { resolveIsNixMode } from "../../config/paths.js"; |
| 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"; |
| import { createDaemonActionContext } from "./response.js"; |
|
|
| export { formatRuntimeStatus }; |
| export { parsePort }; |
|
|
| export function createDaemonInstallActionContext(jsonFlag: unknown) { |
| const json = Boolean(jsonFlag); |
| return { |
| json, |
| ...createDaemonActionContext({ action: "install", json }), |
| }; |
| } |
|
|
| export function failIfNixDaemonInstallMode( |
| fail: (message: string, hints?: string[]) => void, |
| env: NodeJS.ProcessEnv = process.env, |
| ): boolean { |
| if (!resolveIsNixMode(env)) { |
| return false; |
| } |
| fail("Nix mode detected; service install is disabled."); |
| return true; |
| } |
|
|
| 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") { |
| |
| |
| |
| 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<string, string> | undefined): Record<string, string> { |
| if (!env) { |
| return {}; |
| } |
| const filtered: Record<string, string> = {}; |
| 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<string, string> | 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), |
| }); |
| } |
|
|