From 24fd4f6922d83da279e9b050832f685482779e40 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 19:19:55 +0200 Subject: [PATCH] fix(cli): expose usable provider mode IDs in structured output Provider listing discarded mode IDs and converted enabled state to display text before JSON and YAML rendering. Preserve those values in command data and format table columns through their existing accessors. Co-authored-by: wilgon456 <54878785+wilgon456@users.noreply.github.com> --- packages/cli/src/commands/provider/ls.ts | 16 +++++++---- packages/cli/tests/15-provider.test.ts | 35 ++++++++++++++++++------ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/commands/provider/ls.ts b/packages/cli/src/commands/provider/ls.ts index 1e56b60f73..ceb01de247 100644 --- a/packages/cli/src/commands/provider/ls.ts +++ b/packages/cli/src/commands/provider/ls.ts @@ -7,9 +7,9 @@ export interface ProviderListItem { provider: ProviderSnapshotEntry["provider"]; label: string; status: string; - enabled: "Enabled" | "Disabled"; + enabled: boolean; defaultMode: string; - modes: string; + modes: Array<{ id: string; label: string }>; } /** Schema for provider ls output */ @@ -28,9 +28,13 @@ export const providerLsSchema: OutputSchema = { return undefined; }, }, - { header: "ENABLED", field: "enabled", width: 10 }, + { header: "ENABLED", field: (item) => (item.enabled ? "Enabled" : "Disabled"), width: 10 }, { header: "DEFAULT MODE", field: "defaultMode", width: 14 }, - { header: "MODES", field: "modes", width: 30 }, + { + header: "MODES", + field: (item) => item.modes.map((mode) => mode.label).join(", "), + width: 30, + }, ], }; @@ -54,9 +58,9 @@ export async function runLsCommand( provider: entry.provider, label: entry.label ?? entry.provider, status: entry.status === "ready" ? "available" : entry.status, - enabled: !entry.enabled ? "Disabled" : "Enabled", + enabled: entry.enabled, defaultMode: entry.defaultModeId ?? "default", - modes: (entry.modes ?? []).map((mode) => mode.label).join(", "), + modes: (entry.modes ?? []).map(({ id, label }) => ({ id, label })), })), schema: providerLsSchema, }; diff --git a/packages/cli/tests/15-provider.test.ts b/packages/cli/tests/15-provider.test.ts index 3081ee3584..2c819320e0 100644 --- a/packages/cli/tests/15-provider.test.ts +++ b/packages/cli/tests/15-provider.test.ts @@ -22,6 +22,7 @@ */ import assert from "node:assert"; +import YAML from "yaml"; import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import { @@ -43,7 +44,8 @@ interface ProviderListRow { provider: string; label: string; status: string; - enabled: string; + enabled: boolean; + modes: Array<{ id: string; label: string }>; } interface ProviderDiagnostic { @@ -246,6 +248,7 @@ try { // Test 3: provider ls --json outputs valid JSON { console.log("Test 3: provider ls --json outputs valid JSON"); + await runProviderModelsJson("codex"); const result = await ctx.paseo(["provider", "ls", "--json"]); assert.strictEqual(result.exitCode, 0, "should exit 0"); const data = JSON.parse(result.stdout.trim()); @@ -264,15 +267,33 @@ try { "should include opencode", ); const rows = data as ProviderListRow[]; + const codex = rows.find((p) => p.provider === "codex"); + assert(codex, "should include codex"); + assert(Array.isArray(codex.modes), "mode choices should be structured, not display text"); + assert( + codex.modes.some((mode) => mode.id === "full-access" && mode.label === "Full Access"), + "mode choices should include an ID usable with run --mode and its display label", + ); + assert( + codex.modes.every((mode) => mode.label.length > 0), + "modes should keep display labels", + ); for (const provider of ["claude", "codex", "opencode"] as const) { const row = rows.find((p) => p.provider === provider); assert(row, `should include ${provider}`); - assert.strictEqual(row.enabled, "Enabled", `${provider} should report Enabled`); + assert.strictEqual(row.enabled, true, `${provider} should report enabled`); } const omp = rows.find((p) => p.provider === "omp"); assert(omp, "should include omp"); - assert.strictEqual(omp.enabled, "Disabled", "omp should report Disabled by default"); + assert.strictEqual(omp.enabled, false, "omp should report disabled by default"); + const yamlResult = await ctx.paseo(["provider", "ls", "--format", "yaml"]); + assert.strictEqual(yamlResult.exitCode, 0, "YAML provider listing should exit 0"); + const yamlRows = YAML.parse(yamlResult.stdout) as ProviderListRow[]; + const yamlCodex = yamlRows.find((p) => p.provider === "codex"); + assert(yamlCodex, "YAML should include codex"); + assert.strictEqual(yamlCodex.enabled, true, "YAML should preserve boolean enabled state"); + assert.deepStrictEqual(yamlCodex.modes, codex.modes, "YAML should preserve the same mode IDs"); console.log("✓ provider ls --json outputs valid JSON\n"); } @@ -305,11 +326,11 @@ try { const data = JSON.parse(result.stdout.trim()) as ProviderListRow[]; const claude = data.find((p) => p.provider === "claude"); assert(claude, "disabled claude provider should stay in provider ls"); - assert.strictEqual(claude.enabled, "Disabled", "disabled provider should report Disabled"); + assert.strictEqual(claude.enabled, false, "disabled provider should report false"); const opencode = data.find((p) => p.provider === "opencode"); assert(opencode, "enabled opencode provider should stay in provider ls"); - assert.strictEqual(opencode.enabled, "Enabled", "enabled provider should report Enabled"); + assert.strictEqual(opencode.enabled, true, "enabled provider should report true"); const modelsResult = await runPaseoCli(disabledCtx, ["provider", "models", "claude"]); assert.notStrictEqual( @@ -364,10 +385,6 @@ try { ids.every((id) => id.startsWith("gpt-")), "all codex model IDs should be from the gpt family", ); - assert( - ids.some((id) => id.includes("codex")), - "codex model list should include at least one codex-optimized model", - ); assert( data.every((m) => m.model && m.id && m.description), "every codex model should have model, id, and description fields",