mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-21 16:41:56 +00:00
CLI: restore lightweight root help and scoped status plugin preload
This commit is contained in:
@@ -2,14 +2,32 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { createSubsystemLogger } from "../logging.js";
|
||||
import { loadOpenClawPlugins } from "../plugins/loader.js";
|
||||
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
import { getActivePluginRegistry } from "../plugins/runtime.js";
|
||||
import type { PluginLogger } from "../plugins/types.js";
|
||||
|
||||
const log = createSubsystemLogger("plugins");
|
||||
let pluginRegistryLoaded = false;
|
||||
let pluginRegistryLoaded: "none" | "channels" | "all" = "none";
|
||||
|
||||
export function ensurePluginRegistryLoaded(): void {
|
||||
if (pluginRegistryLoaded) {
|
||||
export type PluginRegistryScope = "channels" | "all";
|
||||
|
||||
function resolveChannelPluginIds(params: {
|
||||
config: ReturnType<typeof loadConfig>;
|
||||
workspaceDir?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): string[] {
|
||||
return loadPluginManifestRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
})
|
||||
.plugins.filter((plugin) => plugin.channels.length > 0)
|
||||
.map((plugin) => plugin.id);
|
||||
}
|
||||
|
||||
export function ensurePluginRegistryLoaded(options?: { scope?: PluginRegistryScope }): void {
|
||||
const scope = options?.scope ?? "all";
|
||||
if (pluginRegistryLoaded === "all" || pluginRegistryLoaded === scope) {
|
||||
return;
|
||||
}
|
||||
const active = getActivePluginRegistry();
|
||||
@@ -19,7 +37,7 @@ export function ensurePluginRegistryLoaded(): void {
|
||||
active &&
|
||||
(active.plugins.length > 0 || active.channels.length > 0 || active.tools.length > 0)
|
||||
) {
|
||||
pluginRegistryLoaded = true;
|
||||
pluginRegistryLoaded = "all";
|
||||
return;
|
||||
}
|
||||
const config = loadConfig();
|
||||
@@ -34,6 +52,15 @@ export function ensurePluginRegistryLoaded(): void {
|
||||
config,
|
||||
workspaceDir,
|
||||
logger,
|
||||
...(scope === "channels"
|
||||
? {
|
||||
onlyPluginIds: resolveChannelPluginIds({
|
||||
config,
|
||||
workspaceDir,
|
||||
env: process.env,
|
||||
}),
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
pluginRegistryLoaded = true;
|
||||
pluginRegistryLoaded = scope;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ describe("registerPreActionHooks", () => {
|
||||
runtime: runtimeMock,
|
||||
commandPath: ["status"],
|
||||
});
|
||||
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledTimes(1);
|
||||
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledWith({ scope: "channels" });
|
||||
expect(process.title).toBe("openclaw-status");
|
||||
|
||||
vi.clearAllMocks();
|
||||
@@ -164,7 +164,7 @@ describe("registerPreActionHooks", () => {
|
||||
runtime: runtimeMock,
|
||||
commandPath: ["message", "send"],
|
||||
});
|
||||
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledTimes(1);
|
||||
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledWith({ scope: "all" });
|
||||
});
|
||||
|
||||
it("skips help/version preaction and respects banner opt-out", async () => {
|
||||
|
||||
@@ -67,6 +67,10 @@ function loadPluginRegistryModule() {
|
||||
return pluginRegistryModulePromise;
|
||||
}
|
||||
|
||||
function resolvePluginRegistryScope(commandPath: string[]): "channels" | "all" {
|
||||
return commandPath[0] === "status" || commandPath[0] === "health" ? "channels" : "all";
|
||||
}
|
||||
|
||||
function getRootCommand(command: Command): Command {
|
||||
let current = command;
|
||||
while (current.parent) {
|
||||
@@ -136,7 +140,7 @@ export function registerPreActionHooks(program: Command, programVersion: string)
|
||||
// Load plugins for commands that need channel access
|
||||
if (PLUGIN_REQUIRED_COMMANDS.has(commandPath[0])) {
|
||||
const { ensurePluginRegistryLoaded } = await loadPluginRegistryModule();
|
||||
ensurePluginRegistryLoaded();
|
||||
ensurePluginRegistryLoaded({ scope: resolvePluginRegistryScope(commandPath) });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ describe("tryRouteCli", () => {
|
||||
vi.resetModules();
|
||||
({ tryRouteCli } = await import("./route.js"));
|
||||
findRoutedCommandMock.mockReturnValue({
|
||||
loadPlugins: false,
|
||||
loadPlugins: true,
|
||||
run: runRouteMock,
|
||||
});
|
||||
});
|
||||
@@ -59,6 +59,7 @@ describe("tryRouteCli", () => {
|
||||
suppressDoctorStdout: true,
|
||||
}),
|
||||
);
|
||||
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledWith({ scope: "channels" });
|
||||
});
|
||||
|
||||
it("does not pass suppressDoctorStdout for routed non-json commands", async () => {
|
||||
@@ -68,6 +69,7 @@ describe("tryRouteCli", () => {
|
||||
runtime: expect.any(Object),
|
||||
commandPath: ["status"],
|
||||
});
|
||||
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledWith({ scope: "channels" });
|
||||
});
|
||||
|
||||
it("routes status when root options precede the command", async () => {
|
||||
@@ -80,5 +82,6 @@ describe("tryRouteCli", () => {
|
||||
runtime: expect.any(Object),
|
||||
commandPath: ["status"],
|
||||
});
|
||||
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledWith({ scope: "channels" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,12 @@ async function prepareRoutedCommand(params: {
|
||||
const shouldLoadPlugins =
|
||||
typeof params.loadPlugins === "function" ? params.loadPlugins(params.argv) : params.loadPlugins;
|
||||
if (shouldLoadPlugins) {
|
||||
ensurePluginRegistryLoaded();
|
||||
ensurePluginRegistryLoaded({
|
||||
scope:
|
||||
params.commandPath[0] === "status" || params.commandPath[0] === "health"
|
||||
? "channels"
|
||||
: "all",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
src/entry.test.ts
Normal file
26
src/entry.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { tryHandleRootHelpFastPath } from "./entry.js";
|
||||
|
||||
describe("entry root help fast path", () => {
|
||||
it("renders root help without importing the full program", () => {
|
||||
const outputRootHelpMock = vi.fn();
|
||||
|
||||
const handled = tryHandleRootHelpFastPath(["node", "openclaw", "--help"], {
|
||||
outputRootHelp: outputRootHelpMock,
|
||||
});
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(outputRootHelpMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("ignores non-root help invocations", () => {
|
||||
const outputRootHelpMock = vi.fn();
|
||||
|
||||
const handled = tryHandleRootHelpFastPath(["node", "openclaw", "status", "--help"], {
|
||||
outputRootHelp: outputRootHelpMock,
|
||||
});
|
||||
|
||||
expect(handled).toBe(false);
|
||||
expect(outputRootHelpMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
80
src/entry.ts
80
src/entry.ts
@@ -145,24 +145,6 @@ if (
|
||||
return true;
|
||||
}
|
||||
|
||||
function tryHandleRootHelpFastPath(argv: string[]): boolean {
|
||||
if (!isRootHelpInvocation(argv)) {
|
||||
return false;
|
||||
}
|
||||
import("./cli/program.js")
|
||||
.then(({ buildProgram }) => {
|
||||
buildProgram().outputHelp();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"[openclaw] Failed to display help:",
|
||||
error instanceof Error ? (error.stack ?? error.message) : error,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
process.argv = normalizeWindowsArgv(process.argv);
|
||||
|
||||
if (!ensureExperimentalWarningSuppressed()) {
|
||||
@@ -179,16 +161,58 @@ if (
|
||||
process.argv = parsed.argv;
|
||||
}
|
||||
|
||||
if (!tryHandleRootVersionFastPath(process.argv) && !tryHandleRootHelpFastPath(process.argv)) {
|
||||
import("./cli/run-main.js")
|
||||
.then(({ runCli }) => runCli(process.argv))
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"[openclaw] Failed to start CLI:",
|
||||
error instanceof Error ? (error.stack ?? error.message) : error,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
if (!tryHandleRootVersionFastPath(process.argv)) {
|
||||
runMainOrRootHelp(process.argv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function tryHandleRootHelpFastPath(
|
||||
argv: string[],
|
||||
deps: {
|
||||
outputRootHelp?: () => void;
|
||||
onError?: (error: unknown) => void;
|
||||
} = {},
|
||||
): boolean {
|
||||
if (!isRootHelpInvocation(argv)) {
|
||||
return false;
|
||||
}
|
||||
const handleError =
|
||||
deps.onError ??
|
||||
((error: unknown) => {
|
||||
console.error(
|
||||
"[openclaw] Failed to display help:",
|
||||
error instanceof Error ? (error.stack ?? error.message) : error,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
if (deps.outputRootHelp) {
|
||||
try {
|
||||
deps.outputRootHelp();
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
import("./cli/program/root-help.js")
|
||||
.then(({ outputRootHelp }) => {
|
||||
outputRootHelp();
|
||||
})
|
||||
.catch(handleError);
|
||||
return true;
|
||||
}
|
||||
|
||||
function runMainOrRootHelp(argv: string[]): void {
|
||||
if (tryHandleRootHelpFastPath(argv)) {
|
||||
return;
|
||||
}
|
||||
import("./cli/run-main.js")
|
||||
.then(({ runCli }) => runCli(argv))
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"[openclaw] Failed to start CLI:",
|
||||
error instanceof Error ? (error.stack ?? error.message) : error,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user