460 lines
16 KiB
JavaScript
460 lines
16 KiB
JavaScript
import { t as formatCliCommand } from "./command-format-3xiXujG0.js";
|
|
import { M as VERSION, k as collectConfigEnvVars } from "./config-DxJyvt4o.js";
|
|
import { a as NODE_SERVICE_MARKER, d as resolveGatewaySystemdServiceName, g as resolveNodeWindowsTaskName, h as resolveNodeSystemdServiceName, i as NODE_SERVICE_KIND, l as resolveGatewayLaunchAgentLabel, m as resolveNodeLaunchAgentLabel, n as GATEWAY_SERVICE_KIND, o as NODE_WINDOWS_TASK_SCRIPT_NAME, r as GATEWAY_SERVICE_MARKER } from "./constants-CLUi6T-M.js";
|
|
import { n as isSupportedNodeVersion } from "./runtime-guard-DkjmhnBD.js";
|
|
import { execFile } from "node:child_process";
|
|
import path from "node:path";
|
|
import { promisify } from "node:util";
|
|
import fs from "node:fs/promises";
|
|
|
|
//#region src/daemon/program-args.ts
|
|
function isNodeRuntime(execPath) {
|
|
const base = path.basename(execPath).toLowerCase();
|
|
return base === "node" || base === "node.exe";
|
|
}
|
|
function isBunRuntime(execPath) {
|
|
const base = path.basename(execPath).toLowerCase();
|
|
return base === "bun" || base === "bun.exe";
|
|
}
|
|
async function resolveCliEntrypointPathForService() {
|
|
const argv1 = process.argv[1];
|
|
if (!argv1) throw new Error("Unable to resolve CLI entrypoint path");
|
|
const normalized = path.resolve(argv1);
|
|
const resolvedPath = await resolveRealpathSafe(normalized);
|
|
if (/[/\\]dist[/\\].+\.(cjs|js|mjs)$/.test(resolvedPath)) {
|
|
await fs.access(resolvedPath);
|
|
if (/[/\\]dist[/\\].+\.(cjs|js|mjs)$/.test(normalized) && normalized !== resolvedPath) try {
|
|
await fs.access(normalized);
|
|
return normalized;
|
|
} catch {}
|
|
return resolvedPath;
|
|
}
|
|
const distCandidates = buildDistCandidates(resolvedPath, normalized);
|
|
for (const candidate of distCandidates) try {
|
|
await fs.access(candidate);
|
|
return candidate;
|
|
} catch {}
|
|
throw new Error(`Cannot find built CLI at ${distCandidates.join(" or ")}. Run "pnpm build" first, or use dev mode.`);
|
|
}
|
|
async function resolveRealpathSafe(inputPath) {
|
|
try {
|
|
return await fs.realpath(inputPath);
|
|
} catch {
|
|
return inputPath;
|
|
}
|
|
}
|
|
function buildDistCandidates(...inputs) {
|
|
const candidates = [];
|
|
const seen = /* @__PURE__ */ new Set();
|
|
for (const inputPath of inputs) {
|
|
if (!inputPath) continue;
|
|
const baseDir = path.dirname(inputPath);
|
|
appendDistCandidates(candidates, seen, path.resolve(baseDir, ".."));
|
|
appendDistCandidates(candidates, seen, baseDir);
|
|
appendNodeModulesBinCandidates(candidates, seen, inputPath);
|
|
}
|
|
return candidates;
|
|
}
|
|
function appendDistCandidates(candidates, seen, baseDir) {
|
|
const distDir = path.resolve(baseDir, "dist");
|
|
const distEntries = [
|
|
path.join(distDir, "index.js"),
|
|
path.join(distDir, "index.mjs"),
|
|
path.join(distDir, "entry.js"),
|
|
path.join(distDir, "entry.mjs")
|
|
];
|
|
for (const entry of distEntries) {
|
|
if (seen.has(entry)) continue;
|
|
seen.add(entry);
|
|
candidates.push(entry);
|
|
}
|
|
}
|
|
function appendNodeModulesBinCandidates(candidates, seen, inputPath) {
|
|
const parts = inputPath.split(path.sep);
|
|
const binIndex = parts.lastIndexOf(".bin");
|
|
if (binIndex <= 0) return;
|
|
if (parts[binIndex - 1] !== "node_modules") return;
|
|
const binName = path.basename(inputPath);
|
|
const nodeModulesDir = parts.slice(0, binIndex).join(path.sep);
|
|
appendDistCandidates(candidates, seen, path.join(nodeModulesDir, binName));
|
|
}
|
|
function resolveRepoRootForDev() {
|
|
const argv1 = process.argv[1];
|
|
if (!argv1) throw new Error("Unable to resolve repo root");
|
|
const parts = path.resolve(argv1).split(path.sep);
|
|
const srcIndex = parts.lastIndexOf("src");
|
|
if (srcIndex === -1) throw new Error("Dev mode requires running from repo (src/index.ts)");
|
|
return parts.slice(0, srcIndex).join(path.sep);
|
|
}
|
|
async function resolveBunPath() {
|
|
return await resolveBinaryPath("bun");
|
|
}
|
|
async function resolveNodePath() {
|
|
return await resolveBinaryPath("node");
|
|
}
|
|
async function resolveBinaryPath(binary) {
|
|
const { execSync } = await import("node:child_process");
|
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
try {
|
|
const resolved = execSync(`${cmd} ${binary}`, { encoding: "utf8" }).trim().split(/\r?\n/)[0]?.trim();
|
|
if (!resolved) throw new Error("empty");
|
|
await fs.access(resolved);
|
|
return resolved;
|
|
} catch {
|
|
if (binary === "bun") throw new Error("Bun not found in PATH. Install bun: https://bun.sh");
|
|
throw new Error("Node not found in PATH. Install Node 22+.");
|
|
}
|
|
}
|
|
async function resolveCliProgramArguments(params) {
|
|
const execPath = process.execPath;
|
|
const runtime = params.runtime ?? "auto";
|
|
if (runtime === "node") return { programArguments: [
|
|
params.nodePath ?? (isNodeRuntime(execPath) ? execPath : await resolveNodePath()),
|
|
await resolveCliEntrypointPathForService(),
|
|
...params.args
|
|
] };
|
|
if (runtime === "bun") {
|
|
if (params.dev) {
|
|
const repoRoot = resolveRepoRootForDev();
|
|
const devCliPath = path.join(repoRoot, "src", "index.ts");
|
|
await fs.access(devCliPath);
|
|
return {
|
|
programArguments: [
|
|
isBunRuntime(execPath) ? execPath : await resolveBunPath(),
|
|
devCliPath,
|
|
...params.args
|
|
],
|
|
workingDirectory: repoRoot
|
|
};
|
|
}
|
|
return { programArguments: [
|
|
isBunRuntime(execPath) ? execPath : await resolveBunPath(),
|
|
await resolveCliEntrypointPathForService(),
|
|
...params.args
|
|
] };
|
|
}
|
|
if (!params.dev) try {
|
|
return { programArguments: [
|
|
execPath,
|
|
await resolveCliEntrypointPathForService(),
|
|
...params.args
|
|
] };
|
|
} catch (error) {
|
|
if (!isNodeRuntime(execPath)) return { programArguments: [execPath, ...params.args] };
|
|
throw error;
|
|
}
|
|
const repoRoot = resolveRepoRootForDev();
|
|
const devCliPath = path.join(repoRoot, "src", "index.ts");
|
|
await fs.access(devCliPath);
|
|
if (isBunRuntime(execPath)) return {
|
|
programArguments: [
|
|
execPath,
|
|
devCliPath,
|
|
...params.args
|
|
],
|
|
workingDirectory: repoRoot
|
|
};
|
|
return {
|
|
programArguments: [
|
|
await resolveBunPath(),
|
|
devCliPath,
|
|
...params.args
|
|
],
|
|
workingDirectory: repoRoot
|
|
};
|
|
}
|
|
async function resolveGatewayProgramArguments(params) {
|
|
return resolveCliProgramArguments({
|
|
args: [
|
|
"gateway",
|
|
"--port",
|
|
String(params.port)
|
|
],
|
|
dev: params.dev,
|
|
runtime: params.runtime,
|
|
nodePath: params.nodePath
|
|
});
|
|
}
|
|
async function resolveNodeProgramArguments(params) {
|
|
const args = [
|
|
"node",
|
|
"run",
|
|
"--host",
|
|
params.host,
|
|
"--port",
|
|
String(params.port)
|
|
];
|
|
if (params.tls || params.tlsFingerprint) args.push("--tls");
|
|
if (params.tlsFingerprint) args.push("--tls-fingerprint", params.tlsFingerprint);
|
|
if (params.nodeId) args.push("--node-id", params.nodeId);
|
|
if (params.displayName) args.push("--display-name", params.displayName);
|
|
return resolveCliProgramArguments({
|
|
args,
|
|
dev: params.dev,
|
|
runtime: params.runtime,
|
|
nodePath: params.nodePath
|
|
});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/daemon/runtime-paths.ts
|
|
const VERSION_MANAGER_MARKERS = [
|
|
"/.nvm/",
|
|
"/.fnm/",
|
|
"/.volta/",
|
|
"/.asdf/",
|
|
"/.n/",
|
|
"/.nodenv/",
|
|
"/.nodebrew/",
|
|
"/nvs/"
|
|
];
|
|
function getPathModule(platform) {
|
|
return platform === "win32" ? path.win32 : path.posix;
|
|
}
|
|
function normalizeForCompare(input, platform) {
|
|
const normalized = getPathModule(platform).normalize(input).replaceAll("\\", "/");
|
|
if (platform === "win32") return normalized.toLowerCase();
|
|
return normalized;
|
|
}
|
|
function buildSystemNodeCandidates(env, platform) {
|
|
if (platform === "darwin") return [
|
|
"/opt/homebrew/bin/node",
|
|
"/usr/local/bin/node",
|
|
"/usr/bin/node"
|
|
];
|
|
if (platform === "linux") return ["/usr/local/bin/node", "/usr/bin/node"];
|
|
if (platform === "win32") {
|
|
const pathModule = getPathModule(platform);
|
|
const programFiles = env.ProgramFiles ?? "C:\\Program Files";
|
|
const programFilesX86 = env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
return [pathModule.join(programFiles, "nodejs", "node.exe"), pathModule.join(programFilesX86, "nodejs", "node.exe")];
|
|
}
|
|
return [];
|
|
}
|
|
const execFileAsync = promisify(execFile);
|
|
async function resolveNodeVersion(nodePath, execFileImpl) {
|
|
try {
|
|
const { stdout } = await execFileImpl(nodePath, ["-p", "process.versions.node"], { encoding: "utf8" });
|
|
const value = stdout.trim();
|
|
return value ? value : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
function isVersionManagedNodePath(nodePath, platform = process.platform) {
|
|
const normalized = normalizeForCompare(nodePath, platform);
|
|
return VERSION_MANAGER_MARKERS.some((marker) => normalized.includes(marker));
|
|
}
|
|
function isSystemNodePath(nodePath, env = process.env, platform = process.platform) {
|
|
const normalized = normalizeForCompare(nodePath, platform);
|
|
return buildSystemNodeCandidates(env, platform).some((candidate) => {
|
|
return normalized === normalizeForCompare(candidate, platform);
|
|
});
|
|
}
|
|
async function resolveSystemNodePath(env = process.env, platform = process.platform) {
|
|
const candidates = buildSystemNodeCandidates(env, platform);
|
|
for (const candidate of candidates) try {
|
|
await fs.access(candidate);
|
|
return candidate;
|
|
} catch {}
|
|
return null;
|
|
}
|
|
async function resolveSystemNodeInfo(params) {
|
|
const systemNode = await resolveSystemNodePath(params.env ?? process.env, params.platform ?? process.platform);
|
|
if (!systemNode) return null;
|
|
const version = await resolveNodeVersion(systemNode, params.execFile ?? execFileAsync);
|
|
return {
|
|
path: systemNode,
|
|
version,
|
|
supported: isSupportedNodeVersion(version)
|
|
};
|
|
}
|
|
function renderSystemNodeWarning(systemNode, selectedNodePath) {
|
|
if (!systemNode || systemNode.supported) return null;
|
|
const versionLabel = systemNode.version ?? "unknown";
|
|
const selectedLabel = selectedNodePath ? ` Using ${selectedNodePath} for the daemon.` : "";
|
|
return `System Node ${versionLabel} at ${systemNode.path} is below the required Node 22+.${selectedLabel} Install Node 22+ from nodejs.org or Homebrew.`;
|
|
}
|
|
async function resolvePreferredNodePath(params) {
|
|
if (params.runtime !== "node") return;
|
|
const systemNode = await resolveSystemNodeInfo(params);
|
|
if (!systemNode?.supported) return;
|
|
return systemNode.path;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/daemon/service-env.ts
|
|
function resolveSystemPathDirs(platform) {
|
|
if (platform === "darwin") return [
|
|
"/opt/homebrew/bin",
|
|
"/usr/local/bin",
|
|
"/usr/bin",
|
|
"/bin"
|
|
];
|
|
if (platform === "linux") return [
|
|
"/usr/local/bin",
|
|
"/usr/bin",
|
|
"/bin"
|
|
];
|
|
return [];
|
|
}
|
|
/**
|
|
* Resolve common user bin directories for Linux.
|
|
* These are paths where npm global installs and node version managers typically place binaries.
|
|
*/
|
|
function resolveLinuxUserBinDirs(home, env) {
|
|
if (!home) return [];
|
|
const dirs = [];
|
|
const add = (dir) => {
|
|
if (dir) dirs.push(dir);
|
|
};
|
|
const appendSubdir = (base, subdir) => {
|
|
if (!base) return;
|
|
return base.endsWith(`/${subdir}`) ? base : path.posix.join(base, subdir);
|
|
};
|
|
add(env?.PNPM_HOME);
|
|
add(appendSubdir(env?.NPM_CONFIG_PREFIX, "bin"));
|
|
add(appendSubdir(env?.BUN_INSTALL, "bin"));
|
|
add(appendSubdir(env?.VOLTA_HOME, "bin"));
|
|
add(appendSubdir(env?.ASDF_DATA_DIR, "shims"));
|
|
add(appendSubdir(env?.NVM_DIR, "current/bin"));
|
|
add(appendSubdir(env?.FNM_DIR, "current/bin"));
|
|
dirs.push(`${home}/.local/bin`);
|
|
dirs.push(`${home}/.npm-global/bin`);
|
|
dirs.push(`${home}/bin`);
|
|
dirs.push(`${home}/.nvm/current/bin`);
|
|
dirs.push(`${home}/.fnm/current/bin`);
|
|
dirs.push(`${home}/.volta/bin`);
|
|
dirs.push(`${home}/.asdf/shims`);
|
|
dirs.push(`${home}/.local/share/pnpm`);
|
|
dirs.push(`${home}/.bun/bin`);
|
|
return dirs;
|
|
}
|
|
function getMinimalServicePathParts(options = {}) {
|
|
const platform = options.platform ?? process.platform;
|
|
if (platform === "win32") return [];
|
|
const parts = [];
|
|
const extraDirs = options.extraDirs ?? [];
|
|
const systemDirs = resolveSystemPathDirs(platform);
|
|
const linuxUserDirs = platform === "linux" ? resolveLinuxUserBinDirs(options.home, options.env) : [];
|
|
const add = (dir) => {
|
|
if (!dir) return;
|
|
if (!parts.includes(dir)) parts.push(dir);
|
|
};
|
|
for (const dir of extraDirs) add(dir);
|
|
for (const dir of linuxUserDirs) add(dir);
|
|
for (const dir of systemDirs) add(dir);
|
|
return parts;
|
|
}
|
|
function getMinimalServicePathPartsFromEnv(options = {}) {
|
|
const env = options.env ?? process.env;
|
|
return getMinimalServicePathParts({
|
|
...options,
|
|
home: options.home ?? env.HOME,
|
|
env
|
|
});
|
|
}
|
|
function buildMinimalServicePath(options = {}) {
|
|
const env = options.env ?? process.env;
|
|
if ((options.platform ?? process.platform) === "win32") return env.PATH ?? "";
|
|
return getMinimalServicePathPartsFromEnv({
|
|
...options,
|
|
env
|
|
}).join(path.posix.delimiter);
|
|
}
|
|
function buildServiceEnvironment(params) {
|
|
const { env, port, token, launchdLabel } = params;
|
|
const profile = env.OPENCLAW_PROFILE;
|
|
const resolvedLaunchdLabel = launchdLabel || (process.platform === "darwin" ? resolveGatewayLaunchAgentLabel(profile) : void 0);
|
|
const systemdUnit = `${resolveGatewaySystemdServiceName(profile)}.service`;
|
|
const stateDir = env.OPENCLAW_STATE_DIR;
|
|
const configPath = env.OPENCLAW_CONFIG_PATH;
|
|
return {
|
|
HOME: env.HOME,
|
|
PATH: buildMinimalServicePath({ env }),
|
|
OPENCLAW_PROFILE: profile,
|
|
OPENCLAW_STATE_DIR: stateDir,
|
|
OPENCLAW_CONFIG_PATH: configPath,
|
|
OPENCLAW_GATEWAY_PORT: String(port),
|
|
OPENCLAW_GATEWAY_TOKEN: token,
|
|
OPENCLAW_LAUNCHD_LABEL: resolvedLaunchdLabel,
|
|
OPENCLAW_SYSTEMD_UNIT: systemdUnit,
|
|
OPENCLAW_SERVICE_MARKER: GATEWAY_SERVICE_MARKER,
|
|
OPENCLAW_SERVICE_KIND: GATEWAY_SERVICE_KIND,
|
|
OPENCLAW_SERVICE_VERSION: VERSION
|
|
};
|
|
}
|
|
function buildNodeServiceEnvironment(params) {
|
|
const { env } = params;
|
|
const stateDir = env.OPENCLAW_STATE_DIR;
|
|
const configPath = env.OPENCLAW_CONFIG_PATH;
|
|
return {
|
|
HOME: env.HOME,
|
|
PATH: buildMinimalServicePath({ env }),
|
|
OPENCLAW_STATE_DIR: stateDir,
|
|
OPENCLAW_CONFIG_PATH: configPath,
|
|
OPENCLAW_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel(),
|
|
OPENCLAW_SYSTEMD_UNIT: resolveNodeSystemdServiceName(),
|
|
OPENCLAW_WINDOWS_TASK_NAME: resolveNodeWindowsTaskName(),
|
|
OPENCLAW_TASK_SCRIPT_NAME: NODE_WINDOWS_TASK_SCRIPT_NAME,
|
|
OPENCLAW_LOG_PREFIX: "node",
|
|
OPENCLAW_SERVICE_MARKER: NODE_SERVICE_MARKER,
|
|
OPENCLAW_SERVICE_KIND: NODE_SERVICE_KIND,
|
|
OPENCLAW_SERVICE_VERSION: VERSION
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/commands/daemon-install-helpers.ts
|
|
function resolveGatewayDevMode(argv = process.argv) {
|
|
const normalizedEntry = argv[1]?.replaceAll("\\", "/");
|
|
return Boolean(normalizedEntry?.includes("/src/") && normalizedEntry.endsWith(".ts"));
|
|
}
|
|
async function buildGatewayInstallPlan(params) {
|
|
const devMode = params.devMode ?? resolveGatewayDevMode();
|
|
const nodePath = params.nodePath ?? await resolvePreferredNodePath({
|
|
env: params.env,
|
|
runtime: params.runtime
|
|
});
|
|
const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({
|
|
port: params.port,
|
|
dev: devMode,
|
|
runtime: params.runtime,
|
|
nodePath
|
|
});
|
|
if (params.runtime === "node") {
|
|
const warning = renderSystemNodeWarning(await resolveSystemNodeInfo({ env: params.env }), programArguments[0]);
|
|
if (warning) params.warn?.(warning, "Gateway runtime");
|
|
}
|
|
const serviceEnvironment = buildServiceEnvironment({
|
|
env: params.env,
|
|
port: params.port,
|
|
token: params.token,
|
|
launchdLabel: process.platform === "darwin" ? resolveGatewayLaunchAgentLabel(params.env.OPENCLAW_PROFILE) : void 0
|
|
});
|
|
const environment = { ...collectConfigEnvVars(params.config) };
|
|
Object.assign(environment, serviceEnvironment);
|
|
return {
|
|
programArguments,
|
|
workingDirectory,
|
|
environment
|
|
};
|
|
}
|
|
function gatewayInstallErrorHint(platform = process.platform) {
|
|
return platform === "win32" ? "Tip: rerun from an elevated PowerShell (Start → type PowerShell → right-click → Run as administrator) or skip service install." : `Tip: rerun \`${formatCliCommand("openclaw gateway install")}\` after fixing the error.`;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/commands/daemon-runtime.ts
|
|
const DEFAULT_GATEWAY_DAEMON_RUNTIME = "node";
|
|
const GATEWAY_DAEMON_RUNTIME_OPTIONS = [{
|
|
value: "node",
|
|
label: "Node (recommended)",
|
|
hint: "Required for WhatsApp + Telegram. Bun can corrupt memory on reconnect."
|
|
}];
|
|
function isGatewayDaemonRuntime(value) {
|
|
return value === "node" || value === "bun";
|
|
}
|
|
|
|
//#endregion
|
|
export { gatewayInstallErrorHint as a, getMinimalServicePathPartsFromEnv as c, renderSystemNodeWarning as d, resolvePreferredNodePath as f, resolveNodeProgramArguments as h, buildGatewayInstallPlan as i, isSystemNodePath as l, resolveSystemNodePath as m, GATEWAY_DAEMON_RUNTIME_OPTIONS as n, resolveGatewayDevMode as o, resolveSystemNodeInfo as p, isGatewayDaemonRuntime as r, buildNodeServiceEnvironment as s, DEFAULT_GATEWAY_DAEMON_RUNTIME as t, isVersionManagedNodePath as u }; |