Files
openclaw/src/cli/route.ts
2026-06-16 13:15:52 +08:00

116 lines
3.6 KiB
TypeScript

// Route-first CLI entry point for commands that can run before full Commander setup.
import { FLAG_TERMINATOR, isValueToken } from "../infra/cli-root-options.js";
import { isTruthyEnvValue } from "../infra/env.js";
import { type LogLevel, tryParseLogLevel } from "../logging/levels.js";
import { defaultRuntime } from "../runtime.js";
import { resolveCliArgvInvocation } from "./argv-invocation.js";
import { hasFlag } from "./argv.js";
import {
applyCliExecutionStartupPresentation,
ensureCliExecutionBootstrap,
resolveCliExecutionStartupContext,
} from "./command-execution-startup.js";
import { findRoutedCommand } from "./program/routes.js";
const LOG_LEVEL_FLAG = "--log-level";
const LOG_LEVEL_EQUALS_PREFIX = `${LOG_LEVEL_FLAG}=`;
function resolveRoutedCliLogLevel(argv: string[]): LogLevel | null | undefined {
const args = argv.slice(2);
let logLevel: LogLevel | undefined;
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (!arg || arg === FLAG_TERMINATOR) {
break;
}
if (arg === LOG_LEVEL_FLAG) {
const value = args[index + 1];
if (!isValueToken(value)) {
return null;
}
const parsed = tryParseLogLevel(value);
if (!parsed) {
return null;
}
logLevel = parsed;
index += 1;
continue;
}
if (arg.startsWith(LOG_LEVEL_EQUALS_PREFIX)) {
const parsed = tryParseLogLevel(arg.slice(LOG_LEVEL_EQUALS_PREFIX.length));
if (!parsed) {
return null;
}
logLevel = parsed;
}
}
return logLevel;
}
async function prepareRoutedCommand(params: {
argv: string[];
commandPath: string[];
loadPlugins?: boolean | ((argv: string[]) => boolean);
}) {
const { startupPolicy } = resolveCliExecutionStartupContext({
argv: params.argv,
jsonOutputMode: hasFlag(params.argv, "--json"),
env: process.env,
routeMode: true,
});
const { VERSION } = await import("../version.js");
await applyCliExecutionStartupPresentation({
argv: params.argv,
startupPolicy,
showBanner: process.stdout.isTTY && !startupPolicy.suppressDoctorStdout,
version: VERSION,
});
const shouldLoadPlugins =
typeof params.loadPlugins === "function" ? params.loadPlugins(params.argv) : params.loadPlugins;
// Routed commands still honor config guards, logging policy, and plugin loading decisions.
await ensureCliExecutionBootstrap({
runtime: defaultRuntime,
commandPath: params.commandPath,
startupPolicy,
loadPlugins: shouldLoadPlugins ?? startupPolicy.loadPlugins,
});
}
/** Try a lightweight route-first command before falling back to the full CLI program. */
export async function tryRouteCli(argv: string[]): Promise<boolean> {
if (isTruthyEnvValue(process.env.OPENCLAW_DISABLE_ROUTE_FIRST)) {
return false;
}
const invocation = resolveCliArgvInvocation(argv);
if (invocation.hasHelpOrVersion) {
return false;
}
if (!invocation.commandPath[0]) {
return false;
}
const route = findRoutedCommand(invocation.commandPath, argv);
if (!route) {
return false;
}
if (route.canRun && !route.canRun(argv)) {
// Let Commander own unsupported argv shapes so user-facing validation stays centralized.
return false;
}
const logLevel = resolveRoutedCliLogLevel(argv);
if (logLevel === null) {
// Let Commander own the existing user-facing --log-level validation errors.
return false;
}
if (logLevel) {
process.env.OPENCLAW_LOG_LEVEL = logLevel;
}
await prepareRoutedCommand({
argv,
commandPath: invocation.commandPath,
loadPlugins: route.loadPlugins,
});
return route.run(argv);
}