repospend 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to RepoSpend will be documented in this file.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.1.7
8
+
9
+ - Correct Claude Sonnet 5.5 cache reads to $0.10 per million tokens following the October 7 price reduction, without changing Sonnet 5 or intentional custom overrides.
10
+ - Add Claude Haiku 5.5 pricing for prompts up to and over 100,000 tokens, including both cache-write TTLs. Price deduplicated Claude and Copilot inference requests before summing session costs; leave aggregate-only Haiku usage unpriced when request lengths are unknown.
11
+ - Add Gemini 4 Argon and Copilot aliases at introductory $2 input / $0.10 cached input / $10 output rates, with the November 30 promotion expiry recorded in pricing documentation.
12
+ - Preserve exact custom snapshot cards and migrate unchanged Sonnet 5.5 rows in legacy full-table pricing settings.
13
+ - Sum unrounded request costs before rounding the session estimate to six decimal places.
14
+ - Keep inherited Haiku snapshot edits on the shared base card, expose the higher-tier card alongside used models, and explain missing request context and auxiliary model rates in pricing triage.
15
+ - Preserve Haiku rate-card identity while typing or clearing numeric fields, keep pricing columns compact, and show readable request-pricing diagnostics across repo views.
16
+ - Cache model resolution within each session's cost calculation and preserve tolerated numeric-string Claude input counts during deduplication.
17
+
18
+ ## 0.1.6
19
+
20
+ - Add explicit GPT-6.1 Sol Standard API-equivalent pricing, including its lower $0.10 cached-input rate, without changing GPT-6 Sol estimates.
21
+ - Resolve date-suffixed GPT model IDs to the correct generation while preserving custom snapshot overrides, including case-insensitive and provider-prefixed aliases, and unknown fast-mode cards.
22
+ - Add model pricing and Codex usage guide links to the README and AI-readable documentation, with current pricing sources and estimate limits.
23
+
5
24
  ## 0.1.5
6
25
 
7
26
  - Correct GPT-5.6 Sol and its alias to the current promotional rates, preserving custom pricing overrides.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # RepoSpend
1
+ # RepoSpend — local AI coding cost tracker
2
2
 
3
3
  RepoSpend is a local-first dashboard for tracking AI coding token usage and API-equivalent spend by repository, session, model, and tool. It supports local Codex, Claude Code, and GitHub Copilot usage data, runs with `npx repospend`, and does not upload prompts, code, transcripts, or usage data.
4
4
 
