From 699ca539798c3c73b006b9e9738ff9b3123aaa54 Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Thu, 16 Jul 2026 03:45:36 -0700 Subject: [PATCH] feat(cli): price-override --list --format json --- src/main.ts | 28 ++++++++++++++++++++++++++++ tests/cli-settings-json.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/main.ts b/src/main.ts index 67dd596..d4ca2ac 100644 --- a/src/main.ts +++ b/src/main.ts @@ -75,6 +75,27 @@ type PriceOverrideOptions = { cacheCreation?: number remove?: string list?: boolean + format?: string +} + +type PriceOverrideRow = { + model: string + inputPerM: number + outputPerM: number + cacheReadPerM?: number + cacheCreationPerM?: number +} + +function toPriceOverrideRows(overrides: Map): PriceOverrideRow[] { + return [...overrides.entries()] + .map(([model, rates]) => ({ + model, + inputPerM: rates.input, + outputPerM: rates.output, + ...(typeof rates.cacheRead === 'number' ? { cacheReadPerM: rates.cacheRead } : {}), + ...(typeof rates.cacheCreation === 'number' ? { cacheCreationPerM: rates.cacheCreation } : {}), + })) + .sort((a, b) => a.model < b.model ? -1 : a.model > b.model ? 1 : 0) } function invalidUsdPerMillionRate(option: string, value: number | undefined): string | null { @@ -1099,11 +1120,18 @@ program .option('--cache-creation ', 'Cache-creation token price in USD per 1,000,000 tokens', parseNumber) .option('--remove ', 'Remove a price override') .option('--list', 'List configured price overrides') + .option('--format ', 'Output format: text, json', 'text') .action(async (model?: string, opts?: PriceOverrideOptions) => { + const format = opts?.format ?? 'text' + assertFormat(format, ['text', 'json'], 'price-override') const config = await readConfig() const overrides = new Map(Object.entries(config.priceOverrides ?? {})) if (opts?.list || (!model && !opts?.remove)) { + if (format === 'json') { + console.log(JSON.stringify({ overrides: toPriceOverrideRows(overrides), configPath: getConfigFilePath() }, null, 2)) + return + } const entries = [...overrides.entries()] if (entries.length === 0) { console.log('\n No price overrides configured.') diff --git a/tests/cli-settings-json.test.ts b/tests/cli-settings-json.test.ts index 0d37cb6..5d9f85a 100644 --- a/tests/cli-settings-json.test.ts +++ b/tests/cli-settings-json.test.ts @@ -55,4 +55,29 @@ describe('settings CLI JSON list output', () => { expect(result.status).toBe(0) expect(JSON.parse(result.stdout)).toEqual(['/work/copilot-repo']) }) + + it('lists price overrides as rows sorted by model with a config path', async () => { + const home = await makeHome() + + expect(runCli(['price-override', 'z-model', '--input', '0.5', '--output', '2', '--cache-read', '0.05'], home).status).toBe(0) + expect(runCli(['price-override', 'a-model', '--input', '0.27', '--output', '1.1', '--cache-creation', '0.42'], home).status).toBe(0) + + const result = runCli(['price-override', '--list', '--format', 'json'], home) + expect(result.status).toBe(0) + const parsed = JSON.parse(result.stdout) + expect(parsed.overrides).toEqual([ + { model: 'a-model', inputPerM: 0.27, outputPerM: 1.1, cacheCreationPerM: 0.42 }, + { model: 'z-model', inputPerM: 0.5, outputPerM: 2, cacheReadPerM: 0.05 }, + ]) + expect(typeof parsed.configPath).toBe('string') + expect(parsed.configPath).toContain('config.json') + }) + + it('emits an empty override list as JSON when none are configured', async () => { + const home = await makeHome() + + const result = runCli(['price-override', '--list', '--format', 'json'], home) + expect(result.status).toBe(0) + expect(JSON.parse(result.stdout).overrides).toEqual([]) + }) })