@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.
package/dist/mcp/index.js CHANGED
@@ -85,7 +85,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
85
85
  // package.json
86
86
  var package_default = {
87
87
  name: "@hasna/economy",
88
- version: "0.5.4",
88
+ version: "0.5.5",
89
89
  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",
90
90
  repository: {
91
91
  type: "git",
@@ -1556,8 +1556,8 @@ function sourceTimeMillis(value) {
1556
1556
 
1557
1557
  // src/ingest/claude.ts
1558
1558
  import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync3, statSync } from "fs";
1559
- import { homedir as homedir2 } from "os";
1560
- import { join as join6, basename } from "path";
1559
+ import { homedir as homedir3 } from "os";
1560
+ import { join as join7, basename } from "path";
1561
1561
 
1562
1562
  // src/lib/accounts-store.ts
1563
1563
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
@@ -2164,12 +2164,41 @@ function withAccount(record, account) {
2164
2164
  };
2165
2165
  }
2166
2166
 
2167
+ // src/lib/paths.ts
2168
+ import { homedir as homedir2 } from "os";
2169
+ import { join as join6 } from "path";
2170
+ function getHomeDir() {
2171
+ return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir2();
2172
+ }
2173
+ function agentPaths() {
2174
+ const home = getHomeDir();
2175
+ const claudeHome = process.env["CLAUDE_CONFIG_DIR"] || join6(home, ".claude");
2176
+ const codexHome = process.env["CODEX_HOME"] || join6(home, ".codex");
2177
+ return {
2178
+ claudeProjects: join6(claudeHome, "projects"),
2179
+ claudeCredentials: join6(claudeHome, ".credentials.json"),
2180
+ takumiProjects: join6(home, ".takumi", "projects"),
2181
+ codexDir: codexHome,
2182
+ codexDb: join6(codexHome, "state_5.sqlite"),
2183
+ codexAuth: join6(codexHome, "auth.json"),
2184
+ codexConfig: join6(codexHome, "config.toml"),
2185
+ geminiTmp: join6(home, ".gemini", "tmp"),
2186
+ geminiHistory: join6(home, ".gemini", "history"),
2187
+ opencodeMessages: join6(home, ".local", "share", "opencode", "storage", "message"),
2188
+ piSessions: join6(home, ".pi", "agent", "sessions"),
2189
+ hermesDir: join6(home, ".hermes"),
2190
+ hermesDb: join6(home, ".hermes", "state.db")
2191
+ };
2192
+ }
2193
+
2167
2194
  // src/ingest/claude.ts
2168
2195
  function autoDetectProject(cwd, projects) {
2169
2196
  return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
2170
2197
  }
2171
- var CLAUDE_PROJECTS_DIR = process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? join6(homedir2(), ".claude", "projects");
2172
- var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join6(homedir2(), ".takumi", "projects");
2198
+ var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join7(homedir3(), ".takumi", "projects");
2199
+ function claudeProjectsDir() {
2200
+ return process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? agentPaths().claudeProjects;
2201
+ }
2173
2202
  function dirNameToPath(dirName) {
2174
2203
  return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
2175
2204
  }
@@ -2179,9 +2208,9 @@ function collectJsonlFiles(projectDir) {
2179
2208
  try {
2180
2209
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
2181
2210
  if (entry.isDirectory())
2182
- walk(join6(dir, entry.name));
2211
+ walk(join7(dir, entry.name));
2183
2212
  else if (entry.name.endsWith(".jsonl"))
2184
- files.push(join6(dir, entry.name));
2213
+ files.push(join7(dir, entry.name));
2185
2214
  }
2186
2215
  } catch {
2187
2216
  collectionProblem("failed", "source_unreadable");
@@ -2190,7 +2219,7 @@ function collectJsonlFiles(projectDir) {
2190
2219
  walk(projectDir);
2191
2220
  return files;
2192
2221
  }
2193
- async function ingestClaude(db, verbose = false, projectsDir = CLAUDE_PROJECTS_DIR) {
2222
+ async function ingestClaude(db, verbose = false, projectsDir = claudeProjectsDir()) {
2194
2223
  return ingestJsonlProjects(db, projectsDir, "claude", verbose);
2195
2224
  }
2196
2225
  async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_DIR) {
@@ -2212,7 +2241,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
2212
2241
  const ingestState = loadIngestState(db, agentName);
2213
2242
  const projectDirs = readdirSync3(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
2214
2243
  for (const projectDirEntry of projectDirs) {
2215
- const projectDirPath = join6(projectsDir, projectDirEntry.name);
2244
+ const projectDirPath = join7(projectsDir, projectDirEntry.name);
2216
2245
  const projectPath = dirNameToPath(projectDirEntry.name);
2217
2246
  const jsonlFiles = collectJsonlFiles(projectDirPath);
2218
2247
  for (const filePath of jsonlFiles) {
@@ -2361,24 +2390,19 @@ function supportsClaudeDataResidencyPricing(model) {
2361
2390
  // src/ingest/codex.ts
2362
2391
  import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
2363
2392
  import { createHash as createHash3 } from "crypto";
2364
- import { homedir as homedir3 } from "os";
2365
- import { join as join7, basename as basename2 } from "path";
2366
- var DEFAULT_CODEX_DB_PATH = join7(homedir3(), ".codex", "state_5.sqlite");
2367
- var DEFAULT_CODEX_CONFIG_PATH = join7(homedir3(), ".codex", "config.toml");
2368
- var DEFAULT_CODEWITH_DB_PATH = join7(homedir3(), ".codewith", "state_5.sqlite");
2369
- var DEFAULT_CODEWITH_CONFIG_PATH = join7(homedir3(), ".codewith", "config.toml");
2393
+ import { join as join8, basename as basename2 } from "path";
2370
2394
  var CODEX_INGEST_VERSION = "rollout-events-v4";
2371
2395
  function codexDbPath() {
2372
- return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
2396
+ return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? agentPaths().codexDb;
2373
2397
  }
2374
2398
  function codexConfigPath() {
2375
- return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
2399
+ return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? agentPaths().codexConfig;
2376
2400
  }
2377
2401
  function codewithDbPath() {
2378
- return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
2402
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? join8(getHomeDir(), ".codewith", "state_5.sqlite");
2379
2403
  }
2380
2404
  function codewithConfigPath() {
2381
- return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
2405
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? join8(getHomeDir(), ".codewith", "config.toml");
2382
2406
  }
2383
2407
  function codexSources() {
2384
2408
  const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
@@ -2390,7 +2414,7 @@ function codexSources() {
2390
2414
  sessionPrefix: "codex",
2391
2415
  stateSource: "codex"
2392
2416
  }];
2393
- if (!explicitCodexPath || explicitCodewithPath) {
2417
+ if (!explicitCodexPath && !process.env["CODEX_HOME"] || explicitCodewithPath) {
2394
2418
  sources.push({
2395
2419
  label: "Codewith",
2396
2420
  dbPath: codewithDbPath(),
@@ -2628,9 +2652,9 @@ async function ingestCodex(db, verbose = false) {
2628
2652
  // src/ingest/gemini.ts
2629
2653
  import { readdirSync as readdirSync4, readFileSync as readFileSync6, existsSync as existsSync5, statSync as statSync3 } from "fs";
2630
2654
  import { homedir as homedir4 } from "os";
2631
- import { join as join8, basename as basename3 } from "path";
2632
- var DEFAULT_GEMINI_TMP_DIR = join8(homedir4(), ".gemini", "tmp");
2633
- var DEFAULT_GEMINI_HISTORY_DIR = join8(homedir4(), ".gemini", "history");
2655
+ import { join as join9, basename as basename3 } from "path";
2656
+ var DEFAULT_GEMINI_TMP_DIR = join9(homedir4(), ".gemini", "tmp");
2657
+ var DEFAULT_GEMINI_HISTORY_DIR = join9(homedir4(), ".gemini", "history");
2634
2658
  function geminiTmpDir() {
2635
2659
  return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
2636
2660
  }
@@ -2652,7 +2676,7 @@ function listProjectDirs(...roots) {
2652
2676
  try {
2653
2677
  for (const entry of readdirSync4(root, { withFileTypes: true })) {
2654
2678
  if (entry.isDirectory())
2655
- dirs.add(join8(root, entry.name));
2679
+ dirs.add(join9(root, entry.name));
2656
2680
  }
2657
2681
  } catch {
2658
2682
  collectionProblem("failed", "source_unreadable");
@@ -2665,7 +2689,7 @@ function projectRoot(projectDir, chatData) {
2665
2689
  return chatData.projectPath;
2666
2690
  if (chatData.project_path)
2667
2691
  return chatData.project_path;
2668
- const rootFile = join8(projectDir, ".project_root");
2692
+ const rootFile = join9(projectDir, ".project_root");
2669
2693
  try {
2670
2694
  if (existsSync5(rootFile))
2671
2695
  return readFileSync6(rootFile, "utf-8").trim();
@@ -2690,12 +2714,12 @@ async function ingestGemini(db, verbose) {
2690
2714
  const account = await resolveAccountForAgent("gemini");
2691
2715
  const projectDirs = listProjectDirs(tmpDir, historyDir);
2692
2716
  for (const projectDir of projectDirs) {
2693
- const chatsDir = join8(projectDir, "chats");
2717
+ const chatsDir = join9(projectDir, "chats");
2694
2718
  if (!existsSync5(chatsDir))
2695
2719
  continue;
2696
2720
  let chatFiles = [];
2697
2721
  try {
2698
- chatFiles = readdirSync4(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join8(chatsDir, f));
2722
+ chatFiles = readdirSync4(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join9(chatsDir, f));
2699
2723
  } catch {
2700
2724
  collectionProblem("failed", "source_unreadable");
2701
2725
  continue;
@@ -2805,13 +2829,13 @@ async function ingestGemini(db, verbose) {
2805
2829
  // src/ingest/opencode.ts
2806
2830
  import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
2807
2831
  import { homedir as homedir5 } from "os";
2808
- import { join as join9 } from "path";
2809
- var OPENCODE_STORAGE = join9(homedir5(), ".local", "share", "opencode", "storage");
2832
+ import { join as join10 } from "path";
2833
+ var OPENCODE_STORAGE = join10(homedir5(), ".local", "share", "opencode", "storage");
2810
2834
  function walkJsonFiles(dir, acc = []) {
2811
2835
  if (!existsSync6(dir))
2812
2836
  return acc;
2813
2837
  for (const entry of readdirSync5(dir, { withFileTypes: true })) {
2814
- const p = join9(dir, entry.name);
2838
+ const p = join10(dir, entry.name);
2815
2839
  if (entry.isDirectory())
2816
2840
  walkJsonFiles(p, acc);
2817
2841
  else if (entry.name.endsWith(".json"))
@@ -2831,7 +2855,7 @@ function parseSessionIdFromPath(filePath) {
2831
2855
  return null;
2832
2856
  }
2833
2857
  async function ingestOpenCode(db, verbose = false, sourceDir = OPENCODE_STORAGE) {
2834
- const messageDir = join9(sourceDir, "message");
2858
+ const messageDir = join10(sourceDir, "message");
2835
2859
  if (!existsSync6(messageDir))
2836
2860
  collectionProblem("missing", "source_missing");
2837
2861
  const files = walkJsonFiles(messageDir);
@@ -3023,13 +3047,13 @@ async function ingestCursor(db, verbose = false) {
3023
3047
  // src/ingest/pi.ts
3024
3048
  import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
3025
3049
  import { homedir as homedir6 } from "os";
3026
- import { join as join10 } from "path";
3027
- var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join10(homedir6(), ".pi", "agent", "sessions");
3050
+ import { join as join11 } from "path";
3051
+ var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join11(homedir6(), ".pi", "agent", "sessions");
3028
3052
  function walkSessions(dir, acc = []) {
3029
3053
  if (!existsSync7(dir))
3030
3054
  return acc;
3031
3055
  for (const entry of readdirSync6(dir, { withFileTypes: true })) {
3032
- const p = join10(dir, entry.name);
3056
+ const p = join11(dir, entry.name);
3033
3057
  if (entry.isDirectory())
3034
3058
  walkSessions(p, acc);
3035
3059
  else if (entry.name.endsWith(".json"))
@@ -3129,8 +3153,8 @@ async function ingestPi(db, verbose = false, sourceDir = PI_SESSION_DIR) {
3129
3153
  // src/ingest/hermes.ts
3130
3154
  import { existsSync as existsSync8, statSync as statSync6 } from "fs";
3131
3155
  import { homedir as homedir7 } from "os";
3132
- import { join as join11 } from "path";
3133
- var HERMES_DB = join11(homedir7(), ".hermes", "state.db");
3156
+ import { join as join12 } from "path";
3157
+ var HERMES_DB = join12(homedir7(), ".hermes", "state.db");
3134
3158
  async function ingestHermes(db, verbose = false, sourcePath = HERMES_DB) {
3135
3159
  if (!existsSync8(sourcePath)) {
3136
3160
  collectionProblem("missing", "source_missing");
@@ -3198,7 +3222,7 @@ function statSyncSafe(path) {
3198
3222
  // src/ingest/loops.ts
3199
3223
  import { existsSync as existsSync9 } from "fs";
3200
3224
  import { homedir as homedir8 } from "os";
3201
- import { join as join12 } from "path";
3225
+ import { join as join13 } from "path";
3202
3226
  import { homedir as pathsResolverHomedir } from "os";
3203
3227
  import { join as pathsResolverJoin } from "path";
3204
3228
  var PATHS_RESOLVER_KIND_ENV = {
@@ -3265,8 +3289,8 @@ function defaultLoopsDbPath() {
3265
3289
  app: "loops",
3266
3290
  home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
3267
3291
  });
3268
- const legacy = join12(process.env["HOME"] || process.env["USERPROFILE"] || homedir8(), ".hasna", "loops", "loops.db");
3269
- return existsSync9(join12(resolved, "loops.db")) ? join12(resolved, "loops.db") : legacy;
3292
+ const legacy = join13(process.env["HOME"] || process.env["USERPROFILE"] || homedir8(), ".hasna", "loops", "loops.db");
3293
+ return existsSync9(join13(resolved, "loops.db")) ? join13(resolved, "loops.db") : legacy;
3270
3294
  }
3271
3295
  function loopsDbPath() {
3272
3296
  return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
@@ -3450,33 +3474,6 @@ async function ingestLoops(db, verbose = false) {
3450
3474
 
3451
3475
  // src/ingest/claude-quota.ts
3452
3476
  import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
3453
-
3454
- // src/lib/paths.ts
3455
- import { homedir as homedir9 } from "os";
3456
- import { join as join13 } from "path";
3457
- function getHomeDir() {
3458
- return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir9();
3459
- }
3460
- function agentPaths() {
3461
- const home = getHomeDir();
3462
- return {
3463
- claudeProjects: join13(home, ".claude", "projects"),
3464
- claudeCredentials: join13(home, ".claude", ".credentials.json"),
3465
- takumiProjects: join13(home, ".takumi", "projects"),
3466
- codexDir: join13(home, ".codex"),
3467
- codexDb: join13(home, ".codex", "state_5.sqlite"),
3468
- codexAuth: join13(home, ".codex", "auth.json"),
3469
- codexConfig: join13(home, ".codex", "config.toml"),
3470
- geminiTmp: join13(home, ".gemini", "tmp"),
3471
- geminiHistory: join13(home, ".gemini", "history"),
3472
- opencodeMessages: join13(home, ".local", "share", "opencode", "storage", "message"),
3473
- piSessions: join13(home, ".pi", "agent", "sessions"),
3474
- hermesDir: join13(home, ".hermes"),
3475
- hermesDb: join13(home, ".hermes", "state.db")
3476
- };
3477
- }
3478
-
3479
- // src/ingest/claude-quota.ts
3480
3477
  var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
3481
3478
  var OAUTH_BETA = "oauth-2025-04-20";
3482
3479
  function readClaudeToken() {
@@ -2587,33 +2587,26 @@ var BUILTIN_TOOLS = [
2587
2587
  ];
2588
2588
 
2589
2589
  // src/ingest/claude.ts
2590
- var CLAUDE_PROJECTS_DIR = process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? join3(homedir3(), ".claude", "projects");
2591
2590
  var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join3(homedir3(), ".takumi", "projects");
2592
2591
 
2593
2592
  // src/ingest/codex.ts
2594
2593
  init_database();
2595
2594
  init_pricing();
2596
- import { homedir as homedir4 } from "os";
2597
- import { join as join4, basename as basename2 } from "path";
2598
- var DEFAULT_CODEX_DB_PATH = join4(homedir4(), ".codex", "state_5.sqlite");
2599
- var DEFAULT_CODEX_CONFIG_PATH = join4(homedir4(), ".codex", "config.toml");
2600
- var DEFAULT_CODEWITH_DB_PATH = join4(homedir4(), ".codewith", "state_5.sqlite");
2601
- var DEFAULT_CODEWITH_CONFIG_PATH = join4(homedir4(), ".codewith", "config.toml");
2602
2595
 
2603
2596
  // src/ingest/gemini.ts
2604
2597
  init_database();
2605
2598
  init_pricing();
2606
- import { homedir as homedir5 } from "os";
2607
- import { join as join5, basename as basename3 } from "path";
2608
- var DEFAULT_GEMINI_TMP_DIR = join5(homedir5(), ".gemini", "tmp");
2609
- var DEFAULT_GEMINI_HISTORY_DIR = join5(homedir5(), ".gemini", "history");
2599
+ import { homedir as homedir4 } from "os";
2600
+ import { join as join4, basename as basename2 } from "path";
2601
+ var DEFAULT_GEMINI_TMP_DIR = join4(homedir4(), ".gemini", "tmp");
2602
+ var DEFAULT_GEMINI_HISTORY_DIR = join4(homedir4(), ".gemini", "history");
2610
2603
 
2611
2604
  // src/ingest/opencode.ts
2612
2605
  init_database();
2613
2606
  init_pricing();
2614
- import { homedir as homedir6 } from "os";
2615
- import { join as join6 } from "path";
2616
- var OPENCODE_STORAGE = join6(homedir6(), ".local", "share", "opencode", "storage");
2607
+ import { homedir as homedir5 } from "os";
2608
+ import { join as join5 } from "path";
2609
+ var OPENCODE_STORAGE = join5(homedir5(), ".local", "share", "opencode", "storage");
2617
2610
 
2618
2611
  // src/ingest/quota-observation.ts
2619
2612
  init_database();
@@ -2621,15 +2614,15 @@ init_observation_provenance();
2621
2614
 
2622
2615
  // src/ingest/pi.ts
2623
2616
  init_database();
2624
- import { homedir as homedir7 } from "os";
2625
- import { join as join7 } from "path";
2626
- var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join7(homedir7(), ".pi", "agent", "sessions");
2617
+ import { homedir as homedir6 } from "os";
2618
+ import { join as join6 } from "path";
2619
+ var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join6(homedir6(), ".pi", "agent", "sessions");
2627
2620
 
2628
2621
  // src/ingest/hermes.ts
2629
2622
  init_database();
2630
- import { homedir as homedir8 } from "os";
2631
- import { join as join8 } from "path";
2632
- var HERMES_DB = join8(homedir8(), ".hermes", "state.db");
2623
+ import { homedir as homedir7 } from "os";
2624
+ import { join as join7 } from "path";
2625
+ var HERMES_DB = join7(homedir7(), ".hermes", "state.db");
2633
2626
 
2634
2627
  // src/ingest/loops.ts
2635
2628
  init_database();
@@ -2702,7 +2695,7 @@ init_ingest_validation();
2702
2695
  init_database();
2703
2696
  import { constants, closeSync, fsyncSync, fstatSync, lstatSync, mkdirSync as mkdirSync3, openSync, readFileSync, opendirSync, realpathSync, writeFileSync } from "fs";
2704
2697
  import { createHash as createHash4 } from "crypto";
2705
- import { join as join9, resolve as resolve2 } from "path";
2698
+ import { join as join8, resolve as resolve2 } from "path";
2706
2699
  var MAX_COMMANDS = 1000;
2707
2700
  var MAX_BYTES = 64 * 1024 * 1024;
2708
2701
  var RECEIPT_RESERVE = 4096;
@@ -2730,7 +2723,7 @@ function definitiveIngestRefusal(status, body) {
2730
2723
  };
2731
2724
  }
2732
2725
  function getIngestJournalPath() {
2733
- return process.env.HASNA_ECONOMY_INGEST_JOURNAL ?? join9(cacheDir({ app: "economy", home: process.env.HOME || process.env.USERPROFILE }), "pending-ingest");
2726
+ return process.env.HASNA_ECONOMY_INGEST_JOURNAL ?? join8(cacheDir({ app: "economy", home: process.env.HOME || process.env.USERPROFILE }), "pending-ingest");
2734
2727
  }
2735
2728
  function targetId(cloud) {
2736
2729
  const url = new URL(cloud.client.transport.baseUrl);
@@ -2833,7 +2826,7 @@ class IngestJournal {
2833
2826
  constructor(cloud, path = getIngestJournalPath()) {
2834
2827
  this.target = targetId(cloud);
2835
2828
  secureDirectory(path);
2836
- this.directory = join9(realpathSync(path), this.target);
2829
+ this.directory = join8(realpathSync(path), this.target);
2837
2830
  secureDirectory(this.directory);
2838
2831
  }
2839
2832
  names() {
@@ -2859,7 +2852,7 @@ class IngestJournal {
2859
2852
  throw new Error("Orphaned ingest outcome requires maintenance recovery");
2860
2853
  if (name.endsWith(".refusal.json") && set.has(name.replace(".refusal.", ".receipt.")))
2861
2854
  throw new Error("Conflicting ingest receipt and refusal");
2862
- const stat = lstatSync(join9(this.directory, name));
2855
+ const stat = lstatSync(join8(this.directory, name));
2863
2856
  if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 63) !== 0)
2864
2857
  throw new Error("Unsafe ingest journal record");
2865
2858
  if (stat.size > MAX_INGEST_BYTES + 4096)
@@ -2871,10 +2864,10 @@ class IngestJournal {
2871
2864
  return names;
2872
2865
  }
2873
2866
  file(key, kind) {
2874
- return join9(this.directory, `${sha(key)}.${kind}.json`);
2867
+ return join8(this.directory, `${sha(key)}.${kind}.json`);
2875
2868
  }
2876
2869
  intent(name) {
2877
- const record = readJson(join9(this.directory, name), MAX_INGEST_BYTES + 4096);
2870
+ const record = readJson(join8(this.directory, name), MAX_INGEST_BYTES + 4096);
2878
2871
  if (record.version !== 1 || record.target !== this.target || !record.command || typeof record.command.idempotency_key !== "string" || record.body_hash !== canonicalIngestBodyHash(record.command) || name !== `${sha(record.command.idempotency_key)}.intent.json`)
2879
2872
  throw new Error("Invalid ingest journal intent");
2880
2873
  validateIngest(record.command);
@@ -3033,7 +3026,7 @@ class IngestJournal {
3033
3026
  // src/lib/ingest-journal-maintenance.ts
3034
3027
  import { constants as constants2, closeSync as closeSync2, fstatSync as fstatSync2, lstatSync as lstatSync2, openSync as openSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
3035
3028
  import { createHash as createHash5 } from "crypto";
3036
- import { join as join10 } from "path";
3029
+ import { join as join9 } from "path";
3037
3030
  var uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
3038
3031
  var digest2 = /^[a-f0-9]{64}$/;
3039
3032
  var sha2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
@@ -3093,7 +3086,7 @@ async function confirm(cloud, anchor, deadline) {
3093
3086
  }
3094
3087
  async function verifyJournalAuthority(journal, cloud) {
3095
3088
  journal.assertCloud(cloud);
3096
- const path = join10(journal.directory, ".authority.json");
3089
+ const path = join9(journal.directory, ".authority.json");
3097
3090
  if (!exists(path)) {
3098
3091
  const names = journal.names();
3099
3092
  const name = names.find((n) => n.endsWith(".intent.json") && names.includes(n.replace(".intent.", ".receipt.")));
@@ -3137,8 +3130,8 @@ class IngestJournalMaintenance {
3137
3130
  this.cloud = cloud;
3138
3131
  this.factory = factory;
3139
3132
  journal.assertCloud(cloud);
3140
- this.control = join10(journal.directory, ".maintenance");
3141
- this.operationRoot = join10(journal.directory, ".recovery");
3133
+ this.control = join9(journal.directory, ".maintenance");
3134
+ this.operationRoot = join9(journal.directory, ".recovery");
3142
3135
  }
3143
3136
  manifest() {
3144
3137
  secureDirectory(this.control);
@@ -3147,10 +3140,10 @@ class IngestJournalMaintenance {
3147
3140
  throw new Error("Unexpected maintenance entry");
3148
3141
  let size = 0;
3149
3142
  for (const name of names)
3150
- size += lstatSync2(join10(this.control, name)).size;
3143
+ size += lstatSync2(join9(this.control, name)).size;
3151
3144
  if (size > MAINTENANCE_RESERVE)
3152
3145
  throw new Error("Maintenance metadata exceeds reserve");
3153
- const m = validAnchor(readJson(join10(this.control, "manifest.json"), 8192), this.journal.target);
3146
+ const m = validAnchor(readJson(join9(this.control, "manifest.json"), 8192), this.journal.target);
3154
3147
  for (const part of [m.intent, m.receipt_file])
3155
3148
  if (!part || !digest2.test(part.sha256) || !digest2.test(part.capture_sha256 ?? "") || !Number.isSafeInteger(part.sizeBytes) || part.sizeBytes < 1 || part.sizeBytes > 4198400 || part.mode !== 384 || !/^\d+$/.test(part.device) || !/^\d+$/.test(part.inode) || !Number.isFinite(part.ctime))
3156
3149
  throw new Error("Invalid maintenance snapshot");
@@ -3175,7 +3168,7 @@ class IngestJournalMaintenance {
3175
3168
  if (!operation.receipt || operation.receipt.sha256 !== expected.capture_sha256 || operation.receipt.sizeBytes !== expected.sizeBytes || operation.receipt.mode !== expected.mode)
3176
3169
  throw new Error("Trash snapshot mismatch");
3177
3170
  sameSource(operation.path, expected);
3178
- const path = join10(this.control, `${kind}.operation.json`);
3171
+ const path = join9(this.control, `${kind}.operation.json`);
3179
3172
  createExact(path, { id: operation.id });
3180
3173
  if (readJson(path, 1024).id !== operation.id)
3181
3174
  throw new Error("Conflicting Trash operation");
@@ -3200,7 +3193,7 @@ class IngestJournalMaintenance {
3200
3193
  async pairFile(backend, m, kind, deadline) {
3201
3194
  await confirm(this.cloud, m, deadline);
3202
3195
  const path = this.journal.file(m.key, kind), expected = kind === "receipt" ? m.receipt_file : m.intent;
3203
- const saved = join10(this.control, `${kind}.operation.json`);
3196
+ const saved = join9(this.control, `${kind}.operation.json`);
3204
3197
  let id;
3205
3198
  if (exists(saved))
3206
3199
  id = readJson(saved, 1024).id;
@@ -3306,14 +3299,14 @@ class IngestJournalMaintenance {
3306
3299
  key: intent.command.idempotency_key,
3307
3300
  body_hash: intent.body_hash,
3308
3301
  receipt,
3309
- intent: snapshot(join10(this.journal.directory, name)),
3302
+ intent: snapshot(join9(this.journal.directory, name)),
3310
3303
  receipt_file: snapshot(this.journal.file(intent.command.idempotency_key, "receipt"))
3311
3304
  };
3312
3305
  await confirm(this.cloud, m, deadline);
3313
3306
  m.intent.capture_sha256 = backend.snapshot(this.journal.file(m.key, "intent")).sha256;
3314
3307
  m.receipt_file.capture_sha256 = backend.snapshot(this.journal.file(m.key, "receipt")).sha256;
3315
3308
  secureDirectory(this.control);
3316
- if (!createExact(join10(this.control, "manifest.json"), m))
3309
+ if (!createExact(join9(this.control, "manifest.json"), m))
3317
3310
  throw new Error("Existing maintenance transaction");
3318
3311
  await this.finish(backend, deadline);
3319
3312
  retired++;
@@ -3345,7 +3338,7 @@ class IngestJournalMaintenance {
3345
3338
 
3346
3339
  // src/lib/ingest-journal-lock.ts
3347
3340
  import { constants as constants3, closeSync as closeSync3, fstatSync as fstatSync3, lstatSync as lstatSync3, openSync as openSync3 } from "fs";
3348
- import { join as join11 } from "path";
3341
+ import { join as join10 } from "path";
3349
3342
  async function withIngestJournalLock(directory, action) {
3350
3343
  if (process.platform !== "linux" && process.platform !== "darwin")
3351
3344
  throw new Error("Ingest journal locking requires Linux or macOS");
@@ -3353,7 +3346,7 @@ async function withIngestJournalLock(directory, action) {
3353
3346
  const library = dlopen(process.platform === "darwin" ? "/usr/lib/libSystem.B.dylib" : "libc.so.6", {
3354
3347
  flock: { args: [FFIType.i32, FFIType.i32], returns: FFIType.i32 }
3355
3348
  });
3356
- const path = join11(directory, ".lease");
3349
+ const path = join10(directory, ".lease");
3357
3350
  let fd;
3358
3351
  try {
3359
3352
  fd = openSync3(path, constants3.O_RDWR | constants3.O_CREAT | constants3.O_NOFOLLOW | constants3.O_NONBLOCK, 384);
@@ -3487,7 +3480,7 @@ async function pushIngestRows(cloud, scratch) {
3487
3480
  // package.json
3488
3481
  var package_default = {
3489
3482
  name: "@hasna/economy",
3490
- version: "0.5.4",
3483
+ version: "0.5.5",
3491
3484
  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",
3492
3485
  repository: {
3493
3486
  type: "git",