Files
oh-my-pi/packages/coding-agent/src/cli-commands.ts
T
can1357 30229b97c4 feat(cli): added TTSR CLI command with list and test execution modes
- Added a top-level `ttsr` CLI command with `list` and `test` actions.
- Added snippet input handling for inline text, `--file` path, and stdin via `--file -`.
- Added test-mode context inference and result output for matched and unmatched rules.
- Added CLI tests for source inference, explicit source overrides, JSON output, and list mode.
2026-06-17 20:42:19 +02:00

86 lines
4.8 KiB
TypeScript

/**
* Top-level CLI command table.
*
* Lives in its own module (importable without side effects) so that tests can
* inspect the registered subcommands without triggering the side-effectful
* top-level await in `cli.ts`. Adding a new subcommand here is enough to make
* `runCli` route to it instead of forwarding the argv as a prompt to
* `launch` — see #1496 for the original "args silently leak to the LLM"
* regression that motivated the split.
*/
import type { CommandEntry } from "@oh-my-pi/pi-utils/cli";
export const commands: CommandEntry[] = [
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
{ name: "acp", load: () => import("./commands/acp").then(m => m.default) },
{ name: "auth-broker", load: () => import("./commands/auth-broker").then(m => m.default) },
{ name: "auth-gateway", load: () => import("./commands/auth-gateway").then(m => m.default) },
{ name: "agents", load: () => import("./commands/agents").then(m => m.default) },
{ name: "bench", load: () => import("./commands/bench").then(m => m.default) },
{ name: "commit", load: () => import("./commands/commit").then(m => m.default) },
{ name: "completions", load: () => import("./commands/completions").then(m => m.default) },
{ name: "__complete", load: () => import("./commands/complete").then(m => m.default) },
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
{ name: "dry-balance", load: () => import("./commands/dry-balance").then(m => m.default) },
{ name: "grep", load: () => import("./commands/grep").then(m => m.default) },
{ name: "gallery", load: () => import("./commands/gallery").then(m => m.default) },
{ name: "grievances", load: () => import("./commands/grievances").then(m => m.default) },
{ name: "install", load: () => import("./commands/install").then(m => m.default) },
{ name: "join", load: () => import("./commands/join").then(m => m.default) },
{ name: "models", load: () => import("./commands/models").then(m => m.default) },
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
{ name: "say", load: () => import("./commands/say").then(m => m.default) },
{ name: "setup", load: () => import("./commands/setup").then(m => m.default) },
{ name: "shell", load: () => import("./commands/shell").then(m => m.default) },
{ name: "read", load: () => import("./commands/read").then(m => m.default) },
{ name: "ssh", load: () => import("./commands/ssh").then(m => m.default) },
{ name: "stats", load: () => import("./commands/stats").then(m => m.default) },
{ name: "update", load: () => import("./commands/update").then(m => m.default) },
{ name: "usage", load: () => import("./commands/usage").then(m => m.default) },
{ name: "tiny-models", load: () => import("./commands/tiny-models").then(m => m.default) },
{ name: "token", load: () => import("./commands/token").then(m => m.default) },
{ name: "ttsr", load: () => import("./commands/ttsr").then(m => m.default) },
{ name: "worktree", load: () => import("./commands/worktree").then(m => m.default), aliases: ["wt"] },
{ name: "search", load: () => import("./commands/web-search").then(m => m.default), aliases: ["q"] },
];
const RESERVED_TOP_LEVEL_WORDS = new Map<string, string>([
[
"extensions",
'`omp extensions` is not a management command. Use `omp plugin list` / `omp plugin install`, or run `omp launch extensions` if you meant to send "extensions" as a prompt.',
],
]);
export function reservedTopLevelWordMessage(first: string | undefined, argc = 1): string | undefined {
if (argc !== 1 || !first || first.startsWith("-") || first.startsWith("@")) return undefined;
return RESERVED_TOP_LEVEL_WORDS.get(first);
}
/**
* Return true when `first` matches a registered subcommand name or alias.
*
* Flags (`-…`) and `@file` arguments are never subcommands; for those the CLI
* runner skips ahead to the default `launch` command.
*/
export function isSubcommand(first: string | undefined): boolean {
if (!first || first.startsWith("-") || first.startsWith("@")) return false;
return commands.some(entry => entry.name === first || entry.aliases?.includes(first));
}
export type ResolvedCliArgv = { argv: string[] } | { error: string };
/**
* Decide what the CLI runner should do with raw argv: reject bare reserved
* management words, pass help/version through untouched, and route everything
* that is not a known subcommand to `launch`.
*/
export function resolveCliArgv(argv: string[]): ResolvedCliArgv {
const first = argv[0];
const reservedMessage = reservedTopLevelWordMessage(first, argv.length);
if (reservedMessage) return { error: reservedMessage };
if (first === "--help" || first === "-h" || first === "--version" || first === "-v" || first === "help") {
return { argv };
}
return { argv: isSubcommand(first) ? argv : ["launch", ...argv] };
}