feat(cli): relay-like CLI closure — target manifest, Codex TOML, Gemini launcher, guards

- canonical executable manifest (bin/cli/cli-manifest.mjs): run/configure/completion
  derive targets, aliases and --model wiring from one table; drift test cross-checks
  manifest x cliRuntime x UI catalog (tests/unit/cli/cli-manifest-drift.test.ts)
- dashboard Codex generator converged to ~/.codex/config.toml (modern Codex v0.137+,
  verified against codex-cli 0.147.0): conservative merge, env_key auth (key never
  written), refuses invalid TOML, reports legacy config.yaml as migration note
- omniroute run gemini: launcher over OmniRoute's /v1beta surface via
  GOOGLE_GEMINI_BASE_URL + isolated GEMINI_CLI_HOME forcing gemini-api-key auth
  (contract proven against @google/gemini-cli 0.50.0); ACP registration kept distinct
- opt-in real smoke harness for upstream CLIs (RUN_CLI_SMOKE=1, credential by env
  NAME, redacted output): tests/integration/upstream-cli-smoke.int.test.ts
- container-guard homologation for POST /api/cli-tools/apply (422 in container,
  dry-run preview allowed, host write passes) + docs; guard untouched
- typecheck: omniglyphAdapter union narrowing, usageTracking typed signatures
  (UsageLike, no any), models.ts isValidModel params — typecheck:core and
  typecheck:noimplicit:core now clean
- relay core (prior session of this effort): omniroute run for 6 CLIs, configure
  picker with per-context favorites/recents, contexts with optional keychain +
  0600 fallback, provider CRUD with recursive redaction, completion updates, docs
This commit is contained in:
Xiangzhe
2026-08-18 08:24:05 -03:00
parent 34bb018d21
commit 0a74bfbdea
50 changed files with 3499 additions and 284 deletions

View File

