import { collectUniqueCommandDescriptors } from "../cli/program/command-descriptor-utils.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { resolveManifestActivationPluginIds } from "./activation-planner.js"; import { createPluginCliGatewayNodesRuntime } from "./cli-gateway-nodes-runtime.js"; import type { PluginLoadOptions } from "./loader.js"; import { loadOpenClawPluginCliRegistry, loadOpenClawPlugins } from "./loader.js"; import { createEmptyPluginRegistry } from "./registry-empty.js"; import type { PluginRegistry } from "./registry.js"; import { buildPluginRuntimeLoadOptions, createPluginRuntimeLoaderLogger, resolvePluginRuntimeLoadContext, type PluginRuntimeLoadContext, } from "./runtime/load-context.js"; import type { OpenClawPluginCliCommandDescriptor, OpenClawPluginCliContext, PluginLogger, } from "./types.js"; export type PluginCliLoaderOptions = Pick; export type PluginCliPublicLoadParams = { cfg?: OpenClawConfig; env?: NodeJS.ProcessEnv; loaderOptions?: PluginCliLoaderOptions; logger?: PluginLogger; primaryCommand?: string; }; export type PluginCliLoadContext = PluginRuntimeLoadContext; export type PluginCliRegistryLoadResult = PluginCliLoadContext & { registry: PluginRegistry; }; export type PluginCliCommandGroupEntry = { pluginId: string; parentPath: readonly string[]; placeholders: readonly OpenClawPluginCliCommandDescriptor[]; names: readonly string[]; register: (program: OpenClawPluginCliContext["program"]) => Promise; }; export function createPluginCliLogger(): PluginLogger { return createPluginRuntimeLoaderLogger(); } function resolvePluginCliLogger(logger?: PluginLogger): PluginLogger { return logger ?? createPluginCliLogger(); } function buildPluginCliLoaderParams( context: PluginCliLoadContext, params?: { primaryCommand?: string }, loaderOptions?: PluginCliLoaderOptions, ) { const onlyPluginIds = resolvePrimaryCommandManifestPluginIds(context, params?.primaryCommand); return buildPluginRuntimeLoadOptions(context, { ...loaderOptions, ...(onlyPluginIds && onlyPluginIds.length > 0 ? { onlyPluginIds } : {}), }); } function normalizePluginCliRootName(value: string | undefined): string { return normalizeLowercaseStringOrEmpty(value); } function resolvePrimaryCommandManifestPluginIds( context: PluginCliLoadContext, primaryCommand: string | undefined, ): string[] | undefined { const normalizedPrimary = normalizePluginCliRootName(primaryCommand); if (!normalizedPrimary) { return undefined; } return resolveManifestActivationPluginIds({ trigger: { kind: "command", command: normalizedPrimary, }, config: context.activationSourceConfig, workspaceDir: context.workspaceDir, env: context.env, }); } function listPluginCliRootOwnerIds(registry: PluginRegistry, primaryCommand: string): string[] { const normalizedPrimary = normalizePluginCliRootName(primaryCommand); if (!normalizedPrimary) { return []; } return [ ...new Set( registry.cliRegistrars .filter((entry) => { const parentPath = entry.parentPath ?? []; const roots = parentPath.length > 0 ? [parentPath[0]] : [...entry.commands, ...entry.descriptors.map((descriptor) => descriptor.name)]; return roots.includes(normalizedPrimary); }) .map((entry) => entry.pluginId), ), ]; } async function resolvePrimaryCommandPluginIds( context: PluginCliLoadContext, primaryCommand: string | undefined, loaderOptions?: PluginCliLoaderOptions, ): Promise { const normalizedPrimary = normalizePluginCliRootName(primaryCommand); if (!normalizedPrimary) { return undefined; } const manifestPluginIds = resolvePrimaryCommandManifestPluginIds(context, normalizedPrimary); if (manifestPluginIds && manifestPluginIds.length > 0) { return manifestPluginIds; } const { registry } = await loadPluginCliMetadataRegistryWithContext( context, { primaryCommand: normalizedPrimary }, loaderOptions, ); return listPluginCliRootOwnerIds(registry, normalizedPrimary); } export function resolvePluginCliLoadContext(params: { cfg?: OpenClawConfig; env?: NodeJS.ProcessEnv; logger: PluginLogger; }): PluginCliLoadContext { return resolvePluginRuntimeLoadContext({ config: params.cfg, env: params.env, logger: params.logger, }); } export async function loadPluginCliMetadataRegistryWithContext( context: PluginCliLoadContext, params?: { primaryCommand?: string }, loaderOptions?: PluginCliLoaderOptions, ): Promise { return { ...context, registry: await loadOpenClawPluginCliRegistry( buildPluginCliLoaderParams(context, params, loaderOptions), ), }; } export async function loadPluginCliCommandRegistryWithContext(params: { context: PluginCliLoadContext; primaryCommand?: string; loaderOptions?: PluginCliLoaderOptions; }): Promise { let onlyPluginIds: string[] | undefined; try { onlyPluginIds = await resolvePrimaryCommandPluginIds( params.context, params.primaryCommand, params.loaderOptions, ); } catch { onlyPluginIds = resolvePrimaryCommandManifestPluginIds(params.context, params.primaryCommand); } if (onlyPluginIds && onlyPluginIds.length === 0) { return { ...params.context, registry: createEmptyPluginRegistry(), }; } return { ...params.context, registry: loadOpenClawPlugins( buildPluginRuntimeLoadOptions(params.context, { ...params.loaderOptions, ...(onlyPluginIds && onlyPluginIds.length > 0 ? { onlyPluginIds } : {}), activate: false, cache: false, runtimeOptions: { nodes: createPluginCliGatewayNodesRuntime(), }, }), ), }; } function buildPluginCliCommandGroupEntries(params: { registry: PluginRegistry; config: OpenClawConfig; workspaceDir: string | undefined; logger: PluginLogger; }): PluginCliCommandGroupEntry[] { return params.registry.cliRegistrars.map((entry) => ({ pluginId: entry.pluginId, parentPath: entry.parentPath ?? [], placeholders: entry.descriptors, names: entry.commands, register: async (program) => { await entry.register({ program, parentPath: entry.parentPath ?? [], config: params.config, workspaceDir: params.workspaceDir, logger: params.logger, }); }, })); } export async function loadPluginCliDescriptors( params: PluginCliPublicLoadParams, ): Promise { try { const logger = resolvePluginCliLogger(params.logger); const context = resolvePluginCliLoadContext({ cfg: params.cfg, env: params.env, logger, }); const { registry } = await loadPluginCliMetadataRegistryWithContext( context, { primaryCommand: params.primaryCommand }, params.loaderOptions, ); return collectUniqueCommandDescriptors( registry.cliRegistrars .filter((entry) => (entry.parentPath ?? []).length === 0) .map((entry) => entry.descriptors), ); } catch { return []; } } export async function loadPluginCliRegistrationEntries(params: { cfg?: OpenClawConfig; env?: NodeJS.ProcessEnv; loaderOptions?: PluginCliLoaderOptions; logger?: PluginLogger; primaryCommand?: string; }): Promise { const resolvedLogger = resolvePluginCliLogger(params.logger); const context = resolvePluginCliLoadContext({ cfg: params.cfg, env: params.env, logger: resolvedLogger, }); const { config, workspaceDir, logger, registry } = await loadPluginCliCommandRegistryWithContext({ context, primaryCommand: params.primaryCommand, loaderOptions: params.loaderOptions, }); return buildPluginCliCommandGroupEntries({ registry, config, workspaceDir, logger, }); } export async function resolvePluginCliRootOwnerIds( params: PluginCliPublicLoadParams, ): Promise { const primaryCommand = normalizePluginCliRootName(params.primaryCommand); if (!primaryCommand) { return null; } const logger = resolvePluginCliLogger(params.logger); const context = resolvePluginCliLoadContext({ cfg: params.cfg, env: params.env, logger, }); return ( (await resolvePrimaryCommandPluginIds(context, primaryCommand, params.loaderOptions)) ?? null ); } export async function loadPluginCliRegistrationEntriesWithDefaults( params: PluginCliPublicLoadParams, ): Promise { const logger = resolvePluginCliLogger(params.logger); return loadPluginCliRegistrationEntries({ ...params, logger, }); }