feat(cli): setup-continue — generate ~/.continue/config.yaml for OmniRoute - #4289
Conversation
…Route CLI #6 of the series. `omniroute setup-continue` writes Continue's file-based, mergeable ~/.continue/config.yaml (shared by the VS Code/JetBrains extensions AND the `cn` CLI) from the live model catalog. - Each curated model → a Continue model entry: provider: openai, model: <id>, apiBase WITH /v1 (Continue appends /chat/completions), apiKey: ${{ secrets.OMNIROUTE_API_KEY }} (secret referenced, never written), roles [chat, edit, apply] (+ autocomplete for the fast tier). - Merges into existing config.yaml (js-yaml load/dump): drops prior models on the same apiBase, preserves the user's other models + top-level keys. - Remote-aware (--remote/--api-key → active context → localhost); --only filter. - Prints how to provide the key (shell env for cn; ~/.continue/.env for IDE). Researched against current Continue docs: provider: openai + custom apiBase (with /v1), the ${{ secrets.X }} syntax, roles, and that the `cn` CLI shares the same config. Continue's wire (/v1/chat/completions) already validated → "OK". Tests: buildContinueModels (provider/apiBase/secret/roles, fast→autocomplete, skip uncategorised), mergeContinueConfig (replace-ours/keep-others/defaults), resolveContinueTarget (/v1). 6 unit tests; check:cli-i18n green.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Code Review
This pull request introduces the setup-continue CLI command to configure Continue (continue.dev) for OmniRoute, along with corresponding documentation and unit tests. The review feedback highlights opportunities to improve robustness, specifically by ensuring null/undefined elements are filtered out to prevent invalid YAML generation, using optional chaining to avoid crashes if the model response body is null, and refactoring the config backup logic to avoid redundant file reads and handle potential write errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| export function mergeContinueConfig(existing, newModels, apiBase) { | ||
| const cfg = existing && typeof existing === "object" ? { ...existing } : {}; | ||
| const prior = Array.isArray(cfg.models) ? cfg.models : []; | ||
| const kept = prior.filter((m) => !m || m.apiBase !== apiBase); |
There was a problem hiding this comment.
If m is null or undefined, !m evaluates to true, which means null/undefined elements are kept in the kept array. This will result in null entries in the final models array, producing invalid YAML (e.g., - null) and causing Continue to fail to parse the configuration. Filtering out null/undefined elements entirely ensures a clean and valid configuration.
| const kept = prior.filter((m) => !m || m.apiBase !== apiBase); | |
| const kept = prior.filter((m) => m && m.apiBase !== apiBase); |
| }); | ||
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | ||
| const body = await res.json(); | ||
| const list = Array.isArray(body) ? body : body.data ?? body.models ?? []; |
There was a problem hiding this comment.
If body is null (which can be returned by res.json()), Array.isArray(body) evaluates to false, and accessing body.data will throw a TypeError: Cannot read properties of null (reading 'data'). Using optional chaining body?.data and body?.models prevents this potential crash and adheres to defensive programming practices.
| const list = Array.isArray(body) ? body : body.data ?? body.models ?? []; | |
| const list = Array.isArray(body) ? body : (body?.data ?? body?.models ?? []); |
| let existing = {}; | ||
| if (existsSync(configPath)) { | ||
| try { | ||
| existing = yaml.load(readFileSync(configPath, "utf8")) || {}; | ||
| } catch { | ||
| printInfo("Existing config.yaml unparseable — starting fresh (a .bak is kept)."); | ||
| if (!dryRun) writeFileSync(`${configPath}.bak`, readFileSync(configPath)); | ||
| existing = {}; | ||
| } | ||
| } |
There was a problem hiding this comment.
Reading the file twice (readFileSync inside both try and catch blocks) is redundant. Furthermore, if writeFileSync or the second readFileSync fails (e.g., due to permission issues or a race condition), the error is unhandled and will crash the entire CLI command. Since writing a backup is a best-effort fallback, we should wrap it in a try-catch block and reuse the already-read file content.
| let existing = {}; | |
| if (existsSync(configPath)) { | |
| try { | |
| existing = yaml.load(readFileSync(configPath, "utf8")) || {}; | |
| } catch { | |
| printInfo("Existing config.yaml unparseable — starting fresh (a .bak is kept)."); | |
| if (!dryRun) writeFileSync(`${configPath}.bak`, readFileSync(configPath)); | |
| existing = {}; | |
| } | |
| } | |
| let existing = {}; | |
| if (existsSync(configPath)) { | |
| let rawContent; | |
| try { | |
| rawContent = readFileSync(configPath, "utf8"); | |
| existing = yaml.load(rawContent) || {}; | |
| } catch { | |
| printInfo("Existing config.yaml unparseable — starting fresh (a .bak is kept)."); | |
| if (!dryRun && rawContent !== undefined) { | |
| try { | |
| writeFileSync(configPath + ".bak", rawContent, "utf8"); | |
| } catch (err) { | |
| printError("Could not write backup file: " + err.message); | |
| } | |
| } | |
| existing = {}; | |
| } | |
| } |
…Route (diegosouzapw#4289) CLI diegosouzapw#6 of the series. `omniroute setup-continue` writes Continue's file-based, mergeable ~/.continue/config.yaml (shared by the VS Code/JetBrains extensions AND the `cn` CLI) from the live model catalog. - Each curated model → a Continue model entry: provider: openai, model: <id>, apiBase WITH /v1 (Continue appends /chat/completions), apiKey: ${{ secrets.OMNIROUTE_API_KEY }} (secret referenced, never written), roles [chat, edit, apply] (+ autocomplete for the fast tier). - Merges into existing config.yaml (js-yaml load/dump): drops prior models on the same apiBase, preserves the user's other models + top-level keys. - Remote-aware (--remote/--api-key → active context → localhost); --only filter. - Prints how to provide the key (shell env for cn; ~/.continue/.env for IDE). Researched against current Continue docs: provider: openai + custom apiBase (with /v1), the ${{ secrets.X }} syntax, roles, and that the `cn` CLI shares the same config. Continue's wire (/v1/chat/completions) already validated → "OK". Tests: buildContinueModels (provider/apiBase/secret/roles, fast→autocomplete, skip uncategorised), mergeContinueConfig (replace-ours/keep-others/defaults), resolveContinueTarget (/v1). 6 unit tests; check:cli-i18n green.
What
CLI #6.
omniroute setup-continuegenerates Continue's file-based, mergeable~/.continue/config.yaml(shared by VS Code/JetBrains extensions and thecnCLI) from the live OmniRoute catalog.provider: openai,model: <id>,apiBaseWITH/v1(Continue appends/chat/completions),apiKey: ${{ secrets.OMNIROUTE_API_KEY }}(referenced, never written),roles: [chat, edit, apply](+autocompletefor the fast tier).--onlyfilter; prints how to supply the key (shell env forcn;~/.continue/.envfor IDE).Validation
setup-continue --remote --only glm,kimi --dry-run→ correct YAML./v1/chat/completions) already validated → "OK".check:cli-i18n✓.Series: Codex #4270 · Claude #4274 · OpenCode #4277 · hardening #4278 · Cline #4280 · Kilo #4284 · Continue (this). Next: Cursor, Roo, Crush, Goose, Qwen Code, Gemini CLI, Aider.
🤖 Generated with Claude Code