Skip to content

Instantly share code, notes, and snippets.

@kibotu
Last active March 18, 2026 09:16
Show Gist options
  • Select an option

  • Save kibotu/72c457c48e18e6fedb69d8699d935148 to your computer and use it in GitHub Desktop.

Select an option

Save kibotu/72c457c48e18e6fedb69d8699d935148 to your computer and use it in GitHub Desktop.
Rough estimate for token usage by cursor CSV export.
<?php
error_reporting(0);
$csv = __DIR__ . '/all-time.csv';
/**
* Cursor CSV Cost Calculator
* Computes estimated API cost based on model and token usage.
*
* Pricing (per 1M tokens, USD) — verified March 2026
* Sources: Anthropic docs, OpenAI pricing page, Google AI dev / Vertex docs,
* OpenRouter, Moonshot AI platform
*
* Format: 'model-key' => [input, cache_write, cache_read, output]
* All values are cost per 1,000,000 tokens in USD.
* cache_write / cache_read = null if not supported / not documented by provider.
*
* NOTE ON SUBSCRIPTION-ONLY MODELS (no public API token pricing):
* auto, composer-1, composer-1.5, agent_review
* → Cursor-internal routing/agent layer, not direct API calls.
* gpt-5.3-codex-spark-preview, gpt-5.3-codex-spark-preview-high,
* gpt-5.3-codex-spark-preview-xhigh
* → Research preview on Cerebras hardware; no public API pricing as of Mar 2026
* (available only to ChatGPT Pro subscribers and select design partners).
* These will appear in the "No pricing data" section of the summary.
*/
const PRICING = [
// ── Anthropic ────────────────────────────────────────────────────
// Haiku 4.5 — $1/$5 per 1M (Anthropic docs, OpenRouter Mar 2026)
'claude-4.5-haiku' => ['input' => 1.00, 'cache_write' => 1.25, 'cache_read' => 0.10, 'output' => 5.00],
// Sonnet 4.5 / 4.6 — $3/$15 per 1M
'claude-4.5-sonnet' => ['input' => 3.00, 'cache_write' => 3.75, 'cache_read' => 0.30, 'output' => 15.00],
'claude-4.5-sonnet-thinking' => ['input' => 3.00, 'cache_write' => 3.75, 'cache_read' => 0.30, 'output' => 15.00],
'claude-4.6-sonnet' => ['input' => 3.00, 'cache_write' => 3.75, 'cache_read' => 0.30, 'output' => 15.00],
// Cursor-internal thinking variant of Sonnet 4.6, billed at same rate
'claude-4.6-sonnet-medium-thinking' => ['input' => 3.00, 'cache_write' => 3.75, 'cache_read' => 0.30, 'output' => 15.00],
// Opus 4.5 / 4.6 — $5/$25 per 1M
'claude-4.5-opus' => ['input' => 5.00, 'cache_write' => 6.25, 'cache_read' => 0.50, 'output' => 25.00],
'claude-4.5-opus-high' => ['input' => 5.00, 'cache_write' => 6.25, 'cache_read' => 0.50, 'output' => 25.00],
'claude-4.5-opus-high-thinking' => ['input' => 5.00, 'cache_write' => 6.25, 'cache_read' => 0.50, 'output' => 25.00],
'claude-4.6-opus' => ['input' => 5.00, 'cache_write' => 6.25, 'cache_read' => 0.50, 'output' => 25.00],
'claude-4.6-opus-high' => ['input' => 5.00, 'cache_write' => 6.25, 'cache_read' => 0.50, 'output' => 25.00],
'claude-4.6-opus-high-thinking' => ['input' => 5.00, 'cache_write' => 6.25, 'cache_read' => 0.50, 'output' => 25.00],
// Legacy
'claude-4-sonnet' => ['input' => 3.00, 'cache_write' => 3.75, 'cache_read' => 0.30, 'output' => 15.00],
'claude-3.5-sonnet' => ['input' => 3.00, 'cache_write' => 3.75, 'cache_read' => 0.30, 'output' => 15.00],
// ── OpenAI ───────────────────────────────────────────────────────
// gpt-5 — $1.25/$10 (OpenAI pricing page)
'gpt-5' => ['input' => 1.25, 'cache_write' => null, 'cache_read' => 0.625, 'output' => 10.00],
// gpt-5.2 — $1.75/$14 (OpenAI pricing page)
'gpt-5.2' => ['input' => 1.75, 'cache_write' => null, 'cache_read' => 0.175, 'output' => 14.00],
// gpt-5.3-codex — $1.75/$14 (OpenRouter, pricepertoken.com Feb/Mar 2026)
'gpt-5.3-codex' => ['input' => 1.75, 'cache_write' => null, 'cache_read' => 0.175, 'output' => 14.00],
// gpt-4o — $2.50/$10
'gpt-4o' => ['input' => 2.50, 'cache_write' => null, 'cache_read' => 1.25, 'output' => 10.00],
// ── Google Gemini ────────────────────────────────────────────────
// Gemini 3 Flash Preview — $0.50/$3 (Google blog Jan 2026, OpenRouter)
'gemini-3-flash-preview' => ['input' => 0.50, 'cache_write' => null, 'cache_read' => 0.05, 'output' => 3.00],
// Gemini 3 Pro Preview — $2/$12 (OpenRouter, MetaCTO Jan 2026)
'gemini-3-pro-preview' => ['input' => 2.00, 'cache_write' => null, 'cache_read' => 0.20, 'output' => 12.00],
'gemini-3.1-pro-preview' => ['input' => 2.00, 'cache_write' => null, 'cache_read' => 0.20, 'output' => 12.00],
// Gemini 2.5 Pro — $1.25/$10
'gemini-2.5-pro' => ['input' => 1.25, 'cache_write' => null, 'cache_read' => 0.3125,'output' => 10.00],
// ── Moonshot AI ──────────────────────────────────────────────────
// Kimi K2.5 — $0.60 input / $3.00 output, cached $0.10 (VentureBeat Jan 2026, Moonshot platform)
'kimi-k2.5' => ['input' => 0.60, 'cache_write' => null, 'cache_read' => 0.10, 'output' => 3.00],
];
/**
* Map Cursor model strings -> pricing key.
* Cursor sometimes uses non-standard model identifiers.
* Keys are ordered longest-first so the most specific prefix wins.
*/
function resolveModel(string $raw): ?string
{
$m = strtolower(trim($raw));
// Direct match first
if (isset(PRICING[$m])) {
return $m;
}
// Prefix matching — longest (most specific) entries listed first
$map = [
// Anthropic
'claude-4.6-sonnet-medium-thinking' => 'claude-4.6-sonnet-medium-thinking',
'claude-4.6-opus-high-thinking' => 'claude-4.6-opus-high-thinking',
'claude-4.6-opus-high' => 'claude-4.6-opus-high',
'claude-4.6-opus' => 'claude-4.6-opus',
'claude-4.6-sonnet' => 'claude-4.6-sonnet',
'claude-4.5-opus-high-thinking' => 'claude-4.5-opus-high-thinking',
'claude-4.5-opus-high' => 'claude-4.5-opus-high',
'claude-4.5-opus' => 'claude-4.5-opus',
'claude-4.5-sonnet-thinking' => 'claude-4.5-sonnet-thinking',
'claude-4.5-sonnet' => 'claude-4.5-sonnet',
'claude-4.5-haiku' => 'claude-4.5-haiku',
'claude-4-sonnet' => 'claude-4-sonnet',
'claude-3.5-sonnet' => 'claude-3.5-sonnet',
// OpenAI
'gpt-5.3-codex-spark' => null, // subscription/preview only — no public API price
'gpt-5.3-codex' => 'gpt-5.3-codex',
'gpt-5.2' => 'gpt-5.2',
'gpt-5' => 'gpt-5',
'gpt-4o' => 'gpt-4o',
// Google
'gemini-3.1-pro-preview' => 'gemini-3.1-pro-preview',
'gemini-3-flash-preview' => 'gemini-3-flash-preview',
'gemini-3-pro-preview' => 'gemini-3-pro-preview',
'gemini-2.5-pro' => 'gemini-2.5-pro',
// Moonshot
'kimi-k2.5' => 'kimi-k2.5',
];
foreach ($map as $prefix => $key) {
if (str_starts_with($m, $prefix)) {
return $key; // null means "known but no public pricing"
}
}
return null; // completely unknown
}
/**
* Calculate cost in USD for a single row.
*
* CSV columns (0-indexed):
* 0 Date
* 1 User
* 2 Kind
* 3 Model
* 4 Max Mode
* 5 Input (w/ Cache Write)
* 6 Input (w/o Cache Write)
* 7 Cache Read
* 8 Output Tokens
* 9 Total Tokens
* 10 Cost
*/
function computeRowCost(array $row): array
{
$rawModel = $row[3] ?? '';
$pricingKey = resolveModel($rawModel);
// If Cost column already has a numeric value, use it directly
$csvCost = $row[10] ?? 'Free';
if (is_numeric($csvCost)) {
return [
'model' => $rawModel,
'pricing_key' => $pricingKey ?? $rawModel,
'cost' => (float) $csvCost,
'source' => 'csv',
];
}
// "Free" or unknown cost — compute from tokens if we have pricing
if ($pricingKey === null) {
return [
'model' => $rawModel,
'pricing_key' => null,
'cost' => 0.0,
'source' => 'unknown-model',
];
}
$p = PRICING[$pricingKey];
$inputWithCache = (int) ($row[5] ?? 0);
$inputNoCache = (int) ($row[6] ?? 0);
$cacheRead = (int) ($row[7] ?? 0);
$output = (int) ($row[8] ?? 0);
// Cache-write tokens = inputWithCache - inputNoCache
$cacheWrite = max(0, $inputWithCache - $inputNoCache);
$cost = ($inputNoCache / 1_000_000) * $p['input'];
$cost += ($output / 1_000_000) * $p['output'];
$cost += ($cacheRead / 1_000_000) * $p['cache_read'];
if ($cacheWrite > 0 && $p['cache_write'] !== null) {
$cost += ($cacheWrite / 1_000_000) * $p['cache_write'];
} elseif ($cacheWrite > 0) {
// Fallback: treat cache-write tokens as normal input
$cost += ($cacheWrite / 1_000_000) * $p['input'];
}
return [
'model' => $rawModel,
'pricing_key' => $pricingKey,
'cost' => $cost,
'source' => 'computed',
];
}
// ── Subscription config ───────────────────────────────────────────────────────
/**
* Subscription started 2025-01-01, capped at €20/month, running to today.
* monthsSpanned is computed below from SUB_START to now (not from CSV dates).
* Adjust EUR_TO_USD to the current exchange rate as needed.
*/
const SUBSCRIPTION_EUR = 20.0;
const EUR_TO_USD = 1.08; // ~March 2026 rate; update as needed
const SUB_START = '2025-01-01'; // subscription start date
// ── Main ─────────────────────────────────────────────────────────────────────
$handle = fopen($csv, 'r');
if ($handle === false) {
throw new RuntimeException("Failed to open CSV file: $csv");
}
$header = fgetcsv($handle);
if ($header === false) {
throw new RuntimeException('Failed to read CSV header');
}
// Parse dates to figure out how many calendar months the CSV spans
$firstDate = null;
$lastDate = null;
$totalCost = 0.0;
$totalTokens = 0;
$rowCount = 0;
$unknownModels = [];
$perModel = []; // ['pricing_key' => ['cost' => float, 'tokens' => int]]
while (($row = fgetcsv($handle)) !== false) {
if (count($row) < 9) {
continue; // skip malformed rows
}
$result = computeRowCost($row);
$tokens = (int) ($row[9] ?? 0); // Total Tokens column
$totalCost += $result['cost'];
$totalTokens += $tokens;
$rowCount++;
// Track date range (column 0)
$dateStr = trim($row[0] ?? '');
if ($dateStr !== '') {
$ts = strtotime($dateStr);
if ($ts !== false) {
if ($firstDate === null || $ts < $firstDate) {
$firstDate = $ts;
}
if ($lastDate === null || $ts > $lastDate) {
$lastDate = $ts;
}
}
}
$mk = $result['pricing_key'] ?? ('UNKNOWN:' . $result['model']);
$perModel[$mk]['cost'] = ($perModel[$mk]['cost'] ?? 0.0) + $result['cost'];
$perModel[$mk]['tokens'] = ($perModel[$mk]['tokens'] ?? 0) + $tokens;
if ($result['source'] === 'unknown-model') {
$unknownModels[$result['model']] = ($unknownModels[$result['model']] ?? 0) + 1;
}
}
fclose($handle);
// ── Derive subscription months: 2025-01-01 → today ───────────────────────────
$subStart = new DateTime(SUB_START);
$today = new DateTime('today');
// Whole calendar months elapsed since subscription start (inclusive of start month)
$subY1 = (int) $subStart->format('Y');
$subM1 = (int) $subStart->format('n');
$subY2 = (int) $today->format('Y');
$subM2 = (int) $today->format('n');
$monthsSpanned = max(1, ($subY2 - $subY1) * 12 + ($subM2 - $subM1) + 1);
$totalSubscriptionCost = SUBSCRIPTION_EUR * EUR_TO_USD * $monthsSpanned;
// ── Output ───────────────────────────────────────────────────────────────────
echo PHP_EOL;
echo "╔══════════════════════════════════════════════════╗\n";
echo "║ Cursor Usage Cost Summary ║\n";
echo "╚══════════════════════════════════════════════════╝\n\n";
$avgPerToken = fn(float $cost, int $tokens): string
=> $tokens > 0 ? '$' . number_format($cost / $tokens, 8) : '-';
echo sprintf(" %-40s %14s %16s %14s\n", 'Model (pricing key)', 'Total Tokens', 'Avg $/token', 'Cost (USD)');
echo " " . str_repeat('─', 90) . "\n";
uasort($perModel, fn($a, $b) => $b['cost'] <=> $a['cost']);
foreach ($perModel as $model => $data) {
echo sprintf(
" %-40s %14s %16s %14s\n",
$model,
number_format($data['tokens']),
$avgPerToken($data['cost'], $data['tokens']),
'$' . number_format($data['cost'], 4)
);
}
if (!empty($unknownModels)) {
echo PHP_EOL;
echo " ── No pricing data (counted as \$0) ────────────────────────────────────────────────\n";
arsort($unknownModels);
foreach ($unknownModels as $model => $count) {
echo sprintf(" %-40s %14s %16s %14s\n", $model, "($count rows)", '-', '-');
}
}
echo " " . str_repeat('─', 90) . "\n";
echo sprintf(
" %-40s %14s %16s %14s\n",
"TOTAL ($rowCount rows)",
number_format($totalTokens),
$avgPerToken($totalCost, $totalTokens),
'$' . number_format($totalCost, 4)
);
echo PHP_EOL;
// ── Subscription comparison ───────────────────────────────────────────────────
// Effective cost per 1M tokens under each scenario
$apiCostPer1M = $totalTokens > 0 ? ($totalCost / $totalTokens) * 1_000_000 : 0.0;
$subCostPer1M = $totalTokens > 0 ? ($totalSubscriptionCost / $totalTokens) * 1_000_000 : 0.0;
$savingsUsd = $totalSubscriptionCost - $totalCost;
$savingsPct = $totalSubscriptionCost > 0 ? ($savingsUsd / $totalSubscriptionCost) * 100 : 0.0;
// How many tokens/month would you need to consume for the sub to break even vs API
$avgApiCostPerToken = $totalTokens > 0 ? $totalCost / $totalTokens : 0.0;
$subCostPerMonth = SUBSCRIPTION_EUR * EUR_TO_USD;
$breakevenPerMonth = $avgApiCostPerToken > 0 ? (int) round($subCostPerMonth / $avgApiCostPerToken) : 0;
$actualPerMonth = $monthsSpanned > 0 ? (int) round($totalTokens / $monthsSpanned) : 0;
echo "╔══════════════════════════════════════════════════╗\n";
echo "║ Subscription Value Analysis ║\n";
echo "╚══════════════════════════════════════════════════╝\n\n";
echo sprintf(" Subscription: €%.2f/mo × %d months (%s → %s) = \$%.4f (@ %.4f USD/EUR)\n\n",
SUBSCRIPTION_EUR,
$monthsSpanned,
SUB_START,
$today->format('Y-m-d'),
$totalSubscriptionCost,
EUR_TO_USD
);
$w = 38;
echo " " . str_repeat('─', 70) . "\n";
echo sprintf(" %-{$w}s %14s %14s\n", '', 'API (pay-per-use)', 'Subscription');
echo " " . str_repeat('─', 70) . "\n";
echo sprintf(" %-{$w}s %14s %14s\n", 'Total cost (USD)',
'$' . number_format($totalCost, 4),
'$' . number_format($totalSubscriptionCost, 4)
);
echo sprintf(" %-{$w}s %14s %14s\n", 'Effective $/1M tokens',
'$' . number_format($apiCostPer1M, 4),
'$' . number_format($subCostPer1M, 4)
);
echo sprintf(" %-{$w}s %14s %14s\n", 'Effective $/token',
'$' . number_format($avgApiCostPerToken, 8),
'$' . number_format($totalTokens > 0 ? $totalSubscriptionCost / $totalTokens : 0, 8)
);
echo " " . str_repeat('─', 70) . "\n\n";
if ($savingsUsd >= 0) {
echo sprintf(" You SAVED \$%s vs pay-per-use (%.1f%% of sub cost — sub was the pricier option)\n",
number_format($savingsUsd, 4), $savingsPct);
} else {
echo sprintf(" You SAVED \$%s vs pay-per-use (sub was %.1f%% cheaper than API billing)\n",
number_format(abs($savingsUsd), 4), abs($savingsPct));
}
echo PHP_EOL;
echo sprintf(" Avg tokens/month (actual): %s\n", number_format($actualPerMonth));
echo sprintf(" Breakeven tokens/month: %s ← above this the sub wins, below API wins\n",
$breakevenPerMonth > 0 ? number_format($breakevenPerMonth) : 'n/a');
echo PHP_EOL;
echo " Pricing source: Anthropic / OpenAI / Google / Moonshot docs, March 2026\n";
echo " EUR→USD rate used: " . EUR_TO_USD . " (update EUR_TO_USD constant if needed)\n";
echo " Note: 'Free'/'Included' rows are estimated from token counts\n";
echo " using official API rates.\n";
echo " Note: gpt-5.3-codex-spark-* variants have no public API pricing\n";
echo " (subscription/research preview only as of March 2026).\n\n";
@kibotu
Copy link
Copy Markdown
Author

kibotu commented Mar 16, 2026

my usage up until today if it were prices from today

Screenshot 2026-03-16 at 10 26 18

@kibotu
Copy link
Copy Markdown
Author

kibotu commented Mar 16, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment