refactor: share cli routing metadata

This commit is contained in:
Peter Steinberger
2026-04-06 14:15:06 +01:00
parent f3dd9723e1
commit f43aba40a2
19 changed files with 541 additions and 250 deletions

View File

@@ -0,0 +1,56 @@
import { hasFlag } from "../argv.js";
import { cliCommandCatalog, type CliCommandCatalogEntry } from "../command-catalog.js";
import { matchesCommandPath } from "../command-path-matches.js";
import { resolveCliCommandPathPolicy } from "../command-path-policy.js";
import {
routedCommandDefinitions,
type RoutedCommandDefinition,
} from "./routed-command-definitions.js";
export type RouteSpec = {
match: (path: string[]) => boolean;
loadPlugins?: boolean | ((argv: string[]) => boolean);
run: (argv: string[]) => Promise<boolean>;
};
function createCommandLoadPlugins(commandPath: readonly string[]): (argv: string[]) => boolean {
return (argv) => {
const loadPlugins = resolveCliCommandPathPolicy([...commandPath]).loadPlugins;
return loadPlugins === "always" || (loadPlugins === "text-only" && !hasFlag(argv, "--json"));
};
}
function createParsedRoute<TArgs>(params: {
entry: CliCommandCatalogEntry;
definition: RoutedCommandDefinition<TArgs>;
}): RouteSpec {
return {
match: (path) =>
matchesCommandPath(path, params.entry.commandPath, { exact: params.entry.exact }),
loadPlugins: params.entry.route?.preloadPlugins
? createCommandLoadPlugins(params.entry.commandPath)
: undefined,
run: async (argv) => {
const args = params.definition.parseArgs(argv);
if (!args) {
return false;
}
await params.definition.runParsedArgs(args);
return true;
},
};
}
export const routedCommands: RouteSpec[] = cliCommandCatalog
.filter(
(
entry,
): entry is CliCommandCatalogEntry & { route: { id: keyof typeof routedCommandDefinitions } } =>
Boolean(entry.route),
)
.map((entry) =>
createParsedRoute({
entry,
definition: routedCommandDefinitions[entry.route.id],
}),
);

View File

@@ -0,0 +1,97 @@
import { defaultRuntime } from "../../runtime.js";
import type { CliRoutedCommandId } from "../command-catalog.js";
import {
parseAgentsListRouteArgs,
parseConfigGetRouteArgs,
parseConfigUnsetRouteArgs,
parseGatewayStatusRouteArgs,
parseHealthRouteArgs,
parseModelsListRouteArgs,
parseModelsStatusRouteArgs,
parseSessionsRouteArgs,
parseStatusRouteArgs,
} from "./route-args.js";
export type RoutedCommandDefinition<TArgs = unknown> = {
parseArgs: (argv: string[]) => TArgs | null;
runParsedArgs: (args: TArgs) => Promise<void>;
};
export const routedCommandDefinitions: Record<CliRoutedCommandId, RoutedCommandDefinition> = {
health: {
parseArgs: parseHealthRouteArgs,
runParsedArgs: async (args) => {
const { healthCommand } = await import("../../commands/health.js");
await healthCommand(args, defaultRuntime);
},
},
status: {
parseArgs: parseStatusRouteArgs,
runParsedArgs: async (args) => {
if (args.json) {
const { statusJsonCommand } = await import("../../commands/status-json.js");
await statusJsonCommand(
{
deep: args.deep,
all: args.all,
usage: args.usage,
timeoutMs: args.timeoutMs,
},
defaultRuntime,
);
return;
}
const { statusCommand } = await import("../../commands/status.js");
await statusCommand(args, defaultRuntime);
},
},
"gateway-status": {
parseArgs: parseGatewayStatusRouteArgs,
runParsedArgs: async (args) => {
const { runDaemonStatus } = await import("../daemon-cli/status.js");
await runDaemonStatus(args);
},
},
sessions: {
parseArgs: parseSessionsRouteArgs,
runParsedArgs: async (args) => {
const { sessionsCommand } = await import("../../commands/sessions.js");
await sessionsCommand(args, defaultRuntime);
},
},
"agents-list": {
parseArgs: parseAgentsListRouteArgs,
runParsedArgs: async (args) => {
const { agentsListCommand } = await import("../../commands/agents.js");
await agentsListCommand(args, defaultRuntime);
},
},
"config-get": {
parseArgs: parseConfigGetRouteArgs,
runParsedArgs: async (args) => {
const { runConfigGet } = await import("../config-cli.js");
await runConfigGet(args);
},
},
"config-unset": {
parseArgs: parseConfigUnsetRouteArgs,
runParsedArgs: async (args) => {
const { runConfigUnset } = await import("../config-cli.js");
await runConfigUnset(args);
},
},
"models-list": {
parseArgs: parseModelsListRouteArgs,
runParsedArgs: async (args) => {
const { modelsListCommand } = await import("../../commands/models.js");
await modelsListCommand(args, defaultRuntime);
},
},
"models-status": {
parseArgs: parseModelsStatusRouteArgs,
runParsedArgs: async (args) => {
const { modelsStatusCommand } = await import("../../commands/models.js");
await modelsStatusCommand(args, defaultRuntime);
},
},
};

View File

