repospend 0.1.2 → 0.1.3

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,13 @@
2
2
 
3
3
  All notable changes to RepoSpend will be documented in this file.
4
4
 
5
+ ## 0.1.3
6
+
7
+ - Correct GPT-5.6 Terra and Luna API-equivalent pricing to the reduced July 30, 2026 OpenAI rates, including cached-input and cache-write rates.
8
+ - Migrate legacy full pricing files so the new Terra and Luna defaults take effect while preserving sparse user overrides and custom model rows.
9
+ - Improve unverified repo and folder grouping by using the outermost project marker below a safe shared-folder boundary, keeping nested non-Git workspace paths together without collapsing them into broad folders such as `Desktop`.
10
+ - Show paths relative to the home directory for unverified workspace labels so similarly named folders such as `src` remain distinguishable.
11
+
5
12
  ## 0.1.2
6
13
 
7
14
  - Add bundled GPT-5.6 API-equivalent pricing for the Sol, Terra, and Luna tiers, including cache-write and cached-input rates.
package/README.md CHANGED
@@ -125,7 +125,7 @@ RepoSpend helps you break down local AI coding usage by:
125
125
  - input, cached input, output, and reasoning token shape
126
126
  - estimated API-equivalent cost
127
127
 
128
- Nested paths are grouped by Git root so usage rolls up to the repository.
128
+ Nested paths are grouped by Git root so usage rolls up to the repository. When no Git root is available, RepoSpend uses the outermost project marker below a safe shared-folder boundary, such as `package.json`, and keeps the grouping visibly unverified.
129
129
 
130
130
  ## Screenshots
131
131
 
package/dist/cli.js CHANGED
@@ -324,18 +324,19 @@ var pricingInfo = {
324
324
  sourceUrls: [
325
325
  { label: "OpenAI pricing reference", url: "https://developers.openai.com/api/docs/pricing" },
326
326
  { label: "OpenAI GPT-5.6 preview pricing", url: "https://openai.com/index/previewing-gpt-5-6-sol/" },
327
+ { label: "OpenAI GPT-5.6 price update", url: "https://openai.com/index/advancing-the-price-performance-frontier-with-gpt-5-6/" },
327
328
  { label: "Claude pricing reference", url: "https://platform.claude.com/docs/en/about-claude/pricing" },
328
329
  { label: "GitHub Copilot model pricing reference", url: "https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing" }
329
330
  ],
330
331
  unit: "USD per 1M tokens",
331
- updatedAt: "2026-07-02",
332
- note: "RepoSpend estimates API-equivalent cost from local token counts and public API-style Standard pricing. This is not your actual bill; subscriptions, credits, provider terms, cache behavior, regional processing, or other billing factors can make your real cost different."
332
+ updatedAt: "2026-07-30",
333
+ note: "RepoSpend estimates API-equivalent cost from local token counts and public API-style Standard pricing. GPT-5.6 Terra and Luna use the reduced OpenAI API rates effective July 30, 2026. This is not your actual bill; subscriptions, credits, provider terms, cache behavior, regional processing, or other billing factors can make your real cost different."
333
334
  };
334
335
  var defaultPricing = {
335
336
  "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." },
336
337
  "gpt-5.6-sol": { inputPerMillion: 5, cachedInputPerMillion: 0.5, cacheCreationInputPerMillion: 6.25, outputPerMillion: 30, reasoningOutputPerMillion: 30 },
337
- "gpt-5.6-terra": { inputPerMillion: 2.5, cachedInputPerMillion: 0.25, cacheCreationInputPerMillion: 3.125, outputPerMillion: 15, reasoningOutputPerMillion: 15 },
338
- "gpt-5.6-luna": { inputPerMillion: 1, cachedInputPerMillion: 0.1, cacheCreationInputPerMillion: 1.25, outputPerMillion: 6, reasoningOutputPerMillion: 6 },
338
+ "gpt-5.6-terra": { inputPerMillion: 2, cachedInputPerMillion: 0.2, cacheCreationInputPerMillion: 2.5, outputPerMillion: 12, reasoningOutputPerMillion: 12, note: "GPT-5.6 Terra reduced API pricing effective July 30, 2026. Cache reads are 90% below input and cache writes are 1.25x input." },
339
+ "gpt-5.6-luna": { inputPerMillion: 0.2, cachedInputPerMillion: 0.02, cacheCreationInputPerMillion: 0.25, outputPerMillion: 1.2, reasoningOutputPerMillion: 1.2, note: "GPT-5.6 Luna reduced API pricing effective July 30, 2026. Cache reads are 90% below input and cache writes are 1.25x input." },
339
340
  "gpt-5.5": { inputPerMillion: 5, cachedInputPerMillion: 0.5, outputPerMillion: 30, reasoningOutputPerMillion: 30 },
