| |
| import type { Command } from "commander"; |
| import type { ConfigFileSnapshot } from "../../config/types.js"; |
| import { setVerbose } from "../../globals.js"; |
| import type { LogLevel } from "../../logging/levels.js"; |
| import { resolvePluginInstallInvalidConfigPolicy } from "../../plugins/install-config.js"; |
| import { defaultRuntime } from "../../runtime.js"; |
| import { resolveCliArgvInvocation } from "../argv-invocation.js"; |
| import { getVerboseFlag, isHelpOrVersionInvocation } from "../argv.js"; |
| import { CLI_NAME } from "../cli-name.js"; |
| import { |
| applyCliExecutionStartupPresentation, |
| ensureCliExecutionBootstrap, |
| } from "../command-execution-startup.js"; |
| import { inheritOptionFromParent } from "../command-options.js"; |
| import { resolveCliCommandPathPolicy } from "../command-path-policy.js"; |
| import { resolveCliStartupPolicy } from "../command-startup-policy.js"; |
| import { applyResolvedCommandOutputMode } from "../json-output-mode.js"; |
| import { isModelsPlainMachineOutput } from "../models-output-mode.js"; |
| import { resolvePluginInstallPreactionRequest } from "../plugin-install-config-policy.js"; |
| import { getCommanderCommandPath, hasCommanderOptionToken } from "./commander-parse-facts.js"; |
| import { isCommandJsonOutputMode } from "./json-mode.js"; |
| import { isParentDefaultHelpAction } from "./parent-default-help.js"; |
|
|
| const HELP_OR_VERSION_FLAGS = new Set(["-h", "--help", "-V", "--version"]); |
|
|
| function setProcessTitleForCommand(actionCommand: Command) { |
| let current: Command = actionCommand; |
| while (current.parent && current.parent.parent) { |
| current = current.parent; |
| } |
| const name = current.name(); |
| if (!name || name === CLI_NAME) { |
| return; |
| } |
| process.title = `${CLI_NAME}-${name}`; |
| } |
|
|
| function shouldAllowInvalidConfigForAction(actionCommand: Command, commandPath: string[]): boolean { |
| return ( |
| commandPath[0] === "update" || |
| resolvePluginInstallInvalidConfigPolicy( |
| resolvePluginInstallPreactionRequest({ |
| actionCommand, |
| commandPath, |
| argv: process.argv, |
| }), |
| ) === "allow-plugin-recovery" |
| ); |
| } |
|
|
| function getCliLogLevel(actionCommand: Command): LogLevel | undefined { |
| if (actionCommand.getOptionValueSourceWithGlobals("logLevel") !== "cli") { |
| return undefined; |
| } |
| const logLevel = actionCommand.optsWithGlobals<{ logLevel?: unknown }>().logLevel; |
| return typeof logLevel === "string" ? (logLevel as LogLevel) : undefined; |
| } |
|
|
| function getStateMigrationAgentId(actionCommand: Command): string | undefined { |
| if (!actionCommand.options.some((option) => option.attributeName() === "agent")) { |
| return undefined; |
| } |
| const value = |
| actionCommand.getOptionValueSource("agent") === "cli" |
| ? actionCommand.getOptionValue("agent") |
| : inheritOptionFromParent(actionCommand, "agent", "cli"); |
| return typeof value === "string" && value.trim() ? value.trim() : undefined; |
| } |
|
|
| function isBareParentDefaultHelpInvocation(actionCommand: Command, argv: string[]): boolean { |
| if (!isParentDefaultHelpAction(actionCommand)) { |
| return false; |
| } |
| const { commandPath } = resolveCliArgvInvocation(argv); |
| const [primary, extra] = commandPath; |
| if (extra !== undefined || !primary) { |
| return false; |
| } |
| return primary === actionCommand.name() || actionCommand.aliases().includes(primary); |
| } |
|
|
| function isGuidedConfigAction(actionCommand: Command): boolean { |
| return actionCommand.name() === "config" && !actionCommand.parent?.parent; |
| } |
|
|
| function isGuidedConfigCommandPath(commandPath: string[]): boolean { |
| const [primary, secondary, extra] = commandPath; |
| if (primary !== "config" || extra !== undefined) { |
| return false; |
| } |
| return ( |
| secondary !== "get" && |
| secondary !== "set" && |
| secondary !== "patch" && |
| secondary !== "unset" && |
| secondary !== "file" && |
| secondary !== "schema" && |
| secondary !== "validate" |
| ); |
| } |
|
|
| function isGatewayRunAction(actionCommand: Command): boolean { |
| if (actionCommand.name() === "gateway") { |
| return actionCommand.parent?.parent === null; |
| } |
| return ( |
| actionCommand.name() === "run" && |
| actionCommand.parent?.name() === "gateway" && |
| actionCommand.parent.parent?.parent === null |
| ); |
| } |
|
|
| async function runStateStoreGuard(commandPath: string[]): Promise<void> { |
| if (resolveCliCommandPathPolicy(commandPath).stateStoreGuard !== "run") { |
| return; |
| } |
| let outcome: import("../state-dir-gateway-check.js").CliGatewayStateDirOutcome; |
| try { |
| const { checkCliGatewayStateDir } = await import("../state-dir-gateway-check.js"); |
| outcome = await checkCliGatewayStateDir({ command: `openclaw ${commandPath.join(" ")}` }); |
| } catch (error) { |
| const { formatErrorMessage } = await import("../../infra/errors.js"); |
| const { logDebug } = await import("../../logger.js"); |
| logDebug(`state-store guard unavailable: ${formatErrorMessage(error)}`); |
| return; |
| } |
| if (outcome.kind === "warn") { |
| defaultRuntime.log(outcome.message); |
| } else if (outcome.kind === "refuse") { |
| throw new Error(outcome.message); |
| } |
| } |
|
|
| |
| export function registerPreActionHooks(program: Command, programVersion: string) { |
| program.hook("preAction", async (_thisCommand, actionCommand) => { |
| setProcessTitleForCommand(actionCommand); |
| const argv = process.argv; |
| const helpOrVersionWasOptionValue = hasCommanderOptionToken( |
| actionCommand, |
| argv, |
| HELP_OR_VERSION_FLAGS, |
| "value", |
| ); |
| if ( |
| (isHelpOrVersionInvocation(argv) && !helpOrVersionWasOptionValue) || |
| isBareParentDefaultHelpInvocation(actionCommand, argv) |
| ) { |
| return; |
| } |
| const commandPath = getCommanderCommandPath(actionCommand); |
| const nativeUpdateCapabilityProbe = |
| commandPath.length === 2 && |
| (commandPath[0] === "gateway" || commandPath[0] === "daemon") && |
| ["install", "restart", "stop"].includes(commandPath[1] ?? "") && |
| actionCommand.getOptionValue("updateExecutor") === "check"; |
| const jsonOutputMode = |
| nativeUpdateCapabilityProbe || isCommandJsonOutputMode(actionCommand, argv); |
| const machineOutputMode = jsonOutputMode || isModelsPlainMachineOutput(argv, actionCommand); |
| applyResolvedCommandOutputMode(jsonOutputMode, machineOutputMode); |
| const startupPolicy = resolveCliStartupPolicy({ |
| argv, |
| commandPath, |
| jsonOutputMode, |
| machineOutputMode, |
| env: process.env, |
| }); |
| await applyCliExecutionStartupPresentation({ |
| startupPolicy, |
| version: programVersion, |
| }); |
| const verbose = getVerboseFlag(argv, { includeDebug: true }); |
| setVerbose(verbose); |
| const cliLogLevel = getCliLogLevel(actionCommand); |
| if (cliLogLevel) { |
| process.env.OPENCLAW_LOG_LEVEL = cliLogLevel; |
| } |
| if (!verbose) { |
| process.env.NODE_NO_WARNINGS ??= "1"; |
| } |
| |
| if ( |
| nativeUpdateCapabilityProbe || |
| isGuidedConfigAction(actionCommand) || |
| isGuidedConfigCommandPath(commandPath) |
| ) { |
| return; |
| } |
| await runStateStoreGuard(commandPath); |
| if (startupPolicy.skipConfigGuard) { |
| |
| |
| await ensureCliExecutionBootstrap({ |
| runtime: defaultRuntime, |
| commandPath, |
| startupPolicy, |
| skipConfigGuard: true, |
| }); |
| return; |
| } |
| let beforeStateMigrations: ((snapshot?: ConfigFileSnapshot) => Promise<boolean>) | undefined; |
| let skipPristineStartupStateMigrations = false; |
| let skipPristineCoreStateMigrations = false; |
| let allowInvalid = shouldAllowInvalidConfigForAction(actionCommand, commandPath); |
| if (isGatewayRunAction(actionCommand)) { |
| const { |
| prepareGatewayRunBootstrap, |
| recheckGatewayRunBootstrap, |
| wasPreparedGatewayRunCoreStatePristine, |
| wasPreparedGatewayRunStatePristine, |
| } = await import("../gateway-cli/pre-bootstrap.js"); |
| const { resolveGatewayRunOptions } = await import("../gateway-cli/run-options.js"); |
| const resolvedOptions = resolveGatewayRunOptions(actionCommand.opts(), actionCommand); |
| allowInvalid ||= resolvedOptions.allowUnconfigured === true; |
| const opts = resolvedOptions; |
| const shouldBootstrap = await prepareGatewayRunBootstrap({ opts, runtime: defaultRuntime }); |
| if (!shouldBootstrap) { |
| return; |
| } |
| skipPristineStartupStateMigrations = wasPreparedGatewayRunStatePristine(); |
| skipPristineCoreStateMigrations = wasPreparedGatewayRunCoreStatePristine(); |
| beforeStateMigrations = (snapshot) => |
| recheckGatewayRunBootstrap({ |
| opts, |
| runtime: defaultRuntime, |
| ...(snapshot ? { snapshot } : {}), |
| }); |
| } |
| const stateMigrationAgentId = getStateMigrationAgentId(actionCommand); |
| if (stateMigrationAgentId) { |
| const existingGuard = beforeStateMigrations; |
| beforeStateMigrations = async (snapshot) => { |
| if (snapshot) { |
| const { isValidAgentId, normalizeAgentId } = |
| await import("@openclaw/normalization-core/agent-id"); |
| if (isValidAgentId(stateMigrationAgentId)) { |
| const [{ listAgentIds }, { retainLegacyDefaultAgentId }] = await Promise.all([ |
| import("../../agents/agent-scope-config.js"), |
| import("../../config/legacy.default-agent-owner.js"), |
| ]); |
| const agentId = normalizeAgentId(stateMigrationAgentId); |
| if (listAgentIds(snapshot.sourceConfig).includes(agentId)) { |
| retainLegacyDefaultAgentId(snapshot.sourceConfig, agentId); |
| } |
| } |
| } |
| return (await existingGuard?.(snapshot)) ?? true; |
| }; |
| } |
| await ensureCliExecutionBootstrap({ |
| runtime: defaultRuntime, |
| commandPath, |
| startupPolicy, |
| allowInvalid, |
| ...(beforeStateMigrations ? { beforeStateMigrations } : {}), |
| ...(skipPristineStartupStateMigrations ? { skipPristineStartupStateMigrations: true } : {}), |
| ...(skipPristineCoreStateMigrations ? { skipPristineCoreStateMigrations: true } : {}), |
| }); |
| if (beforeStateMigrations && isGatewayRunAction(actionCommand)) { |
| const { reloadTrustedGatewayRunEnvironment } = |
| await import("../gateway-cli/pre-bootstrap.js"); |
| await reloadTrustedGatewayRunEnvironment({ runtime: defaultRuntime }); |
| } |
| }); |
| } |
|
|