@@ -1,180 +1,9 @@
import { defaultRuntime } from "../../runtime.js";
import { hasFlag } from "../argv.js";
import { shouldLoadPluginsForCommandPath } from "../command-startup-policy.js";
import {
parseAgentsListRouteArgs,
parseConfigGetRouteArgs,
parseConfigUnsetRouteArgs,
parseGatewayStatusRouteArgs,
parseHealthRouteArgs,
parseModelsListRouteArgs,
parseModelsStatusRouteArgs,
parseSessionsRouteArgs,
parseStatusRouteArgs,
} from "./route-args.js";
import { routedCommands, type RouteSpec } from "./route-specs.js";
export type RouteSpec = {
match: (path: string[]) => boolean;
loadPlugins?: boolean | ((argv: string[]) => boolean);
run: (argv: string[]) => Promise<boolean>;
};
const routeHealth: RouteSpec = {
match: (path) => path[0] === "health",
// `health --json` only relays gateway RPC output and does not need local plugin metadata.
// Keep plugin preload for text output where channel diagnostics/logSelfId are rendered.
loadPlugins: (argv) =>
shouldLoadPluginsForCommandPath({
commandPath: ["health"],
jsonOutputMode: hasFlag(argv, "--json"),
}),
run: async (argv) => {
const args = parseHealthRouteArgs(argv);
if (!args) {
return false;
}
const { healthCommand } = await import("../../commands/health.js");
await healthCommand(args, defaultRuntime);
return true;
},
};
const routeStatus: RouteSpec = {
match: (path) => path[0] === "status",
// `status --json` can defer channel plugin loading until config/env inspection
// proves it is needed, which keeps the fast-path startup lightweight.
loadPlugins: (argv) =>
shouldLoadPluginsForCommandPath({
commandPath: ["status"],
jsonOutputMode: hasFlag(argv, "--json"),
}),
run: async (argv) => {
const args = parseStatusRouteArgs(argv);
if (!args) {
return false;
}
if (args.json) {
const { statusJsonCommand } = await import("../../commands/status-json.js");
await statusJsonCommand(
{
deep: args.deep,
all: args.all,
usage: args.usage,
timeoutMs: args.timeoutMs,
},
defaultRuntime,
);
return true;
}
const { statusCommand } = await import("../../commands/status.js");
await statusCommand(args, defaultRuntime);
return true;
},
};
const routeGatewayStatus: RouteSpec = {
match: (path) => path[0] === "gateway" && path[1] === "status",
run: async (argv) => {
const args = parseGatewayStatusRouteArgs(argv);
if (!args) {
return false;
}
const { runDaemonStatus } = await import("../daemon-cli/status.js");
await runDaemonStatus(args);
return true;
},
};
const routeSessions: RouteSpec = {
// Fast-path only bare `sessions`; subcommands (e.g. `sessions cleanup`)
// must fall through to Commander so nested handlers run.
match: (path) => path[0] === "sessions" && !path[1],
run: async (argv) => {
const args = parseSessionsRouteArgs(argv);
if (!args) {
return false;
}
const { sessionsCommand } = await import("../../commands/sessions.js");
await sessionsCommand(args, defaultRuntime);
return true;
},
};
const routeAgentsList: RouteSpec = {
match: (path) => path[0] === "agents" && path[1] === "list",
run: async (argv) => {
const { agentsListCommand } = await import("../../commands/agents.js");
await agentsListCommand(parseAgentsListRouteArgs(argv), defaultRuntime);
return true;
},
};
const routeConfigGet: RouteSpec = {
match: (path) => path[0] === "config" && path[1] === "get",
run: async (argv) => {
const args = parseConfigGetRouteArgs(argv);
if (!args) {
return false;
}
const { runConfigGet } = await import("../config-cli.js");
await runConfigGet(args);
return true;
},
};
const routeConfigUnset: RouteSpec = {
match: (path) => path[0] === "config" && path[1] === "unset",
run: async (argv) => {
const args = parseConfigUnsetRouteArgs(argv);
if (!args) {
return false;
}
const { runConfigUnset } = await import("../config-cli.js");
await runConfigUnset(args);
return true;
},
};
const routeModelsList: RouteSpec = {
match: (path) => path[0] === "models" && path[1] === "list",
run: async (argv) => {
const args = parseModelsListRouteArgs(argv);
if (!args) {
return false;
}
const { modelsListCommand } = await import("../../commands/models.js");
await modelsListCommand(args, defaultRuntime);
return true;
},
};
const routeModelsStatus: RouteSpec = {
match: (path) => path[0] === "models" && path[1] === "status",
run: async (argv) => {
const args = parseModelsStatusRouteArgs(argv);
if (!args) {
return false;
}
const { modelsStatusCommand } = await import("../../commands/models.js");
await modelsStatusCommand(args, defaultRuntime);
return true;
},
};
const routes: RouteSpec[] = [
routeHealth,
routeStatus,
routeGatewayStatus,
routeSessions,
routeAgentsList,
routeConfigGet,
routeConfigUnset,
routeModelsList,
routeModelsStatus,
];
export type { RouteSpec } from "./route-specs.js";
export function findRoutedCommand(path: string[]): RouteSpec | null {
for (const route of routes) {
for (const route of routedCommands) {
if (route.match(path)) {
return route;
}