fix(stats): Opus output price $75->$25 for 4.5+ era; Windows statusline UTF-8

Folds in #466 (correct Opus 4.5-4.8 output price to $25/M, keep legacy 4.0/4.1 carve-outs at $75/M; fixes #465) and #459 (UTF-8 console + Get-Content so the pickaxe renders on Windows; +spacing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Julius Brussee
2026-06-01 21:04:58 +02:00
co-authored by Claude Opus 4.8
parent f06348cbd3
commit f68111acc3
3 changed files with 22 additions and 12 deletions
+14 -7
View File
@@ -22,13 +22,20 @@ const COMPRESSION = { 'full': 0.65 };
// Match by model id prefix so this stays correct across point releases
// (e.g. claude-sonnet-4-20250514, claude-sonnet-4-7). Update from
// https://www.anthropic.com/pricing if a release changes the tier.
// Most-specific prefixes MUST come first — priceForModel returns the first match.
const MODEL_OUTPUT_PRICE_PER_M = [
['claude-opus-4', 75.00],
['claude-sonnet-4', 15.00],
['claude-haiku-4', 4.00],
['claude-3-5-sonnet', 15.00],
['claude-3-5-haiku', 4.00],
['claude-3-opus', 75.00],
// Legacy Opus 4.0 / 4.1 (pre-4.5) billed at the old $75/M output tier,
// including the dated ids (e.g. claude-opus-4-20250514).
['claude-opus-4-0', 75.00],
['claude-opus-4-1', 75.00],
['claude-opus-4-2025', 75.00],
// Opus 4.54.8 dropped to $25/M output (rate card held since 4.5).
['claude-opus-4', 25.00],
['claude-sonnet-4', 15.00],
['claude-haiku-4', 5.00], // Haiku 4.5 = $5/M output
['claude-3-5-sonnet', 15.00],
['claude-3-5-haiku', 4.00],
['claude-3-opus', 75.00],
];
function priceForModel(model) {
@@ -324,7 +331,7 @@ function main() {
// Routed through safeWriteFlag — the suffix path is predictable and
// user-owned, same symlink-clobber surface as the .caveman-active flag.
const agg = aggregateHistory(historyPath, null);
const suffix = agg.estSavedTokens > 0 ? `${humanizeTokens(agg.estSavedTokens)}` : '';
const suffix = agg.estSavedTokens > 0 ? ` ${humanizeTokens(agg.estSavedTokens)}` : '';
safeWriteFlag(path.join(claudeDir, '.caveman-statusline-suffix'), suffix);
}
+2 -1
View File
@@ -1,3 +1,4 @@
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$ClaudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME ".claude" }
$Flag = Join-Path $ClaudeDir ".caveman-active"
if (-not (Test-Path $Flag)) { exit 0 }
@@ -50,7 +51,7 @@ if ($env:CAVEMAN_STATUSLINE_SAVINGS -ne "0") {
$SavingsItem = Get-Item -LiteralPath $SavingsFile -Force -ErrorAction Stop
if (-not ($SavingsItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -and
$SavingsItem.Length -le 64) {
$Savings = (Get-Content -LiteralPath $SavingsFile -Raw -ErrorAction Stop).TrimEnd()
$Savings = (Get-Content -LiteralPath $SavingsFile -Encoding UTF8 -Raw -ErrorAction Stop).TrimEnd()
$Savings = ($Savings -replace '[\x00-\x1F]', '')
if ($Savings.Length -gt 0) {
[Console]::Write(" ${Esc}[38;5;172m$Savings${Esc}[0m")
+6 -4
View File
@@ -160,10 +160,12 @@ test('omits USD line when model is unknown', (tmp) => {
test('priceForModel matches by prefix across point releases', () => {
const { priceForModel } = require(path.join(ROOT, 'src', 'hooks', 'caveman-stats.js'));
assert.strictEqual(priceForModel('claude-opus-4-7'), 75.00);
assert.strictEqual(priceForModel('claude-opus-4-7'), 25.00);
assert.strictEqual(priceForModel('claude-opus-4-8'), 25.00);
assert.strictEqual(priceForModel('claude-opus-4-20250101'), 75.00);
assert.strictEqual(priceForModel('claude-opus-4-1-20250805'), 75.00);
assert.strictEqual(priceForModel('claude-sonnet-4-7-20260315'), 15.00);
assert.strictEqual(priceForModel('claude-haiku-4-5'), 4.00);
assert.strictEqual(priceForModel('claude-haiku-4-5'), 5.00);
assert.strictEqual(priceForModel('claude-3-5-sonnet-20241022'), 15.00);
assert.strictEqual(priceForModel(null), null);
assert.strictEqual(priceForModel('gpt-4'), null);
@@ -342,9 +344,9 @@ test('writes statusline suffix file after a stats run', (tmp) => {
});
const suffixPath = path.join(claudeDir, '.caveman-statusline-suffix');
assert.ok(fs.existsSync(suffixPath));
// 1500 / 0.35 = 4286, saved = 2786 → "⛏ 2.8k"
// 1500 / 0.35 = 4286, saved = 2786 → "⛏ 2.8k" (two spaces after ⛏, #459)
const suffix = fs.readFileSync(suffixPath, 'utf8');
assert.match(suffix, /^⛏ 2\.8k$/);
assert.match(suffix, /^⛏ 2\.8k$/);
});
test('humanizeTokens formats small/medium/large correctly', () => {