Files
openclaw/src/cli/daemon-cli/shared.ts
2026-03-07 21:09:26 +00:00

171 lines
5.0 KiB
TypeScript

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<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),
});
}