#!/usr/bin/env node import { splitSpec, type Config, type ConfigOverrides } from "./config.js"; import { type Candidate, type CandidateAvailability } from "./candidates.js"; import { type DispatchView } from "./dispatch.js"; import { type DispatchMode } from "./dispatch-lane-stats.js"; import type { DispatchedQuotaReport } from "./mcp/lane-runner.js"; import type { DispatchedTelemetryReport } from "./dispatch-lane-stats.js"; import { type AccountingStore } from "./accounting-store.js"; import { ModelCatalog } from "./catalog.js"; import { type CommandEffect } from "./self-update.js"; import { type KeysCliDependencies } from "./keys-cli.js"; /** * Canonical flag to short aliases mapping. * * Single-dash long aliases (`-task` for `--task`) are expanded automatically by * `expandFlagAliases`. This table declares the explicit short aliases advertised * by help and accepted by the CLI parser. */ export declare const FLAG_ALIASES: Readonly>; export declare const CLI_FLAG_ALIASES: Readonly>; export declare function expandFlagAliases(flags: readonly string[]): Set; export declare const CANONICAL_VALUE_FLAGS: readonly ["--config", "--provider", "--default", "--mode", "--listen", "--task", "--exhausted", "--outcome", "--retry-after-ms", "--after", "--lane", "--tier", "--host", "--client", "--relay-model", "--scope", "--credential", "--include", "--window", "--by", "--effort", "--shell", "--class", "--members", "--cost-class", "--rationale", "--reset-field", "--reset-ms", "--sig", "--import", "--label", "--env-name", "--out", "--repo"]; export declare const VALUE_FLAGS: Set; export declare function argValue(...flags: string[]): string | undefined; export declare function hasFlag(...flags: string[]): boolean; /** Extract non-flag positional arguments from an argv array, skipping flags and their values. */ export declare function getPositionalArgs(argv?: string[]): string[]; /** * The shipped template, exposed for `test/first-run.test.ts`. * * A test that rebuilt this object would be testing itself: the defect being pinned was that the * SHIPPED bytes routed `claude-*` to a free pool while three user-facing documents said Anthropic. * Only the real constant can observe that. */ export declare const DEFAULT_CONFIG_TEMPLATE_FOR_TEST: string; export { splitSpec }; export declare function resolveConfigPath(): string; /** The first-run marker file, beside `config.json` in whichever config dir won. */ export declare const FIRST_RUN_MARKER = "first-run"; /** Is the first-run routing question still unanswered? Absent file ⇒ no. Never throws. */ export declare function firstRunPending(configDir?: string): boolean; /** * Tell whoever is reading that the first-run routing question is still open. * * ⚠ STDERR, never stdout. `routing show` and `offload status` are JSON surfaces that scripts and * agents parse; a synthetic key inside `cfg.routing` would also be a lie about the config, since * nothing in the file says this. A notice beside the JSON reaches a human and an agent both, * and breaks no parser. */ export declare function printFirstRunNotice(configDir?: string, cfg?: Config): void; /** * The environment half of the first-run notice: what is installed, and what already has a key. * * ⚠ **Why this is here and not in `onboard`.** Everything it reports already existed — * `presets.ts` has carried a `signupUrl` for every free provider since the beginning, and * `printOnboardingGuide()` has printed them. What did not exist was any moment at which a user * SAW it: onboarding is pull-only, so it fires when somebody types `llm-relay onboard`, and a * first-time user has no reason to guess that command exists. The first-run notice is the one * place the relay already speaks unprompted, so the report belongs beside the routing question. * * ⚠ It reports, and never acts. No key is read aloud, nothing is written, and no provider is * added — a notice that changed configuration would be exactly the silent behaviour the first-run * marker exists to avoid. `llm-relay onboard` remains the only thing that writes credentials. * * ⚠ Absence is reported as "not detected", never as "not installed". `installed-hosts.ts` requires * positive evidence, so a false there means no evidence was found — the `key-checker.ts` * `unverified` rule, applied to tools instead of credentials. * * Never throws: the notice is advisory, and a detection fault must not break the command it rides * on. A caller with no `Config` still gets the host half. */ /** The inputs the report renders. Injected so the rendering is testable without a real machine. */ export interface FirstRunEnvironmentInputs { /** Hosts for which POSITIVE evidence was found. Never a claim about what is absent. */ detected: readonly { readonly label: string; }[]; /** Provider credential status, as `getOnboardingStatusList` reports it. */ statuses: readonly { readonly displayName: string; readonly hasKey: boolean; readonly signupUrl?: string | undefined; }[]; } /** * PURE renderer for the environment half of the first-run notice. * * ⚠ Separated from the IO so it can be tested at all. Adversarial review found the first version * had no seam and zero coverage, unlike every sibling in this file — and it was the only part of * the notice that makes factual claims about the operator's machine, so it was exactly the part * that needed pinning. * * ⚠ **It makes no claim about ladder membership**, and that is a deliberate correction. The first * version printed "none is configured as a lane yet" unconditionally whenever any host was * detected — false the moment an operator adds a rung, which is a supported edit that does not * clear the first-run marker. Checking properly is not available either: `laneOfCommand`'s closed * vocabulary recognises only `agy` and `codex`, so Claude Code and OpenCode would always look * unconfigured. Rather than duplicate ladder logic in a notice, or assert what it cannot verify, * this points at the surface that DOES know. That is the same "ask the relay, don't guess" rule * the skill states for dispatch order. */ export declare function renderFirstRunEnvironment(inputs: FirstRunEnvironmentInputs): string; /** Record that the operator has been asked and has answered. Idempotent; never throws. */ export declare function clearFirstRun(configDir?: string): boolean; /** * Merge every credential source into the environment, once per process, before anything reads a * key or expands a `${ENV}` in the config. Already-set variables always win, at every layer. * * Order is least-explicit first, and both layers only ever FILL GAPS: * 1. Windows User/Machine registry scopes — recovers variables the OS could not deliver to an * already-running process. A long-lived relay launched at logon otherwise never sees a key * added afterwards, while every shell the user opens does; that mismatch made six working * credentials look like `401 Wrong API Key`. * 2. `~/.llm-relay/.env` — what the onboarding wizard writes. */ export declare function ensureEnvFileLoaded(): void; export declare function configOverridesFromCli(): ConfigOverrides; export declare function loadOrExit(): Config; /** `llm-relay models` — dynamic, cached model discovery per provider. */ export declare function runModels(): Promise; /** * Warm every provider's catalog and warn about any routing target the provider * does not serve — non-blocking (fire-and-forget) so it never delays listen(). */ export declare function warmAndValidate(cfg: Config, catalog: ModelCatalog): Promise; /** * What `runProxy` does when the listener cannot bind — ONE policy, exported for its test. * * `EADDRINUSE` means another relay already fronts this address, and the right answer is one * line and exit 1, never an uncaught exception with a stack. Everything else is rethrown so a * genuine bug still fails loudly. ⚠ The exit deliberately skips every shutdown flush: the * accounting store's constructor only READS `usage/` (`loadFixedSnapshots`) and its first * write is a flush, so a second relay that exits here has touched no file under the first * relay's ledger — which is the whole point of refusing to start (backlog item, audit DR-009). */ export declare function onListenError(err: NodeJS.ErrnoException, cfg: Pick, io: { stderr: (line: string) => void; exit: (code: number) => never; }): never; export declare function runProxy(): import("http").Server; /** `llm-relay ping` — probe and output model stability, latency, and quota across providers. */ export declare function runPingCommand(): Promise; /** * Render the key checker's quota percent WITH its basis and limit/usage figures — never alone. * The percent is the relay's own arithmetic over figures the provider stated * (`fetchProviderQuota`: OpenRouter's credit `limit` and `usage`), not a typed observation, * but the axis IS known because that endpoint is the only producer. * Spec §6.1: every reported number carries its basis. */ export declare function formatKeyQuota(quotaPercent: number | null | undefined): string; /** `llm-relay check-keys` — pre-flight verification of provider environment keys. */ export declare function runCheckKeys(): Promise; /** `llm-relay keys add` — CLI-local credential insertion; secret input is never argv. */ export declare function runKeysAdd(provider?: string | undefined, deps?: KeysCliDependencies): Promise; export declare function runKeysList(deps?: KeysCliDependencies): Promise; export declare function runKeysRotate(credential?: string | undefined, deps?: KeysCliDependencies): Promise; export declare function runKeysRevoke(credential?: string | undefined, deps?: KeysCliDependencies): Promise; export declare function runKeysRemove(credential?: string | undefined, deps?: KeysCliDependencies): Promise; export declare function runKeysDisable(credential?: string | undefined, deps?: KeysCliDependencies): Promise; export declare function runKeysEnable(credential?: string | undefined, deps?: KeysCliDependencies): Promise; export declare function runKeysExport(deps?: KeysCliDependencies): Promise; export declare function runKeysImport(importPath?: string | undefined, deps?: KeysCliDependencies): Promise; export declare function runKeysUnlock(deps?: KeysCliDependencies): Promise; export interface TelemetryDeps { readonly loadConfig?: () => Config; readonly request?: (cfg: Config) => Promise; readonly localReport?: (cfg: Config) => unknown; readonly write?: (text: string) => void; } /** Print telemetry from the live relay when available, falling back to the local snapshot. */ export declare function runTelemetry(deps?: TelemetryDeps): Promise; export type DispatchReportRequest = (cfg: Config, path: string, init: RequestInit) => Promise; /** Forward positive MCP lane quota evidence to the live relay's authenticated control route. */ export declare function reportMcpExhaustion(cfg: Config, report: DispatchedQuotaReport, request?: DispatchReportRequest): Promise; /** * Forward one MCP lane-execution report to the live relay's `POST /dispatch/telemetry`. * * ⚠ Asymmetry with `reportMcpExhaustion` is deliberate: it throws because a lost quota * report changes routing; a lost telemetry row changes only a ledger, so this is * best-effort — false when no proxy answered, and it NEVER throws. */ export declare function reportMcpTelemetry(cfg: Config, report: DispatchedTelemetryReport, request?: DispatchReportRequest): Promise; /** * A dispatch lane's live traffic from the running relay (`GET /dispatch/activity`). Null when no * relay answers, the relay holds no record, or the answer is malformed — each of which the MCP * server reads as no signal, never as idle. */ export declare function readMcpLaneActivity(cfg: Config, tag: string, request?: DispatchReportRequest): Promise<{ inFlight: number; lastActivityAt: number; } | null>; /** Render a normalized listener address as an HTTP URL, including required IPv6 brackets. */ export declare function proxyUrl(cfg: Pick, path: string): string; export declare const CLI_COMMAND_NAMES: ReadonlySet; /** * Refuse a command given more positionals than it can read, or fewer than it needs. * * Pure and exported so the whole table is testable without driving `main()`. * * ⚠ Returns null on an EMPTY positional list. That is bare `llm-relay` — the documented way to * start the proxy — and also the `--ping` flag entry, which reaches the ping command with no * positional at all. Applying a minimum there is the one guaranteed false positive available. * * ⚠ Returns null on a table MISS. An unknown command name belongs to `dispatchDashboardOrProxy`, * which already refuses it against `CLI_COMMAND_NAMES`; two refusals for one mistake would be * worse than one. * * ⚠ The extra token is deliberately NOT echoed, unlike the unknown-command guard. An unknown * COMMAND is by definition not a secret-bearing position and naming it is the whole diagnostic; * a stray POSITIONAL can be anything the user pasted — and `check-keys` here is the same command * as `keys check`, whose parser never echoes argv for exactly that reason. The count plus a * verified hint is enough to act on, and the user can still see what they typed. */ /** * Commands whose arity is owned elsewhere, exported so the coverage test can state WHY each is * absent from `COMMAND_ARITY` rather than letting a future omission look identical to an oversight. */ export declare const ARITY_EXEMPT: ReadonlySet; /** The commands this guard bounds — exported for the drift test, not for dispatch. */ export declare const ARITY_GUARDED_COMMANDS: readonly string[]; type CliOptionSpec = Readonly>; export declare const CLI_OPTIONS: CliOptionSpec; export declare const ACTION_OPTIONS: Readonly>; /** Return a bounded, value-free diagnostic for an option not accepted by this command/action. */ export declare function commandOptionError(argv: readonly string[]): string | null; export declare function commandArityError(positionals: readonly string[]): string | null; /** Clear process-local routing cooldowns through the protected live control plane only. */ export declare function runCooldowns(_action: string | undefined, _spec: string | undefined): Promise; export type DashboardBrowserOpener = (url: string) => Promise | void; export interface DashboardBrowserProcess { once(event: "error", listener: () => void): DashboardBrowserProcess; once(event: "close", listener: (code: number | null, signal: NodeJS.Signals | null) => void): DashboardBrowserProcess; /** Best-effort termination for a hung native helper; callers still settle independently. */ kill(signal?: NodeJS.Signals): boolean; } export type DashboardProcessSpawner = (command: string, args: string[], options: { readonly detached: boolean; readonly shell: false; readonly stdio: "ignore"; readonly windowsHide: boolean; }) => DashboardBrowserProcess; export declare const DASHBOARD_BROWSER_OPEN_TIMEOUT_MS = 5000; export interface DashboardCommandDependencies { readonly fetch?: typeof fetch; readonly openBrowser?: DashboardBrowserOpener; readonly now?: () => number; readonly write?: (message: string) => void; } /** Open a URL through the platform's native launcher without shell interpolation. */ export declare function openDashboardInBrowser(url: string, platform?: NodeJS.Platform, spawnProcess?: DashboardProcessSpawner, timeoutMs?: number): Promise; /** * Get one opaque bootstrap from an already-running relay and launch its read-only dashboard. * The persistent control capability stays exclusively in this request header; only the one-use * bootstrap may appear in the fallback link when no browser can be opened. */ export declare function runDashboardCommand(cfg: Config, dependencies?: DashboardCommandDependencies): Promise; export type CostWindow = "1h" | "24h" | "7d" | "30d" | "all"; /** The cost roll-up's dependencies; every default is overridable for tests. */ export interface CostCommandDependencies { readonly now?: () => number | Date | string; readonly write?: (message: string) => void; readonly exit?: (code: number) => never; /** Overrides the store directory; defaults to the production `~/.llm-relay/usage/`. */ readonly usageDir?: string; /** * Overrides the read-only store construction; tests inject a store in a failing writer * state. Defaults to a read-only store over `usageDir`, which reports `read_only`. */ readonly store?: AccountingStore; /** * Loads the relay config for the `--by model` lane-id footnote. Defaults to the live * `loadOrExit`; tests inject a config holding the ladder under test. Read ONLY when * `--by model` is asked — every other dimension must keep working config-less. */ readonly loadConfig?: () => Config; } /** * `llm-relay cost` — the C1 roll-up (open-decisions-2026-08-16.md C1): what did this * relay's traffic cost me, in the four provenance-labelled spend cells, optionally with * tool-call repair shown as its own share. * * Reads the LOCAL accounting store files directly (Gap 7 resolution: no HTTP endpoint), * so it answers whether or not the proxy is running — but a running proxy holds unflushed * in-memory deltas, so recent minutes can lag until its write-behind flush lands. */ export declare function runCostCommand(dependencies?: CostCommandDependencies): Promise; export interface DashboardCommandRouteDependencies { readonly loadConfig: () => Config; readonly runDashboard: (cfg: Config) => Promise; readonly reportError: (error: unknown) => void; readonly runProxy: () => unknown; /** * How to refuse a bare token that is not a command. REQUIRED, not optional — an optional * reporter would let a future caller silently reinstate the fall-through this exists to close. */ readonly reportUnknownCommand: (name: string) => void; /** * Serve MCP on stdio. REQUIRED for the same reason as `reportUnknownCommand`: `mcp` is in * `CLI_COMMAND_NAMES`, so without a branch here it is a KNOWN name that falls through to * `runProxy()` — a host adding the MCP server to its config would silently start a second relay * instead. An optional dependency would let that regression back in. */ readonly runMcpServer: () => void; } /** * The real final command dispatch: dashboard returns before any proxy/store/signal lifecycle. * * ⚠ This is the END of `main`'s ladder, so ANY positional no branch above claimed lands here — and * it used to mean "start the proxy". A mistyped command therefore started a second relay instead * of reporting the typo: loud where a relay is already running (`EADDRINUSE`), silent where none * is, and in both cases nothing said the command was not understood. * * The guard is gated on `CLI_COMMAND_NAMES`, the set that already exists, so it is ONE shared * check rather than a special case per command: a KNOWN name that reaches here still falls through * to `runProxy()` exactly as before — nothing that worked changes — while an unknown one is * refused. A bare `llm-relay` (no positional) still starts the proxy, the documented primary usage. * * ⚠ Consequence worth knowing, and an improvement rather than a cost: a value-taking flag missing * from `VALUE_FLAGS` has its VALUE read as a positional — the hazard that constant's own comment * warns about, after `--host routed` was once parsed as the lane id "routed". That case now fails * loudly instead of quietly starting a proxy or selecting the wrong lane. */ export declare function dispatchDashboardOrProxy(positional: string | undefined, dependencies: DashboardCommandRouteDependencies): unknown; /** * What actually comes back from `GET /dispatch` — which is NOT necessarily a `DispatchView`. * The proxy answering may be an older build than this CLI (they are separate processes with * separate lifetimes; the relay runs for days). Fields this CLI requires can therefore be absent * on the wire, and typing the response as the current shape would assert a guarantee the other * process never made. */ export type WireDispatchView = Omit & { host?: DispatchView["host"]; }; /** Normalize structured output from an older live proxy before exposing it to this host. */ export declare function normalizeDispatchCommands(view: WireDispatchView, platform?: NodeJS.Platform): DispatchView; /** * Substitute `{task}` placeholder into lane arguments and set `view.task`. * Applied locally so dispatch view queries never need to send arbitrary task text in HTTP query strings. */ export declare function substituteTaskInView(view: DispatchView, task: string | undefined): DispatchView; /** Safely reload configuration from disk without exiting if unreadable. */ export declare function loadConfigSafely(): Config | null; /** Which shell's literal-quoting rules a rendered command line is written for. */ export type RenderShell = "sh" | "pwsh"; /** Human name for the shell a line was quoted for. Precise on purpose — see `quoteArg`. */ export declare const SHELL_LABEL: Record; /** * The shell a host on `platform` is going to paste a rendered command into. * * A guess, and it can be wrong in a way that matters: Git Bash on Windows is `sh`, not * PowerShell. `--shell` overrides it, which is why `parseRenderShell` exists. */ export declare function shellFor(platform?: NodeJS.Platform): RenderShell; /** `--shell sh|pwsh`. An unrecognised value is a loud error, never a silent default. */ export declare function parseRenderShell(value: string | undefined): RenderShell | null; /** * Quote ONE argv element so it stays exactly one argv element. * * `llm-relay dispatch` prints a command line the host is told to run verbatim, and the task * text inside it is caller-supplied (`--task`, or `?task=` on a proxy that answered). This used * to render `args.join(" ")`, so a task containing a space, a quote or a `&` broke out into * extra shell words and `-t "fix the bug & rm -rf /"` printed a line with a second command in * it. `dispatch.ts` deliberately hands over `{ command, args }` and never a pre-joined string — * quoting is this renderer's job, and it is the only place that can know which shell. * * Both forms below are LITERAL: `sh` performs no expansion of any kind inside single quotes, * and neither does PowerShell, so the content cannot be re-parsed whatever it contains. * - `sh` — `'…'`; an embedded `'` closes, escapes, reopens: `'\''`. * - `pwsh` — `'…'`; an embedded `'` is doubled: `''`. * * ⚠ `pwsh` means **PowerShell 7+**, and the version is load-bearing, not pedantry. Measured on * this platform: Windows PowerShell 5.1 does not escape an embedded `"` when it builds the * command line for a native executable, so `'a " b " c'` — correctly single-quoted, one PS * string — reaches the program as THREE argv elements (`a `, `b`, ` c`). pwsh 7.6 passes it as * one. No single-quoting can fix 5.1: the split happens after PowerShell is done parsing, and * the 5.1 workaround (writing `\"` inside the string) is itself wrong under 7.x. The two are * irreconcilable in one rendering, so this targets 7+, `SHELL_LABEL` names the version out * loud, and `--shell sh` is the way out for anyone pasting somewhere else (Git Bash included). */ export declare function quoteArg(arg: string, shell?: RenderShell): string; /** * Render a cli rung's `{ command, args }` as a runnable line. Every element is quoted FIRST and * only the quoted forms are joined — never `args.join(" ")`, which is the defect this replaces. * * A quoted command NAME is not a command in PowerShell (`'agy' -p x` evaluates a string and * throws the rest away), so a command that needed quoting gets the call operator in front of it. * * A rung's `env` renders as part of the same line: `env -u UNSET NAME=value cmd …` for `sh`, * and `Remove-Item Env:UNSET …; $env:NAME = 'value'; cmd …` for PowerShell. The PowerShell form * mutates the calling session's environment rather than scoping to the child — PowerShell has no * `env(1)` equivalent, and the wrapper scripts this replaces (`scripts/claude-proxied.ps1`) have * always done the same. Variable NAMES are interpolated bare in both forms; they are safe because * config load rejects names containing `=`, whitespace or control characters, and they read * better than a quoted form that suggests they might be data. */ export declare function renderCommand(invoke: { command: string; args: string[]; env?: Record; } | undefined, shell?: RenderShell): string; /** * `llm-relay lanes [--probe]` — what each cli lane's own tool says it serves. * * ⚠ `--probe` spawns lane commands as an explicit operator action — same precedent as * `pools --probe` sending real completions. The request path reads the cached manifest and never * spawns anything; outside it the only OTHER spawn site is the relay's background lane cadence * (owner decision 2026-08-29, docs/history/quota-reprobe-design-2026-08-29.md). Without `--probe` this * just prints the cache. */ export declare function runLanes(): Promise; export declare function resolveDispatchView(opts: { task?: string | undefined; tier?: string | undefined; lane?: string | undefined; client?: string | undefined; mode?: DispatchMode | undefined; model?: string | undefined; cfg?: Config; }): Promise; /** * `llm-relay mcp` — serve the dispatch verb over MCP on stdio. * * Launched by a HOST (`claude mcp add`, `~/.codex/config.toml`, agy's MCP config), never by the * relay daemon. Gives every host one call that returns an ANSWER rather than a command to execute * — see `src/mcp/server.ts` for the full reasoning. */ export declare function runMcp(): Promise; /** * `llm-relay dispatch [lane]` — which lane to hand a delegated task to next. * * Prefers a running proxy so the answer reflects live exhaustion state reported by whichever * host last walked the ladder; falls back to a cold local read, which is still correct about * order and configuration but knows nothing about what is currently spent. */ export declare function runDispatch(arg: string | undefined): Promise; /** `llm-relay offload [status]` or `llm-relay offload [on|off|status]`. */ export declare function runOffload(arg: string | undefined, nextArg?: string): Promise; /** `llm-relay reload` — atomically reload supported config through POST /reload. */ export declare function runReload(): Promise; /** `llm-relay stop` — stop the running relay via the admitted POST /stop. */ export declare function runStop(): Promise; export declare function runEligibility(sub: string | undefined, arg: string | undefined): void; export declare function runCandidates(): Promise; /** Render observations without collapsing their axes into a misleading percentage. */ export declare function formatCandidateQuota(quota: readonly Candidate["quota"][number][], now: number): string; /** * Render a REACHED operator-set hard cap (G2), or null when this row has none. * * The provenance labels are read off the wire object rather than re-typed here: `basis` says the * ceiling is the operator's own assertion (not a measurement), and `scope` says whose usage * `used` counts — the credential as a whole, or this one deployment — which is exactly where the * cap was declared. A reader who cannot tell a declared ceiling from a measured one, or a * credential-wide count from a per-model one, cannot act on either. */ export declare function formatCandidateHardCap(hardCap: Candidate["hardCap"], now: number): string | null; /** * Render the resolved availability ladders WITH every basis. Unknown stays "-" (never 0), a * negative remaining prints as-is — overshoot is information — and a learned basis is labelled * display-only so nobody reads it as something routing acts on. */ export declare function formatCandidateAvailability(availability: readonly CandidateAvailability[]): string; /** `llm-relay config show|get|set|unset` — generic, scriptable JSON configuration editing. */ export declare function runConfigCommand(): Promise; /** `llm-relay routing ...` — convenient typed commands for the fields operators edit most. */ export declare function runRoutingCommand(): Promise; /** * `llm-relay pools` — list pool members; `--probe` sends a real completion to each. * * The listing is cheap and offline. The probe is the only thing that can actually catch a * member that is configured, catalogued, and nonetheless dead — see pool-health.ts. */ export declare function runPools(deps?: { catalog?: ModelCatalog; probeAll?: typeof probeAllPools; }): Promise; import { probeAllPools } from "./pool-health.js"; /** Strict, secret-safe keys parser. Diagnostics deliberately never echo rejected argv tokens. */ export declare function validateKeysCommandArgs(argv: string[]): string | null; export declare function main(): void; /** * Does THIS invocation already change durable state on this machine? * * Only such an invocation may be the moment the global install is replaced and the process * re-execed. `llm-relay keys` is a status query: reinstalling the user's global package * underneath a question about their credentials is a side effect nobody asked for, and it used * to happen on every read-only subcommand. * * Read-only is the DEFAULT and the fall-through, so a subcommand added later is safe until * someone deliberately classifies it — the failure mode of an unlisted command is "no update * check", never "surprise reinstall". * * This is passed to `shouldCheckUpdates()` as a RUNTIME PARAMETER. `self-update.ts` cannot * import this table: `cli.ts` already imports that module, so the reverse import would be a * cycle. The classification travels as an argument precisely to keep the dependency one-way. */ export declare function classifyCommand(argv: string[]): CommandEffect; /** * Entrypoint: fail-closed mutation syntax, then the currency gate (which may replace this install * and re-exec), then the command itself. `main` stays synchronous so its exit paths are direct. */ export declare function run(): Promise;