Skip to content

Commit 884e00f

Browse files
committed
fix: address review feedback (#5871)
1 parent 9d0a3ff commit 884e00f

File tree

4 files changed

+140
-18
lines changed

4 files changed

+140
-18
lines changed

src/cli/argv.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getVerboseFlag,
99
hasHelpOrVersion,
1010
hasFlag,
11+
isRootHelpRequest,
1112
isRootVersionRequest,
1213
shouldMigrateState,
1314
shouldMigrateStateFromPath,
@@ -332,4 +333,55 @@ describe("argv helpers", () => {
332333
])("isRootVersionRequest: $name", ({ argv, expected }) => {
333334
expect(isRootVersionRequest(argv)).toBe(expected);
334335
});
336+
337+
// isRootHelpRequest: guards the entry.ts fast-path help exit.
338+
it.each([
339+
{
340+
name: "--help flag at root",
341+
argv: ["node", "openclaw", "--help"],
342+
expected: true,
343+
},
344+
{
345+
name: "-h flag at root",
346+
argv: ["node", "openclaw", "-h"],
347+
expected: true,
348+
},
349+
{
350+
name: "--help after a root boolean flag",
351+
argv: ["node", "openclaw", "--dev", "--help"],
352+
expected: true,
353+
},
354+
{
355+
name: "--help after a root value flag",
356+
argv: ["node", "openclaw", "--profile", "dev", "--help"],
357+
expected: true,
358+
},
359+
{
360+
name: "--help after -- terminator must NOT trigger (forwarded arg)",
361+
argv: ["node", "openclaw", "nodes", "run", "--", "git", "--help"],
362+
expected: false,
363+
},
364+
{
365+
name: "-h after -- terminator must NOT trigger",
366+
argv: ["node", "openclaw", "--", "-h"],
367+
expected: false,
368+
},
369+
{
370+
name: "--help with subcommand must NOT trigger (subcommand-scoped)",
371+
argv: ["node", "openclaw", "status", "--help"],
372+
expected: false,
373+
},
374+
{
375+
name: "-h with subcommand must NOT trigger",
376+
argv: ["node", "openclaw", "models", "-h"],
377+
expected: false,
378+
},
379+
{
380+
name: "normal command without help flag",
381+
argv: ["node", "openclaw", "status"],
382+
expected: false,
383+
},
384+
])("isRootHelpRequest: $name", ({ argv, expected }) => {
385+
expect(isRootHelpRequest(argv)).toBe(expected);
386+
});
335387
});

src/cli/argv.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ const ROOT_BOOLEAN_FLAGS = new Set(["--dev", "--no-color"]);
77
const ROOT_VALUE_FLAGS = new Set(["--profile", "--log-level"]);
88
const FLAG_TERMINATOR = "--";
99

