fix: unify daemon service label resolution with env
This commit is contained in:
committed by
Peter Steinberger
parent
cb78fa46a1
commit
daf471c450
@@ -46,10 +46,9 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
|
||||
}
|
||||
|
||||
const service = resolveGatewayService();
|
||||
const profile = process.env.CLAWDBOT_PROFILE;
|
||||
let loaded = false;
|
||||
try {
|
||||
loaded = await service.isLoaded({ profile });
|
||||
loaded = await service.isLoaded({ env: process.env });
|
||||
} catch (err) {
|
||||
defaultRuntime.error(`Gateway service check failed: ${String(err)}`);
|
||||
defaultRuntime.exit(1);
|
||||
@@ -85,7 +84,9 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
|
||||
port,
|
||||
token: opts.token || cfg.gateway?.auth?.token || process.env.CLAWDBOT_GATEWAY_TOKEN,
|
||||
launchdLabel:
|
||||
process.platform === "darwin" ? resolveGatewayLaunchAgentLabel(profile) : undefined,
|
||||
process.platform === "darwin"
|
||||
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
@@ -21,10 +21,9 @@ export async function runDaemonUninstall() {
|
||||
|
||||
export async function runDaemonStart() {
|
||||
const service = resolveGatewayService();
|
||||
const profile = process.env.CLAWDBOT_PROFILE;
|
||||
let loaded = false;
|
||||
try {
|
||||
loaded = await service.isLoaded({ profile });
|
||||
loaded = await service.isLoaded({ env: process.env });
|
||||
} catch (err) {
|
||||
defaultRuntime.error(`Gateway service check failed: ${String(err)}`);
|
||||
defaultRuntime.exit(1);
|
||||
@@ -38,7 +37,7 @@ export async function runDaemonStart() {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await service.restart({ profile, stdout: process.stdout });
|
||||
await service.restart({ env: process.env, stdout: process.stdout });
|
||||
} catch (err) {
|
||||
defaultRuntime.error(`Gateway start failed: ${String(err)}`);
|
||||
for (const hint of renderGatewayServiceStartHints()) {
|
||||
@@ -50,10 +49,9 @@ export async function runDaemonStart() {
|
||||
|
||||
export async function runDaemonStop() {
|
||||
const service = resolveGatewayService();
|
||||
const profile = process.env.CLAWDBOT_PROFILE;
|
||||
let loaded = false;
|
||||
try {
|
||||
loaded = await service.isLoaded({ profile });
|
||||
loaded = await service.isLoaded({ env: process.env });
|
||||
} catch (err) {
|
||||
defaultRuntime.error(`Gateway service check failed: ${String(err)}`);
|
||||
defaultRuntime.exit(1);
|
||||
@@ -64,7 +62,7 @@ export async function runDaemonStop() {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await service.stop({ profile, stdout: process.stdout });
|
||||
await service.stop({ env: process.env, stdout: process.stdout });
|
||||
} catch (err) {
|
||||
defaultRuntime.error(`Gateway stop failed: ${String(err)}`);
|
||||
defaultRuntime.exit(1);
|
||||
@@ -78,10 +76,9 @@ export async function runDaemonStop() {
|
||||
*/
|
||||
export async function runDaemonRestart(): Promise<boolean> {
|
||||
const service = resolveGatewayService();
|
||||
const profile = process.env.CLAWDBOT_PROFILE;
|
||||
let loaded = false;
|
||||
try {
|
||||
loaded = await service.isLoaded({ profile });
|
||||
loaded = await service.isLoaded({ env: process.env });
|
||||
} catch (err) {
|
||||
defaultRuntime.error(`Gateway service check failed: ${String(err)}`);
|
||||
defaultRuntime.exit(1);
|
||||
@@ -95,7 +92,7 @@ export async function runDaemonRestart(): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
await service.restart({ profile, stdout: process.stdout });
|
||||
await service.restart({ env: process.env, stdout: process.stdout });
|
||||
return true;
|
||||
} catch (err) {
|
||||
defaultRuntime.error(`Gateway restart failed: ${String(err)}`);
|
||||
|
||||
@@ -112,7 +112,7 @@ export async function gatherDaemonStatus(
|
||||
): Promise<DaemonStatus> {
|
||||
const service = resolveGatewayService();
|
||||
const [loaded, command, runtime] = await Promise.all([
|
||||
service.isLoaded({ profile: process.env.CLAWDBOT_PROFILE }).catch(() => false),
|
||||
service.isLoaded({ env: process.env }).catch(() => false),
|
||||
service.readCommand(process.env).catch(() => null),
|
||||
service.readRuntime(process.env).catch(() => undefined),
|
||||
]);
|
||||
|
||||
@@ -262,12 +262,12 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
if (legacyServices.length > 0 || extraServices.length > 0) {
|
||||
defaultRuntime.error(
|
||||
errorText(
|
||||
"Recommendation: run a single gateway per machine. One gateway supports multiple agents.",
|
||||
"Recommendation: run a single gateway per machine for most setups. One gateway supports multiple agents (see docs: /gateway#multiple-gateways-same-host).",
|
||||
),
|
||||
);
|
||||
defaultRuntime.error(
|
||||
errorText(
|
||||
"If you need multiple gateways, isolate ports + config/state (see docs: /gateway#multiple-gateways-same-host).",
|
||||
"If you need multiple gateways (e.g., a recovery bot on the same host), isolate ports + config/state (see docs: /gateway#multiple-gateways-same-host).",
|
||||
),
|
||||
);
|
||||
spacer();
|
||||
|
||||
@@ -89,7 +89,7 @@ export async function maybeExplainGatewayServiceStop() {
|
||||
const service = resolveGatewayService();
|
||||
let loaded: boolean | null = null;
|
||||
try {
|
||||
loaded = await service.isLoaded({ profile: process.env.CLAWDBOT_PROFILE });
|
||||
loaded = await service.isLoaded({ env: process.env });
|
||||
} catch {
|
||||
loaded = null;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function maybeInstallDaemon(params: {
|
||||
daemonRuntime?: GatewayDaemonRuntime;
|
||||
}) {
|
||||
const service = resolveGatewayService();
|
||||
const loaded = await service.isLoaded({ profile: process.env.CLAWDBOT_PROFILE });
|
||||
const loaded = await service.isLoaded({ env: process.env });
|
||||
let shouldCheckLinger = false;
|
||||
let shouldInstall = true;
|
||||
let daemonRuntime = params.daemonRuntime ?? DEFAULT_GATEWAY_DAEMON_RUNTIME;
|
||||
@@ -49,7 +49,7 @@ export async function maybeInstallDaemon(params: {
|
||||
async (progress) => {
|
||||
progress.setLabel("Restarting Gateway daemon…");
|
||||
await service.restart({
|
||||
profile: process.env.CLAWDBOT_PROFILE,
|
||||
env: process.env,
|
||||
stdout: process.stdout,
|
||||
});
|
||||
progress.setLabel("Gateway daemon restarted.");
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function maybeRepairGatewayDaemon(params: {
|
||||
if (params.healthOk) return;
|
||||
|
||||
const service = resolveGatewayService();
|
||||
const loaded = await service.isLoaded({ profile: process.env.CLAWDBOT_PROFILE });
|
||||
const loaded = await service.isLoaded({ env: process.env });
|
||||
let serviceRuntime: Awaited<ReturnType<typeof service.readRuntime>> | undefined;
|
||||
if (loaded) {
|
||||
serviceRuntime = await service.readRuntime(process.env).catch(() => undefined);
|
||||
@@ -129,7 +129,7 @@ export async function maybeRepairGatewayDaemon(params: {
|
||||
});
|
||||
if (start) {
|
||||
await service.restart({
|
||||
profile: process.env.CLAWDBOT_PROFILE,
|
||||
env: process.env,
|
||||
stdout: process.stdout,
|
||||
});
|
||||
await sleep(1500);
|
||||
@@ -151,7 +151,7 @@ export async function maybeRepairGatewayDaemon(params: {
|
||||
});
|
||||
if (restart) {
|
||||
await service.restart({
|
||||
profile: process.env.CLAWDBOT_PROFILE,
|
||||
env: process.env,
|
||||
stdout: process.stdout,
|
||||
});
|
||||
await sleep(1500);
|
||||
|
||||
@@ -89,7 +89,7 @@ export async function maybeMigrateLegacyGatewayService(
|
||||
}
|
||||
|
||||
const service = resolveGatewayService();
|
||||
const loaded = await service.isLoaded({ profile: process.env.CLAWDBOT_PROFILE });
|
||||
const loaded = await service.isLoaded({ env: process.env });
|
||||
if (loaded) {
|
||||
note(`Clawdbot ${service.label} already ${service.loadedText}.`, "Gateway");
|
||||
return;
|
||||
@@ -280,9 +280,9 @@ export async function maybeScanExtraGatewayServices(options: DoctorOptions) {
|
||||
|
||||
note(
|
||||
[
|
||||
"Recommendation: run a single gateway per machine.",
|
||||
"Recommendation: run a single gateway per machine for most setups.",
|
||||
"One gateway supports multiple agents.",
|
||||
"If you need multiple gateways, isolate ports + config/state (see docs: /gateway#multiple-gateways-same-host).",
|
||||
"If you need multiple gateways (e.g., a recovery bot on the same host), isolate ports + config/state (see docs: /gateway#multiple-gateways-same-host).",
|
||||
].join("\n"),
|
||||
"Gateway recommendation",
|
||||
);
|
||||
|
||||
@@ -209,7 +209,7 @@ export async function doctorCommand(
|
||||
const service = resolveGatewayService();
|
||||
let loaded = false;
|
||||
try {
|
||||
loaded = await service.isLoaded({ profile: process.env.CLAWDBOT_PROFILE });
|
||||
loaded = await service.isLoaded({ env: process.env });
|
||||
} catch {
|
||||
loaded = false;
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ export async function gatewayStatusCommand(
|
||||
warnings.push({
|
||||
code: "multiple_gateways",
|
||||
message:
|
||||
"Unconventional setup: multiple reachable gateways detected. Usually only one gateway should exist on a network.",
|
||||
"Unconventional setup: multiple reachable gateways detected. Usually one gateway per network is recommended unless you intentionally run isolated profiles, like a recovery bot (see docs: /gateway#multiple-gateways-same-host).",
|
||||
targetIds: reachable.map((p) => p.target.id),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,17 +38,16 @@ const selectStyled = <T>(params: Parameters<typeof select<T>>[0]) =>
|
||||
async function stopGatewayIfRunning(runtime: RuntimeEnv) {
|
||||
if (isNixMode) return;
|
||||
const service = resolveGatewayService();
|
||||
const profile = process.env.CLAWDBOT_PROFILE;
|
||||
let loaded = false;
|
||||
try {
|
||||
loaded = await service.isLoaded({ profile });
|
||||
loaded = await service.isLoaded({ env: process.env });
|
||||
} catch (err) {
|
||||
runtime.error(`Gateway service check failed: ${String(err)}`);
|
||||
return;
|
||||
}
|
||||
if (!loaded) return;
|
||||
try {
|
||||
await service.stop({ profile, stdout: process.stdout });
|
||||
await service.stop({ env: process.env, stdout: process.stdout });
|
||||
} catch (err) {
|
||||
runtime.error(`Gateway stop failed: ${String(err)}`);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ export async function statusAllCommand(
|
||||
try {
|
||||
const service = resolveGatewayService();
|
||||
const [loaded, runtimeInfo, command] = await Promise.all([
|
||||
service.isLoaded({ profile: process.env.CLAWDBOT_PROFILE }).catch(() => false),
|
||||
service.isLoaded({ env: process.env }).catch(() => false),
|
||||
service.readRuntime(process.env).catch(() => undefined),
|
||||
service.readCommand(process.env).catch(() => null),
|
||||
]);
|
||||
|
||||
@@ -10,7 +10,7 @@ export async function getDaemonStatusSummary(): Promise<{
|
||||
try {
|
||||
const service = resolveGatewayService();
|
||||
const [loaded, runtime, command] = await Promise.all([
|
||||
service.isLoaded({ profile: process.env.CLAWDBOT_PROFILE }).catch(() => false),
|
||||
service.isLoaded({ env: process.env }).catch(() => false),
|
||||
service.readRuntime(process.env).catch(() => undefined),
|
||||
service.readCommand(process.env).catch(() => null),
|
||||
]);
|
||||
|
||||
@@ -55,10 +55,9 @@ async function stopAndUninstallService(runtime: RuntimeEnv): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
const service = resolveGatewayService();
|
||||
const profile = process.env.CLAWDBOT_PROFILE;
|
||||
let loaded = false;
|
||||
try {
|
||||
loaded = await service.isLoaded({ profile });
|
||||
loaded = await service.isLoaded({ env: process.env });
|
||||
} catch (err) {
|
||||
runtime.error(`Gateway service check failed: ${String(err)}`);
|
||||
return false;
|
||||
@@ -68,7 +67,7 @@ async function stopAndUninstallService(runtime: RuntimeEnv): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
await service.stop({ profile, stdout: process.stdout });
|
||||
await service.stop({ env: process.env, stdout: process.stdout });
|
||||
} catch (err) {
|
||||
runtime.error(`Gateway stop failed: ${String(err)}`);
|
||||
}
|
||||
|
||||
@@ -98,6 +98,11 @@ describe("resolveGatewaySystemdServiceName", () => {
|
||||
const result = resolveGatewaySystemdServiceName("");
|
||||
expect(result).toBe(GATEWAY_SYSTEMD_SERVICE_NAME);
|
||||
});
|
||||
|
||||
it("returns default service name for whitespace-only profile", () => {
|
||||
const result = resolveGatewaySystemdServiceName(" ");
|
||||
expect(result).toBe(GATEWAY_SYSTEMD_SERVICE_NAME);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveGatewayWindowsTaskName", () => {
|
||||
@@ -141,6 +146,11 @@ describe("resolveGatewayWindowsTaskName", () => {
|
||||
const result = resolveGatewayWindowsTaskName("");
|
||||
expect(result).toBe(GATEWAY_WINDOWS_TASK_NAME);
|
||||
});
|
||||
|
||||
it("returns default task name for whitespace-only profile", () => {
|
||||
const result = resolveGatewayWindowsTaskName(" ");
|
||||
expect(result).toBe(GATEWAY_WINDOWS_TASK_NAME);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatGatewayServiceDescription", () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { PassThrough } from "node:stream";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { installLaunchAgent, parseLaunchctlPrint } from "./launchd.js";
|
||||
import { installLaunchAgent, parseLaunchctlPrint, resolveLaunchAgentPlistPath } from "./launchd.js";
|
||||
|
||||
describe("launchd runtime parsing", () => {
|
||||
it("parses state, pid, and exit status", () => {
|
||||
@@ -108,3 +108,79 @@ describe("launchd install", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveLaunchAgentPlistPath", () => {
|
||||
it("uses default label when CLAWDBOT_PROFILE is default", () => {
|
||||
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "default" };
|
||||
expect(resolveLaunchAgentPlistPath(env)).toBe(
|
||||
"/Users/test/Library/LaunchAgents/com.clawdbot.gateway.plist",
|
||||
);
|
||||
});
|
||||
|
||||
it("uses default label when CLAWDBOT_PROFILE is unset", () => {
|
||||
const env = { HOME: "/Users/test" };
|
||||
expect(resolveLaunchAgentPlistPath(env)).toBe(
|
||||
"/Users/test/Library/LaunchAgents/com.clawdbot.gateway.plist",
|
||||
);
|
||||
});
|
||||
|
||||
it("uses profile-specific label when CLAWDBOT_PROFILE is set to a custom value", () => {
|
||||
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "jbphoenix" };
|
||||
expect(resolveLaunchAgentPlistPath(env)).toBe(
|
||||
"/Users/test/Library/LaunchAgents/com.clawdbot.jbphoenix.plist",
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers CLAWDBOT_LAUNCHD_LABEL over CLAWDBOT_PROFILE", () => {
|
||||
const env = {
|
||||
HOME: "/Users/test",
|
||||
CLAWDBOT_PROFILE: "jbphoenix",
|
||||
CLAWDBOT_LAUNCHD_LABEL: "com.custom.label",
|
||||
};
|
||||
expect(resolveLaunchAgentPlistPath(env)).toBe(
|
||||
"/Users/test/Library/LaunchAgents/com.custom.label.plist",
|
||||
);
|
||||
});
|
||||
|
||||
it("trims whitespace from CLAWDBOT_LAUNCHD_LABEL", () => {
|
||||
const env = {
|
||||
HOME: "/Users/test",
|
||||
CLAWDBOT_LAUNCHD_LABEL: " com.custom.label ",
|
||||
};
|
||||
expect(resolveLaunchAgentPlistPath(env)).toBe(
|
||||
"/Users/test/Library/LaunchAgents/com.custom.label.plist",
|
||||
);
|
||||
});
|
||||
|
||||
it("ignores empty CLAWDBOT_LAUNCHD_LABEL and falls back to profile", () => {
|
||||
const env = {
|
||||
HOME: "/Users/test",
|
||||
CLAWDBOT_PROFILE: "myprofile",
|
||||
CLAWDBOT_LAUNCHD_LABEL: " ",
|
||||
};
|
||||
expect(resolveLaunchAgentPlistPath(env)).toBe(
|
||||
"/Users/test/Library/LaunchAgents/com.clawdbot.myprofile.plist",
|
||||
);
|
||||
});
|
||||
|
||||
it("handles case-insensitive 'Default' profile", () => {
|
||||
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "Default" };
|
||||
expect(resolveLaunchAgentPlistPath(env)).toBe(
|
||||
"/Users/test/Library/LaunchAgents/com.clawdbot.gateway.plist",
|
||||
);
|
||||
});
|
||||
|
||||
it("handles case-insensitive 'DEFAULT' profile", () => {
|
||||
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: "DEFAULT" };
|
||||
expect(resolveLaunchAgentPlistPath(env)).toBe(
|
||||
"/Users/test/Library/LaunchAgents/com.clawdbot.gateway.plist",
|
||||
);
|
||||
});
|
||||
|
||||
it("trims whitespace from CLAWDBOT_PROFILE", () => {
|
||||
const env = { HOME: "/Users/test", CLAWDBOT_PROFILE: " myprofile " };
|
||||
expect(resolveLaunchAgentPlistPath(env)).toBe(
|
||||
"/Users/test/Library/LaunchAgents/com.clawdbot.myprofile.plist",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,13 +24,10 @@ const formatLine = (label: string, value: string) => {
|
||||
return `${colorize(rich, theme.muted, `${label}:`)} ${colorize(rich, theme.command, value)}`;
|
||||
};
|
||||
|
||||
function resolveLaunchAgentLabel(params?: {
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
}): string {
|
||||
const envLabel = params?.env?.CLAWDBOT_LAUNCHD_LABEL?.trim();
|
||||
function resolveLaunchAgentLabel(args?: { env?: Record<string, string | undefined> }): string {
|
||||
const envLabel = args?.env?.CLAWDBOT_LAUNCHD_LABEL?.trim();
|
||||
if (envLabel) return envLabel;
|
||||
return resolveGatewayLaunchAgentLabel(params?.profile);
|
||||
return resolveGatewayLaunchAgentLabel(args?.env?.CLAWDBOT_PROFILE);
|
||||
}
|
||||
function resolveHomeDir(env: Record<string, string | undefined>): string {
|
||||
const home = env.HOME?.trim() || env.USERPROFILE?.trim();
|
||||
@@ -181,12 +178,11 @@ export function parseLaunchctlPrint(output: string): LaunchctlPrintInfo {
|
||||
return info;
|
||||
}
|
||||
|
||||
export async function isLaunchAgentLoaded(params?: {
|
||||
export async function isLaunchAgentLoaded(args: {
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
}): Promise<boolean> {
|
||||
const domain = resolveGuiDomain();
|
||||
const label = resolveLaunchAgentLabel(params);
|
||||
const label = resolveLaunchAgentLabel({ env: args.env });
|
||||
const res = await execLaunchctl(["print", `${domain}/${label}`]);
|
||||
return res.code === 0;
|
||||
}
|
||||
@@ -343,14 +339,12 @@ function isLaunchctlNotLoaded(res: { stdout: string; stderr: string; code: numbe
|
||||
export async function stopLaunchAgent({
|
||||
stdout,
|
||||
env,
|
||||
profile,
|
||||
}: {
|
||||
stdout: NodeJS.WritableStream;
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
}): Promise<void> {
|
||||
const domain = resolveGuiDomain();
|
||||
const label = resolveLaunchAgentLabel({ env, profile });
|
||||
const label = resolveLaunchAgentLabel({ env });
|
||||
const res = await execLaunchctl(["bootout", `${domain}/${label}`]);
|
||||
if (res.code !== 0 && !isLaunchctlNotLoaded(res)) {
|
||||
throw new Error(`launchctl bootout failed: ${res.stderr || res.stdout}`.trim());
|
||||
@@ -425,14 +419,12 @@ export async function installLaunchAgent({
|
||||
export async function restartLaunchAgent({
|
||||
stdout,
|
||||
env,
|
||||
profile,
|
||||
}: {
|
||||
stdout: NodeJS.WritableStream;
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
}): Promise<void> {
|
||||
const domain = resolveGuiDomain();
|
||||
const label = resolveLaunchAgentLabel({ env, profile });
|
||||
const label = resolveLaunchAgentLabel({ env });
|
||||
const res = await execLaunchctl(["kickstart", "-k", `${domain}/${label}`]);
|
||||
if (res.code !== 0) {
|
||||
throw new Error(`launchctl kickstart failed: ${res.stderr || res.stdout}`.trim());
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { parseSchtasksQuery } from "./schtasks.js";
|
||||
import { parseSchtasksQuery, readScheduledTaskCommand, resolveTaskScriptPath } from "./schtasks.js";
|
||||
|
||||
describe("schtasks runtime parsing", () => {
|
||||
it("parses status and last run info", () => {
|
||||
@@ -16,4 +20,222 @@ describe("schtasks runtime parsing", () => {
|
||||
lastRunResult: "0x0",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses running status", () => {
|
||||
const output = [
|
||||
"TaskName: \\Clawdbot Gateway",
|
||||
"Status: Running",
|
||||
"Last Run Time: 1/8/2026 1:23:45 AM",
|
||||
"Last Run Result: 0x0",
|
||||
].join("\r\n");
|
||||
expect(parseSchtasksQuery(output)).toEqual({
|
||||
status: "Running",
|
||||
lastRunTime: "1/8/2026 1:23:45 AM",
|
||||
lastRunResult: "0x0",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveTaskScriptPath", () => {
|
||||
it("uses default path when CLAWDBOT_PROFILE is default", () => {
|
||||
const env = { USERPROFILE: "C:\\Users\\test", CLAWDBOT_PROFILE: "default" };
|
||||
expect(resolveTaskScriptPath(env)).toBe(
|
||||
path.join("C:\\Users\\test", ".clawdbot", "gateway.cmd"),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses default path when CLAWDBOT_PROFILE is unset", () => {
|
||||
const env = { USERPROFILE: "C:\\Users\\test" };
|
||||
expect(resolveTaskScriptPath(env)).toBe(
|
||||
path.join("C:\\Users\\test", ".clawdbot", "gateway.cmd"),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses profile-specific path when CLAWDBOT_PROFILE is set to a custom value", () => {
|
||||
const env = { USERPROFILE: "C:\\Users\\test", CLAWDBOT_PROFILE: "jbphoenix" };
|
||||
expect(resolveTaskScriptPath(env)).toBe(
|
||||
path.join("C:\\Users\\test", ".clawdbot-jbphoenix", "gateway.cmd"),
|
||||
);
|
||||
});
|
||||
|
||||
it("handles case-insensitive 'Default' profile", () => {
|
||||
const env = { USERPROFILE: "C:\\Users\\test", CLAWDBOT_PROFILE: "Default" };
|
||||
expect(resolveTaskScriptPath(env)).toBe(
|
||||
path.join("C:\\Users\\test", ".clawdbot", "gateway.cmd"),
|
||||
);
|
||||
});
|
||||
|
||||
it("handles case-insensitive 'DEFAULT' profile", () => {
|
||||
const env = { USERPROFILE: "C:\\Users\\test", CLAWDBOT_PROFILE: "DEFAULT" };
|
||||
expect(resolveTaskScriptPath(env)).toBe(
|
||||
path.join("C:\\Users\\test", ".clawdbot", "gateway.cmd"),
|
||||
);
|
||||
});
|
||||
|
||||
it("trims whitespace from CLAWDBOT_PROFILE", () => {
|
||||
const env = { USERPROFILE: "C:\\Users\\test", CLAWDBOT_PROFILE: " myprofile " };
|
||||
expect(resolveTaskScriptPath(env)).toBe(
|
||||
path.join("C:\\Users\\test", ".clawdbot-myprofile", "gateway.cmd"),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to HOME when USERPROFILE is not set", () => {
|
||||
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: "default" };
|
||||
expect(resolveTaskScriptPath(env)).toBe(path.join("/home/test", ".clawdbot", "gateway.cmd"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("readScheduledTaskCommand", () => {
|
||||
it("parses basic command script", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-schtasks-test-"));
|
||||
try {
|
||||
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
|
||||
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
scriptPath,
|
||||
["@echo off", "node gateway.js --port 18789"].join("\r\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
|
||||
const result = await readScheduledTaskCommand(env);
|
||||
expect(result).toEqual({
|
||||
programArguments: ["node", "gateway.js", "--port", "18789"],
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("parses script with working directory", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-schtasks-test-"));
|
||||
try {
|
||||
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
|
||||
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
scriptPath,
|
||||
["@echo off", "cd /d C:\\Projects\\clawdbot", "node gateway.js"].join("\r\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
|
||||
const result = await readScheduledTaskCommand(env);
|
||||
expect(result).toEqual({
|
||||
programArguments: ["node", "gateway.js"],
|
||||
workingDirectory: "C:\\Projects\\clawdbot",
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("parses script with environment variables", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-schtasks-test-"));
|
||||
try {
|
||||
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
|
||||
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
scriptPath,
|
||||
["@echo off", "set NODE_ENV=production", "set PORT=18789", "node gateway.js"].join("\r\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
|
||||
const result = await readScheduledTaskCommand(env);
|
||||
expect(result).toEqual({
|
||||
programArguments: ["node", "gateway.js"],
|
||||
environment: {
|
||||
NODE_ENV: "production",
|
||||
PORT: "18789",
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("parses script with quoted arguments containing spaces", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-schtasks-test-"));
|
||||
try {
|
||||
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
|
||||
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
||||
// Use forward slashes which work in Windows cmd and avoid escape parsing issues
|
||||
await fs.writeFile(
|
||||
scriptPath,
|
||||
["@echo off", '"C:/Program Files/Node/node.exe" gateway.js'].join("\r\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
|
||||
const result = await readScheduledTaskCommand(env);
|
||||
expect(result).toEqual({
|
||||
programArguments: ["C:/Program Files/Node/node.exe", "gateway.js"],
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("returns null when script does not exist", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-schtasks-test-"));
|
||||
try {
|
||||
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
|
||||
const result = await readScheduledTaskCommand(env);
|
||||
expect(result).toBeNull();
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("returns null when script has no command", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-schtasks-test-"));
|
||||
try {
|
||||
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
|
||||
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
scriptPath,
|
||||
["@echo off", "rem This is just a comment"].join("\r\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
|
||||
const result = await readScheduledTaskCommand(env);
|
||||
expect(result).toBeNull();
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("parses full script with all components", async () => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-schtasks-test-"));
|
||||
try {
|
||||
const scriptPath = path.join(tmpDir, ".clawdbot", "gateway.cmd");
|
||||
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
scriptPath,
|
||||
[
|
||||
"@echo off",
|
||||
"rem Clawdbot Gateway",
|
||||
"cd /d C:\\Projects\\clawdbot",
|
||||
"set NODE_ENV=production",
|
||||
"set CLAWDBOT_PORT=18789",
|
||||
"node gateway.js --verbose",
|
||||
].join("\r\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const env = { USERPROFILE: tmpDir, CLAWDBOT_PROFILE: "default" };
|
||||
const result = await readScheduledTaskCommand(env);
|
||||
expect(result).toEqual({
|
||||
programArguments: ["node", "gateway.js", "--verbose"],
|
||||
workingDirectory: "C:\\Projects\\clawdbot",
|
||||
environment: {
|
||||
NODE_ENV: "production",
|
||||
CLAWDBOT_PORT: "18789",
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ function resolveHomeDir(env: Record<string, string | undefined>): string {
|
||||
return home;
|
||||
}
|
||||
|
||||
function resolveTaskScriptPath(env: Record<string, string | undefined>): string {
|
||||
export function resolveTaskScriptPath(env: Record<string, string | undefined>): string {
|
||||
const home = resolveHomeDir(env);
|
||||
const profile = env.CLAWDBOT_PROFILE?.trim();
|
||||
const suffix = profile && profile.toLowerCase() !== "default" ? `-${profile}` : "";
|
||||
@@ -274,13 +274,13 @@ function isTaskNotRunning(res: { stdout: string; stderr: string; code: number })
|
||||
|
||||
export async function stopScheduledTask({
|
||||
stdout,
|
||||
profile,
|
||||
env,
|
||||
}: {
|
||||
stdout: NodeJS.WritableStream;
|
||||
profile?: string;
|
||||
env?: Record<string, string | undefined>;
|
||||
}): Promise<void> {
|
||||
await assertSchtasksAvailable();
|
||||
const taskName = resolveGatewayWindowsTaskName(profile);
|
||||
const taskName = resolveGatewayWindowsTaskName(env?.CLAWDBOT_PROFILE);
|
||||
const res = await execSchtasks(["/End", "/TN", taskName]);
|
||||
if (res.code !== 0 && !isTaskNotRunning(res)) {
|
||||
throw new Error(`schtasks end failed: ${res.stderr || res.stdout}`.trim());
|
||||
@@ -290,13 +290,13 @@ export async function stopScheduledTask({
|
||||
|
||||
export async function restartScheduledTask({
|
||||
stdout,
|
||||
profile,
|
||||
env,
|
||||
}: {
|
||||
stdout: NodeJS.WritableStream;
|
||||
profile?: string;
|
||||
env?: Record<string, string | undefined>;
|
||||
}): Promise<void> {
|
||||
await assertSchtasksAvailable();
|
||||
const taskName = resolveGatewayWindowsTaskName(profile);
|
||||
const taskName = resolveGatewayWindowsTaskName(env?.CLAWDBOT_PROFILE);
|
||||
await execSchtasks(["/End", "/TN", taskName]);
|
||||
const res = await execSchtasks(["/Run", "/TN", taskName]);
|
||||
if (res.code !== 0) {
|
||||
@@ -305,9 +305,11 @@ export async function restartScheduledTask({
|
||||
stdout.write(`${formatLine("Restarted Scheduled Task", taskName)}\n`);
|
||||
}
|
||||
|
||||
export async function isScheduledTaskInstalled(profile?: string): Promise<boolean> {
|
||||
export async function isScheduledTaskInstalled(args: {
|
||||
env?: Record<string, string | undefined>;
|
||||
}): Promise<boolean> {
|
||||
await assertSchtasksAvailable();
|
||||
const taskName = resolveGatewayWindowsTaskName(profile);
|
||||
const taskName = resolveGatewayWindowsTaskName(args.env?.CLAWDBOT_PROFILE);
|
||||
const res = await execSchtasks(["/Query", "/TN", taskName]);
|
||||
return res.code === 0;
|
||||
}
|
||||
|
||||
@@ -46,18 +46,13 @@ export type GatewayService = {
|
||||
}) => Promise<void>;
|
||||
stop: (args: {
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
stdout: NodeJS.WritableStream;
|
||||
}) => Promise<void>;
|
||||
restart: (args: {
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
stdout: NodeJS.WritableStream;
|
||||
}) => Promise<void>;
|
||||
isLoaded: (args: {
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
}) => Promise<boolean>;
|
||||
isLoaded: (args: { env?: Record<string, string | undefined> }) => Promise<boolean>;
|
||||
readCommand: (env: Record<string, string | undefined>) => Promise<{
|
||||
programArguments: string[];
|
||||
workingDirectory?: string;
|
||||
@@ -82,18 +77,16 @@ export function resolveGatewayService(): GatewayService {
|
||||
stop: async (args) => {
|
||||
await stopLaunchAgent({
|
||||
stdout: args.stdout,
|
||||
profile: args.profile,
|
||||
env: args.env,
|
||||
});
|
||||
},
|
||||
restart: async (args) => {
|
||||
await restartLaunchAgent({
|
||||
stdout: args.stdout,
|
||||
profile: args.profile,
|
||||
env: args.env,
|
||||
});
|
||||
},
|
||||
isLoaded: async (args) => isLaunchAgentLoaded({ profile: args.profile, env: args.env }),
|
||||
isLoaded: async (args) => isLaunchAgentLoaded(args),
|
||||
readCommand: readLaunchAgentProgramArguments,
|
||||
readRuntime: readLaunchAgentRuntime,
|
||||
};
|
||||
@@ -113,18 +106,16 @@ export function resolveGatewayService(): GatewayService {
|
||||
stop: async (args) => {
|
||||
await stopSystemdService({
|
||||
stdout: args.stdout,
|
||||
profile: args.profile,
|
||||
env: args.env,
|
||||
});
|
||||
},
|
||||
restart: async (args) => {
|
||||
await restartSystemdService({
|
||||
stdout: args.stdout,
|
||||
profile: args.profile,
|
||||
env: args.env,
|
||||
});
|
||||
},
|
||||
isLoaded: async (args) => isSystemdServiceEnabled({ profile: args.profile, env: args.env }),
|
||||
isLoaded: async (args) => isSystemdServiceEnabled(args),
|
||||
readCommand: readSystemdServiceExecStart,
|
||||
readRuntime: async (env) => await readSystemdServiceRuntime(env),
|
||||
};
|
||||
@@ -144,16 +135,16 @@ export function resolveGatewayService(): GatewayService {
|
||||
stop: async (args) => {
|
||||
await stopScheduledTask({
|
||||
stdout: args.stdout,
|
||||
profile: args.profile,
|
||||
env: args.env,
|
||||
});
|
||||
},
|
||||
restart: async (args) => {
|
||||
await restartScheduledTask({
|
||||
stdout: args.stdout,
|
||||
profile: args.profile,
|
||||
env: args.env,
|
||||
});
|
||||
},
|
||||
isLoaded: async (args) => isScheduledTaskInstalled(args.profile),
|
||||
isLoaded: async (args) => isScheduledTaskInstalled(args),
|
||||
readCommand: readScheduledTaskCommand,
|
||||
readRuntime: async (env) => await readScheduledTaskRuntime(env),
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { parseSystemdShow } from "./systemd.js";
|
||||
import { parseSystemdShow, resolveSystemdUserUnitPath } from "./systemd.js";
|
||||
|
||||
describe("systemd runtime parsing", () => {
|
||||
it("parses active state details", () => {
|
||||
@@ -19,3 +19,78 @@ describe("systemd runtime parsing", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveSystemdUserUnitPath", () => {
|
||||
it("uses default service name when CLAWDBOT_PROFILE is default", () => {
|
||||
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: "default" };
|
||||
expect(resolveSystemdUserUnitPath(env)).toBe(
|
||||
"/home/test/.config/systemd/user/clawdbot-gateway.service",
|
||||
);
|
||||
});
|
||||
|
||||
it("uses default service name when CLAWDBOT_PROFILE is unset", () => {
|
||||
const env = { HOME: "/home/test" };
|
||||
expect(resolveSystemdUserUnitPath(env)).toBe(
|
||||
"/home/test/.config/systemd/user/clawdbot-gateway.service",
|
||||
);
|
||||
});
|
||||
|
||||
it("uses profile-specific service name when CLAWDBOT_PROFILE is set to a custom value", () => {
|
||||
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: "jbphoenix" };
|
||||
expect(resolveSystemdUserUnitPath(env)).toBe(
|
||||
"/home/test/.config/systemd/user/clawdbot-gateway-jbphoenix.service",
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers CLAWDBOT_SYSTEMD_UNIT over CLAWDBOT_PROFILE", () => {
|
||||
const env = {
|
||||
HOME: "/home/test",
|
||||
CLAWDBOT_PROFILE: "jbphoenix",
|
||||
CLAWDBOT_SYSTEMD_UNIT: "custom-unit",
|
||||
};
|
||||
expect(resolveSystemdUserUnitPath(env)).toBe(
|
||||
"/home/test/.config/systemd/user/custom-unit.service",
|
||||
);
|
||||
});
|
||||
|
||||
it("handles CLAWDBOT_SYSTEMD_UNIT with .service suffix", () => {
|
||||
const env = {
|
||||
HOME: "/home/test",
|
||||
CLAWDBOT_SYSTEMD_UNIT: "custom-unit.service",
|
||||
};
|
||||
expect(resolveSystemdUserUnitPath(env)).toBe(
|
||||
"/home/test/.config/systemd/user/custom-unit.service",
|
||||
);
|
||||
});
|
||||
|
||||
it("trims whitespace from CLAWDBOT_SYSTEMD_UNIT", () => {
|
||||
const env = {
|
||||
HOME: "/home/test",
|
||||
CLAWDBOT_SYSTEMD_UNIT: " custom-unit ",
|
||||
};
|
||||
expect(resolveSystemdUserUnitPath(env)).toBe(
|
||||
"/home/test/.config/systemd/user/custom-unit.service",
|
||||
);
|
||||
});
|
||||
|
||||
it("handles case-insensitive 'Default' profile", () => {
|
||||
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: "Default" };
|
||||
expect(resolveSystemdUserUnitPath(env)).toBe(
|
||||
"/home/test/.config/systemd/user/clawdbot-gateway.service",
|
||||
);
|
||||
});
|
||||
|
||||
it("handles case-insensitive 'DEFAULT' profile", () => {
|
||||
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: "DEFAULT" };
|
||||
expect(resolveSystemdUserUnitPath(env)).toBe(
|
||||
"/home/test/.config/systemd/user/clawdbot-gateway.service",
|
||||
);
|
||||
});
|
||||
|
||||
it("trims whitespace from CLAWDBOT_PROFILE", () => {
|
||||
const env = { HOME: "/home/test", CLAWDBOT_PROFILE: " myprofile " };
|
||||
expect(resolveSystemdUserUnitPath(env)).toBe(
|
||||
"/home/test/.config/systemd/user/clawdbot-gateway-myprofile.service",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,14 +50,6 @@ function resolveSystemdServiceName(env: Record<string, string | undefined>): str
|
||||
return resolveGatewaySystemdServiceName(env.CLAWDBOT_PROFILE);
|
||||
}
|
||||
|
||||
function resolveSystemdServiceNameFromParams(params?: {
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
}): string {
|
||||
if (params?.env) return resolveSystemdServiceName(params.env);
|
||||
return resolveGatewaySystemdServiceName(params?.profile);
|
||||
}
|
||||
|
||||
function resolveSystemdUnitPath(env: Record<string, string | undefined>): string {
|
||||
return resolveSystemdUnitPathForName(env, resolveSystemdServiceName(env));
|
||||
}
|
||||
@@ -268,14 +260,12 @@ export async function uninstallSystemdService({
|
||||
export async function stopSystemdService({
|
||||
stdout,
|
||||
env,
|
||||
profile,
|
||||
}: {
|
||||
stdout: NodeJS.WritableStream;
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
}): Promise<void> {
|
||||
await assertSystemdAvailable();
|
||||
const serviceName = resolveSystemdServiceNameFromParams({ env, profile });
|
||||
const serviceName = resolveSystemdServiceName(env ?? {});
|
||||
const unitName = `${serviceName}.service`;
|
||||
const res = await execSystemctl(["--user", "stop", unitName]);
|
||||
if (res.code !== 0) {
|
||||
@@ -287,14 +277,12 @@ export async function stopSystemdService({
|
||||
export async function restartSystemdService({
|
||||
stdout,
|
||||
env,
|
||||
profile,
|
||||
}: {
|
||||
stdout: NodeJS.WritableStream;
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
}): Promise<void> {
|
||||
await assertSystemdAvailable();
|
||||
const serviceName = resolveSystemdServiceNameFromParams({ env, profile });
|
||||
const serviceName = resolveSystemdServiceName(env ?? {});
|
||||
const unitName = `${serviceName}.service`;
|
||||
const res = await execSystemctl(["--user", "restart", unitName]);
|
||||
if (res.code !== 0) {
|
||||
@@ -303,12 +291,11 @@ export async function restartSystemdService({
|
||||
stdout.write(`${formatLine("Restarted systemd service", unitName)}\n`);
|
||||
}
|
||||
|
||||
export async function isSystemdServiceEnabled(params?: {
|
||||
export async function isSystemdServiceEnabled(args: {
|
||||
env?: Record<string, string | undefined>;
|
||||
profile?: string;
|
||||
}): Promise<boolean> {
|
||||
await assertSystemdAvailable();
|
||||
const serviceName = resolveSystemdServiceNameFromParams(params);
|
||||
const serviceName = resolveSystemdServiceName(args.env ?? {});
|
||||
const unitName = `${serviceName}.service`;
|
||||
const res = await execSystemctl(["--user", "is-enabled", unitName]);
|
||||
return res.code === 0;
|
||||
|
||||
@@ -32,7 +32,9 @@ export function buildPortHints(listeners: PortListener[], port: number): string[
|
||||
hints.push("Another process is listening on this port.");
|
||||
}
|
||||
if (listeners.length > 1) {
|
||||
hints.push("Multiple listeners detected; ensure only one gateway/tunnel.");
|
||||
hints.push(
|
||||
"Multiple listeners detected; ensure only one gateway/tunnel per port unless intentionally running isolated profiles.",
|
||||
);
|
||||
}
|
||||
return hints;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
|
||||
);
|
||||
}
|
||||
const service = resolveGatewayService();
|
||||
const loaded = await service.isLoaded({ profile: process.env.CLAWDBOT_PROFILE });
|
||||
const loaded = await service.isLoaded({ env: process.env });
|
||||
if (loaded) {
|
||||
const action = (await prompter.select({
|
||||
message: "Gateway service already installed",
|
||||
@@ -143,7 +143,7 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
|
||||
async (progress) => {
|
||||
progress.update("Restarting Gateway daemon…");
|
||||
await service.restart({
|
||||
profile: process.env.CLAWDBOT_PROFILE,
|
||||
env: process.env,
|
||||
stdout: process.stdout,
|
||||
});
|
||||
},
|
||||
@@ -160,10 +160,7 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!loaded ||
|
||||
(loaded && (await service.isLoaded({ profile: process.env.CLAWDBOT_PROFILE })) === false)
|
||||
) {
|
||||
if (!loaded || (loaded && (await service.isLoaded({ env: process.env })) === false)) {
|
||||
const devMode =
|
||||
process.argv[1]?.includes(`${path.sep}src${path.sep}`) && process.argv[1]?.endsWith(".ts");
|
||||
await withWizardProgress(
|
||||
|
||||
Reference in New Issue
Block a user