@hasna/economy 0.5.4 → 0.5.5

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.
@@ -5529,7 +5529,7 @@ function isAuthorizedRequest(req, path) {
5529
5529
  // package.json
5530
5530
  var package_default = {
5531
5531
  name: "@hasna/economy",
5532
- version: "0.5.4",
5532
+ version: "0.5.5",
5533
5533
  description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
5534
5534
  repository: {
5535
5535
  type: "git",
@@ -8812,8 +8812,8 @@ function sourceTimeMillis(value) {
8812
8812
  init_database();
8813
8813
  init_pricing();
8814
8814
  import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync4, statSync as statSync2 } from "fs";
8815
- import { homedir as homedir3 } from "os";
8816
- import { join as join5, basename } from "path";
8815
+ import { homedir as homedir4 } from "os";
8816
+ import { join as join6, basename } from "path";
8817
8817
 
8818
8818
  // src/lib/accounts-store.ts
8819
8819
  import { existsSync as existsSync3, readFileSync } from "fs";
@@ -9436,12 +9436,41 @@ function withAccount(record, account) {
9436
9436
  };
9437
9437
  }
9438
9438
 
9439
+ // src/lib/paths.ts
9440
+ import { homedir as homedir3 } from "os";
9441
+ import { join as join5 } from "path";
9442
+ function getHomeDir2() {
9443
+ return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir3();
9444
+ }
9445
+ function agentPaths() {
9446
+ const home = getHomeDir2();
9447
+ const claudeHome = process.env["CLAUDE_CONFIG_DIR"] || join5(home, ".claude");
9448
+ const codexHome = process.env["CODEX_HOME"] || join5(home, ".codex");
9449
+ return {
9450
+ claudeProjects: join5(claudeHome, "projects"),
9451
+ claudeCredentials: join5(claudeHome, ".credentials.json"),
9452
+ takumiProjects: join5(home, ".takumi", "projects"),
9453
+ codexDir: codexHome,
9454
+ codexDb: join5(codexHome, "state_5.sqlite"),
9455
+ codexAuth: join5(codexHome, "auth.json"),
9456
+ codexConfig: join5(codexHome, "config.toml"),
9457
+ geminiTmp: join5(home, ".gemini", "tmp"),
9458
+ geminiHistory: join5(home, ".gemini", "history"),
9459
+ opencodeMessages: join5(home, ".local", "share", "opencode", "storage", "message"),
9460
+ piSessions: join5(home, ".pi", "agent", "sessions"),
9461
+ hermesDir: join5(home, ".hermes"),
9462
+ hermesDb: join5(home, ".hermes", "state.db")
9463
+ };
9464
+ }
9465
+
9439
9466
  // src/ingest/claude.ts
9440
9467
  function autoDetectProject(cwd, projects) {
9441
9468
  return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
9442
9469
  }
9443
- var CLAUDE_PROJECTS_DIR = process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? join5(homedir3(), ".claude", "projects");
9444
- var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join5(homedir3(), ".takumi", "projects");
9470
+ var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join6(homedir4(), ".takumi", "projects");
9471
+ function claudeProjectsDir() {
9472
+ return process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? agentPaths().claudeProjects;
9473
+ }
9445
9474
  function dirNameToPath(dirName) {
9446
9475
  return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
9447
9476
  }
@@ -9451,9 +9480,9 @@ function collectJsonlFiles(projectDir) {
9451
9480
  try {
9452
9481
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
9453
9482
  if (entry.isDirectory())
9454
- walk(join5(dir, entry.name));
9483
+ walk(join6(dir, entry.name));
9455
9484
  else if (entry.name.endsWith(".jsonl"))
9456
- files.push(join5(dir, entry.name));
9485
+ files.push(join6(dir, entry.name));
9457
9486
  }
9458
9487
  } catch {
9459
9488
  collectionProblem("failed", "source_unreadable");
@@ -9462,7 +9491,7 @@ function collectJsonlFiles(projectDir) {
9462
9491
  walk(projectDir);
9463
9492
  return files;
9464
9493
  }
9465
- async function ingestClaude(db, verbose = false, projectsDir = CLAUDE_PROJECTS_DIR) {
9494
+ async function ingestClaude(db, verbose = false, projectsDir = claudeProjectsDir()) {
9466
9495
  return ingestJsonlProjects(db, projectsDir, "claude", verbose);
9467
9496
  }
9468
9497
  async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_DIR) {
@@ -9484,7 +9513,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
9484
9513
  const ingestState = loadIngestState(db, agentName);
9485
9514
  const projectDirs = readdirSync2(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
9486
9515
  for (const projectDirEntry of projectDirs) {
9487
- const projectDirPath = join5(projectsDir, projectDirEntry.name);
9516
+ const projectDirPath = join6(projectsDir, projectDirEntry.name);
9488
9517
  const projectPath = dirNameToPath(projectDirEntry.name);
9489
9518
  const jsonlFiles = collectJsonlFiles(projectDirPath);
9490
9519
  for (const filePath of jsonlFiles) {
@@ -9635,24 +9664,19 @@ init_database();
9635
9664
  init_pricing();
9636
9665
  import { existsSync as existsSync5, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
9637
9666
  import { createHash as createHash4 } from "crypto";
9638
- import { homedir as homedir4 } from "os";
9639
- import { join as join6, basename as basename2 } from "path";
9640
- var DEFAULT_CODEX_DB_PATH = join6(homedir4(), ".codex", "state_5.sqlite");
9641
- var DEFAULT_CODEX_CONFIG_PATH = join6(homedir4(), ".codex", "config.toml");
9642
- var DEFAULT_CODEWITH_DB_PATH = join6(homedir4(), ".codewith", "state_5.sqlite");
9643
- var DEFAULT_CODEWITH_CONFIG_PATH = join6(homedir4(), ".codewith", "config.toml");
9667
+ import { join as join7, basename as basename2 } from "path";
9644
9668
  var CODEX_INGEST_VERSION = "rollout-events-v4";
9645
9669
  function codexDbPath() {
9646
- return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
9670
+ return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? agentPaths().codexDb;
9647
9671
  }
9648
9672
  function codexConfigPath() {
9649
- return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
9673
+ return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? agentPaths().codexConfig;
9650
9674
  }
9651
9675
  function codewithDbPath() {
9652
- return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
9676
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? join7(getHomeDir2(), ".codewith", "state_5.sqlite");
9653
9677
  }
9654
9678
  function codewithConfigPath() {
9655
- return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
9679
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? join7(getHomeDir2(), ".codewith", "config.toml");
9656
9680
  }
9657
9681
  function codexSources() {
9658
9682
  const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
@@ -9664,7 +9688,7 @@ function codexSources() {
9664
9688
  sessionPrefix: "codex",
9665
9689
  stateSource: "codex"
9666
9690
  }];
9667
- if (!explicitCodexPath || explicitCodewithPath) {
9691
+ if (!explicitCodexPath && !process.env["CODEX_HOME"] || explicitCodewithPath) {
9668
9692
  sources.push({
9669
9693
  label: "Codewith",
9670
9694
  dbPath: codewithDbPath(),
@@ -9904,9 +9928,9 @@ init_database();
9904
9928
  init_pricing();
9905
9929
  import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync6, statSync as statSync4 } from "fs";
9906
9930
  import { homedir as homedir5 } from "os";
9907
- import { join as join7, basename as basename3 } from "path";
9908
- var DEFAULT_GEMINI_TMP_DIR = join7(homedir5(), ".gemini", "tmp");
9909
- var DEFAULT_GEMINI_HISTORY_DIR = join7(homedir5(), ".gemini", "history");
9931
+ import { join as join8, basename as basename3 } from "path";
9932
+ var DEFAULT_GEMINI_TMP_DIR = join8(homedir5(), ".gemini", "tmp");
9933
+ var DEFAULT_GEMINI_HISTORY_DIR = join8(homedir5(), ".gemini", "history");
9910
9934
  function geminiTmpDir() {
9911
9935
  return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
9912
9936
  }
@@ -9928,7 +9952,7 @@ function listProjectDirs(...roots) {
9928
9952
  try {
9929
9953
  for (const entry of readdirSync3(root, { withFileTypes: true })) {
9930
9954
  if (entry.isDirectory())
9931
- dirs.add(join7(root, entry.name));
9955
+ dirs.add(join8(root, entry.name));
9932
9956
  }
9933
9957
  } catch {
9934
9958
  collectionProblem("failed", "source_unreadable");
@@ -9941,7 +9965,7 @@ function projectRoot(projectDir, chatData) {
9941
9965
  return chatData.projectPath;
9942
9966
  if (chatData.project_path)
9943
9967
  return chatData.project_path;
9944
- const rootFile = join7(projectDir, ".project_root");
9968
+ const rootFile = join8(projectDir, ".project_root");
9945
9969
  try {
9946
9970
  if (existsSync6(rootFile))
9947
9971
  return readFileSync4(rootFile, "utf-8").trim();
@@ -9966,12 +9990,12 @@ async function ingestGemini(db, verbose) {
9966
9990
  const account = await resolveAccountForAgent("gemini");
9967
9991
  const projectDirs = listProjectDirs(tmpDir, historyDir);
9968
9992
  for (const projectDir of projectDirs) {
9969
- const chatsDir = join7(projectDir, "chats");
9993
+ const chatsDir = join8(projectDir, "chats");
9970
9994
  if (!existsSync6(chatsDir))
9971
9995
  continue;
9972
9996
  let chatFiles = [];
9973
9997
  try {
9974
- chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join7(chatsDir, f));
9998
+ chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join8(chatsDir, f));
9975
9999
  } catch {
9976
10000
  collectionProblem("failed", "source_unreadable");
9977
10001
  continue;
@@ -10083,13 +10107,13 @@ init_database();
10083
10107
  init_pricing();
10084
10108
  import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
10085
10109
  import { homedir as homedir6 } from "os";
10086
- import { join as join8 } from "path";
10087
- var OPENCODE_STORAGE = join8(homedir6(), ".local", "share", "opencode", "storage");
10110
+ import { join as join9 } from "path";
10111
+ var OPENCODE_STORAGE = join9(homedir6(), ".local", "share", "opencode", "storage");
10088
10112
  function walkJsonFiles(dir, acc = []) {
10089
10113
  if (!existsSync7(dir))
10090
10114
  return acc;
10091
10115
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
10092
- const p = join8(dir, entry.name);
10116
+ const p = join9(dir, entry.name);
10093
10117
  if (entry.isDirectory())
10094
10118
  walkJsonFiles(p, acc);
10095
10119
  else if (entry.name.endsWith(".json"))
@@ -10109,7 +10133,7 @@ function parseSessionIdFromPath(filePath) {
10109
10133
  return null;
10110
10134
  }
10111
10135
  async function ingestOpenCode(db, verbose = false, sourceDir = OPENCODE_STORAGE) {
10112
- const messageDir = join8(sourceDir, "message");
10136
+ const messageDir = join9(sourceDir, "message");
10113
10137
  if (!existsSync7(messageDir))
10114
10138
  collectionProblem("missing", "source_missing");
10115
10139
  const files = walkJsonFiles(messageDir);
@@ -10304,13 +10328,13 @@ async function ingestCursor(db, verbose = false) {
10304
10328
  init_database();
10305
10329
  import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
10306
10330
  import { homedir as homedir7 } from "os";
10307
- import { join as join9 } from "path";
10308
- var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join9(homedir7(), ".pi", "agent", "sessions");
10331
+ import { join as join10 } from "path";
10332
+ var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join10(homedir7(), ".pi", "agent", "sessions");
10309
10333
  function walkSessions(dir, acc = []) {
10310
10334
  if (!existsSync8(dir))
10311
10335
  return acc;
10312
10336
  for (const entry of readdirSync5(dir, { withFileTypes: true })) {
10313
- const p = join9(dir, entry.name);
10337
+ const p = join10(dir, entry.name);
10314
10338
  if (entry.isDirectory())
10315
10339
  walkSessions(p, acc);
10316
10340
  else if (entry.name.endsWith(".json"))
@@ -10411,8 +10435,8 @@ async function ingestPi(db, verbose = false, sourceDir = PI_SESSION_DIR) {
10411
10435
  init_database();
10412
10436
  import { existsSync as existsSync9, statSync as statSync7 } from "fs";
10413
10437
  import { homedir as homedir8 } from "os";
10414
- import { join as join10 } from "path";
10415
- var HERMES_DB = join10(homedir8(), ".hermes", "state.db");
10438
+ import { join as join11 } from "path";
10439
+ var HERMES_DB = join11(homedir8(), ".hermes", "state.db");
10416
10440
  async function ingestHermes(db, verbose = false, sourcePath = HERMES_DB) {
10417
10441
  if (!existsSync9(sourcePath)) {
10418
10442
  collectionProblem("missing", "source_missing");
@@ -10482,7 +10506,7 @@ init_database();
10482
10506
  init_pricing();
10483
10507
  import { existsSync as existsSync10 } from "fs";
10484
10508
  import { homedir as homedir9 } from "os";
10485
- import { join as join11 } from "path";
10509
+ import { join as join12 } from "path";
10486
10510
  import { homedir as pathsResolverHomedir2 } from "os";
10487
10511
  import { join as pathsResolverJoin2 } from "path";
10488
10512
  var PATHS_RESOLVER_KIND_ENV2 = {
@@ -10549,8 +10573,8 @@ function defaultLoopsDbPath() {
10549
10573
  app: "loops",
10550
10574
  home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
10551
10575
  });
10552
- const legacy = join11(process.env["HOME"] || process.env["USERPROFILE"] || homedir9(), ".hasna", "loops", "loops.db");
10553
- return existsSync10(join11(resolved, "loops.db")) ? join11(resolved, "loops.db") : legacy;
10576
+ const legacy = join12(process.env["HOME"] || process.env["USERPROFILE"] || homedir9(), ".hasna", "loops", "loops.db");
10577
+ return existsSync10(join12(resolved, "loops.db")) ? join12(resolved, "loops.db") : legacy;
10554
10578
  }
10555
10579
  function loopsDbPath() {
10556
10580
  return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
@@ -10734,33 +10758,6 @@ async function ingestLoops(db, verbose = false) {
10734
10758
 
10735
10759
  // src/ingest/claude-quota.ts
10736
10760
  import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
10737
-
10738
- // src/lib/paths.ts
10739
- import { homedir as homedir10 } from "os";
10740
- import { join as join12 } from "path";
10741
- function getHomeDir2() {
10742
- return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir10();
10743
- }
10744
- function agentPaths() {
10745
- const home = getHomeDir2();
10746
- return {
10747
- claudeProjects: join12(home, ".claude", "projects"),
10748
- claudeCredentials: join12(home, ".claude", ".credentials.json"),
10749
- takumiProjects: join12(home, ".takumi", "projects"),
10750
- codexDir: join12(home, ".codex"),
10751
- codexDb: join12(home, ".codex", "state_5.sqlite"),
10752
- codexAuth: join12(home, ".codex", "auth.json"),
10753
- codexConfig: join12(home, ".codex", "config.toml"),
10754
- geminiTmp: join12(home, ".gemini", "tmp"),
10755
- geminiHistory: join12(home, ".gemini", "history"),
10756
- opencodeMessages: join12(home, ".local", "share", "opencode", "storage", "message"),
10757
- piSessions: join12(home, ".pi", "agent", "sessions"),
10758
- hermesDir: join12(home, ".hermes"),
10759
- hermesDb: join12(home, ".hermes", "state.db")
10760
- };
10761
- }
10762
-
10763
- // src/ingest/claude-quota.ts
10764
10761
  var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
10765
10762
  var OAUTH_BETA = "oauth-2025-04-20";
10766
10763
  function readClaudeToken() {
@@ -4288,8 +4288,8 @@ function sourceTimeMillis(value) {
4288
4288
  init_database();
4289
4289
  init_pricing();
4290
4290
  import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync5, statSync as statSync2 } from "fs";
4291
- import { homedir as homedir3 } from "os";
4292
- import { join as join4, basename } from "path";
4291
+ import { homedir as homedir4 } from "os";
4292
+ import { join as join5, basename } from "path";
4293
4293
 
4294
4294
  // src/lib/accounts-store.ts
4295
4295
  import { existsSync as existsSync4, readFileSync } from "fs";
@@ -4912,12 +4912,41 @@ function withAccount(record, account) {
4912
4912
  };
4913
4913
  }
4914
4914
 
4915
+ // src/lib/paths.ts
4916
+ import { homedir as homedir3 } from "os";
4917
+ import { join as join4 } from "path";
4918
+ function getHomeDir2() {
4919
+ return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir3();
4920
+ }
4921
+ function agentPaths() {
4922
+ const home = getHomeDir2();
4923
+ const claudeHome = process.env["CLAUDE_CONFIG_DIR"] || join4(home, ".claude");
4924
+ const codexHome = process.env["CODEX_HOME"] || join4(home, ".codex");
4925
+ return {
4926
+ claudeProjects: join4(claudeHome, "projects"),
4927
+ claudeCredentials: join4(claudeHome, ".credentials.json"),
4928
+ takumiProjects: join4(home, ".takumi", "projects"),
4929
+ codexDir: codexHome,
4930
+ codexDb: join4(codexHome, "state_5.sqlite"),
4931
+ codexAuth: join4(codexHome, "auth.json"),
4932
+ codexConfig: join4(codexHome, "config.toml"),
4933
+ geminiTmp: join4(home, ".gemini", "tmp"),
4934
+ geminiHistory: join4(home, ".gemini", "history"),
4935
+ opencodeMessages: join4(home, ".local", "share", "opencode", "storage", "message"),
4936
+ piSessions: join4(home, ".pi", "agent", "sessions"),
4937
+ hermesDir: join4(home, ".hermes"),
4938
+ hermesDb: join4(home, ".hermes", "state.db")
4939
+ };
4940
+ }
4941
+
4915
4942
  // src/ingest/claude.ts
4916
4943
  function autoDetectProject(cwd, projects) {
4917
4944
  return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
4918
4945
  }
4919
- var CLAUDE_PROJECTS_DIR = process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? join4(homedir3(), ".claude", "projects");
4920
- var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join4(homedir3(), ".takumi", "projects");
4946
+ var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join5(homedir4(), ".takumi", "projects");
4947
+ function claudeProjectsDir() {
4948
+ return process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? agentPaths().claudeProjects;
4949
+ }
4921
4950
  function dirNameToPath(dirName) {
4922
4951
  return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
4923
4952
  }
@@ -4927,9 +4956,9 @@ function collectJsonlFiles(projectDir) {
4927
4956
  try {
4928
4957
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
4929
4958
  if (entry.isDirectory())
4930
- walk(join4(dir, entry.name));
4959
+ walk(join5(dir, entry.name));
4931
4960
  else if (entry.name.endsWith(".jsonl"))
4932
- files.push(join4(dir, entry.name));
4961
+ files.push(join5(dir, entry.name));
4933
4962
  }
4934
4963
  } catch {
4935
4964
  collectionProblem("failed", "source_unreadable");
@@ -4938,7 +4967,7 @@ function collectJsonlFiles(projectDir) {
4938
4967
  walk(projectDir);
4939
4968
  return files;
4940
4969
  }
4941
- async function ingestClaude(db, verbose = false, projectsDir = CLAUDE_PROJECTS_DIR) {
4970
+ async function ingestClaude(db, verbose = false, projectsDir = claudeProjectsDir()) {
4942
4971
  return ingestJsonlProjects(db, projectsDir, "claude", verbose);
4943
4972
  }
4944
4973
  async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_DIR) {
@@ -4960,7 +4989,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
4960
4989
  const ingestState = loadIngestState(db, agentName);
4961
4990
  const projectDirs = readdirSync2(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
4962
4991
  for (const projectDirEntry of projectDirs) {
4963
- const projectDirPath = join4(projectsDir, projectDirEntry.name);
4992
+ const projectDirPath = join5(projectsDir, projectDirEntry.name);
4964
4993
  const projectPath = dirNameToPath(projectDirEntry.name);
4965
4994
  const jsonlFiles = collectJsonlFiles(projectDirPath);
4966
4995
  for (const filePath of jsonlFiles) {
@@ -5111,24 +5140,19 @@ init_database();
5111
5140
  init_pricing();
5112
5141
  import { existsSync as existsSync6, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
5113
5142
  import { createHash as createHash4 } from "crypto";
5114
- import { homedir as homedir4 } from "os";
5115
- import { join as join5, basename as basename2 } from "path";
5116
- var DEFAULT_CODEX_DB_PATH = join5(homedir4(), ".codex", "state_5.sqlite");
5117
- var DEFAULT_CODEX_CONFIG_PATH = join5(homedir4(), ".codex", "config.toml");
5118
- var DEFAULT_CODEWITH_DB_PATH = join5(homedir4(), ".codewith", "state_5.sqlite");
5119
- var DEFAULT_CODEWITH_CONFIG_PATH = join5(homedir4(), ".codewith", "config.toml");
5143
+ import { join as join6, basename as basename2 } from "path";
5120
5144
  var CODEX_INGEST_VERSION = "rollout-events-v4";
5121
5145
  function codexDbPath() {
5122
- return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
5146
+ return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? agentPaths().codexDb;
5123
5147
  }
5124
5148
  function codexConfigPath() {
5125
- return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
5149
+ return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? agentPaths().codexConfig;
5126
5150
  }
5127
5151
  function codewithDbPath() {
5128
- return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
5152
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? join6(getHomeDir2(), ".codewith", "state_5.sqlite");
5129
5153
  }
5130
5154
  function codewithConfigPath() {
5131
- return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
5155
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? join6(getHomeDir2(), ".codewith", "config.toml");
5132
5156
  }
5133
5157
  function codexSources() {
5134
5158
  const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
@@ -5140,7 +5164,7 @@ function codexSources() {
5140
5164
  sessionPrefix: "codex",
5141
5165
  stateSource: "codex"
5142
5166
  }];
5143
- if (!explicitCodexPath || explicitCodewithPath) {
5167
+ if (!explicitCodexPath && !process.env["CODEX_HOME"] || explicitCodewithPath) {
5144
5168
  sources.push({
5145
5169
  label: "Codewith",
5146
5170
  dbPath: codewithDbPath(),
@@ -5380,9 +5404,9 @@ init_database();
5380
5404
  init_pricing();
5381
5405
  import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync7, statSync as statSync4 } from "fs";
5382
5406
  import { homedir as homedir5 } from "os";
5383
- import { join as join6, basename as basename3 } from "path";
5384
- var DEFAULT_GEMINI_TMP_DIR = join6(homedir5(), ".gemini", "tmp");
5385
- var DEFAULT_GEMINI_HISTORY_DIR = join6(homedir5(), ".gemini", "history");
5407
+ import { join as join7, basename as basename3 } from "path";
5408
+ var DEFAULT_GEMINI_TMP_DIR = join7(homedir5(), ".gemini", "tmp");
5409
+ var DEFAULT_GEMINI_HISTORY_DIR = join7(homedir5(), ".gemini", "history");
5386
5410
  function geminiTmpDir() {
5387
5411
  return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
5388
5412
  }
@@ -5404,7 +5428,7 @@ function listProjectDirs(...roots) {
5404
5428
  try {
5405
5429
  for (const entry of readdirSync3(root, { withFileTypes: true })) {
5406
5430
  if (entry.isDirectory())
5407
- dirs.add(join6(root, entry.name));
5431
+ dirs.add(join7(root, entry.name));
5408
5432
  }
5409
5433
  } catch {
5410
5434
  collectionProblem("failed", "source_unreadable");
@@ -5417,7 +5441,7 @@ function projectRoot(projectDir, chatData) {
5417
5441
  return chatData.projectPath;
5418
5442
  if (chatData.project_path)
5419
5443
  return chatData.project_path;
5420
- const rootFile = join6(projectDir, ".project_root");
5444
+ const rootFile = join7(projectDir, ".project_root");
5421
5445
  try {
5422
5446
  if (existsSync7(rootFile))
5423
5447
  return readFileSync4(rootFile, "utf-8").trim();
@@ -5442,12 +5466,12 @@ async function ingestGemini(db, verbose) {
5442
5466
  const account = await resolveAccountForAgent("gemini");
5443
5467
  const projectDirs = listProjectDirs(tmpDir, historyDir);
5444
5468
  for (const projectDir of projectDirs) {
5445
- const chatsDir = join6(projectDir, "chats");
5469
+ const chatsDir = join7(projectDir, "chats");
5446
5470
  if (!existsSync7(chatsDir))
5447
5471
  continue;
5448
5472
  let chatFiles = [];
5449
5473
  try {
5450
- chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join6(chatsDir, f));
5474
+ chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join7(chatsDir, f));
5451
5475
  } catch {
5452
5476
  collectionProblem("failed", "source_unreadable");
5453
5477
  continue;
@@ -5559,13 +5583,13 @@ init_database();
5559
5583
  init_pricing();
5560
5584
  import { existsSync as existsSync8, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
5561
5585
  import { homedir as homedir6 } from "os";
5562
- import { join as join7 } from "path";
5563
- var OPENCODE_STORAGE = join7(homedir6(), ".local", "share", "opencode", "storage");
5586
+ import { join as join8 } from "path";
5587
+ var OPENCODE_STORAGE = join8(homedir6(), ".local", "share", "opencode", "storage");
5564
5588
  function walkJsonFiles(dir, acc = []) {
5565
5589
  if (!existsSync8(dir))
5566
5590
  return acc;
5567
5591
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
5568
- const p = join7(dir, entry.name);
5592
+ const p = join8(dir, entry.name);
5569
5593
  if (entry.isDirectory())
5570
5594
  walkJsonFiles(p, acc);
5571
5595
  else if (entry.name.endsWith(".json"))
@@ -5585,7 +5609,7 @@ function parseSessionIdFromPath(filePath) {
5585
5609
  return null;
5586
5610
  }
5587
5611
  async function ingestOpenCode(db, verbose = false, sourceDir = OPENCODE_STORAGE) {
5588
- const messageDir = join7(sourceDir, "message");
5612
+ const messageDir = join8(sourceDir, "message");
5589
5613
  if (!existsSync8(messageDir))
5590
5614
  collectionProblem("missing", "source_missing");
5591
5615
  const files = walkJsonFiles(messageDir);
@@ -5780,13 +5804,13 @@ async function ingestCursor(db, verbose = false) {
5780
5804
  init_database();
5781
5805
  import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
5782
5806
  import { homedir as homedir7 } from "os";
5783
- import { join as join8 } from "path";
5784
- var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join8(homedir7(), ".pi", "agent", "sessions");
5807
+ import { join as join9 } from "path";
5808
+ var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join9(homedir7(), ".pi", "agent", "sessions");
5785
5809
  function walkSessions(dir, acc = []) {
5786
5810
  if (!existsSync9(dir))
5787
5811
  return acc;
5788
5812
  for (const entry of readdirSync5(dir, { withFileTypes: true })) {
5789
- const p = join8(dir, entry.name);
5813
+ const p = join9(dir, entry.name);
5790
5814
  if (entry.isDirectory())
5791
5815
  walkSessions(p, acc);
5792
5816
  else if (entry.name.endsWith(".json"))
@@ -5887,8 +5911,8 @@ async function ingestPi(db, verbose = false, sourceDir = PI_SESSION_DIR) {
5887
5911
  init_database();
5888
5912
  import { existsSync as existsSync10, statSync as statSync7 } from "fs";
5889
5913
  import { homedir as homedir8 } from "os";
5890
- import { join as join9 } from "path";
5891
- var HERMES_DB = join9(homedir8(), ".hermes", "state.db");
5914
+ import { join as join10 } from "path";
5915
+ var HERMES_DB = join10(homedir8(), ".hermes", "state.db");
5892
5916
  async function ingestHermes(db, verbose = false, sourcePath = HERMES_DB) {
5893
5917
  if (!existsSync10(sourcePath)) {
5894
5918
  collectionProblem("missing", "source_missing");
@@ -5958,7 +5982,7 @@ init_database();
5958
5982
  init_pricing();
5959
5983
  import { existsSync as existsSync11 } from "fs";
5960
5984
  import { homedir as homedir9 } from "os";
5961
- import { join as join10 } from "path";
5985
+ import { join as join11 } from "path";
5962
5986
  import { homedir as pathsResolverHomedir2 } from "os";
5963
5987
  import { join as pathsResolverJoin2 } from "path";
5964
5988
  var PATHS_RESOLVER_KIND_ENV2 = {
@@ -6025,8 +6049,8 @@ function defaultLoopsDbPath() {
6025
6049
  app: "loops",
6026
6050
  home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
6027
6051
  });
6028
- const legacy = join10(process.env["HOME"] || process.env["USERPROFILE"] || homedir9(), ".hasna", "loops", "loops.db");
6029
- return existsSync11(join10(resolved, "loops.db")) ? join10(resolved, "loops.db") : legacy;
6052
+ const legacy = join11(process.env["HOME"] || process.env["USERPROFILE"] || homedir9(), ".hasna", "loops", "loops.db");
6053
+ return existsSync11(join11(resolved, "loops.db")) ? join11(resolved, "loops.db") : legacy;
6030
6054
  }
6031
6055
  function loopsDbPath() {
6032
6056
  return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
@@ -6210,33 +6234,6 @@ async function ingestLoops(db, verbose = false) {
6210
6234
 
6211
6235
  // src/ingest/claude-quota.ts
6212
6236
  import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
6213
-
6214
- // src/lib/paths.ts
6215
- import { homedir as homedir10 } from "os";
6216
- import { join as join11 } from "path";
6217
- function getHomeDir2() {
6218
- return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir10();
6219
- }
6220
- function agentPaths() {
6221
- const home = getHomeDir2();
6222
- return {
6223
- claudeProjects: join11(home, ".claude", "projects"),
6224
- claudeCredentials: join11(home, ".claude", ".credentials.json"),
6225
- takumiProjects: join11(home, ".takumi", "projects"),
6226
- codexDir: join11(home, ".codex"),
6227
- codexDb: join11(home, ".codex", "state_5.sqlite"),
6228
- codexAuth: join11(home, ".codex", "auth.json"),
6229
- codexConfig: join11(home, ".codex", "config.toml"),
6230
- geminiTmp: join11(home, ".gemini", "tmp"),
6231
- geminiHistory: join11(home, ".gemini", "history"),
6232
- opencodeMessages: join11(home, ".local", "share", "opencode", "storage", "message"),
6233
- piSessions: join11(home, ".pi", "agent", "sessions"),
6234
- hermesDir: join11(home, ".hermes"),
6235
- hermesDb: join11(home, ".hermes", "state.db")
6236
- };
6237
- }
6238
-
6239
- // src/ingest/claude-quota.ts
6240
6237
  var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
6241
6238
  var OAUTH_BETA = "oauth-2025-04-20";
6242
6239
  function readClaudeToken() {
@@ -7429,7 +7426,7 @@ import { randomUUID } from "crypto";
7429
7426
  // package.json
7430
7427
  var package_default = {
7431
7428
  name: "@hasna/economy",
7432
- version: "0.5.4",
7429
+ version: "0.5.5",
7433
7430
  description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
7434
7431
  repository: {
7435
7432
  type: "git",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/economy",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "AI coding cost tracker — CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
5
5
  "repository": {
6
6
  "type": "git",