





















@@ -26,6 +26,7 @@ const distDir = path.join(rootDir, "dist");
2626const outputPath = path.join(distDir, "cli-startup-metadata.json");
2727const extensionsDir = path.join(rootDir, "extensions");
2828const ROOT_HELP_RENDER_TIMEOUT_MS = 120_000;
29+const BROWSER_HELP_RENDER_TIMEOUT_MS = 120_000;
2930const CORE_CHANNEL_ORDER = [
3031"telegram",
3132"whatsapp",
@@ -70,6 +71,29 @@ function resolveRootHelpBundleIdentity(
7071};
7172}
727374+function updateHashFromFiles(hash: ReturnType<typeof createHash>, files: string[]) {
75+for (const file of files.toSorted()) {
76+hash.update(`${path.relative(rootDir, file)}\0`);
77+hash.update(readFileSync(file));
78+hash.update("\0");
79+}
80+}
81+82+function resolveBrowserHelpSourceSignature(): string {
83+const hash = createHash("sha1");
84+const browserCliDir = path.join(rootDir, "extensions/browser/src/cli");
85+const browserCliFiles = readdirSync(browserCliDir)
86+.filter((entry) => entry.endsWith(".ts"))
87+.map((entry) => path.join(browserCliDir, entry));
88+updateHashFromFiles(hash, browserCliFiles);
89+updateHashFromFiles(hash, [
90+path.join(rootDir, "src/cli/program/help.ts"),
91+path.join(rootDir, "src/cli/program/context.ts"),
92+path.join(rootDir, "src/cli/banner.ts"),
93+]);
94+return hash.digest("hex");
95+}
96+7397export function readBundledChannelCatalog(
7498extensionsDirOverride: string = extensionsDir,
7599): BundledChannelCatalog {
@@ -245,6 +269,53 @@ function renderSourceRootHelpText(
245269return result.stdout ?? "";
246270}
247271272+function renderSourceBrowserHelpText(
273+renderContext: RootHelpRenderContext = createIsolatedRootHelpRenderContext(),
274+): string {
275+const browserCliUrl = pathToFileURL(
276+path.join(rootDir, "extensions/browser/src/cli/browser-cli.ts"),
277+).href;
278+const helpUrl = pathToFileURL(path.join(rootDir, "src/cli/program/help.ts")).href;
279+const contextUrl = pathToFileURL(path.join(rootDir, "src/cli/program/context.ts")).href;
280+const inlineModule = [
281+`const { Command } = await import("commander");`,
282+`const { registerBrowserCli } = await import(${JSON.stringify(browserCliUrl)});`,
283+`const { configureProgramHelp } = await import(${JSON.stringify(helpUrl)});`,
284+`const { createProgramContext } = await import(${JSON.stringify(contextUrl)});`,
285+`const program = new Command();`,
286+`configureProgramHelp(program, createProgramContext());`,
287+`registerBrowserCli(program, ["node", "openclaw", "browser", "--help"]);`,
288+`const browser = program.commands.find((cmd) => cmd.name() === "browser");`,
289+`if (!browser) throw new Error("Browser command was not registered.");`,
290+`browser.outputHelp();`,
291+"process.exit(0);",
292+].join("\n");
293+const result = spawnSync(
294+process.execPath,
295+["--import", "tsx", "--input-type=module", "--eval", inlineModule],
296+{
297+cwd: rootDir,
298+encoding: "utf8",
299+env: {
300+ ...renderContext.env,
301+OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH: "1",
302+},
303+timeout: BROWSER_HELP_RENDER_TIMEOUT_MS,
304+},
305+);
306+if (result.error) {
307+throw result.error;
308+}
309+if (result.status !== 0) {
310+const stderr = result.stderr?.trim();
311+throw new Error(
312+"Failed to render source browser help" +
313+(stderr ? `: ${stderr}` : result.signal ? `: terminated by ${result.signal}` : ""),
314+);
315+}
316+return result.stdout ?? "";
317+}
318+248319export async function writeCliStartupMetadata(options?: {
249320distDir?: string;
250321outputPath?: string;
@@ -255,6 +326,7 @@ export async function writeCliStartupMetadata(options?: {
255326const resolvedExtensionsDir = options?.extensionsDir ?? extensionsDir;
256327const channelCatalog = readBundledChannelCatalog(resolvedExtensionsDir);
257328const bundleIdentity = resolveRootHelpBundleIdentity(resolvedDistDir);
329+const browserHelpSourceSignature = resolveBrowserHelpSourceSignature();
258330const bundledPluginsDir = path.join(resolvedDistDir, "extensions");
259331const renderContext = createIsolatedRootHelpRenderContext(
260332existsSync(bundledPluginsDir) ? bundledPluginsDir : resolvedExtensionsDir,
@@ -264,12 +336,17 @@ export async function writeCliStartupMetadata(options?: {
264336try {
265337const existing = JSON.parse(readFileSync(resolvedOutputPath, "utf8")) as {
266338rootHelpBundleSignature?: unknown;
339+browserHelpSourceSignature?: unknown;
267340channelCatalogSignature?: unknown;
341+browserHelpText?: unknown;
268342};
269343if (
270344bundleIdentity &&
271345existing.rootHelpBundleSignature === bundleIdentity.signature &&
272-existing.channelCatalogSignature === channelCatalog.signature
346+existing.browserHelpSourceSignature === browserHelpSourceSignature &&
347+existing.channelCatalogSignature === channelCatalog.signature &&
348+typeof existing.browserHelpText === "string" &&
349+existing.browserHelpText.length > 0
273350) {
274351return;
275352}
@@ -283,6 +360,7 @@ export async function writeCliStartupMetadata(options?: {
283360} catch {
284361rootHelpText = renderSourceRootHelpText(renderContext);
285362}
363+const browserHelpText = renderSourceBrowserHelpText(renderContext);
286364287365mkdirSync(resolvedDistDir, { recursive: true });
288366writeFileSync(
@@ -293,6 +371,8 @@ export async function writeCliStartupMetadata(options?: {
293371 channelOptions,
294372 channelCatalogSignature: channelCatalog.signature,
295373 rootHelpBundleSignature: bundleIdentity?.signature ?? null,
374+ browserHelpSourceSignature,
375+ browserHelpText,
296376 rootHelpText,
297377 },
298378 null,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。