openclaw/src/cli/run-main.ts

84 lines
2.9 KiB
TypeScript

import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
import { loadDotEnv } from "../infra/dotenv.js";
import { normalizeEnv } from "../infra/env.js";
import { isMainModule } from "../infra/is-main.js";
import { ensureClawdbotCliOnPath } from "../infra/path-env.js";
import { assertSupportedRuntime } from "../infra/runtime-guard.js";
import { installUnhandledRejectionHandler } from "../infra/unhandled-rejections.js";
import { enableConsoleCapture } from "../logging.js";
import { tryRouteCli } from "./route.js";
export function rewriteUpdateFlagArgv(argv: string[]): string[] {
const index = argv.indexOf("--update");
if (index === -1) return argv;
const next = [...argv];
next.splice(index, 1, "update");
return next;
}
export async function runCli(argv: string[] = process.argv) {
const normalizedArgv = stripWindowsNodeExec(argv);
loadDotEnv({ quiet: true });
normalizeEnv();
ensureClawdbotCliOnPath();
// Enforce the minimum supported runtime before doing any work.
assertSupportedRuntime();
if (await tryRouteCli(normalizedArgv)) return;
// Capture all console output into structured logs while keeping stdout/stderr behavior.
enableConsoleCapture();
const { buildProgram } = await import("./program.js");
const program = buildProgram();
// Global error handlers to prevent silent crashes from unhandled rejections/exceptions.
// These log the error and exit gracefully instead of crashing without trace.
installUnhandledRejectionHandler();
process.on("uncaughtException", (error) => {
console.error("[clawdbot] Uncaught exception:", error.stack ?? error.message);
process.exit(1);
});
await program.parseAsync(rewriteUpdateFlagArgv(normalizedArgv));
}
function stripWindowsNodeExec(argv: string[]): string[] {
if (process.platform !== "win32") return argv;
const normalizeArg = (value: string): string => value.replace(/^"+|"+$/g, "");
const normalizeCandidate = (value: string): string => normalizeArg(value).replace(/^\\\\\\?\\/, "");
const execPath = normalizeCandidate(process.execPath);
const execPathLower = execPath.toLowerCase();
const execBase = path.basename(execPath).toLowerCase();
const isExecPath = (value: string | undefined): boolean => {
if (!value) return false;
const lower = normalizeCandidate(value).toLowerCase();
return (
lower === execPathLower ||
path.basename(lower) === execBase ||
lower.endsWith("\\node.exe") ||
lower.endsWith("/node.exe")
);
};
const filtered = argv.filter((arg, index) => index === 0 || !isExecPath(arg));
if (filtered.length < 3) return filtered;
const cleaned = [...filtered];
if (isExecPath(cleaned[1])) {
cleaned.splice(1, 1);
}
if (isExecPath(cleaned[2])) {
cleaned.splice(2, 1);
}
return cleaned;
}
export function isCliMainModule(): boolean {
return isMainModule({ currentFile: fileURLToPath(import.meta.url) });
}