diff --git a/src/shared/constants/cliTools.ts b/src/shared/constants/cliTools.ts index 172a9597f5..644c8999d2 100644 --- a/src/shared/constants/cliTools.ts +++ b/src/shared/constants/cliTools.ts @@ -1,6 +1,7 @@ // CLI Tools configuration import { getClaudeCodeDefaultModels } from "@omniroute/open-sse/config/providerRegistry"; import type { CliCatalogEntry } from "@/shared/schemas/cliCatalog"; +import { GROK_BUILD_CLI_TOOL } from "@/shared/constants/cliToolsGrokBuild"; const _cc = getClaudeCodeDefaultModels(); @@ -540,7 +541,6 @@ export const CLI_TOOLS: Record = { acpSpawnable: false, baseUrlSupport: "full", }, - // ── Code entries — aider ────────────────────────────────────────────────── aider: { id: "aider", @@ -567,7 +567,6 @@ export const CLI_TOOLS: Record = { aider --openai-api-base "{{baseUrl}}" --model "{{model}}"`, }, }, - // ── Code entries — forge ────────────────────────────────────────────────── forge: { id: "forge", @@ -584,22 +583,7 @@ aider --openai-api-base "{{baseUrl}}" --model "{{model}}"`, defaultCommand: "forge", }, - // ── Code entries — grok-build ───────────────────────────────────────────── - "grok-build": { - id: "grok-build", - name: "Grok Build", - icon: "terminal", - color: "#1DA1F2", - description: "xAI Grok Build TUI coding agent — custom provider via ~/.grok/config.toml", - docsUrl: "https://x.ai/cli", - configType: "custom", - category: "code", - vendor: "xAI", - acpSpawnable: false, - baseUrlSupport: "full", - defaultCommand: "grok", - }, - + "grok-build": GROK_BUILD_CLI_TOOL, // ── Code entries — cursor-cli ───────────────────────────────────────────── "cursor-cli": { id: "cursor-cli", diff --git a/src/shared/constants/cliToolsGrokBuild.ts b/src/shared/constants/cliToolsGrokBuild.ts new file mode 100644 index 0000000000..e766295892 --- /dev/null +++ b/src/shared/constants/cliToolsGrokBuild.ts @@ -0,0 +1,19 @@ +// Grok Build CLI tool registry entry — extracted from cliTools.ts to keep the +// frozen registry file under its file-size ratchet cap (config/quality/file-size-baseline.json). +import type { CliCatalogEntry } from "@/shared/schemas/cliCatalog"; + +/** xAI Grok Build TUI coding agent — custom provider via ~/.grok/config.toml */ +export const GROK_BUILD_CLI_TOOL: CliCatalogEntry = { + id: "grok-build", + name: "Grok Build", + icon: "terminal", + color: "#1DA1F2", + description: "xAI Grok Build TUI coding agent — custom provider via ~/.grok/config.toml", + docsUrl: "https://x.ai/cli", + configType: "custom", + category: "code", + vendor: "xAI", + acpSpawnable: false, + baseUrlSupport: "full", + defaultCommand: "grok", +}; diff --git a/src/shared/schemas/cliCatalog.ts b/src/shared/schemas/cliCatalog.ts index d5b43a55bc..27ab19e3c9 100644 --- a/src/shared/schemas/cliCatalog.ts +++ b/src/shared/schemas/cliCatalog.ts @@ -62,7 +62,8 @@ export const CliCatalogSchema = z.record(CliCatalogEntrySchema); /** Cardinalidade obrigatória (Plano §3.1/§3.2 + D15). +1 (crush, decolua/9router#1233). */ // +1 (2026-07-02): "codewhale" added as a dual entry alongside "deepseek-tui" // (CodeWhale is the actively-maintained successor to DeepSeek TUI). -export const EXPECTED_CODE_COUNT = 20; +// +1 (grok-build, decolua/9router#2571): xAI Grok Build TUI coding agent. +export const EXPECTED_CODE_COUNT = 21; // +2 (#6318): "omp" (Oh My Pi) and "letta" (Letta CLI) added as agent entries. // Note: #6318 originally also shipped duplicate "pi"/"jcode"/"codewhale" entries — // those tools were already delivered by a separate PR, so only omp+letta landed here. diff --git a/src/shared/services/cliRuntime.ts b/src/shared/services/cliRuntime.ts index 84fd0d34c8..f1f50c0b3f 100644 --- a/src/shared/services/cliRuntime.ts +++ b/src/shared/services/cliRuntime.ts @@ -6,6 +6,7 @@ import { spawn, execFileSync } from "child_process"; import { getHermesHome } from "@/lib/cli-helper/config-generator/hermesHome"; import { getCachedLoginShellPath, mergeShellPath } from "./loginShellPath"; import { withSettingsFallback } from "./cliInstallFallback"; +import { GROK_BUILD_RUNTIME_ENTRY, AMP_RUNTIME_ENTRY } from "./cliRuntimeGrokBuild"; const VALID_RUNTIME_MODES = new Set(["auto", "host", "container"]); const FALSE_VALUES = new Set(["0", "false", "no", "off"]); @@ -155,13 +156,7 @@ const CLI_TOOLS: Record = { config: "config.yaml", }, }, - amp: { - defaultCommand: "amp", - envBinKey: "CLI_AMP_BIN", - requiresBinary: true, - healthcheckTimeoutMs: 12000, - paths: {}, - }, + amp: AMP_RUNTIME_ENTRY, qoder: { defaultCommand: "qodercli", envBinKey: "CLI_QODER_BIN", @@ -201,15 +196,7 @@ const CLI_TOOLS: Record = { config: ".jcode/config.json", }, }, - "grok-build": { - defaultCommand: "grok", - envBinKey: "CLI_GROK_BUILD_BIN", - requiresBinary: true, - healthcheckTimeoutMs: 8000, - paths: { - config: ".grok/config.toml", - }, - }, + "grok-build": GROK_BUILD_RUNTIME_ENTRY, "deepseek-tui": { defaultCommand: "deepseek-tui", envBinKey: "CLI_DEEPSEEK_TUI_BIN", diff --git a/src/shared/services/cliRuntimeGrokBuild.ts b/src/shared/services/cliRuntimeGrokBuild.ts new file mode 100644 index 0000000000..e219d6ffce --- /dev/null +++ b/src/shared/services/cliRuntimeGrokBuild.ts @@ -0,0 +1,26 @@ +// Runtime-detection metadata entries extracted from cliRuntime.ts to keep that +// frozen file under its file-size ratchet cap (config/quality/file-size-baseline.json). +// Deliberately untyped (matches cliRuntime.ts's own `Record` CLI_TOOLS +// shape) and has NO import of the CliCatalogEntry schema — keep it that way, so this +// module never drags src/shared/schemas/cliCatalog.ts into the typecheck-core +// transitive graph (that file is not on typecheck:core's curated allowlist). + +/** Grok Build runtime-detection metadata (binary lookup + healthcheck). */ +export const GROK_BUILD_RUNTIME_ENTRY = { + defaultCommand: "grok", + envBinKey: "CLI_GROK_BUILD_BIN", + requiresBinary: true, + healthcheckTimeoutMs: 8000, + paths: { + config: ".grok/config.toml", + }, +}; + +/** Amp runtime-detection metadata (extracted alongside grok-build for file-size headroom). */ +export const AMP_RUNTIME_ENTRY = { + defaultCommand: "amp", + envBinKey: "CLI_AMP_BIN", + requiresBinary: true, + healthcheckTimeoutMs: 12000, + paths: {}, +}; diff --git a/tests/unit/cli-catalog-counts.test.ts b/tests/unit/cli-catalog-counts.test.ts index 681993eccb..c6f203d928 100644 --- a/tests/unit/cli-catalog-counts.test.ts +++ b/tests/unit/cli-catalog-counts.test.ts @@ -30,7 +30,7 @@ test(`CLI_TOOLS has exactly ${EXPECTED_AGENT_COUNT} agent entries`, () => { ); }); -test("CLI_TOOLS total code entries (including none) equals 24 (20 visible + 4 none)", () => { +test("CLI_TOOLS total code entries (including none) equals 25 (21 visible + 4 none)", () => { // code-none entries: antigravity, kiro, cursor (app), hermes (simple guide) const codeNone = codeAll.filter((t) => t.baseUrlSupport === "none"); assert.equal( @@ -38,11 +38,11 @@ test("CLI_TOOLS total code entries (including none) equals 24 (20 visible + 4 no 4, `Expected 4 code entries with baseUrlSupport='none', got ${codeNone.length}: ${codeNone.map((t) => t.id).join(", ")}` ); - assert.equal(codeAll.length, 24, `Expected 24 total code entries, got ${codeAll.length}`); + assert.equal(codeAll.length, 25, `Expected 25 total code entries, got ${codeAll.length}`); }); -test("CLI_TOOLS total (code + agent) = 32", () => { - assert.equal(all.length, 32, `Expected 32 total entries, got ${all.length}`); +test("CLI_TOOLS total (code + agent) = 33", () => { + assert.equal(all.length, 33, `Expected 33 total entries, got ${all.length}`); }); test("All code-none entries have configType mitm OR are legacy excluded entries", () => { @@ -66,7 +66,7 @@ test("All agent entries have baseUrlSupport 'full' or 'partial' (no agent is 'no } }); -test("The 20 visible code entries match D15 list exactly (+ crush + codewhale)", () => { +test("The 21 visible code entries match D15 list exactly (+ crush + codewhale + grok-build)", () => { const d15List = new Set([ "claude", "codex", @@ -88,6 +88,7 @@ test("The 20 visible code entries match D15 list exactly (+ crush + codewhale)", "pi", "custom", "crush", + "grok-build", ]); const visibleIds = new Set(codeVisible.map((t) => t.id)); for (const id of d15List) { diff --git a/tests/unit/cli-tools-schema.test.ts b/tests/unit/cli-tools-schema.test.ts index 4648f448ee..85841732cb 100644 --- a/tests/unit/cli-tools-schema.test.ts +++ b/tests/unit/cli-tools-schema.test.ts @@ -1,7 +1,7 @@ import test from "node:test"; import assert from "node:assert/strict"; -test("CLI_TOOLS registry contains all expected tools (plan 14 — 32 total + crush + codewhale + omp + letta)", async () => { +test("CLI_TOOLS registry contains all expected tools (plan 14 — 33 total + crush + codewhale + omp + letta + grok-build)", async () => { const { CLI_TOOLS } = await import("../../src/shared/constants/cliTools.ts"); // windsurf and amp removed per plan 14 D17 (MITM backlog plan 11) // New entries added: roo, jcode, deepseek-tui, smelt, pi, aider, forge, @@ -10,6 +10,7 @@ test("CLI_TOOLS registry contains all expected tools (plan 14 — 32 total + cru // codewhale added 2026-07-02 as a dual entry alongside deepseek-tui // (CodeWhale is the actively-maintained successor to DeepSeek TUI). // omp + letta added by #6318 (agent-category CLI integrations). + // grok-build added — xAI Grok Build TUI coding agent (ported from upstream decolua/9router#2571). const expected = [ "claude", "codex", @@ -43,6 +44,7 @@ test("CLI_TOOLS registry contains all expected tools (plan 14 — 32 total + cru "letta", "agent-deck", "crush", + "grok-build", ]; for (const id of expected) { assert.ok(id in CLI_TOOLS, `Missing tool: ${id}`);