repospend 0.0.3 → 0.0.4

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,10 @@
2
2
 
3
3
  All notable changes to RepoSpend will be documented in this file.
4
4
 
5
+ ## 0.0.4
6
+
7
+ - Automatically retry Codex SQLite access after attempting to rebuild `better-sqlite3` when an `npx` or local install has a Node native module ABI mismatch.
8
+
5
9
  ## 0.0.3
6
10
 
7
11
  - Add initial Claude Code support for local JSONL session transcripts, including projects, timestamps, models, token usage when available, and source status.
package/dist/cli.js CHANGED
@@ -936,9 +936,11 @@ function parseBranch(head) {
936
936
  }
937
937
 
938
938
  // packages/core/src/sources/codex.ts
939
+ import { spawnSync } from "node:child_process";
939
940
  import fs4 from "node:fs";
940
941
  import os2 from "node:os";
941
942
  import path4 from "node:path";
943
+ import { fileURLToPath } from "node:url";
942
944
  import Database from "better-sqlite3";
943
945
 
944
946
  // packages/core/src/sources/agent-friction.ts
@@ -1268,6 +1270,7 @@ function numberFromKeys(object, keys) {
1268
1270
  }
1269
1271
 
1270
1272
  // packages/core/src/sources/codex.ts
1273
+ var nativeSqliteRebuildAttempted = false;
1271
1274
  function scanCodex(options) {
1272
1275
  const codexHome = options.codexHome ?? path4.join(os2.homedir(), ".codex");
1273
1276
  const statePath = path4.join(codexHome, "state_5.sqlite");
@@ -1322,7 +1325,7 @@ function scanCodex(options) {
1322
1325
  function readThreads(statePath, sessionFiles, options, warnings) {
1323
1326
  let db;
1324
1327
  try {
1325
- db = new Database(statePath, { readonly: true, fileMustExist: true });
1328
+ db = openCodexStateDatabase(statePath, warnings);
1326
1329
  const tableNames = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
1327
1330
  if (!tableNames.some((table) => table.name === "threads")) {
1328
1331
  warnings.push("Codex SQLite state does not contain a threads table.");
@@ -1343,6 +1346,70 @@ function readThreads(statePath, sessionFiles, options, warnings) {
1343
1346
  db?.close();
1344
1347
  }
1345
1348
  }
1349
+ function openCodexStateDatabase(statePath, warnings) {
1350
+ try {
1351
+ return new Database(statePath, { readonly: true, fileMustExist: true });
1352
+ } catch (error) {
1353
+ if (isNativeSqliteMismatch(error) && attemptNativeSqliteRebuild(warnings)) {
1354
+ return new Database(statePath, { readonly: true, fileMustExist: true });
1355
+ }
1356
+ throw error;
1357
+ }
1358
+ }
1359
+ function attemptNativeSqliteRebuild(warnings) {
1360
+ if (nativeSqliteRebuildAttempted || process.env.REPOSPEND_SKIP_NATIVE_REBUILD === "1") return false;
1361
+ nativeSqliteRebuildAttempted = true;
1362
+ const packageRoot = findRepoSpendPackageRoot();
1363
+ if (!packageRoot) {
1364
+ warnings.push("Codex SQLite native module mismatch detected, but RepoSpend could not locate its package root for an automatic rebuild.");
1365
+ return false;
1366
+ }
1367
+ const packageManager = fs4.existsSync(path4.join(packageRoot, "pnpm-lock.yaml")) ? "pnpm" : "npm";
1368
+ const result = spawnSync(packageManager, ["rebuild", "better-sqlite3"], {
1369
+ cwd: packageRoot,
1370
+ encoding: "utf8",
1371
+ stdio: "pipe",
1372
+ timeout: 12e4
1373
+ });
1374
+ if (result.status === 0) return true;
1375
+ const detail = [result.stderr, result.stdout].filter(Boolean).join("\n").split("\n").map((line) => line.trim()).filter(Boolean).slice(-3).join(" ");
1376
+ warnings.push(`Codex SQLite native module mismatch detected. RepoSpend tried to rebuild better-sqlite3 automatically with ${packageManager}, but it failed${exitLabel(result.status)}.${detail ? ` ${detail}` : ""}`);
1377
+ return false;
1378
+ }
1379
+ function findRepoSpendPackageRoot() {
1380
+ const starts = [
1381
+ process.argv[1] ? path4.dirname(fs4.realpathSync(process.argv[1])) : void 0,
1382
+ path4.dirname(fileURLToPath(import.meta.url)),
1383
+ process.cwd()
1384
+ ].filter((item) => Boolean(item));
1385
+ for (const start of starts) {
1386
+ const root = findPackageRootNamedRepoSpend(start);
1387
+ if (root) return root;
1388
+ }
1389
+ return void 0;
1390
+ }
1391
+ function findPackageRootNamedRepoSpend(start) {
1392
+ let current = path4.resolve(start);
1393
+ while (true) {
1394
+ const packagePath = path4.join(current, "package.json");
1395
+ if (fs4.existsSync(packagePath)) {
1396
+ try {
1397
+ const packageJson = JSON.parse(fs4.readFileSync(packagePath, "utf8"));
1398
+ if (packageJson.name === "repospend") return current;
1399
+ } catch {
1400
+ }
1401
+ }
1402
+ const parent = path4.dirname(current);
1403
+ if (parent === current) return void 0;
1404
+ current = parent;
1405
+ }
1406
+ }
1407
+ function isNativeSqliteMismatch(error) {
1408
+ return errorMessage(error).includes("NODE_MODULE_VERSION");
1409
+ }
1410
+ function exitLabel(status) {
1411
+ return status === null ? "" : ` with exit code ${status}`;
1412
+ }
1346
1413
  function threadToUsage(row, index, sessionFiles, options) {
1347
1414
  const sourcePath = stringValue2(row.rollout_path) ?? matchSessionPath(stringValue2(row.id), sessionFiles) ?? "";
1348
1415
  const sessionBreakdown = sourcePath ? readSessionBreakdown(sourcePath) : emptyBreakdown();
@@ -2311,7 +2378,7 @@ function runtimeCwd() {
2311
2378
  // apps/server/src/server.ts
2312
2379
  import fs7 from "node:fs";
2313
2380
  import path6 from "node:path";
2314
- import { fileURLToPath } from "node:url";
2381
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2315
2382
  import fastifyStatic from "@fastify/static";
2316
2383
  import Fastify from "fastify";
2317
2384
 
@@ -3147,7 +3214,7 @@ function createServer(options = {}) {
3147
3214
  return exportCsv();
3148
3215
  });
3149
3216
  if (options.serveWeb !== false) {
3150
- const serverDist = path6.dirname(fileURLToPath(import.meta.url));
3217
+ const serverDist = path6.dirname(fileURLToPath2(import.meta.url));
3151
3218
  const packagedWebDist = path6.resolve(serverDist, "../web-dist");
3152
3219
  const workspaceWebDist = path6.resolve(serverDist, "../../web/dist");
3153
3220
  const webDist = fsExists(packagedWebDist) ? packagedWebDist : workspaceWebDist;
@@ -3173,7 +3240,7 @@ function fsExists(inputPath) {
3173
3240
  return Boolean(inputPath) && path6.isAbsolute(inputPath) && fs7.existsSync(inputPath);
3174
3241
  }
3175
3242
  function readPackageVersion() {
3176
- const serverDist = path6.dirname(fileURLToPath(import.meta.url));
3243
+ const serverDist = path6.dirname(fileURLToPath2(import.meta.url));
3177
3244
  const packagePath = path6.resolve(serverDist, "../package.json");
3178
3245
  try {
3179
3246
  const parsed = JSON.parse(fs7.readFileSync(packagePath, "utf8"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repospend",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Local-first dashboard for tracking AI coding token usage by repo, source, session, model, and day.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Mehmet Mustafa Demir",