10+
/**
11+
* Returns true if argv contains any help or version flag.
12+
* NOTE: uses `argv.some()` without `--` terminator awareness, so forwarded args
13+
* like `nodes run -- git --help` will produce a false positive. Use
14+
* `isRootHelpRequest`/`isRootVersionRequest` for fast-path guards where
15+
* correctness around forwarded args matters.
16+
*/
1017
export function hasHelpOrVersion(argv: string[]): boolean {
1118
return (
1219
argv.some((arg) => HELP_FLAGS.has(arg) || VERSION_FLAGS.has(arg)) || hasRootVersionAlias(argv)
@@ -24,6 +31,48 @@ export function isRootVersionRequest(argv: string[]): boolean {
2431
return hasFlag(argv, "--version") || hasFlag(argv, "-V") || hasRootVersionAlias(argv);
2532
}
2633

34+
/**
35+
* Returns true only when the process is a root-level help request:
36+
* - `-h` or `--help` appears before any `--` terminator, and
37+
* - no subcommand (non-flag token) precedes the help flag.
38+
* `openclaw status --help` returns false (subcommand-scoped); `openclaw --help` returns true.
39+
* Used by the entry.ts fast path to display help without loading route.ts or run-main.js.
40+
*/
41+
export function isRootHelpRequest(argv: string[]): boolean {
42+
const args = argv.slice(2);
43+
for (let i = 0; i < args.length; i += 1) {
44+
const arg = args[i];
45+
if (!arg) {
46+
continue;
47+
}
48+
if (arg === FLAG_TERMINATOR) {
49+
break;
50+
}
51+
if (arg === "-h" || arg === "--help") {
52+
return true;
53+
}
54+
if (ROOT_BOOLEAN_FLAGS.has(arg)) {
55+
continue;
56+
}
57+
if (arg.startsWith("--profile=")) {
58+
continue;
59+
}
60+
if (ROOT_VALUE_FLAGS.has(arg)) {
61+
const next = args[i + 1];
62+
if (isValueToken(next)) {
63+
i += 1;
64+
}
65+
continue;
66+
}
67+
if (arg.startsWith("-")) {
68+
continue;
69+
}
70+
// Non-flag token is a subcommand — this is a subcommand-scoped help request.
71+
return false;
72+
}
73+
return false;
74+
}
75+
2776
function isValueToken(arg: string | undefined): boolean {
2877
if (!arg) {
2978
return false;

src/cli/channel-options.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@ export function resolveCliChannelOptions(): string[] {
2323
const catalog = listChannelPluginCatalogEntries().map((entry) => entry.id);
2424
const base = dedupe([...CHAT_CHANNEL_ORDER, ...catalog]);
2525
if (isTruthyEnvValue(process.env.OPENCLAW_EAGER_CHANNEL_OPTIONS)) {
26-
// CHANGED SEMANTIC: ensurePluginRegistryLoaded() is intentionally NOT called
27-
// here to avoid pulling in plugins/loader.ts → jiti at startup (slow on
28-
// low-powered devices). As a result, OPENCLAW_EAGER_CHANNEL_OPTIONS is
29-
// effectively a no-op for its original purpose of force-loading all plugins
30-
// into the option list before Commander parses args. Plugin IDs are only
31-
// included here if the registry was already populated by some other means
32-
// (e.g. the preaction hook for a real command). If this env var behaviour is
33-
// needed, restore the ensurePluginRegistryLoaded() call explicitly.
26+
// Emit a deprecation warning: ensurePluginRegistryLoaded() was removed to avoid
27+
// pulling plugins/loader.ts → jiti at startup (slow on low-powered devices).
28+
// OPENCLAW_EAGER_CHANNEL_OPTIONS no longer force-loads plugin channels; plugin IDs
29+
// are only included if the registry was already populated by another code path.
30+
process.emitWarning(
31+
"OPENCLAW_EAGER_CHANNEL_OPTIONS no longer force-loads plugin channels at startup. " +
32+
"Plugin IDs are only included if the registry was pre-loaded by another means. " +
33+
"Remove this env var to silence this warning.",
34+
{ code: "OPENCLAW_EAGER_CHANNEL_OPTIONS_DEPRECATED" },
35+
);
3436
const pluginIds = listChannelPlugins().map((plugin) => plugin.id);
3537
return dedupe([...base, ...pluginIds]);
3638
}

src/entry.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { spawn } from "node:child_process";
33
import process from "node:process";
44
import { fileURLToPath } from "node:url";
5-
import { isRootVersionRequest } from "./cli/argv.js";
5+
import { isRootHelpRequest, isRootVersionRequest } from "./cli/argv.js";
66
import { applyCliProfileEnv, parseCliProfileArgs } from "./cli/profile.js";
77
import { shouldSkipRespawnForArgv } from "./cli/respawn-policy.js";
88
import { normalizeWindowsArgv } from "./cli/windows-argv.js";
@@ -143,14 +143,33 @@ if (
143143
process.exit(0);
144144
}
145145

146-
import("./cli/run-main.js")
147-
.then(({ runCli }) => runCli(process.argv))
148-
.catch((error) => {
149-
console.error(
150-
"[openclaw] Failed to start CLI:",
151-
error instanceof Error ? (error.stack ?? error.message) : error,
152-
);
153-
process.exitCode = 1;
154-
});
146+
// Fast path: display root help without loading run-main.js or route.ts.
147+
// isRootHelpRequest stops at -- and requires no subcommand before -h/--help,
148+
// so `openclaw status --help` still falls through to the full CLI path.
149+
// Importing program.js directly avoids the route.ts static import in run-main.ts.
150+
if (isRootHelpRequest(argv)) {
151+
import("./cli/program.js")
152+
.then(({ buildProgram }) => {
153+
buildProgram().outputHelp();
154+
process.exit(0);
155+
})
156+
.catch((error) => {
157+
console.error(
158+
"[openclaw] Failed to display help:",
159+
error instanceof Error ? (error.stack ?? error.message) : error,
160+
);
161+
process.exit(1);
162+
});
163+
} else {
164+
import("./cli/run-main.js")
165+
.then(({ runCli }) => runCli(process.argv))
166+
.catch((error) => {
167+
console.error(
168+
"[openclaw] Failed to start CLI:",
169+
error instanceof Error ? (error.stack ?? error.message) : error,
170+
);
171+
process.exitCode = 1;
172+
});
173+
}
155174
}
156175
}

0 commit comments

Comments
 (0)