feat(cli): price-override --list --format json

This commit is contained in:
iamtoruk 2026-07-16 03:45:36 -07:00
parent 09ac453fcc
commit 699ca53979
2 changed files with 53 additions and 0 deletions

View file

@ -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<string, PriceOverrideConfig>): 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 <usd-per-1M>', 'Cache-creation token price in USD per 1,000,000 tokens', parseNumber)
.option('--remove <model>', 'Remove a price override')
.option('--list', 'List configured price overrides')
.option('--format <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<string, PriceOverrideConfig>(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.')

View file

@ -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([])
})
})