340
341
  "gpt-5.5-pro": { inputPerMillion: 30, outputPerMillion: 180, reasoningOutputPerMillion: 180 },
341
342
  "gpt-5.4": { inputPerMillion: 2.5, cachedInputPerMillion: 0.25, outputPerMillion: 15, reasoningOutputPerMillion: 15 },
@@ -386,17 +387,44 @@ var defaultPricing = {
386
387
  "claude-haiku-4-5": { inputPerMillion: 1, cacheCreationInput5mPerMillion: 1.25, cacheCreationInput1hPerMillion: 2, cacheCreationInputPerMillion: 2, cachedInputPerMillion: 0.1, outputPerMillion: 5 },
387
388
  "claude-3-5-haiku": { inputPerMillion: 0.8, cacheCreationInput5mPerMillion: 1, cacheCreationInput1hPerMillion: 1.6, cacheCreationInputPerMillion: 1.6, cachedInputPerMillion: 0.08, outputPerMillion: 4 }
388
389
  };
390
+ var legacyBundledPricing = {
391
+ "gpt-5.6-terra": { inputPerMillion: 2.5, cachedInputPerMillion: 0.25, cacheCreationInputPerMillion: 3.125, outputPerMillion: 15, reasoningOutputPerMillion: 15 },
392
+ "gpt-5.6-luna": { inputPerMillion: 1, cachedInputPerMillion: 0.1, cacheCreationInputPerMillion: 1.25, outputPerMillion: 6, reasoningOutputPerMillion: 6 }
393
+ };
389
394
  function loadPricingTable(pricingPath) {
390
395
  if (!pricingPath) {
391
396
  return defaultPricing;
392
397
  }
393
398
  try {
394
399
  const resolved = path.resolve(pricingPath);
395
- return { ...defaultPricing, ...JSON.parse(fs.readFileSync(resolved, "utf8")) };
400
+ const stored = JSON.parse(fs.readFileSync(resolved, "utf8"));
401
+ return { ...defaultPricing, ...migrateLegacyBundledPricing(stored) };
396
402
  } catch {
397
403
  return defaultPricing;
398
404
  }
399
405
  }
406
+ function pricingOverrides(pricing) {
407
+ return Object.fromEntries(
408
+ Object.entries(pricing).filter(([model, modelPricing]) => {
409
+ const bundled = defaultPricing[model];
410
+ return !bundled || !pricingEntriesEqual(modelPricing, bundled);
411
+ })
412
+ );
413
+ }
414
+ function migrateLegacyBundledPricing(stored) {
415
+ const isLegacyFullTable = Object.keys(defaultPricing).every((model) => Object.hasOwn(stored, model));
416
+ if (!isLegacyFullTable) return stored;
417
+ const migrated = { ...stored };
418
+ for (const [model, legacyPricing] of Object.entries(legacyBundledPricing)) {
419
+ if (pricingEntriesEqual(migrated[model], legacyPricing)) delete migrated[model];
420
+ }
421
+ return pricingOverrides(migrated);
422
+ }
423
+ function pricingEntriesEqual(left, right) {
424
+ if (!left || !right) return left === right;
425
+ const keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
426
+ return [...keys].every((key) => left[key] === right[key]);
427
+ }
400
428
  function resolvePricingPath(config = {}) {
401
429
  return config.pricingPath ? path.resolve(repospendHome(), config.pricingPath) : path.join(repospendHome(), "pricing.json");
402
430
  }
