mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-04-28 15:09:43 +00:00
Adds `codeburn plan set <id>` to configure a subscription plan (Claude Pro, Claude Max, Cursor Pro, or custom). When set, the Overview panel renders an API-equivalent progress bar against subscription price with a projected month-end cost. Closes the loudest demand signal on the repo: issue #11 ("Subscription vs API Use") from two independent voices, plus the routing-decision use case raised in #12. - src/config.ts: extends CodeburnConfig with Plan, adds readPlan/savePlan/clearPlan - src/plans.ts: presets (claude-pro $20, claude-max $200, cursor-pro $20) - src/plan-usage.ts: getPlanUsage, resetDay-aware period math (1-28), median-of-7-day-trailing projection - src/cli.ts: `codeburn plan [show|set|reset]` subcommand, plan wired into JSON outputs for report/today/month/status (only when active) - src/dashboard.tsx: Plan row in Overview, color-coded (green under 80%, orange near, red over), with days-until-reset - README.md: Plans section with honest framing (API-equivalent vs subscription price, not token allowance) - tests/plan-usage.test.ts, tests/plans.test.ts, tests/cli-plan.test.ts: period math, presets, CLI round-trip Resets respect resetDay across month boundaries. Uses median daily spend (not mean) so one huge day doesn't distort the month-end projection. Fixes #11
55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
import { mkdtemp, readFile, rm } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { spawnSync } from 'node:child_process'
|
|
|
|
import { describe, it, expect } from 'vitest'
|
|
|
|
function runCli(args: string[], home: string) {
|
|
return spawnSync(process.execPath, ['--import', 'tsx', 'src/cli.ts', ...args], {
|
|
cwd: process.cwd(),
|
|
env: {
|
|
...process.env,
|
|
HOME: home,
|
|
},
|
|
encoding: 'utf-8',
|
|
})
|
|
}
|
|
|
|
describe('codeburn plan command', () => {
|
|
it('persists plan set and clears on reset', async () => {
|
|
const home = await mkdtemp(join(tmpdir(), 'codeburn-cli-plan-'))
|
|
|
|
try {
|
|
const setResult = runCli(['plan', 'set', 'claude-max'], home)
|
|
expect(setResult.status).toBe(0)
|
|
|
|
const configPath = join(home, '.config', 'codeburn', 'config.json')
|
|
const configRaw = await readFile(configPath, 'utf-8')
|
|
const config = JSON.parse(configRaw) as { plan?: { id?: string; monthlyUsd?: number } }
|
|
expect(config.plan?.id).toBe('claude-max')
|
|
expect(config.plan?.monthlyUsd).toBe(200)
|
|
|
|
const resetResult = runCli(['plan', 'reset'], home)
|
|
expect(resetResult.status).toBe(0)
|
|
|
|
const afterResetRaw = await readFile(configPath, 'utf-8')
|
|
const afterReset = JSON.parse(afterResetRaw) as { plan?: unknown }
|
|
expect(afterReset.plan).toBeUndefined()
|
|
} finally {
|
|
await rm(home, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('shows invalid reset-day value in error output', async () => {
|
|
const home = await mkdtemp(join(tmpdir(), 'codeburn-cli-plan-'))
|
|
|
|
try {
|
|
const result = runCli(['plan', 'set', 'claude-max', '--reset-day', '99'], home)
|
|
expect(result.status).toBe(1)
|
|
expect(result.stderr).toContain('--reset-day must be an integer from 1 to 28; got 99.')
|
|
} finally {
|
|
await rm(home, { recursive: true, force: true })
|
|
}
|
|
})
|
|
})
|