@@ -19,6 +19,26 @@ Project links:
19
19
  - npm package: [`repospend`](https://www.npmjs.com/package/repospend)
20
20
  - AI-readable summary: [docs/llms.txt](docs/llms.txt)
21
21
 
22
+ ## Model pricing and usage guides
23
+
24
+ The 0.1.7 pricing update (October 17) adds Claude Haiku 5.5's two prompt-length tiers,
25
+ reduces Sonnet 5.5 cache reads to $0.10 per million tokens, and adds Gemini 4
26
+ Argon's introductory rates through November 30. Claude and Copilot inference
27
+ records are priced per request; aggregate-only Haiku usage stays unpriced when
28
+ request lengths are unavailable. These are current API-equivalent estimates,
29
+ not historical invoices. See [pricing methodology](docs/pricing.md) for rates,
30
+ promotion dates, and custom overrides.
31
+
32
+ RepoSpend includes explicit **GPT-6.1 Sol** API-equivalent pricing: $2 input,
33
+ $0.10 cached input, $2.50 cache writes, and $10 output per million tokens at
34
+ Standard short-context rates. GPT-6 Sol keeps its separate $0.20 cache-read rate.
35
+ Estimates describe local token usage, not your ChatGPT or Codex subscription bill.
36
+
37
+ - [GPT-6.1 Sol pricing and Codex cost tracking](https://repospend.com/guides/gpt-6-1-sol-pricing) — caching, a worked estimate, and local repository usage.
38
+ - [The AI coding price war: Claude 5.5 vs GPT-6.1 Sol](https://repospend.com/guides/ai-coding-price-war) — provider rates and cost per successful task.
39
+ - [Pricing methodology and local overrides](docs/pricing.md)
40
+ - [Token accounting: input, caching, output, and reasoning](docs/token-accounting.md)
41
+
22
42
  ```bash
23
43
  npx repospend
24
44
  ```
package/dist/cli.js CHANGED
@@ -253,9 +253,11 @@ function claudePricingFamilyModel(model, isUsable = () => true) {
253
253
  "claude-sonnet-4-5",
254
254
  "claude-sonnet-4",
255
255
  "claude-haiku-4-5",
256
+ "claude-haiku-5-5",
256
257
  "claude-3-5-haiku"
257
258
  ];
258
259
  const normalized = normalizePricingModelId(model);
260
+ if (normalized.includes("-long-context")) return void 0;
259
261
  const matchesFamily = (candidate) => normalized === candidate || normalized.startsWith(`${candidate}-`) || normalized.startsWith(`${candidate}.`);
260
262
  if (normalized.includes("fast-mode")) {
261
263
  return families.find((candidate) => candidate.includes("fast-mode") && matchesFamily(candidate) && isUsable(candidate));
@@ -271,7 +273,7 @@ function copilotPricingFamilyModel(model, isUsable = () => true) {
271
273
  if ((normalized === "gemini-3-flash" || normalized.startsWith("gemini-3-flash-")) && isUsable("gemini-3-flash")) return "gemini-3-flash";
272
274
  if ((normalized === "gemini-3.1-pro" || normalized.startsWith("gemini-3.1-pro-")) && isUsable("gemini-3.1-pro")) return "gemini-3.1-pro";
273
275
  if ((normalized === "gemini-3.5-flash" || normalized.startsWith("gemini-3.5-flash-")) && isUsable("gemini-3.5-flash")) return "gemini-3.5-flash";
274
- for (const candidate of ["gemini-3.6-flash", "gemini-3.7-flash", "gemini-3.8-flash", "grok-4.5", "grok-4.6", "grok-4.7", "mai-code-1.1-flash", "kimi-k3"]) {
276
+ for (const candidate of ["gemini-3.6-flash", "gemini-3.7-flash", "gemini-3.8-flash", "gemini-4-argon", "grok-4.5", "grok-4.6", "grok-4.7", "mai-code-1.1-flash", "kimi-k3"]) {
275
277
  if ((normalized === candidate || normalized.startsWith(`${candidate}-`)) && isUsable(candidate)) return candidate;
276
278
  }
277
279
  if ((normalized === "raptor-mini" || normalized.startsWith("raptor-mini-") || normalized.startsWith("oswe-vscode")) && isUsable("raptor-mini")) return "raptor-mini";
@@ -282,13 +284,14 @@ function copilotPricingFamilyModel(model, isUsable = () => true) {
282
284
  return void 0;
283
285
  }
284
286
  function parseVersionedModel(normalized) {
285
- if (normalized.includes("fast-mode")) return void 0;
287
+ if (normalized.includes("fast-mode") || normalized.includes("-long-context")) return void 0;
286
288
  const claude = normalized.match(/^(claude-(?:fable|mythos|opus|sonnet|haiku))-(\d+)(?:-(\d{1,2}))?(?!\d)/);
287
289
  if (claude) {
288
290
  const minor = claude[3] ? Number(claude[3]) : 0;
289
291
  return { tier: claude[1], version: Number(claude[2]) + minor / 100 };
290
292
  }
291
- const gpt = normalized.match(/^(gpt)-(\d)(?:\.(\d+))?(-[a-z][a-z0-9-]*)?$/);
293
+ const gptModel = normalized.replace(/-(?:\d{4}-\d{2}-\d{2}|\d{8})$/, "");
294
+ const gpt = gptModel.match(/^(gpt)-(\d)(?:\.(\d+))?(-[a-z][a-z0-9-]*)?$/);
292
295
  if (gpt) {
293
296
  const minor = gpt[3] ? Number(gpt[3]) : 0;
294
297
  return { tier: `gpt${gpt[4] ?? ""}`, version: Number(gpt[2]) + minor / 100 };
@@ -317,17 +320,36 @@ function positiveRate(value) {
317
320
  function isUsableModelPricing(pricing) {
318
321
  return positiveRate(pricing?.inputPerMillion) && positiveRate(pricing?.outputPerMillion);
319
322
  }
320
- function findModelPricing(model, pricing) {
323
+ function resolvePricingForModel(model, table) {
321
324
  const lower = normalizePricingModelId(model);
322
- const exact = pricing[model] ?? pricing[lower];
323
- if (isUsableModelPricing(exact)) return exact;
324
- const versioned = versionedFallbackModel(lower, Object.keys(pricing), (candidate) => isUsableModelPricing(pricing[candidate]));
325
- if (versioned) return pricing[versioned];
326
- const claudeFamily = claudePricingFamilyModel(lower, (candidate) => isUsableModelPricing(pricing[candidate]));
327
- if (claudeFamily) return pricing[claudeFamily];
328
- const copilotFamily = copilotPricingFamilyModel(lower, (candidate) => isUsableModelPricing(pricing[candidate]));
329
- if (copilotFamily) return pricing[copilotFamily];
330
- return void 0;
325
+ const exactModel = table[model] ? model : table[lower] ? lower : void 0;
326
+ const exactPricing = exactModel ? table[exactModel] : void 0;
327
+ if (isUsableModelPricing(exactPricing)) {
328
+ return { pricing: exactPricing, sourceModel: exactModel, inherited: false };
329
+ }
330
+ const versioned = versionedFallbackModel(lower, Object.keys(table), (candidate) => isUsableModelPricing(table[candidate]));
331
+ if (versioned) {
332
+ return { pricing: table[versioned], sourceModel: versioned, inherited: versioned !== model && versioned !== lower };
333
+ }
334
+ const claudeFamily = claudePricingFamilyModel(lower, (candidate) => isUsableModelPricing(table[candidate]));
335
+ if (claudeFamily) {
336
+ return { pricing: table[claudeFamily], sourceModel: claudeFamily, inherited: claudeFamily !== model && claudeFamily !== lower };
337
+ }
338
+ const copilotFamily = copilotPricingFamilyModel(lower, (candidate) => isUsableModelPricing(table[candidate]));
339
+ if (copilotFamily) {
340
+ return { pricing: table[copilotFamily], sourceModel: copilotFamily, inherited: copilotFamily !== model && copilotFamily !== lower };
341
+ }
342
+ return {
343
+ pricing: exactPricing ?? {
344
+ inputPerMillion: 0,
345
+ cacheCreationInputPerMillion: 0,
346
+ cachedInputPerMillion: 0,
347
+ outputPerMillion: 0,
348
+ reasoningOutputPerMillion: 0
349
+ },
350
+ sourceModel: exactModel,
351
+ inherited: false
352
+ };
331
353
  }
332
354
 
333
355
  // packages/core/src/pricing.ts
@@ -337,18 +359,22 @@ var pricingInfo = {
337
359
  sourceUrls: [
338
360
  { label: "OpenAI pricing reference", url: "https://developers.openai.com/api/docs/pricing" },
339
361
  { label: "OpenAI GPT-6 model reference", url: "https://developers.openai.com/api/docs/models" },
362
+ { label: "OpenAI GPT-6.1 Sol reference", url: "https://developers.openai.com/api/docs/models/gpt-6.1-sol" },
340
363
  { label: "OpenAI GPT-5.6 preview pricing", url: "https://openai.com/index/previewing-gpt-5-6-sol/" },
341
364
  { label: "OpenAI GPT-5.6 price update", url: "https://openai.com/index/advancing-the-price-performance-frontier-with-gpt-5-6/" },
342
365
  { label: "Claude pricing reference", url: "https://platform.claude.com/docs/en/about-claude/pricing" },
366
+ { label: "Claude Haiku 5.5 and Sonnet cache update", url: "https://www.anthropic.com/claude-haiku-5-5" },
367
+ { label: "Gemini 4 Argon pricing", url: "https://ai.google.dev/gemini-api/docs/latest-model?hl=en" },
343
368
  { label: "GitHub Copilot model pricing reference", url: "https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing" }
344
369
  ],
345
370
  unit: "USD per 1M tokens",
346
- updatedAt: "2026-09-28",
371
+ updatedAt: "2026-10-17",
347
372
  note: "RepoSpend estimates API-equivalent cost from local token counts and public Standard pricing. GPT-6 defaults use short-context rates. Claude transcript fast-mode speeds use explicit published cards. Long-context, other processing modes, regional, and account-specific pricing can differ. This is not your actual bill."
348
373
  };
349
374
  var defaultPricing = {
350
375
  "gpt-6-astra": { inputPerMillion: 10, cachedInputPerMillion: 1, cacheCreationInputPerMillion: 12.5, outputPerMillion: 50, reasoningOutputPerMillion: 50 },
351
376
  "gpt-6-sol": { inputPerMillion: 2, cachedInputPerMillion: 0.2, cacheCreationInputPerMillion: 2.5, outputPerMillion: 10, reasoningOutputPerMillion: 10 },
377
+ "gpt-6.1-sol": { inputPerMillion: 2, cachedInputPerMillion: 0.1, cacheCreationInputPerMillion: 2.5, outputPerMillion: 10, reasoningOutputPerMillion: 10 },
352
378
  "gpt-6-luna": { inputPerMillion: 0.1, cachedInputPerMillion: 0.01, cacheCreationInputPerMillion: 0.125, outputPerMillion: 0.5, reasoningOutputPerMillion: 0.5 },
353
379
  "gpt-5.6": { inputPerMillion: 4, cachedInputPerMillion: 0.4, cacheCreationInputPerMillion: 5, outputPerMillion: 20, reasoningOutputPerMillion: 20, note: "GPT-5.6 aliases Sol. Promotional Standard short-context rates are available at least through November 21, 2026: $4 input / $20 output, $0.40 cache reads, and $5 cache writes per 1M tokens." },
354
380
  "gpt-5.6-sol": { inputPerMillion: 4, cachedInputPerMillion: 0.4, cacheCreationInputPerMillion: 5, outputPerMillion: 20, reasoningOutputPerMillion: 20, note: "Promotional Standard short-context rates available at least through November 21, 2026." },
@@ -384,6 +410,7 @@ var defaultPricing = {
384
410
  "gemini-3.6-flash": { inputPerMillion: 0.75, cachedInputPerMillion: 0.075, outputPerMillion: 3.75, note: "GitHub Copilot promotional rates through December 31, 2026; API-equivalent estimate." },
385
411
  "gemini-3.7-flash": { inputPerMillion: 0.75, cachedInputPerMillion: 0.075, outputPerMillion: 3.75, note: "GitHub Copilot promotional rates through December 31, 2026; API-equivalent estimate." },
386
412
  "gemini-3.8-flash": { inputPerMillion: 0.75, cachedInputPerMillion: 0.075, outputPerMillion: 3.75, note: "GitHub Copilot promotional rates through December 31, 2026; API-equivalent estimate." },
413
+ "gemini-4-argon": { inputPerMillion: 2, cachedInputPerMillion: 0.1, outputPerMillion: 10, reasoningOutputPerMillion: 10, note: "Introductory rates through November 30, 2026. From December 1: $4 input / $0.20 cached input / $20 output (including thinking) per 1M tokens. No explicit cache-write charge. Bundled prices are a dated snapshot, not an automatic future rate switch." },
387
414
  "grok-4.5": { inputPerMillion: 2, cachedInputPerMillion: 0.5, outputPerMillion: 6, note: "GitHub Copilot Standard rates up to 200K input tokens; longer requests use different rates." },
388
415
  "grok-4.6": { inputPerMillion: 2, cachedInputPerMillion: 0.5, outputPerMillion: 6, note: "GitHub Copilot Standard rates up to 200K input tokens; longer requests use different rates." },
389
416
  "grok-4.7": { inputPerMillion: 2, cachedInputPerMillion: 0.5, outputPerMillion: 6, note: "GitHub Copilot Standard rates up to 200K input tokens; longer requests use different rates." },
@@ -411,15 +438,18 @@ var defaultPricing = {
411
438
  "claude-opus-4-5": { inputPerMillion: 5, cacheCreationInput5mPerMillion: 6.25, cacheCreationInput1hPerMillion: 10, cacheCreationInputPerMillion: 10, cachedInputPerMillion: 0.5, outputPerMillion: 25 },
412
439
  "claude-opus-4-1": { inputPerMillion: 15, cacheCreationInput5mPerMillion: 18.75, cacheCreationInput1hPerMillion: 30, cacheCreationInputPerMillion: 30, cachedInputPerMillion: 1.5, outputPerMillion: 75 },
413
440
  "claude-opus-4": { inputPerMillion: 15, cacheCreationInput5mPerMillion: 18.75, cacheCreationInput1hPerMillion: 30, cacheCreationInputPerMillion: 30, cachedInputPerMillion: 1.5, outputPerMillion: 75 },
414
- "claude-sonnet-5-5": { inputPerMillion: 2, cacheCreationInput5mPerMillion: 2.5, cacheCreationInput1hPerMillion: 4, cacheCreationInputPerMillion: 4, cachedInputPerMillion: 0.2, outputPerMillion: 10 },
441
+ "claude-sonnet-5-5": { inputPerMillion: 2, cacheCreationInput5mPerMillion: 2.5, cacheCreationInput1hPerMillion: 4, cacheCreationInputPerMillion: 4, cachedInputPerMillion: 0.1, outputPerMillion: 10 },
415
442
  "claude-sonnet-5": { inputPerMillion: 2, cacheCreationInput5mPerMillion: 2.5, cacheCreationInput1hPerMillion: 4, cacheCreationInputPerMillion: 4, cachedInputPerMillion: 0.2, outputPerMillion: 10, note: "Anthropic made the $2 input / $10 output introductory pricing permanent on August 10, 2026." },
416
443
  "claude-sonnet-4-6": { inputPerMillion: 3, cacheCreationInput5mPerMillion: 3.75, cacheCreationInput1hPerMillion: 6, cacheCreationInputPerMillion: 6, cachedInputPerMillion: 0.3, outputPerMillion: 15 },
417
444
  "claude-sonnet-4-5": { inputPerMillion: 3, cacheCreationInput5mPerMillion: 3.75, cacheCreationInput1hPerMillion: 6, cacheCreationInputPerMillion: 6, cachedInputPerMillion: 0.3, outputPerMillion: 15 },
418
445
  "claude-sonnet-4": { inputPerMillion: 3, cacheCreationInput5mPerMillion: 3.75, cacheCreationInput1hPerMillion: 6, cacheCreationInputPerMillion: 6, cachedInputPerMillion: 0.3, outputPerMillion: 15 },
419
446
  "claude-haiku-4-5": { inputPerMillion: 1, cacheCreationInput5mPerMillion: 1.25, cacheCreationInput1hPerMillion: 2, cacheCreationInputPerMillion: 2, cachedInputPerMillion: 0.1, outputPerMillion: 5 },
447
+ "claude-haiku-5-5": { inputPerMillion: 0.1, cacheCreationInput5mPerMillion: 0.125, cacheCreationInput1hPerMillion: 0.2, cacheCreationInputPerMillion: 0.2, cachedInputPerMillion: 0.01, outputPerMillion: 0.5, note: "Prompts up to 100,000 input tokens per request, including cache reads and writes. Larger prompts use the separately editable claude-haiku-5-5-long-context card. Aggregate-only usage stays unpriced when request lengths are unknown." },
448
+ "claude-haiku-5-5-long-context": { inputPerMillion: 0.5, cacheCreationInput5mPerMillion: 0.625, cacheCreationInput1hPerMillion: 1, cacheCreationInputPerMillion: 1, cachedInputPerMillion: 0.05, outputPerMillion: 2.5, note: "RepoSpend pricing card, not a provider model ID. Applied to the whole Haiku 5.5 request when total prompt length exceeds 100,000 tokens." },
420
449
  "claude-3-5-haiku": { inputPerMillion: 0.8, cacheCreationInput5mPerMillion: 1, cacheCreationInput1hPerMillion: 1.6, cacheCreationInputPerMillion: 1.6, cachedInputPerMillion: 0.08, outputPerMillion: 4 }
421
450
  };
422
451
  var legacyBundledPricing = {
452
+ "claude-sonnet-5-5": { inputPerMillion: 2, cacheCreationInput5mPerMillion: 2.5, cacheCreationInput1hPerMillion: 4, cacheCreationInputPerMillion: 4, cachedInputPerMillion: 0.2, outputPerMillion: 10 },
423
453
  "gpt-5.6": { inputPerMillion: 5, cachedInputPerMillion: 0.5, cacheCreationInputPerMillion: 6.25, outputPerMillion: 30, reasoningOutputPerMillion: 30, note: "GPT-5.6 Sol flagship tier. OpenAI preview pricing lists Sol at $5 input / $30 output per 1M tokens, cache writes at 1.25x input, and cache reads at a 90% discount." },
424
454
  "gpt-5.6-sol": { inputPerMillion: 5, cachedInputPerMillion: 0.5, cacheCreationInputPerMillion: 6.25, outputPerMillion: 30, reasoningOutputPerMillion: 30 },
425
455
  "gpt-5.6-terra": { inputPerMillion: 2.5, cachedInputPerMillion: 0.25, cacheCreationInputPerMillion: 3.125, outputPerMillion: 15, reasoningOutputPerMillion: 15 },
@@ -526,12 +556,43 @@ function savePricingTable(pricingPath, pricing) {
526
556
  function repospendHome() {
527
557
  return process.env.REPOSPEND_HOME || path.join(os.homedir(), ".repospend");
528
558
  }
529
- function calculateCostUsd(usage, pricing) {
559
+ function calculateCostUsd(usage, pricing, requestInputTokens) {
560
+ const cost = unroundedCostUsd(usage, pricing, requestInputTokens);
561
+ return cost === void 0 ? void 0 : Number((cost + Number.EPSILON).toFixed(6));
562
+ }
563
+ function calculateRequestCosts(requests, pricing) {
564
+ const resolutions = /* @__PURE__ */ new Map();
565
+ const warnings = /* @__PURE__ */ new Set();
566
+ let total = 0;
567
+ for (const request of requests) {
568
+ let resolution = request.model ? resolutions.get(request.model) : void 0;
569
+ if (request.model && !resolution) {
570
+ resolution = resolvePricingForModel(request.model, pricing);
571
+ resolutions.set(request.model, resolution);
572
+ }
573
+ const cost = unroundedCostUsd(request, pricing, request.requestInputTokens, resolution);
574
+ if (cost !== void 0) total += cost;
575
+ else if (request.model && resolution) {
576
+ const missingContext = needsHaikuContext(request.model, resolution) && (request.requestInputTokens === void 0 || !Number.isFinite(request.requestInputTokens) || request.requestInputTokens < 0);
577
+ warnings.add(missingContext ? "unknown_request_context" : `unknown_pricing_model:${normalizePricingModelId(request.model)}`);
578
+ } else warnings.add("unknown_pricing");
579
+ }
580
+ return { costUsd: requests.length && !warnings.size ? Number((total + Number.EPSILON).toFixed(6)) : void 0, warnings: [...warnings] };
581
+ }
582
+ function needsHaikuContext(model, resolution) {
583
+ return resolution.sourceModel === "claude-haiku-5-5" || resolution.inherited && /^claude-haiku-5-5(?:$|[-.@[])/.test(normalizePricingModelId(model));
584
+ }
585
+ function unroundedCostUsd(usage, pricing, requestInputTokens, cachedResolution) {
530
586
  if (!usage.model) {
531
587
  return void 0;
532
588
  }
533
- const modelPricing = findModelPricing(usage.model, pricing);
534
- if (!modelPricing) {
589
+ const resolution = cachedResolution ?? resolvePricingForModel(usage.model, pricing);
590
+ let modelPricing = resolution.pricing;
591
+ if (needsHaikuContext(usage.model, resolution)) {
592
+ if (requestInputTokens === void 0 || !Number.isFinite(requestInputTokens) || requestInputTokens < 0) return void 0;
593
+ modelPricing = pricing[requestInputTokens > 1e5 ? "claude-haiku-5-5-long-context" : "claude-haiku-5-5"];
594
+ }
595
+ if (!isUsableModelPricing(modelPricing)) {
535
596
  return void 0;
536
597
  }
537
598
  const cacheCreationInput5m = usage.cacheCreationInputTokens5m ?? 0;
@@ -546,7 +607,7 @@ function calculateCostUsd(usage, pricing) {
546
607
  const cacheCreation1hRate = firstPositiveRate(modelPricing.cacheCreationInput1hPerMillion, modelPricing.cacheCreationInputPerMillion, modelPricing.inputPerMillion);
547
608
  const reasoningRate = modelPricing.reasoningOutputPerMillion ?? modelPricing.outputPerMillion;
548
609
  const cost = billableInput / 1e6 * modelPricing.inputPerMillion + usage.cachedInputTokens / 1e6 * cachedRate + cacheCreationInput5m / 1e6 * cacheCreation5mRate + cacheCreationInput1h / 1e6 * cacheCreation1hRate + unclassifiedCacheCreationInput / 1e6 * cacheCreationRate + usage.outputTokens / 1e6 * modelPricing.outputPerMillion + usage.reasoningTokens / 1e6 * reasoningRate;
549
- return Number(cost.toFixed(6));
610
+ return cost;
550
611
  }
551
612
  function firstPositiveRate(...rates) {
552
613
  return rates.find((rate) => typeof rate === "number" && Number.isFinite(rate) && rate > 0) ?? 0;
@@ -2285,12 +2346,13 @@ function threadToUsage(row, index, sessionFiles, options, importedModels = /* @_
2285
2346
  sessionOutcome: inferOutcome(sessionBreakdown),
2286
2347
  compaction: sessionBreakdown.compaction
2287
2348
  };
2288
- const cost = hasPricedBreakdown ? calculateCostUsd(usage, options.pricing) : void 0;
2349
+ const estimate = hasPricedBreakdown ? calculateRequestCosts([usage], options.pricing) : void 0;
2350
+ const cost = estimate?.costUsd;
2289
2351
  const costWarnings = hasPricedBreakdown ? warnings : [...warnings, "missing_token_breakdown"];
2290
2352
  return {
2291
2353
  ...usage,
2292
2354
  estimatedCostUsd: cost,
2293
- warnings: cost === void 0 ? [...costWarnings, "unknown_pricing"] : warnings
2355
+ warnings: cost === void 0 ? [.../* @__PURE__ */ new Set([...costWarnings, "unknown_pricing", ...estimate?.warnings ?? []])] : warnings
2294
2356
  };
2295
2357
  }
2296
2358
  function readStandaloneSessions(sessionFiles, options) {
@@ -2360,11 +2422,12 @@ function readStandaloneSessions(sessionFiles, options) {
2360
2422
  compaction: breakdown.compaction
2361
2423
  };
2362
2424
  const hasPricedBreakdown = breakdown.inputTokens + breakdown.cachedInputTokens + breakdown.outputTokens + breakdown.reasoningTokens > 0;
2363
- const cost = hasPricedBreakdown ? calculateCostUsd(usage, options.pricing) : void 0;
2425
+ const estimate = hasPricedBreakdown ? calculateRequestCosts([usage], options.pricing) : void 0;
2426
+ const cost = estimate?.costUsd;
2364
2427
  return {
2365
2428
  ...usage,
2366
2429
  estimatedCostUsd: cost,
2367
- warnings: cost === void 0 ? [...usage.warnings, hasPricedBreakdown ? "unknown_pricing" : "missing_token_breakdown"] : usage.warnings
2430
+ warnings: cost === void 0 ? [.../* @__PURE__ */ new Set([...usage.warnings, hasPricedBreakdown ? "unknown_pricing" : "missing_token_breakdown", ...estimate?.warnings ?? []])] : usage.warnings
2368
2431
  };
2369
2432
  });
2370
2433
  }
@@ -3050,21 +3113,30 @@ function claudeBreakdownToUsage(file, index, options, breakdown, idSuffix = "")
3050
3113
  promptTimeline: breakdown.promptTimeline,
3051
3114
  sessionOutcome: inferOutcome2(breakdown)
3052
3115
  };
3053
- const costBuckets = /* @__PURE__ */ new Map();
3054
- for (const event of breakdown.usageEvents) {
3116
+ const requests = breakdown.usageEvents.map((event) => {
3055
3117
  const model = event.model ?? breakdown.model;
3056
3118
  const normalized = model ? normalizePricingModelId(model) : void 0;
3057
3119
  const pricingModel = normalized && normalizedServiceTier(event.usage.speed) === "fast" && !normalized.includes("fast-mode") && !/^claude-opus-4-6(?:$|-)/.test(normalized) ? `${normalized}-fast-mode` : model;
3058
- const totals = costBuckets.get(pricingModel) ?? emptyBreakdown2();
3120
+ const totals = emptyBreakdown2();
3059
3121
  applyUsage(totals, event.usage);
3060
- costBuckets.set(pricingModel, totals);
3061
- }
3062
- const costs = hasTokenBreakdown ? [...costBuckets].map(([model, totals]) => calculateCostUsd({ ...totals, model, reasoningTokens: 0 }, options.pricing)) : [];
3063
- const cost = costs.length && costs.every((value) => value !== void 0) ? Number(costs.reduce((sum2, value) => sum2 + (value ?? 0), 0).toFixed(6)) : void 0;
3122
+ return {
3123
+ model: pricingModel,
3124
+ inputTokens: totals.inputTokens,
3125
+ cachedInputTokens: totals.cachedInputTokens,
3126
+ cacheCreationInputTokens: totals.cacheCreationInputTokens,
3127
+ cacheCreationInputTokens5m: totals.cacheCreationInputTokens5m,
3128
+ cacheCreationInputTokens1h: totals.cacheCreationInputTokens1h,
3129
+ outputTokens: totals.outputTokens,
3130
+ reasoningTokens: 0,
3131
+ requestInputTokens: hasInputTokenCount(event.usage) ? totals.inputTokens : void 0
3132
+ };
3133
+ });
3134
+ const estimate = hasTokenBreakdown ? calculateRequestCosts(requests, options.pricing) : void 0;
3135
+ const cost = estimate?.costUsd;
3064
3136
  return {
3065
3137
  ...usage,
3066
3138
  estimatedCostUsd: cost,
3067
- warnings: cost === void 0 ? [...usage.warnings, "unknown_pricing", ...costs.some((value, index2) => value === void 0 && [...costBuckets.keys()][index2]?.includes("fast-mode")) ? ["unknown_fast_mode_pricing"] : []] : usage.warnings
3139
+ warnings: cost === void 0 ? [.../* @__PURE__ */ new Set([...usage.warnings, "unknown_pricing", ...estimate?.warnings ?? [], ...estimate?.warnings.some((warning) => warning.startsWith("unknown_pricing_model:") && warning.includes("fast-mode")) ? ["unknown_fast_mode_pricing"] : []])] : usage.warnings
3068
3140
  };
3069
3141
  }
3070
3142
  function inferClaudeEntrypointFromPath(filePath) {
@@ -3166,10 +3238,14 @@ function usageDedupKey(record, message) {
3166
3238
  function occurrenceKey(filePath, recordIndex) {
3167
3239
  return `${path6.resolve(filePath)}:${recordIndex}`;
3168
3240
  }
3241
+ function hasInputTokenCount(usage) {
3242
+ const value = usage.input_tokens;
3243
+ return (typeof value === "number" || typeof value === "string" && value.trim() !== "") && Number.isFinite(Number(value)) && Number(value) >= 0;
3244
+ }
3169
3245
  function cloneUsage(usage) {
3170
3246
  const cacheCreation = firstObject4(usage.cache_creation);
3171
3247
  return {
3172
- input_tokens: numberValue(usage.input_tokens),
3248
+ input_tokens: hasInputTokenCount(usage) ? numberValue(usage.input_tokens) : void 0,
3173
3249
  cache_read_input_tokens: numberValue(usage.cache_read_input_tokens),
3174
3250
  cache_creation_input_tokens: numberValue(usage.cache_creation_input_tokens),
3175
3251
  cache_creation: cacheCreation ? {
@@ -3184,7 +3260,7 @@ function cloneUsage(usage) {
3184
3260
  function mergeUsageMax(target, usage) {
3185
3261
  const targetCacheCreation = firstObject4(target.cache_creation) ?? {};
3186
3262
  const usageCacheCreation = firstObject4(usage.cache_creation) ?? {};
3187
- target.input_tokens = Math.max(numberValue(target.input_tokens), numberValue(usage.input_tokens));
3263
+ if (hasInputTokenCount(target) || hasInputTokenCount(usage)) target.input_tokens = Math.max(numberValue(target.input_tokens), numberValue(usage.input_tokens));
3188
3264
  target.cache_read_input_tokens = Math.max(numberValue(target.cache_read_input_tokens), numberValue(usage.cache_read_input_tokens));
3189
3265
  target.cache_creation_input_tokens = Math.max(numberValue(target.cache_creation_input_tokens), numberValue(usage.cache_creation_input_tokens));
3190
3266
  target.cache_creation = {
@@ -3767,6 +3843,8 @@ function usageCandidateFromRecord(record, index, fallbackTimestamp, traceContext
3767
3843
  const output = numberValue2(attributes["gen_ai.usage.output_tokens"]);
3768
3844
  const cached = numberValue2(attributes["gen_ai.usage.cache_read.input_tokens"]);
3769
3845
  const cacheCreation = firstPositiveNumber(attributes, ["gen_ai.usage.cache_write.input_tokens", "gen_ai.usage.cache_creation.input_tokens"]);
3846
+ const rawInput = attributes["gen_ai.usage.input_tokens"];
3847
+ const requestInputTokens = typeof rawInput === "number" && Number.isFinite(rawInput) && rawInput >= cached + cacheCreation ? rawInput : void 0;
3770
3848
  const reasoning = firstPositiveNumber(attributes, ["gen_ai.usage.reasoning.output_tokens", "gen_ai.usage.reasoning_tokens"]);
3771
3849
  const total = firstPositiveNumber(attributes, ["gen_ai.usage.total_tokens", "gen_ai.usage.total.token_count"]);
3772
3850
  const outputWithFallback = output || outputTokensFromTotal(total, input, reasoning);
@@ -3783,6 +3861,7 @@ function usageCandidateFromRecord(record, index, fallbackTimestamp, traceContext
3783
3861
  model: firstNonEmptyAttr(attributes, modelAttrs) ?? traceContext?.model,
3784
3862
  timestamp: timestamp3,
3785
3863
  durationMs: durationMsFromRecord(record),
3864
+ requestInputTokens,
3786
3865
  inputTokens: input,
3787
3866
  cachedInputTokens: cached,
3788
3867
  cacheCreationInputTokens: cacheCreation,
@@ -3845,6 +3924,8 @@ function applyCandidate(breakdown, candidate) {
3845
3924
  breakdown.endedAt = maxDate2(breakdown.endedAt, candidate.timestamp);
3846
3925
  applyTokenEvent(breakdown, {
3847
3926
  dedupKey: candidate.dedupKey,
3927
+ model: candidate.model,
3928
+ requestInputTokens: candidate.source === "chat_span" || candidate.source === "inference_log" ? candidate.requestInputTokens : void 0,
3848
3929
  detailed: candidate.inputTokens > 0 || candidate.cachedInputTokens > 0 || candidate.cacheCreationInputTokens > 0 || candidate.reasoningTokens > 0,
3849
3930
  inputTokens: candidate.inputTokens,
3850
3931
  cachedInputTokens: candidate.cachedInputTokens,
@@ -3966,8 +4047,9 @@ function breakdownToUsage(breakdown, index, options) {
3966
4047
  fileEditCount: breakdown.fileEditCount,
3967
4048
  sourceMetadata: { copilot: { sourcePaths: breakdown.sourcePaths, surface: breakdown.surface } }
3968
4049
  };
3969
- const cost = hasPricedBreakdown ? calculateCostUsd(usage, options.pricing) : void 0;
3970
- return { ...usage, estimatedCostUsd: cost, warnings: hasPricedBreakdown && cost === void 0 ? [...usage.warnings, "unknown_pricing"] : usage.warnings };
4050
+ const estimate = hasPricedBreakdown ? calculateRequestCosts(breakdown.tokenEvents.map((event) => ({ ...event, model: normalizeCopilotModel(hasKnownModel(event.model) ? event.model : breakdown.model) })), options.pricing) : void 0;
4051
+ const cost = estimate?.costUsd;
4052
+ return { ...usage, estimatedCostUsd: cost, warnings: hasPricedBreakdown && cost === void 0 ? [.../* @__PURE__ */ new Set([...usage.warnings, "unknown_pricing", ...estimate?.warnings ?? []])] : usage.warnings };
3971
4053
  }
3972
4054
  function unknownCopilotRepoInfo() {
3973
4055
  return {
@@ -4872,12 +4954,13 @@ function candidateToUsage(candidate, index, options) {
4872
4954
  }
4873
4955
  }
4874
4956
  };
4875
- const pricedCost = hasTokenBreakdown ? calculateCostUsd(usage, options.pricing) : void 0;
4957
+ const estimate = hasTokenBreakdown ? calculateRequestCosts([usage], options.pricing) : void 0;
4958
+ const pricedCost = estimate?.costUsd;
4876
4959
  const estimatedCostUsd = pricedCost ?? breakdown.estimatedCostUsd;
4877
4960
  return {
4878
4961
  ...usage,
4879
4962
  estimatedCostUsd,
4880
- warnings: estimatedCostUsd === void 0 ? [...usage.warnings, breakdown.hasEstimatedPromptTokens ? "cursor_cost_not_estimated_from_prompt_tokens" : hasTokenBreakdown ? "unknown_pricing" : "unknown_cost"] : usage.warnings
4963
+ warnings: estimatedCostUsd === void 0 ? [.../* @__PURE__ */ new Set([...usage.warnings, breakdown.hasEstimatedPromptTokens ? "cursor_cost_not_estimated_from_prompt_tokens" : hasTokenBreakdown ? "unknown_pricing" : "unknown_cost", ...estimate?.warnings ?? []])] : usage.warnings
4881
4964
  };
4882
4965
  }
4883
4966
  function inferProjectPathFromCursorPath(filePath, cursorHome) {
package/docs/llms.txt CHANGED
@@ -9,6 +9,8 @@ Canonical URLs:
9
9
  - npm: https://www.npmjs.com/package/repospend
10
10
  - Package name: repospend
11
11
  - Run command: npx repospend
12
+ - GPT-6.1 Sol pricing and Codex tracking: https://repospend.com/guides/gpt-6-1-sol-pricing
13
+ - AI coding price comparison: https://repospend.com/guides/ai-coding-price-war
12
14
 
13
15
  What RepoSpend is:
14
16
  - A local-first AI coding token usage dashboard.
@@ -56,5 +58,10 @@ Positioning:
56
58
  - RepoSpend totals can intentionally differ from ccusage or Tokscale because it includes Claude Desktop/local-agent session roots when present, treats cache reads/writes as input sub-buckets, prices Claude cache writes by recorded 5-minute vs 1-hour TTL, and separates Codex visible output from reasoning output.
57
59
 
58
60
  Cost language:
61
+ - October 17, 2026 pricing update: Sonnet 5.5 cache reads are $0.10 per million tokens; Sonnet 5 remains at $0.20.
62
+ - Haiku 5.5 has two per-request prompt-length tiers: input/cache read/output $0.10/$0.01/$0.50 up to 100K, and $0.50/$0.05/$2.50 above 100K. Cache writes have separate 5-minute and 1-hour rates. Unknown request lengths stay unpriced.
63
+ - Gemini 4 Argon introductory input/cache read/output rates are $2/$0.10/$10 through November 30, 2026, then published rates become $4/$0.20/$20. Bundled tables are a dated snapshot, not an automatic future rate switch.
64
+ - Current API-equivalent pricing applied to older usage is not historical billing reconstruction.
65
+ - GPT-6.1 Sol has an explicit Standard short-context card: $2 input, $0.10 cached input, $2.50 cache writes, $10 output per million tokens, checked September 29, 2026.
59
66
  - RepoSpend shows estimated API-equivalent cost.
60
67
  - RepoSpend does not claim to show actual bills, invoices, subscription usage, savings, credits, or account-specific charges.
package/docs/pricing.md CHANGED
@@ -4,10 +4,35 @@ RepoSpend estimates API-equivalent cost from a local pricing table. The bundled
4
4
 
5
5
  The bundled table is seeded from public OpenAI, Anthropic, Google, and GitHub Copilot model references and is expressed as USD per 1M tokens. Pricing changes over time, so treat RepoSpend costs as API-equivalent estimates rather than invoice-grade accounting.
6
6
 
7
- The bundled defaults were checked on September 28, 2026 and use published Standard API prices. Claude Sonnet 5.5 has explicit $2 input / $10 output rates with the same cache rates as Sonnet 5. Claude Sonnet 5 remains at $2 input and $10 output per 1M tokens because Anthropic made its introductory rate permanent on August 10, 2026.
7
+ The October 17, 2026 update checks Claude Haiku 5.5, Sonnet 5.5, and Gemini 4 Argon against provider documentation. The GPT-6.1 Sol card was checked on September 29; the other bundled defaults retain the September 28 snapshot. Defaults use published Standard API prices. Sonnet 5.5 cache reads fell from $0.20 to $0.10 per 1M tokens on October 7; its $2 input, $10 output, $2.50 5-minute writes, and $4 1-hour writes are unchanged. Sonnet 5 retains its $0.20 cache-read rate.
8
+
9
+ ### Claude Haiku 5.5 and request-length pricing
10
+
11
+ All rates are USD per 1M tokens, using Anthropic's global Standard API prices:
12
+
13
+ | Prompt length per request | Input | Cache reads | 5m writes | 1h writes | Output |
14
+ | --- | ---: | ---: | ---: | ---: | ---: |
15
+ | Up to and including 100,000 tokens | $0.10 | $0.01 | $0.125 | $0.20 | $0.50 |
16
+ | Over 100,000 tokens | $0.50 | $0.05 | $0.625 | $1 | $2.50 |
17
+
18
+ Prompt length includes uncached input, cache reads, and cache writes. The higher rates apply to the whole qualifying request, not just tokens above 100K. Multiple short requests keep the lower rates even when combined session input exceeds 100K. Claude's deduplicated usage events and Copilot chat/inference records are priced individually, then their unrounded costs are summed before rounding the session estimate once.
19
+
20
+ Settings exposes `claude-haiku-5-5` for the lower tier and `claude-haiku-5-5-long-context` for the higher tier. The latter is a RepoSpend rate card, not a provider model ID; both can be edited separately. The higher-tier card appears with used Haiku models. Editing an inherited Haiku snapshot updates the shared base card and retains automatic tier selection. Use Add model to intentionally create an exact custom snapshot with flat rates; such overrides remain authoritative. Haiku usage without a known request length (including aggregate-only Copilot, Codex, or experimental Cursor totals) stays unpriced unless an explicit custom flat snapshot card or recorded Cursor cost is available. Pricing triage distinguishes missing request lengths from missing rates and exposes any unpriced auxiliary request models. Unknown fast-mode cards remain unpriced.
21
+
22
+ These are current API-equivalent rates applied to the selected usage window, not historical date-effective billing. The Sonnet reduction does not claim that pre-October 7 API requests were billed at the new price. Sources: [Anthropic pricing](https://platform.claude.com/docs/en/about-claude/pricing), [October 7 announcement](https://www.anthropic.com/claude-haiku-5-5).
23
+
24
+ ### Gemini 4 Argon introductory rates
25
+
26
+ `gemini-4-argon`, including Copilot-prefixed/suffixed aliases, uses $2 input, $0.10 cached input, and $10 output (including thinking) per 1M tokens through November 30, 2026. Google lists $4 input, $0.20 cached input, and $20 output from December 1. Argon has no explicit cache-write charge. Bundled rates are a verified snapshot: the application does not silently switch prices based on the machine clock. Refresh the table at the promotion boundary or save intentional overrides. Source: [Google's model and pricing guide](https://ai.google.dev/gemini-api/docs/latest-model?hl=en).
8
27
 
9
28
  GPT-6 Astra, Sol, and Luna use OpenAI's Standard short-context rates. Their long-context requests above 272,000 input tokens use different rates, and local session totals do not identify every request's pricing tier. Fast, batch, and regional processing can also differ from these defaults. RepoSpend keeps the result labeled as an API-equivalent estimate.
10
29
 
30
+ ### GPT-6.1 Sol (September 29, 2026)
31
+
32
+ The explicit `gpt-6.1-sol` card uses $2 input, $0.10 cached input, $2.50 cache writes, and $10 output/reasoning per 1M tokens, verified against [OpenAI's model reference](https://developers.openai.com/api/docs/models/gpt-6.1-sol) on September 29. GPT-6 Sol remains separate at $0.20 for cached input. Case-insensitive and Copilot-prefixed IDs resolve to the same card; terminal snapshot dates preserve the model generation. Snapshot support is defensive parsing, not a claim that OpenAI publishes those snapshot IDs. Custom exact-ID overrides continue to take precedence.
33
+
34
+ Above 272K input tokens per request, GPT-6.1 Sol's Standard rates become $4 input, $0.20 cached input, $5 cache writes, and $15 output per 1M tokens. Fast costs twice Standard; Batch and Flex cost half Standard. RepoSpend's default estimate uses Standard short-context pricing because aggregate local usage cannot reliably identify these modifiers for every request. See the [GPT-6.1 Sol pricing and Codex tracking guide](https://repospend.com/guides/gpt-6-1-sol-pricing) for a worked example.
35
+
11
36
  Claude Fable 5.1 and Mythos 5.1 retain their predecessor's base rates but have lower cache-read pricing. Claude Opus 5.5 has its own lower base and cache rates. These model rows use Anthropic's global Standard rates.
12
37
 
13
38
  OpenAI reduced GPT-5.6 pricing effective July 30, 2026. RepoSpend uses $2 input and $12 output per 1M tokens for Terra, and $0.20 input and $1.20 output for Luna. Cached input remains 90% below the uncached input rate, and cache writes are billed at 1.25x the uncached input rate, so Luna's bundled cache-write rate is $0.25 per 1M tokens.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repospend",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Local-first dashboard for tracking AI coding token usage and API-equivalent spend by repository, session, model, and tool.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Mehmet Mustafa Demir",