@@ -1235,6 +1263,7 @@ function isDashboardVisibleSession(session) {
1235
1263
 
1236
1264
  // packages/core/src/repo.ts
1237
1265
  import fs4 from "node:fs";
1266
+ import os2 from "node:os";
1238
1267
  import path4 from "node:path";
1239
1268
  function normalizeRoot(p) {
1240
1269
  const stripped = p.startsWith("\\\\?\\") ? p.slice(4) : p;
@@ -1264,9 +1293,9 @@ function findGitRoot(cwd, fileSystem = fs4) {
1264
1293
  function resolveRepoInfo(cwd, config = {}, fileSystem = fs4) {
1265
1294
  const warnings = [];
1266
1295
  const gitRoot = safeFindGitRoot(cwd, fileSystem);
1267
- const repoRoot = gitRoot ?? fallbackWorkspaceRoot(cwd);
1296
+ const repoRoot = gitRoot ?? fallbackWorkspaceRoot(cwd, fileSystem);
1268
1297
  const alias = aliasForPath(repoRoot, config);
1269
- const repoName = alias ?? (path4.basename(repoRoot) || "unknown-workspace");
1298
+ const repoName = alias ?? (gitRoot ? path4.basename(repoRoot) : fallbackWorkspaceLabel(repoRoot));
1270
1299
  const gitMeta = gitRoot ? readGitMetadata(gitRoot, fileSystem) : { remote: void 0, branch: void 0 };
1271
1300
  if (!gitRoot) {
1272
1301
  warnings.push("repo_unverified_no_git_root");
@@ -1287,16 +1316,59 @@ function safeFindGitRoot(cwd, fileSystem) {
1287
1316
  return void 0;
1288
1317
  }
1289
1318
  }
1290
- function fallbackWorkspaceRoot(cwd) {
1319
+ var workspaceMarkers = [
1320
+ "package.json",
1321
+ "pyproject.toml",
1322
+ "Cargo.toml",
1323
+ "go.mod",
1324
+ "mix.exs",
1325
+ "composer.json",
1326
+ "Gemfile",
1327
+ "pom.xml",
1328
+ "build.gradle",
1329
+ "Package.swift",
1330
+ "CMakeLists.txt"
1331
+ ];
1332
+ var fallbackWorkspaceCache = /* @__PURE__ */ new Map();
1333
+ function fallbackWorkspaceRoot(cwd, fileSystem = fs4) {
1291
1334
  const resolved = normalizeRoot(path4.resolve(cwd || process.cwd()));
1292
- const home = process.env.HOME ? normalizeRoot(path4.resolve(process.env.HOME)) : void 0;
1293
- if (home && (resolved === home || resolved.startsWith(`${home}${path4.sep}`))) {
1294
- const relative = path4.relative(home, resolved).split(path4.sep).filter(Boolean);
1295
- const first = relative[0];
1296
- return first ? path4.join(home, first) : home;
1335
+ if (fileSystem === fs4) {
1336
+ const cached = fallbackWorkspaceCache.get(resolved);
1337
+ if (cached) return cached;
1338
+ }
1339
+ const root = findWorkspaceMarkerRoot(resolved, fileSystem) ?? resolved;
1340
+ if (fileSystem === fs4) fallbackWorkspaceCache.set(resolved, root);
1341
+ return root;
1342
+ }
1343
+ function findWorkspaceMarkerRoot(start, fileSystem) {
1344
+ const home = normalizeRoot(path4.resolve(os2.homedir()));
1345
+ const boundary = isWithin(start, home) ? home : path4.parse(start).root;
1346
+ let candidate;
1347
+ let current = start;
1348
+ while (true) {
1349
+ if (current !== boundary && isAllowedMarkerRoot(current, boundary) && workspaceMarkers.some((marker) => fileSystem.existsSync(path4.join(current, marker)))) {
1350
+ candidate = current;
1351
+ }
1352
+ if (current === boundary) return candidate;
1353
+ const parent = path4.dirname(current);
1354
+ if (parent === current) return candidate;
1355
+ current = parent;
1297
1356
  }
1298
- const parts = resolved.split(path4.sep).filter(Boolean);
1299
- return `${path4.sep}${parts.slice(0, Math.min(parts.length, 3)).join(path4.sep)}`;
1357
+ }
1358
+ function isAllowedMarkerRoot(candidate, boundary) {
1359
+ const relative = path4.relative(boundary, candidate).split(path4.sep).filter(Boolean);
1360
+ return relative.length > 1;
1361
+ }
1362
+ function isWithin(candidate, parent) {
1363
+ return candidate === parent || candidate.startsWith(`${parent}${path4.sep}`);
1364
+ }
1365
+ function fallbackWorkspaceLabel(repoRoot) {
1366
+ const home = normalizeRoot(path4.resolve(os2.homedir()));
1367
+ const relativeToHome = path4.relative(home, repoRoot);
1368
+ if (relativeToHome && !relativeToHome.startsWith("..") && !path4.isAbsolute(relativeToHome)) {
1369
+ return relativeToHome.split(path4.sep).filter(Boolean).join("/");
1370
+ }
1371
+ return path4.basename(repoRoot) || "unknown-workspace";
1300
1372
  }
1301
1373
  function readGitMetadata(repoRoot, fileSystem) {
1302
1374
  const gitPath = path4.join(repoRoot, ".git");
@@ -1331,7 +1403,7 @@ function parseBranch(head) {
1331
1403
  // packages/core/src/sources/codex.ts
1332
1404
  import { spawnSync } from "node:child_process";
1333
1405
  import fs5 from "node:fs";
1334
- import os2 from "node:os";
1406
+ import os3 from "node:os";
1335
1407
  import path5 from "node:path";
1336
1408
  import { fileURLToPath } from "node:url";
1337
1409
  import Database from "better-sqlite3";
@@ -1856,7 +1928,7 @@ function numberFromKeys(object, keys) {
1856
1928
  var codexSessionCacheVersion = "codex-session-v1";
1857
1929
  var nativeSqliteRebuildAttempted = false;
1858
1930
  function scanCodex(options) {
1859
- const codexHome = options.codexHome ?? path5.join(os2.homedir(), ".codex");
1931
+ const codexHome = options.codexHome ?? path5.join(os3.homedir(), ".codex");
1860
1932
  const statePath = path5.join(codexHome, "state_5.sqlite");
1861
1933
  const sessionsPath = path5.join(codexHome, "sessions");
1862
1934
  const configPath2 = path5.join(codexHome, "config.toml");
@@ -1938,7 +2010,7 @@ function readThreads(statePath, sessionFiles, options, warnings) {
1938
2010
  warnings.push("Codex threads table has no recognized columns.");
1939
2011
  return [];
1940
2012
  }
1941
- const importedModels = readImportedModelMap(options.codexHome ?? path5.join(os2.homedir(), ".codex"), warnings);
2013
+ const importedModels = readImportedModelMap(options.codexHome ?? path5.join(os3.homedir(), ".codex"), warnings);
1942
2014
  const rows = db.prepare(`SELECT ${selectedColumns.map((column) => `"${column}"`).join(", ")} FROM threads`).all().filter((row) => rowMatchesScanWindow(row, options.scanWindow));
1943
2015
  const sessions = rows.map((row, index) => threadToUsage(row, index, sessionFiles, options, importedModels));
1944
2016
  return attributeSubagentsToParentSurfaces(sessions, rows);
@@ -2095,7 +2167,7 @@ function threadToUsage(row, index, sessionFiles, options, importedModels = /* @_
2095
2167
  messageCount: sessionBreakdown.messageCount,
2096
2168
  rawTokenTotal: dbTokens || sessionBreakdown.totalTokens || void 0,
2097
2169
  warnings,
2098
- codexHome: options.codexHome ?? path5.join(os2.homedir(), ".codex"),
2170
+ codexHome: options.codexHome ?? path5.join(os3.homedir(), ".codex"),
2099
2171
  durationMs: durationMs(startedAt, endedAt),
2100
2172
  rawEventCount: sessionBreakdown.rawEventCount,
2101
2173
  parseStatus: sessionBreakdown.parseStatus,
@@ -2169,7 +2241,7 @@ function readStandaloneSessions(sessionFiles, options) {
2169
2241
  messageCount: breakdown.messageCount,
2170
2242
  rawTokenTotal: breakdown.totalTokens || void 0,
2171
2243
  warnings: [...repo.warnings, ...breakdown.warnings],
2172
- codexHome: options.codexHome ?? path5.join(os2.homedir(), ".codex"),
2244
+ codexHome: options.codexHome ?? path5.join(os3.homedir(), ".codex"),
2173
2245
  durationMs: durationMs(breakdown.startedAt, breakdown.endedAt),
2174
2246
  rawEventCount: breakdown.rawEventCount,
2175
2247
  parseStatus: breakdown.parseStatus,
@@ -2680,7 +2752,7 @@ function errorMessage(error) {
2680
2752
 
2681
2753
  // packages/core/src/sources/claude.ts
2682
2754
  import fs6 from "node:fs";
2683
- import os3 from "node:os";
2755
+ import os4 from "node:os";
2684
2756
  import path6 from "node:path";
2685
2757
  function scanClaude(options) {
2686
2758
  const claudeHome = options.claudeHome ?? defaultClaudeHome();
@@ -2734,7 +2806,7 @@ function scanClaude(options) {
2734
2806
  function discoverClaudeSessionFiles(claudeHome, warnings, explicitHome) {
2735
2807
  const files = [];
2736
2808
  const projectDirs = /* @__PURE__ */ new Set();
2737
- const projectsPaths = explicitHome ? [path6.join(claudeHome, "projects")] : unique3([...configuredClaudeHomes(claudeHome).map((root) => path6.join(root, "projects")), path6.join(os3.homedir(), ".config", "claude", "projects")]);
2809
+ const projectsPaths = explicitHome ? [path6.join(claudeHome, "projects")] : unique3([...configuredClaudeHomes(claudeHome).map((root) => path6.join(root, "projects")), path6.join(os4.homedir(), ".config", "claude", "projects")]);
2738
2810
  for (const projectsPath of projectsPaths) {
2739
2811
  collectProjectFiles(projectsPath, files, projectDirs, warnings);
2740
2812
  }
@@ -3248,10 +3320,10 @@ function claudeSourceAppLabel(entrypoint) {
3248
3320
  return "Claude Code";
3249
3321
  }
3250
3322
  function defaultClaudeHome() {
3251
- return process.env.CLAUDE_CONFIG_DIR?.split(",").map((item) => item.trim()).filter(Boolean)[0] ?? path6.join(os3.homedir(), ".claude");
3323
+ return process.env.CLAUDE_CONFIG_DIR?.split(",").map((item) => item.trim()).filter(Boolean)[0] ?? path6.join(os4.homedir(), ".claude");
3252
3324
  }
3253
3325
  function defaultDesktopSessionRoots() {
3254
- const home = os3.homedir();
3326
+ const home = os4.homedir();
3255
3327
  const xdg = process.env.XDG_CONFIG_HOME || path6.join(home, ".config");
3256
3328
  return [
3257
3329
  path6.join(home, "Library", "Application Support", "Claude", "local-agent-mode-sessions"),
@@ -3342,12 +3414,12 @@ function safeMtimeMs(filePath) {
3342
3414
 
3343
3415
  // packages/core/src/sources/copilot.ts
3344
3416
  import fs7 from "node:fs";
3345
- import os4 from "node:os";
3417
+ import os5 from "node:os";
3346
3418
  import path7 from "node:path";
3347
3419
  var maxJsonlBytes = 50 * 1024 * 1024;
3348
3420
  var copilotOtelEnv = "COPILOT_OTEL_FILE_EXPORTER_PATH";
3349
3421
  function scanCopilot(options) {
3350
- const copilotHome = options.copilotHome ?? path7.join(os4.homedir(), ".copilot");
3422
+ const copilotHome = options.copilotHome ?? path7.join(os5.homedir(), ".copilot");
3351
3423
  const discovery = discoverCopilotFiles(copilotHome, Boolean(options.copilotHome), options.codeUserRoots);
3352
3424
  const files = discovery.files.filter((file) => fileMayOverlapScanWindow2(file.filePath, options.scanWindow));
3353
3425
  const sessionMap = /* @__PURE__ */ new Map();
@@ -4016,7 +4088,7 @@ function timestampMs2(value) {
4016
4088
  return iso ? Date.parse(iso) : void 0;
4017
4089
  }
4018
4090
  function defaultCodeUserRoots() {
4019
- const home = os4.homedir();
4091
+ const home = os5.homedir();
4020
4092
  if (process.platform === "win32") {
4021
4093
  const appData = process.env.APPDATA ?? path7.join(home, "AppData", "Roaming");
4022
4094
  return ["Code", "Code - Insiders", "VSCodium"].map((name) => path7.join(appData, name, "User"));
@@ -4151,7 +4223,7 @@ function errorMessage3(error) {
4151
4223
 
4152
4224
  // packages/core/src/sources/cursor.ts
4153
4225
  import fs8 from "node:fs";
4154
- import os5 from "node:os";
4226
+ import os6 from "node:os";
4155
4227
  import path8 from "node:path";
4156
4228
  import Database2 from "better-sqlite3";
4157
4229
  var parserVersion = "cursor-v1";
@@ -4160,7 +4232,7 @@ var maxDatabaseBytes = 100 * 1024 * 1024;
4160
4232
  var cursorStoragePrefixes = ["cursor.", "anysphere."];
4161
4233
  var interestingTerms = ["cursor", "composer", "aichat", "ai_chat", "aiservice", "agent-transcript", "prompttokenbreakdown"];
4162
4234
  function scanCursor(options) {
4163
- const cursorHome = options.cursorHome ?? path8.join(os5.homedir(), ".cursor");
4235
+ const cursorHome = options.cursorHome ?? path8.join(os6.homedir(), ".cursor");
4164
4236
  const discovery = discoverCursorFiles(cursorHome, Boolean(options.cursorHome));
4165
4237
  const jsonlFiles = discovery.jsonlFiles.filter((filePath) => fileMayOverlapScanWindow3(filePath, options.scanWindow));
4166
4238
  const databaseFiles = discovery.databaseFiles.filter((filePath) => fileMayOverlapScanWindow3(filePath, options.scanWindow));
@@ -4276,7 +4348,7 @@ function cursorOwnedChildDirs(root) {
4276
4348
  }
4277
4349
  }
4278
4350
  function defaultCursorAppPaths() {
4279
- const home = os5.homedir();
4351
+ const home = os6.homedir();
4280
4352
  if (process.platform === "win32") {
4281
4353
  const appData = process.env.APPDATA ?? path8.join(home, "AppData", "Roaming");
4282
4354
  const globalStoragePath2 = path8.join(appData, "Cursor", "User", "globalStorage");
@@ -5116,7 +5188,7 @@ function writeConfigData(nextConfig) {
5116
5188
  function writePricingData(models) {
5117
5189
  const config = loadConfig();
5118
5190
  const pricingPath = resolvePricingPath(config);
5119
- savePricingTable(pricingPath, models);
5191
+ savePricingTable(pricingPath, pricingOverrides(models));
5120
5192
  clearScanCache();
5121
5193
  return {
5122
5194
  path: pricingPath,
package/docs/pricing.md CHANGED
@@ -6,6 +6,10 @@ The bundled table is seeded from public OpenAI, Anthropic, Google, and GitHub Co
6
6
 
7
7
  The bundled defaults use currently published standard API prices unless a provider has an active introductory API rate. For example, Claude Sonnet 5 uses Anthropic's $2 input and $10 output introductory rate through August 31, 2026, with a pricing-table note for the $3 input and $15 output standard rate that starts September 1, 2026.
8
8
 
9
+ 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.
10
+
11
+ The Settings page persists only custom or intentionally edited model rows. This lets future bundled rate updates flow through automatically. Older full pricing files are migrated for the GPT-5.6 Terra and Luna reductions when they still contain the previous bundled rates.
12
+
9
13
  Some GitHub Copilot model rows are hosted or fine-tuned vendor models such as Raptor mini, MAI-Code-1-Flash, and Kimi K2.7 Code. RepoSpend prices those rows from GitHub's public per-token table as API-equivalent estimates, not as a Copilot invoice.
10
14
 
11
15
  For Claude models, RepoSpend uses Anthropic's cache-write TTL split when local
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repospend",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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",