diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 668c125dc7..80ce591759 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,6 +144,12 @@ jobs: - run: npm ci - run: npm run check:node-runtime - run: npm run build + - name: Upload Next.js build for E2E shards + uses: actions/upload-artifact@v4 + with: + name: e2e-next-build + path: .build/next/ + retention-days: 1 package-artifact: name: Package Artifact @@ -498,19 +504,18 @@ jobs: } test-e2e: - name: E2E Tests (${{ matrix.shard }}/6) + name: E2E Tests (${{ matrix.shard }}/9) runs-on: ubuntu-latest - # The heaviest shard (responsive viewport matrix + studio/smoke) re-runs - # `npm run build` (~5m) then ~24 serial tests; at 35m it was still cancelled - # mid-run, so it genuinely needs more wall-clock. 50m gives ample headroom - # while the per-test cap (playwright.config.ts) bounds any real hang to a fast - # visible failure and the `line` reporter streams which test is slow. - timeout-minutes: 50 + # Build artifact from the `build` job is downloaded instead of rebuilding + # (~5min saved per shard). 9 shards (up from 6) reduces tests per shard by + # ~33%. Playwright browser is cached across runs (~1.5min saved per shard). + # Heavy shard target: ≤20min (was ~40min). + timeout-minutes: 30 needs: build strategy: fail-fast: false matrix: - shard: [1, 2, 3, 4, 5, 6] + shard: [1, 2, 3, 4, 5, 6, 7, 8, 9] env: JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-test-api-key-secret-long @@ -524,9 +529,19 @@ jobs: cache: npm - run: npm ci - run: npm run check:node-runtime + - name: Cache Playwright browsers + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: playwright-chromium-${{ runner.os }}-${{ hashFiles('package-lock.json') }} + restore-keys: playwright-chromium-${{ runner.os }}- - run: npx playwright install --with-deps chromium - - run: npm run build - - run: npx playwright test tests/e2e/*.spec.ts --shard=${{ matrix.shard }}/6 + - name: Download Next.js build artifact + uses: actions/download-artifact@v4 + with: + name: e2e-next-build + path: .build/next/ + - run: npx playwright test tests/e2e/*.spec.ts --shard=${{ matrix.shard }}/9 test-integration: name: Integration Tests (${{ matrix.shard }}/2) diff --git a/.gitignore b/.gitignore index 746f05b94f..656b3a6f82 100644 --- a/.gitignore +++ b/.gitignore @@ -198,4 +198,7 @@ pr_reviews*.json #hidden local data directories (never commit) .local-data/ -.data-dev/ \ No newline at end of file +.data-dev/ + +# internal setup prompts with personal credentials — never commit +CODEX-SETUP-PROMPT.md \ No newline at end of file diff --git a/skills/config-codex-cli/SKILL.md b/skills/config-codex-cli/SKILL.md new file mode 100644 index 0000000000..db75f91d81 --- /dev/null +++ b/skills/config-codex-cli/SKILL.md @@ -0,0 +1,367 @@ +--- +name: config-codex-cli +description: Step-by-step agent workflow to configure the OpenAI Codex CLI on any machine (Linux, macOS, Windows) to use OmniRoute as backend. Detects OS and shell, writes config.toml and 7 named profiles, sets environment variables, and verifies the setup. +--- + +# /setup-codex-cli — Codex CLI Configuration Workflow + +Configure the Codex CLI on this machine to use an OmniRoute instance as backend. + +After this skill completes, `codex` will use OmniRoute by default with `cx/gpt-5.5` (xhigh reasoning), 7 named profiles for quick switching, and proper token limits configured for each model. + +--- + +## Step 0 — Collect required inputs from the user + +Before doing anything, ask the user for the two required values. Do not proceed until both are provided: + +1. **OmniRoute host** — the IP or hostname of the OmniRoute server (e.g. `192.168.0.1`, `100.x.x.x` for Tailscale, or `localhost`) +2. **OmniRoute API key** — the API key for OmniRoute (starts with `sk-`) + +Store these as local variables for the rest of the skill: +- `OMNI_HOST` = the host the user provided (no trailing slash, no port — port 20128 is appended by this skill) +- `OMNI_KEY` = the API key + +--- + +## Step 1 — Detect environment + +Run the following to gather machine facts. Store results — they are used in later steps. + +```bash +# OS detection +uname -s # Linux / Darwin (macOS) / MINGW*/CYGWIN*/MSYS* = Windows/Git Bash + +# Home directory +echo $HOME # Linux / macOS / Git Bash +echo $USERPROFILE # Windows native (PowerShell / cmd) + +# Current shell +echo $SHELL # Linux / macOS: /bin/bash, /bin/zsh, /bin/fish, etc. + +# Shell profile file +# Resolve which file to append env vars to: +# bash → ~/.bashrc (Linux) or ~/.bash_profile (macOS) +# zsh → ~/.zshrc +# fish → ~/.config/fish/config.fish +# PowerShell (Windows) → $PROFILE (run: echo $PROFILE inside PowerShell) + +# PATH and common tool directories (to populate shell_environment_policy later) +echo $PATH +which node 2>/dev/null && node --version +echo ${NVM_DIR:-not-set} +echo ${BUN_INSTALL:-not-set} +echo ${SDKMAN_DIR:-not-set} +echo ${JAVA_HOME:-not-set} +``` + +Based on the OS result, set the **Codex config directory**: +- Linux / macOS / Git Bash: `~/.codex/` +- Windows native (PowerShell): `$env:USERPROFILE\.codex\` + +--- + +## Step 2 — Verify Codex CLI is installed + +```bash +codex --version +``` + +If this fails, stop and tell the user to install the Codex CLI first: + +```bash +npm install -g @openai/codex +``` + +Then re-run the skill from Step 1. + +--- + +## Step 3 — Create the Codex config directory + +```bash +mkdir -p ~/.codex # Linux / macOS +# Windows PowerShell: New-Item -ItemType Directory -Force "$env:USERPROFILE\.codex" +``` + +--- + +## Step 4 — Write `~/.codex/config.toml` + +Read the existing file first if it exists (to avoid overwriting MCP server entries, skills, projects, or notify configurations the user may already have). + +If the file **does not exist**: create it with the full content below. + +If the file **already exists**: apply only the fields listed in the "Model/Inference", "Behaviour", "Auth/Credentials", "Features", "TUI", "Notice", and "Model providers" sections — do **not** remove existing `[mcp_servers.*]`, `[projects.*]`, `[[skills.config]]`, or `notify` entries. + +Replace `` with the value collected in Step 0. + +**Content to write (or merge):** + +```toml +# ── Model / Inference ───────────────────────────────────────────────────────── +model = "cx/gpt-5.5" +model_provider = "omniroute" +model_reasoning_effort = "xhigh" +model_reasoning_summary = "detailed" +model_verbosity = "high" +model_context_window = 400000 +model_auto_compact_token_limit = 350000 +model_max_output_tokens = 65536 +tool_output_token_limit = 32768 + +# ── Behaviour ───────────────────────────────────────────────────────────────── +approval_policy = "never" +sandbox_mode = "danger-full-access" +personality = "pragmatic" +web_search = "live" +check_for_update_on_startup = true + +# ── Auth / Credentials ──────────────────────────────────────────────────────── +cli_auth_credentials_store = "file" +mcp_oauth_credentials_store = "file" + +# ── Features ────────────────────────────────────────────────────────────────── +[features] +shell_snapshot = true +unified_exec = true +multi_agent = true +memories = true +js_repl = true +apps = false +terminal_resize_reflow = true + +# ── TUI ─────────────────────────────────────────────────────────────────────── +[tui] +theme = "dracula" +status_line = ["model-with-reasoning", "current-dir", "context-remaining", "context-used", "five-hour-limit"] + +[tui.model_availability_nux] +"gpt-5.5" = 4 + +# ── Shell environment passed into sandboxed commands ────────────────────────── +# Populate [shell_environment_policy.set] with the PATH and tool dirs discovered +# in Step 1. Only include paths that actually exist on this machine. +# Minimum required: SHELL. +[shell_environment_policy] +inherit = "all" +experimental_use_profile = true + +[shell_environment_policy.set] +SHELL = "" +# Add any of the following that exist on this machine: +# PATH = "" +# NVM_DIR = "" +# BUN_INSTALL = "" +# SDKMAN_DIR = "" +# JAVA_HOME = "" + +# ── Notice / UI flags ───────────────────────────────────────────────────────── +[notice] +hide_full_access_warning = true +hide_rate_limit_model_nudge = true +fast_default_opt_out = true + +# ── Model providers ─────────────────────────────────────────────────────────── +# env_key = NAME of the environment variable (not the value). +# The actual key is stored in the shell profile (Step 5), never here. +[model_providers.omniroute] +name = "OmniRoute" +base_url = "http://:20128/v1" +env_key = "OMNIROUTE_API_KEY" +requires_openai_auth = false +wire_api = "responses" +``` + +> **TOML rule:** `[[skills.config]]` array-of-tables must be the **last** section in the file. If the file already has `[[skills.config]]` entries, keep them at the end after inserting the new provider block. + +--- + +## Step 5 — Write profile files + +Create each file below in the Codex config directory. If a file already exists, overwrite it. + +### `profile-chat.config.toml` — no reasoning (server default = medium) + +```toml +model = "cx/gpt-5.5" +model_provider = "omniroute" +``` + +### `profile-low.config.toml` + +```toml +model = "cx/gpt-5.5" +model_reasoning_effort = "low" +model_provider = "omniroute" +``` + +### `profile-medium.config.toml` + +```toml +model = "cx/gpt-5.5" +model_reasoning_effort = "medium" +model_provider = "omniroute" +``` + +### `profile-high.config.toml` + +```toml +model = "cx/gpt-5.5" +model_reasoning_effort = "high" +model_provider = "omniroute" +``` + +### `profile-xhigh.config.toml` + +```toml +model = "cx/gpt-5.5" +model_reasoning_effort = "xhigh" +model_provider = "omniroute" +``` + +### `profile-deepseek.config.toml` — DeepSeek V4 Pro, 1M context + +```toml +model = "ds/deepseek-v4-pro" +model_provider = "omniroute" + +model_context_window = 1000000 +model_auto_compact_token_limit = 900000 +model_max_output_tokens = 65536 +tool_output_token_limit = 65536 +``` + +### `profile-mistral.config.toml` — Mistral Large Latest, 256k context + +```toml +model = "mistral/mistral-large-latest" +model_provider = "omniroute" + +model_context_window = 262144 +model_auto_compact_token_limit = 220000 +model_max_output_tokens = 32768 +tool_output_token_limit = 16384 +``` + +--- + +## Step 6 — Set environment variables in the shell profile + +Determine the correct shell profile file from Step 1, then append the following block **only if the variables are not already present**. + +Before writing, check: +```bash +grep -l "OMNIROUTE_API_KEY" ~/.bashrc ~/.zshrc ~/.bash_profile 2>/dev/null +``` + +If the variable already exists in any profile file, update the value in-place instead of appending a duplicate. + +**Block to append (replace `` with the key collected in Step 0):** + +```bash +# OmniRoute API key — used by Codex CLI (env_key = "OMNIROUTE_API_KEY" in config) +export OMNIROUTE_API_KEY="" + +# Codex CLI / Claude Code output cap (64k — covers any file or diff a coding assistant generates) +export CLAUDE_CODE_MAX_OUTPUT_TOKENS=65536 +``` + +**Shell profile file by OS/shell:** + +| OS / Shell | Profile file | +|------------|-------------| +| Linux — bash | `~/.bashrc` | +| Linux — zsh | `~/.zshrc` | +| macOS — zsh (default) | `~/.zshrc` | +| macOS — bash | `~/.bash_profile` | +| Linux/macOS — fish | `~/.config/fish/config.fish` (use `set -Ux` syntax instead of `export`) | +| Windows — PowerShell | `$PROFILE` (run `echo $PROFILE` in PowerShell to get the path) | +| Windows — Git Bash | `~/.bashrc` | + +**fish syntax:** + +```fish +set -Ux OMNIROUTE_API_KEY "" +set -Ux CLAUDE_CODE_MAX_OUTPUT_TOKENS 65536 +``` + +**PowerShell syntax:** + +```powershell +[System.Environment]::SetEnvironmentVariable("OMNIROUTE_API_KEY", "", "User") +[System.Environment]::SetEnvironmentVariable("CLAUDE_CODE_MAX_OUTPUT_TOKENS", "65536", "User") +``` + +--- + +## Step 7 — Apply and verify + +```bash +# Apply the shell profile (Linux/macOS) +source ~/.bashrc # or ~/.zshrc depending on Step 1 + +# Verify variables are set +echo $OMNIROUTE_API_KEY # must print the key +echo $CLAUDE_CODE_MAX_OUTPUT_TOKENS # must print 65536 + +# Verify Codex picks up the provider +codex config get model_provider # must print: omniroute + +# Smoke test — must return a response without auth errors +codex -p chat "reply with only the word OK" +``` + +If the smoke test returns an authentication error: +- Re-check that `source ~/.bashrc` was run (or open a new terminal) +- Run `curl http://:20128/v1/models` to confirm OmniRoute is reachable +- Confirm the key is correct with `echo $OMNIROUTE_API_KEY` + +--- + +## Reference — profiles quick table + +| Profile | Command | Best for | +|---------|---------|----------| +| `chat` | `codex -p chat "..."` | Explain, light questions | +| `low` | `codex -p low "..."` | Rename, format, trivial edits | +| `medium` | `codex -p medium "..."` | Debug, moderate refactor | +| `high` | `codex -p high "..."` | New features, complex tests | +| `xhigh` | *(default)* | Architecture, deep analysis | +| `deepseek` | `codex -p deepseek "..."` | Long codebase analysis (1M context) | +| `mistral` | `codex -p mistral "..."` | Cost-conscious tasks | + +Per-invocation overrides (bypass profiles entirely): + +```bash +codex -m cx/gpt-5.5 -c model_reasoning_effort=low "rename var x to count" +codex -m ds/deepseek-v4-pro "analyze the entire repo" +``` + +--- + +## Reference — why `wire_api = "responses"` works for all models + +Codex CLI deprecated `wire_api = "chat"` in February 2026. OmniRoute bridges the gap transparently: + +``` +Codex CLI → POST /v1/responses → OmniRoute → POST /chat/completions → DeepSeek / Mistral / any provider +``` + +All profiles use the same `wire_api = "responses"`. OmniRoute handles translation for every upstream provider. + +--- + +## Reference — token fields + +| Field | Controls | +|-------|----------| +| `model_context_window` | Total token budget | +| `model_auto_compact_token_limit` | Compaction trigger (max 90% of context window) | +| `model_max_output_tokens` | Max tokens per API response (sent on every request) | +| `tool_output_token_limit` | Max tokens stored per tool call in session history | +| `CLAUDE_CODE_MAX_OUTPUT_TOKENS` | Same concept for the Claude Code CLI | + +--- + +*Full reference: `docs/guides/CODEX-CLI-CONFIGURATION.md` in the OmniRoute repository.* diff --git a/src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient.tsx b/src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient.tsx index 6521e0e3fe..3736347a7e 100644 --- a/src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient.tsx +++ b/src/app/(dashboard)/dashboard/agent-skills/AgentSkillsPageClient.tsx @@ -9,7 +9,7 @@ import { SkillCard } from "./components/SkillCard"; import { SkillPreviewPane } from "./components/SkillPreviewPane"; import type { AgentSkill, SkillCoverage } from "@/lib/agentSkills/types"; -type FilterCategory = "all" | "api" | "cli"; +type FilterCategory = "all" | "api" | "cli" | "config"; // ── Skeleton helpers ───────────────────────────────────────────────────────── @@ -238,7 +238,7 @@ export function AgentSkillsPageClient(): JSX.Element { />
- {(["all", "api", "cli"] as FilterCategory[]).map((cat) => ( + {(["all", "api", "cli", "config"] as FilterCategory[]).map((cat) => ( ))}
diff --git a/src/app/(dashboard)/dashboard/agent-skills/components/SkillCard.tsx b/src/app/(dashboard)/dashboard/agent-skills/components/SkillCard.tsx index aab1ac205f..61cd6b1331 100644 --- a/src/app/(dashboard)/dashboard/agent-skills/components/SkillCard.tsx +++ b/src/app/(dashboard)/dashboard/agent-skills/components/SkillCard.tsx @@ -26,7 +26,9 @@ export function SkillCard({ skill, selected, onClick }: SkillCardProps): JSX.Ele const previewItems: string[] = skill.category === "api" ? (skill.endpoints ?? []).slice(0, 2) - : (skill.cliCommands ?? []).slice(0, 2); + : skill.category === "cli" + ? (skill.cliCommands ?? []).slice(0, 2) + : []; return (
- {skill.category === "api" ? t("categoryApi") : t("categoryCli")} + {skill.category === "api" + ? t("categoryApi") + : skill.category === "cli" + ? t("categoryCli") + : t("categoryConfig")} {skill.isEntry && ( diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 3c04b237ba..bbdc7fd292 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -8596,6 +8596,7 @@ }, "categoryApi": "API", "categoryCli": "CLI", + "categoryConfig": "Config", "filterAll": "All", "coverageLabel": "Coverage", "mcpUrl": "MCP URL", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index b7783e797a..30adc6ad2c 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -8522,6 +8522,7 @@ }, "categoryApi": "API", "categoryCli": "CLI", + "categoryConfig": "Config", "filterAll": "Todas", "coverageLabel": "Cobertura", "mcpUrl": "URL MCP", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index a800474c1b..37f752d4e4 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -8477,6 +8477,7 @@ }, "categoryApi": "API", "categoryCli": "CLI", + "categoryConfig": "Config", "filterAll": "Все", "coverageLabel": "Покрытие", "mcpUrl": "MCP URL", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 4868ac7cb8..844e0492e1 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -8477,6 +8477,7 @@ }, "categoryApi": "API", "categoryCli": "命令行界面", + "categoryConfig": "Config", "filterAll": "所有", "coverageLabel": "覆盖率", "mcpUrl": "MCP URL", diff --git a/src/lib/agentSkills/catalog.ts b/src/lib/agentSkills/catalog.ts index 05e8c7119d..bde35158b7 100644 --- a/src/lib/agentSkills/catalog.ts +++ b/src/lib/agentSkills/catalog.ts @@ -38,6 +38,11 @@ export const API_SKILL_IDS: readonly string[] = [ "omni-inference", ] as const; +/** Config skill IDs. */ +export const CONFIG_SKILL_IDS: readonly string[] = [ + "config-codex-cli", +] as const; + /** 20 canonical CLI skill IDs, in spec order. */ export const CLI_SKILL_IDS: readonly string[] = [ "cli-serve", @@ -138,11 +143,14 @@ export function computeCoverage(): SkillCoverage { const apiHave = catalog.filter((s) => s.category === "api" && presentIds.has(s.id)).length; const cliHave = catalog.filter((s) => s.category === "cli" && presentIds.has(s.id)).length; + const configTotal = CONFIG_SKILL_IDS.length; + const configHave = catalog.filter((s) => s.category === "config" && presentIds.has(s.id)).length; return { api: { have: apiHave, total: 22 }, cli: { have: cliHave, total: 20 }, - totalSkills: apiHave + cliHave, + config: { have: configHave, total: configTotal }, + totalSkills: apiHave + cliHave + configHave, generatedAt: new Date().toISOString(), }; } @@ -190,7 +198,6 @@ export async function fetchSkillMarkdown(id: string): Promise { } const response = await fetch(skill.rawUrl, { - // @ts-expect-error — Next.js extended fetch options next: { revalidate: 3600 }, }); diff --git a/src/lib/agentSkills/types.ts b/src/lib/agentSkills/types.ts index d187ff1566..75f43fcdc3 100644 --- a/src/lib/agentSkills/types.ts +++ b/src/lib/agentSkills/types.ts @@ -1,4 +1,4 @@ -export type SkillCategory = "api" | "cli"; +export type SkillCategory = "api" | "cli" | "config"; export type SkillArea = // API areas (22) @@ -24,6 +24,8 @@ export type SkillArea = | "agents-a2a" | "version-manager" | "inference" + // Config skills + | "config-codex-cli" // CLI families (20) | "cli-serve" | "cli-health" @@ -64,6 +66,7 @@ export interface AgentSkill { export interface SkillCoverage { api: { have: number; total: 22 }; cli: { have: number; total: 20 }; + config: { have: number; total: number }; totalSkills: number; // sum generatedAt: string; // ISO datetime } diff --git a/src/shared/constants/agentSkills.ts b/src/shared/constants/agentSkills.ts index 0d92d10a80..5a8407320e 100644 --- a/src/shared/constants/agentSkills.ts +++ b/src/shared/constants/agentSkills.ts @@ -429,5 +429,18 @@ export const CURATED_SKILLS: CuratedSkillEntry[] = [ area: "cli-setup", icon: "build", }, + + // ── Config Skills ──────────────────────────────────────────────────────────── + + { + id: "config-codex-cli", + name: "Config: Codex CLI", + description: + "Step-by-step agent workflow to configure the OpenAI Codex CLI on any machine (Linux, macOS, Windows) to use OmniRoute as an OpenAI-compatible backend. Detects OS and shell, writes config.toml and 7 named profiles, sets environment variables, and verifies the setup.", + category: "config", + area: "config-codex-cli", + icon: "terminal", + isNew: true, + }, ];