
























11// Formats OpenClaw CLI command snippets for chat-facing command responses.
2+import fs from "node:fs";
3+import { createRequire } from "node:module";
4+import path from "node:path";
5+import { isBunRuntime } from "../../daemon/runtime-binary.js";
6+import { resolveOpenClawPackageRootSync } from "../../infra/openclaw-root.js";
7+8+const requireFromHere = createRequire(import.meta.url);
9+const OPENCLAW_CLI_ENTRY_BASENAMES = new Set(["openclaw", "openclaw.mjs"]);
10+const OPENCLAW_PACKAGE_ENTRY_PATHS = new Set([
11+path.join("dist", "entry.js"),
12+path.join("dist", "entry.mjs"),
13+path.join("dist", "index.js"),
14+path.join("dist", "index.mjs"),
15+path.join("src", "entry.ts"),
16+]);
17+const TEST_RUNNER_ENV_PREFIXES = ["VITEST_", "OPENCLAW_VITEST_"];
18+219function quoteShellArg(value: string): string {
320if (process.platform === "win32") {
421return `'${value.replaceAll("'", "''")}'`;
522}
623return `'${value.replaceAll("'", "'\\''")}'`;
724}
82526+function isOpenClawCliLauncherEntry(entry: string): boolean {
27+return OPENCLAW_CLI_ENTRY_BASENAMES.has(path.basename(entry));
28+}
29+30+function isOpenClawPackageEntry(entry: string, packageRoot: string): boolean {
31+const relativeEntry = path.relative(path.resolve(packageRoot), path.resolve(entry));
32+return OPENCLAW_PACKAGE_ENTRY_PATHS.has(relativeEntry);
33+}
34+35+function safeCwd(): string | undefined {
36+try {
37+return process.cwd();
38+} catch {
39+return undefined;
40+}
41+}
42+43+function buildPackageRootCliArgvPrefix(packageRoot: string): string[] {
44+const sourceEntry = path.join(packageRoot, "src", "entry.ts");
45+if (fs.existsSync(sourceEntry)) {
46+const tsxLoader = resolveTrustedTsxLoader(packageRoot);
47+return isBunRuntime(process.execPath)
48+ ? [process.execPath, sourceEntry]
49+ : tsxLoader
50+ ? [process.execPath, "--import", tsxLoader, sourceEntry]
51+ : [process.execPath, path.join(packageRoot, "openclaw.mjs")];
52+}
53+return [process.execPath, path.join(packageRoot, "openclaw.mjs")];
54+}
55+56+function resolveTrustedTsxLoader(packageRoot: string): string | null {
57+try {
58+return requireFromHere.resolve("tsx", { paths: [packageRoot] });
59+} catch {
60+return null;
61+}
62+}
63+64+function resolveCurrentOpenClawCliArgvPrefix(): string[] {
65+const entry = process.argv[1]?.trim();
66+if (entry && entry !== process.execPath && isOpenClawCliLauncherEntry(entry)) {
67+return [process.execPath, ...process.execArgv, entry];
68+}
69+const entryPackageRoot = entry ? resolveOpenClawPackageRootSync({ argv1: entry }) : null;
70+if (entry && entryPackageRoot && isOpenClawPackageEntry(entry, entryPackageRoot)) {
71+return [process.execPath, ...process.execArgv, entry];
72+}
73+const packageRoot = resolveOpenClawPackageRootSync({
74+argv1: entry,
75+cwd: safeCwd(),
76+moduleUrl: import.meta.url,
77+});
78+if (packageRoot) {
79+return buildPackageRootCliArgvPrefix(packageRoot);
80+}
81+return entry && entry !== process.execPath ? [process.execPath, entry] : [process.execPath];
82+}
83+984/** Reconstructs the current OpenClaw CLI invocation with extra args. */
1085export function buildCurrentOpenClawCliArgv(args: string[]): string[] {
11-const entry = process.argv[1]?.trim();
12-return entry && entry !== process.execPath
13- ? [process.execPath, ...process.execArgv, entry, ...args]
14- : [process.execPath, ...args];
86+return [...resolveCurrentOpenClawCliArgvPrefix(), ...args];
87+}
88+89+/** Clears test-runner env inherited by harness-hosted gateways before spawning the CLI. */
90+export function buildCurrentOpenClawCliExecEnv(
91+env: NodeJS.ProcessEnv = process.env,
92+): Record<string, string> | undefined {
93+const overrides: Record<string, string> = {};
94+for (const key of Object.keys(env)) {
95+if (key === "VITEST" || TEST_RUNNER_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) {
96+overrides[key] = "";
97+}
98+}
99+return Object.keys(overrides).length > 0 ? overrides : undefined;
15100}
1610117102/** Builds a shell-quoted command string for rerunning the current OpenClaw CLI. */
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。