| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| import fs from "node:fs";
|
| import path from "node:path";
|
| import type { SkillArea } from "./types";
|
|
|
|
|
|
|
| export interface CliCommand {
|
|
|
| name: string;
|
|
|
| description: string;
|
|
|
| flags: string[];
|
|
|
| isSubcommand: boolean;
|
| }
|
|
|
| export interface ParsedCliRegistry {
|
|
|
| commands: Map<string, CliCommand>;
|
|
|
| families: Map<SkillArea, CliCommand[]>;
|
| }
|
|
|
|
|
|
|
| |
| |
| |
|
|
| const FILE_FAMILY_MAP: Record<string, SkillArea> = {
|
| "serve": "cli-serve",
|
| "dashboard": "cli-serve",
|
| "stop": "cli-serve",
|
| "restart": "cli-serve",
|
| "health": "cli-health",
|
| "status": "cli-health",
|
| "doctor": "cli-health",
|
| "providers": "cli-providers",
|
| "provider-cmd": "cli-providers",
|
| "test-provider": "cli-providers",
|
| "keys": "cli-keys",
|
| "oauth": "cli-keys",
|
| "models": "cli-models",
|
| "chat": "cli-chat",
|
| "stream": "cli-chat",
|
| "repl": "cli-chat",
|
| "combo": "cli-routing",
|
| "routing": "cli-routing",
|
| "resilience": "cli-resilience",
|
| "quota": "cli-resilience",
|
| "compression": "cli-compression",
|
| "context-eng": "cli-contexts",
|
| "contexts": "cli-contexts",
|
| "sessions": "cli-contexts",
|
| "cost": "cli-cost-usage",
|
| "usage": "cli-cost-usage",
|
| "pricing": "cli-cost-usage",
|
| "mcp": "cli-mcp",
|
| "a2a": "cli-a2a",
|
| "tunnel": "cli-tunnel",
|
| "backup": "cli-backup-sync",
|
| "sync": "cli-backup-sync",
|
| "cloud": "cli-backup-sync",
|
| "audit": "cli-policy-audit",
|
| "policy": "cli-policy-audit",
|
| "logs": "cli-policy-audit",
|
| "telemetry": "cli-policy-audit",
|
| "batches": "cli-batches",
|
| "files": "cli-batches",
|
| "eval": "cli-eval",
|
| "simulate": "cli-eval",
|
| "skills": "cli-plugins-skills",
|
| "plugin": "cli-plugins-skills",
|
| "memory": "cli-plugins-skills",
|
| "setup": "cli-setup",
|
| "config": "cli-setup",
|
| "env": "cli-setup",
|
| "update": "cli-setup",
|
| "autostart": "cli-setup",
|
| };
|
|
|
|
|
|
|
|
|
| const COMMAND_RE = /\.command\(\s*["']([^"']+)["']/g;
|
|
|
|
|
| const DESCRIPTION_RE = /\.description\(\s*["']([^"']+)["']/g;
|
|
|
|
|
| const OPTION_RE = /\.option\(\s*["']([^"']+)["']/g;
|
|
|
|
|
|
|
| interface RawCommand {
|
| name: string;
|
| description: string;
|
| flags: string[];
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| function extractCommandsFromContent(content: string, topLevelName: string): RawCommand[] {
|
| const commands: RawCommand[] = [];
|
|
|
|
|
| COMMAND_RE.lastIndex = 0;
|
| let match: RegExpExecArray | null;
|
| const commandMatches: Array<{ name: string; index: number }> = [];
|
|
|
| while ((match = COMMAND_RE.exec(content)) !== null) {
|
| commandMatches.push({ name: match[1], index: match.index });
|
| }
|
|
|
| for (let i = 0; i < commandMatches.length; i++) {
|
| const { name: rawName, index: cmdIndex } = commandMatches[i];
|
| const nextIndex = commandMatches[i + 1]?.index ?? content.length;
|
|
|
|
|
| const slice = content.slice(cmdIndex, nextIndex);
|
|
|
|
|
| DESCRIPTION_RE.lastIndex = 0;
|
| const descMatch = DESCRIPTION_RE.exec(slice);
|
| const description = descMatch ? descMatch[1] : "";
|
|
|
|
|
| const flags: string[] = [];
|
| OPTION_RE.lastIndex = 0;
|
| let optMatch: RegExpExecArray | null;
|
| while ((optMatch = OPTION_RE.exec(slice)) !== null) {
|
| flags.push(optMatch[1]);
|
| }
|
|
|
|
|
|
|
|
|
| const isTopLevel =
|
| rawName === topLevelName ||
|
| rawName.startsWith(topLevelName + " ") ||
|
|
|
| !rawName.includes(" ");
|
|
|
| const fullName = isTopLevel && i === 0 ? rawName : `${topLevelName} ${rawName}`;
|
|
|
| commands.push({ name: fullName.trim(), description, flags });
|
| }
|
|
|
| return commands;
|
| }
|
|
|
|
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| export function parseCliRegistry(): ParsedCliRegistry {
|
| const commandsDir = path.resolve(process.cwd(), "bin", "cli", "commands");
|
|
|
| let files: string[];
|
| try {
|
| files = fs.readdirSync(commandsDir).filter((f) => f.endsWith(".mjs"));
|
| } catch (err) {
|
| throw new Error(
|
| `cliRegistryParser: could not read ${commandsDir}. ` +
|
| `Run from project root. Underlying error: ${err instanceof Error ? err.message : String(err)}`,
|
| );
|
| }
|
|
|
| const commands = new Map<string, CliCommand>();
|
| const families = new Map<SkillArea, CliCommand[]>();
|
|
|
| for (const file of files) {
|
| const basename = path.basename(file, ".mjs");
|
| const family = FILE_FAMILY_MAP[basename];
|
| if (!family) continue;
|
|
|
| const filePath = path.join(commandsDir, file);
|
| let content: string;
|
| try {
|
| content = fs.readFileSync(filePath, "utf-8");
|
| } catch {
|
| continue;
|
| }
|
|
|
| const rawCmds = extractCommandsFromContent(content, basename);
|
| if (rawCmds.length === 0) continue;
|
|
|
| for (let i = 0; i < rawCmds.length; i++) {
|
| const rc = rawCmds[i];
|
| const cliCmd: CliCommand = {
|
| name: rc.name,
|
| description: rc.description,
|
| flags: rc.flags,
|
| isSubcommand: i > 0,
|
| };
|
|
|
| commands.set(rc.name, cliCmd);
|
|
|
| if (!families.has(family)) {
|
| families.set(family, []);
|
| }
|
| families.get(family)!.push(cliCmd);
|
| }
|
| }
|
|
|
| return { commands, families };
|
| }
|
|
|
| |
| |
| |
|
|
| export function getCommandsForFamily(family: SkillArea): string[] {
|
| const { families } = parseCliRegistry();
|
| const cmds = families.get(family) ?? [];
|
| return cmds.map((c) => c.name);
|
| }
|
|
|