@@ -83,6 +83,17 @@ actually meant; `OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE=true` does the same for
the server. See
[Docker Guide → Configuring host CLI tools](../guides/DOCKER_GUIDE.md#configuring-host-cli-tools-when-omniroute-runs-in-docker).
The dashboard's **apply endpoint** (`POST /api/cli-tools/apply`) enforces the
same guard: in a container, a write whose target is not bind-mounted from the
host answers **`422`** with `containerEphemeralTarget: true`, the safe error
text and a `hostSetupCommand` (e.g. `omniroute setup-opencode`) to run on the
host instead — nothing is written. `dryRun: true` keeps working in container
mode and returns the generated content + target path without touching disk, so
you can preview from the dashboard and apply on the host. This behavior is
intentional and regression-guarded by
`tests/unit/api/cli-tools/apply-container-guard.test.ts` — never "fix" a 422
by removing the guard.
---
## Source of Truth
@@ -102,6 +113,26 @@ Each entry has these fields (defined in `src/shared/schemas/cliCatalog.ts`):
Entries with `baseUrlSupport: "none"` are **not shown** in the dashboard pages — they are registered in the MITM backlog for plan 11 (see `_tasks/features-v3.8.6/refactorpages/_orchestration/_plan11-mitm-backlog.md`).
### Capability tiers (cataloged × detectable × configurable × launchable)
Not every cataloged tool is detectable, configurable or launchable. Each tier has one
declaring source, and a drift test keeps them aligned:
| Tier | Meaning | Declared in |
| ---------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------- |
| **Cataloged** | Appears in the dashboard catalog (name, vendor, docs, config type) | `src/shared/constants/cliTools.ts` (`CLI_TOOLS`) |
| **Detectable** | Binary/config detection, health checks, config paths | `src/shared/services/cliRuntime.ts` (`CLI_TOOLS` runtime catalog) |
| **Configurable** | Supported by `omniroute configure <cli>` (setup recipe exists) | `bin/cli/cli-manifest.mjs` (`configure: true`) |
| **Launchable** | Supported by `omniroute run <target>` (env/args injection defined) | `bin/cli/cli-manifest.mjs` (`run: true`) |
`bin/cli/cli-manifest.mjs` is the canonical executable manifest for the CLI command
surfaces: `run`, `configure` and the shell-completion generators all derive their
target lists, alias resolution (for example `kilocode`/`kilo-code`/`kilo_cli``kilo`)
and `--model` flag wiring from it. The drift guard
`tests/unit/cli/cli-manifest-drift.test.ts` asserts that the manifest, the runtime
catalog, the UI catalog and every consumer surface stay in sync — a target added to
one surface without the others fails the suite instead of drifting silently.
---
## 1. CLI Code's Catalog (25 tools)
@@ -384,14 +415,26 @@ Use the unified Anthropic gateway root for Claude Code. Do not append `/v1` here
#### OpenAI Codex
Modern Codex (v0.137+) reads `~/.codex/config.toml` only — the old
`config.yaml` belongs to the legacy npm CLI and is silently ignored. The API
key stays in the `OMNIROUTE_API_KEY` environment variable (`env_key`), never
inside the file:
```bash
mkdir -p ~/.codex && cat > ~/.codex/config.yaml << EOF
model: auto
apiKey: sk-your-omniroute-key
apiBaseUrl: http://localhost:20128/v1
mkdir -p ~/.codex && cat > ~/.codex/config.toml << EOF
model_provider = "omniroute"
[model_providers.omniroute]
name = "OmniRoute"
base_url = "http://localhost:20128/v1"
env_key = "OMNIROUTE_API_KEY"
requires_openai_auth = false
EOF
export OMNIROUTE_API_KEY="sk-your-omniroute-key"
```
Full reference (profiles, `wire_api`, context windows): [CODEX-CLI-CONFIGURATION.md](../guides/CODEX-CLI-CONFIGURATION.md).
**Test:** `codex "what is 2+2?"`
---
@@ -613,10 +656,19 @@ omniroute providers list --json
omniroute providers test <id|name> # Test one configured connection
omniroute providers test-all # Test every active connection
omniroute providers validate # Local-only structural validation
omniroute providers add <provider> --credential-env PROVIDER_KEY
omniroute providers import ./providers.json --dry-run --json
omniroute providers auth <provider> # Existing OAuth flow
omniroute providers edit <id|name> --default-model <model>
omniroute providers remove <id|name> --yes
```
> `providers available` reads the OmniRoute catalog; `providers list/test/test-all/validate`
> read the local SQLite database directly and do not require the server to be running.
`providers add/import/auth/edit/remove` are API-first and therefore work against
the active local or remote context. Credential input should use
`--credential-stdin` or `--credential-env`; `--dry-run --json` reports only
redacted presence/shape. `providers available` reads the OmniRoute catalog;
`providers list/test/test-all/validate` retain their local SQLite behavior and
do not require the server to be running.
### Recovery & Reset

View File

@@ -474,6 +474,7 @@ detection above).
| `OMNIROUTE_ISSUE_AGENT_ENABLED` | `false` | `src/app/api/issue-agent/runs/route.ts` | Enables the offline/local Issue Agent recorded-triage endpoint. Leave disabled unless explicitly running local recorded-triage workflows. |
| `OMNIROUTE_ISSUE_AGENT_TIMEOUT_MS` | _(unset)_ | `src/lib/issueAgent/execution.ts` | Timeout (ms) for a single Issue Agent recorded-triage run. Clamped to an internal maximum; falls back to the built-in default when unset or invalid. |
| `OMNIROUTE_CONTEXT` | _(active context)_ | `bin/cli/program.mjs`, `bin/cli/api.mjs` | CLI remote-mode context/profile for `omniroute` commands; overrides the active context in the local contexts store. Equivalent to `--context <name>`. |
| `OMNIROUTE_CONTEXT_KEYCHAIN_DISABLED` | `0` | `bin/cli/contexts.mjs` | Disable the optional `keytar` OS-keychain backend for CLI context credentials. When enabled, credentials remain in `config.json` mode `0600` and the CLI emits a one-time fallback warning; intended for deliberate headless/container operation. |
| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `true` | `open-sse/mcp-server/server.ts` | Enforce scope-based access control on MCP tool calls. |
| `OMNIROUTE_MCP_SCOPES` | _(all)_ | `open-sse/mcp-server/server.ts` | Comma-separated scopes: `admin`, `combos`, `health`, `models`, `routing`, `budget`, `metrics`, `pricing`, `memory`, `skills`. |
| `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS` | `false` | `open-sse/mcp-server/descriptionCompressor.ts` | Compress MCP tool descriptions before serializing the manifest. Enable values: `1`, `true`, `on`. |