diff --git a/src/cli/daemon-cli/shared.ts b/src/cli/daemon-cli/shared.ts index cc520781d1c..525b04682b0 100644 --- a/src/cli/daemon-cli/shared.ts +++ b/src/cli/daemon-cli/shared.ts @@ -3,8 +3,11 @@ import { resolveGatewaySystemdServiceName, resolveGatewayWindowsTaskName, } from "../../daemon/constants.js"; -import { resolveGatewayLogPaths } from "../../daemon/launchd.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"; @@ -144,41 +147,24 @@ export function renderRuntimeHints( 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`); - } + 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 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; - } + 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), + }); } diff --git a/src/cli/node-cli/daemon.ts b/src/cli/node-cli/daemon.ts index d16e0e09134..b293c88c15c 100644 --- a/src/cli/node-cli/daemon.ts +++ b/src/cli/node-cli/daemon.ts @@ -9,8 +9,11 @@ import { resolveNodeSystemdServiceName, resolveNodeWindowsTaskName, } from "../../daemon/constants.js"; -import { resolveGatewayLogPaths } from "../../daemon/launchd.js"; import { resolveNodeService } from "../../daemon/node-service.js"; +import { + buildPlatformRuntimeLogHints, + buildPlatformServiceStartHints, +} from "../../daemon/runtime-hints.js"; import type { GatewayServiceRuntime } from "../../daemon/service-runtime.js"; import { loadNodeHostConfig } from "../../node-host/config.js"; import { defaultRuntime } from "../../runtime.js"; @@ -55,39 +58,21 @@ type NodeDaemonStatusOptions = { }; function renderNodeServiceStartHints(): string[] { - const base = [formatCliCommand("openclaw node install"), formatCliCommand("openclaw node start")]; - switch (process.platform) { - case "darwin": - return [ - ...base, - `launchctl bootstrap gui/$UID ~/Library/LaunchAgents/${resolveNodeLaunchAgentLabel()}.plist`, - ]; - case "linux": - return [...base, `systemctl --user start ${resolveNodeSystemdServiceName()}.service`]; - case "win32": - return [...base, `schtasks /Run /TN "${resolveNodeWindowsTaskName()}"`]; - default: - return base; - } + return buildPlatformServiceStartHints({ + installCommand: formatCliCommand("openclaw node install"), + startCommand: formatCliCommand("openclaw node start"), + launchAgentPlistPath: `~/Library/LaunchAgents/${resolveNodeLaunchAgentLabel()}.plist`, + systemdServiceName: resolveNodeSystemdServiceName(), + windowsTaskName: resolveNodeWindowsTaskName(), + }); } function buildNodeRuntimeHints(env: NodeJS.ProcessEnv = process.env): string[] { - if (process.platform === "darwin") { - const logs = resolveGatewayLogPaths(env); - return [ - `Launchd stdout (if installed): ${logs.stdoutPath}`, - `Launchd stderr (if installed): ${logs.stderrPath}`, - ]; - } - if (process.platform === "linux") { - const unit = resolveNodeSystemdServiceName(); - return [`Logs: journalctl --user -u ${unit}.service -n 200 --no-pager`]; - } - if (process.platform === "win32") { - const task = resolveNodeWindowsTaskName(); - return [`Logs: schtasks /Query /TN "${task}" /V /FO LIST`]; - } - return []; + return buildPlatformRuntimeLogHints({ + env, + systemdServiceName: resolveNodeSystemdServiceName(), + windowsTaskName: resolveNodeWindowsTaskName(), + }); } function resolveNodeDefaults( diff --git a/src/commands/doctor-format.ts b/src/commands/doctor-format.ts index fea545e5b54..c41ba5a017f 100644 --- a/src/commands/doctor-format.ts +++ b/src/commands/doctor-format.ts @@ -4,8 +4,8 @@ import { resolveGatewaySystemdServiceName, resolveGatewayWindowsTaskName, } from "../daemon/constants.js"; -import { resolveGatewayLogPaths } from "../daemon/launchd.js"; import { formatRuntimeStatus } from "../daemon/runtime-format.js"; +import { buildPlatformRuntimeLogHints } from "../daemon/runtime-hints.js"; import type { GatewayServiceRuntime } from "../daemon/service-runtime.js"; import { isSystemdUnavailableDetail, @@ -68,17 +68,14 @@ export function buildGatewayRuntimeHints( if (fileLog) { hints.push(`File logs: ${fileLog}`); } - if (platform === "darwin") { - const logs = resolveGatewayLogPaths(env); - hints.push(`Launchd stdout (if installed): ${logs.stdoutPath}`); - hints.push(`Launchd stderr (if installed): ${logs.stderrPath}`); - } else if (platform === "linux") { - const unit = resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE); - hints.push(`Logs: journalctl --user -u ${unit}.service -n 200 --no-pager`); - } else if (platform === "win32") { - const task = resolveGatewayWindowsTaskName(env.OPENCLAW_PROFILE); - hints.push(`Logs: schtasks /Query /TN "${task}" /V /FO LIST`); - } + hints.push( + ...buildPlatformRuntimeLogHints({ + platform, + env, + systemdServiceName: resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE), + windowsTaskName: resolveGatewayWindowsTaskName(env.OPENCLAW_PROFILE), + }), + ); } return hints; } diff --git a/src/daemon/runtime-hints.test.ts b/src/daemon/runtime-hints.test.ts new file mode 100644 index 00000000000..725edc48dfe --- /dev/null +++ b/src/daemon/runtime-hints.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from "vitest"; +import { buildPlatformRuntimeLogHints, buildPlatformServiceStartHints } from "./runtime-hints.js"; + +describe("buildPlatformRuntimeLogHints", () => { + it("renders launchd log hints on darwin", () => { + expect( + buildPlatformRuntimeLogHints({ + platform: "darwin", + env: { + OPENCLAW_STATE_DIR: "/tmp/openclaw-state", + OPENCLAW_LOG_PREFIX: "gateway", + }, + systemdServiceName: "openclaw-gateway", + windowsTaskName: "OpenClaw Gateway", + }), + ).toEqual([ + "Launchd stdout (if installed): /tmp/openclaw-state/logs/gateway.log", + "Launchd stderr (if installed): /tmp/openclaw-state/logs/gateway.err.log", + ]); + }); + + it("renders systemd and windows hints by platform", () => { + expect( + buildPlatformRuntimeLogHints({ + platform: "linux", + systemdServiceName: "openclaw-gateway", + windowsTaskName: "OpenClaw Gateway", + }), + ).toEqual(["Logs: journalctl --user -u openclaw-gateway.service -n 200 --no-pager"]); + expect( + buildPlatformRuntimeLogHints({ + platform: "win32", + systemdServiceName: "openclaw-gateway", + windowsTaskName: "OpenClaw Gateway", + }), + ).toEqual(['Logs: schtasks /Query /TN "OpenClaw Gateway" /V /FO LIST']); + }); +}); + +describe("buildPlatformServiceStartHints", () => { + it("builds platform-specific service start hints", () => { + expect( + buildPlatformServiceStartHints({ + platform: "darwin", + installCommand: "openclaw gateway install", + startCommand: "openclaw gateway", + launchAgentPlistPath: "~/Library/LaunchAgents/com.openclaw.gateway.plist", + systemdServiceName: "openclaw-gateway", + windowsTaskName: "OpenClaw Gateway", + }), + ).toEqual([ + "openclaw gateway install", + "openclaw gateway", + "launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.openclaw.gateway.plist", + ]); + expect( + buildPlatformServiceStartHints({ + platform: "linux", + installCommand: "openclaw gateway install", + startCommand: "openclaw gateway", + launchAgentPlistPath: "~/Library/LaunchAgents/com.openclaw.gateway.plist", + systemdServiceName: "openclaw-gateway", + windowsTaskName: "OpenClaw Gateway", + }), + ).toEqual([ + "openclaw gateway install", + "openclaw gateway", + "systemctl --user start openclaw-gateway.service", + ]); + }); +}); diff --git a/src/daemon/runtime-hints.ts b/src/daemon/runtime-hints.ts new file mode 100644 index 00000000000..037e5163c39 --- /dev/null +++ b/src/daemon/runtime-hints.ts @@ -0,0 +1,47 @@ +import { resolveGatewayLogPaths } from "./launchd.js"; + +export function buildPlatformRuntimeLogHints(params: { + platform?: NodeJS.Platform; + env?: NodeJS.ProcessEnv; + systemdServiceName: string; + windowsTaskName: string; +}): string[] { + const platform = params.platform ?? process.platform; + const env = params.env ?? process.env; + if (platform === "darwin") { + const logs = resolveGatewayLogPaths(env); + return [ + `Launchd stdout (if installed): ${logs.stdoutPath}`, + `Launchd stderr (if installed): ${logs.stderrPath}`, + ]; + } + if (platform === "linux") { + return [`Logs: journalctl --user -u ${params.systemdServiceName}.service -n 200 --no-pager`]; + } + if (platform === "win32") { + return [`Logs: schtasks /Query /TN "${params.windowsTaskName}" /V /FO LIST`]; + } + return []; +} + +export function buildPlatformServiceStartHints(params: { + platform?: NodeJS.Platform; + installCommand: string; + startCommand: string; + launchAgentPlistPath: string; + systemdServiceName: string; + windowsTaskName: string; +}): string[] { + const platform = params.platform ?? process.platform; + const base = [params.installCommand, params.startCommand]; + switch (platform) { + case "darwin": + return [...base, `launchctl bootstrap gui/$UID ${params.launchAgentPlistPath}`]; + case "linux": + return [...base, `systemctl --user start ${params.systemdServiceName}.service`]; + case "win32": + return [...base, `schtasks /Run /TN "${params.windowsTaskName}"`]; + default: + return base; + } +}