@hasna/economy 0.2.36 → 0.2.37

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/README.md CHANGED
@@ -102,7 +102,7 @@ economy sync --recalculate
102
102
  economy sync --backfill-machine
103
103
  ```
104
104
 
105
- Full sync also imports active project metadata from `@hasna/projects` when the registry is available.
105
+ Full sync also imports active project metadata from `@hasna/projects` when the registry is available. The Codex source reads both legacy `~/.codex/state_5.sqlite` and current Codewith `~/.codewith/state_5.sqlite` usage stores by default; explicit `HASNA_ECONOMY_CODEX_DB_PATH` and `HASNA_ECONOMY_CODEWITH_DB_PATH` values override those locations.
106
106
 
107
107
  Account attribution is automatic when `@hasna/accounts` has a matching active, applied, or env-dir profile for the agent. Account identity is the email address plus coding agent, so `work@example.com` under Codex and Claude is reported as two accounts. You can also force attribution for a process with `ECONOMY_ACCOUNT=tool:name` or agent-specific overrides such as `ECONOMY_CODEX_ACCOUNT=codex:work`.
108
108
 
package/dist/cli/index.js CHANGED
@@ -2766,8 +2766,40 @@ function codexDbPath() {
2766
2766
  function codexConfigPath() {
2767
2767
  return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
2768
2768
  }
2769
- function readCodexModel() {
2770
- const configPath = codexConfigPath();
2769
+ function codewithDbPath() {
2770
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
2771
+ }
2772
+ function codewithConfigPath() {
2773
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
2774
+ }
2775
+ function codexSources() {
2776
+ const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
2777
+ const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
2778
+ const sources = [{
2779
+ label: "Codex",
2780
+ dbPath: codexDbPath(),
2781
+ configPath: codexConfigPath(),
2782
+ sessionPrefix: "codex",
2783
+ stateSource: "codex"
2784
+ }];
2785
+ if (!explicitCodexPath || explicitCodewithPath) {
2786
+ sources.push({
2787
+ label: "Codewith",
2788
+ dbPath: codewithDbPath(),
2789
+ configPath: codewithConfigPath(),
2790
+ sessionPrefix: "codex-codewith",
2791
+ stateSource: "codex-codewith"
2792
+ });
2793
+ }
2794
+ const seen = new Set;
2795
+ return sources.filter((source) => {
2796
+ if (seen.has(source.dbPath))
2797
+ return false;
2798
+ seen.add(source.dbPath);
2799
+ return true;
2800
+ });
2801
+ }
2802
+ function readCodexModel(configPath = codexConfigPath()) {
2771
2803
  if (!existsSync5(configPath))
2772
2804
  return "gpt-5-codex";
2773
2805
  try {
@@ -2885,94 +2917,97 @@ function fallbackEvents(totalTokens) {
2885
2917
  }];
2886
2918
  }
2887
2919
  async function ingestCodex(db, verbose = false) {
2888
- const dbPath = codexDbPath();
2889
- if (!existsSync5(dbPath)) {
2890
- if (verbose)
2891
- console.log("Codex DB not found:", dbPath);
2892
- return { sessions: 0, requests: 0 };
2893
- }
2894
2920
  const machineId = getMachineId();
2895
- let codexDb = null;
2896
2921
  let ingested = 0;
2897
2922
  let requests = 0;
2898
2923
  const account = await resolveAccountForAgent("codex");
2899
- try {
2900
- codexDb = openCodexDb(dbPath, verbose);
2901
- if (!codexDb)
2902
- return { sessions: 0, requests: 0 };
2903
- const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2904
- for (const thread of threads) {
2905
- const model = thread.model ?? readCodexModel();
2906
- const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2907
- const processed = getIngestState(db, "codex", thread.id);
2908
- if (processed === stateValue)
2924
+ for (const source of codexSources()) {
2925
+ if (!existsSync5(source.dbPath)) {
2926
+ if (verbose)
2927
+ console.log(`${source.label} DB not found:`, source.dbPath);
2928
+ continue;
2929
+ }
2930
+ let codexDb = null;
2931
+ try {
2932
+ codexDb = openCodexDb(source.dbPath, verbose);
2933
+ if (!codexDb)
2909
2934
  continue;
2910
- const projectPath = thread.cwd ?? "";
2911
- const projectName = projectPath ? basename2(projectPath) : "unknown";
2912
- const sessionId = `codex-${thread.id}`;
2913
- const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2914
- const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2915
- upsertSession(db, withAccount({
2916
- id: sessionId,
2917
- agent: "codex",
2918
- project_path: projectPath,
2919
- project_name: projectName,
2920
- started_at: startedAt,
2921
- ended_at: endedAt,
2922
- total_cost_usd: 0,
2923
- total_tokens: 0,
2924
- request_count: 0,
2925
- machine_id: machineId
2926
- }, account));
2927
- const events = readTokenEvents(thread.rollout_path);
2928
- const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2929
- const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2930
- db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2931
- tokenEvents.forEach((event, index) => {
2932
- const usage = event.usage;
2933
- const inputTotal = usage.input_tokens ?? 0;
2934
- const cacheReadTokens = usage.cached_input_tokens ?? 0;
2935
- const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2936
- const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2937
- const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2938
- const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2939
- const requestId = `${sessionId}-${index}`;
2940
- upsertRequest(db, withAccount({
2941
- id: requestId,
2935
+ const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2936
+ for (const thread of threads) {
2937
+ const model = thread.model ?? readCodexModel(source.configPath);
2938
+ const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2939
+ const processed = getIngestState(db, source.stateSource, thread.id);
2940
+ if (processed === stateValue)
2941
+ continue;
2942
+ const projectPath = thread.cwd ?? "";
2943
+ const projectName = projectPath ? basename2(projectPath) : "unknown";
2944
+ const sessionId = `${source.sessionPrefix}-${thread.id}`;
2945
+ const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2946
+ const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2947
+ upsertSession(db, withAccount({
2948
+ id: sessionId,
2942
2949
  agent: "codex",
2943
- session_id: sessionId,
2944
- model,
2945
- input_tokens: inputTokens,
2946
- output_tokens: outputTokens,
2947
- cache_read_tokens: cacheReadTokens,
2948
- cache_create_tokens: 0,
2949
- cost_usd: costUsd,
2950
- cost_basis: defaultCostBasisForAgent("codex"),
2951
- duration_ms: 0,
2952
- timestamp,
2953
- source_request_id: requestId,
2950
+ project_path: projectPath,
2951
+ project_name: projectName,
2952
+ started_at: startedAt,
2953
+ ended_at: endedAt,
2954
+ total_cost_usd: 0,
2955
+ total_tokens: 0,
2956
+ request_count: 0,
2954
2957
  machine_id: machineId
2955
2958
  }, account));
2956
- requests++;
2957
- });
2958
- rollupSession(db, sessionId);
2959
- setIngestState(db, "codex", thread.id, stateValue);
2960
- ingested++;
2961
- if (verbose)
2962
- console.log(`Codex session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2959
+ const events = readTokenEvents(thread.rollout_path);
2960
+ const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2961
+ const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2962
+ db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2963
+ tokenEvents.forEach((event, index) => {
2964
+ const usage = event.usage;
2965
+ const inputTotal = usage.input_tokens ?? 0;
2966
+ const cacheReadTokens = usage.cached_input_tokens ?? 0;
2967
+ const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2968
+ const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2969
+ const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2970
+ const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2971
+ const requestId = `${sessionId}-${index}`;
2972
+ upsertRequest(db, withAccount({
2973
+ id: requestId,
2974
+ agent: "codex",
2975
+ session_id: sessionId,
2976
+ model,
2977
+ input_tokens: inputTokens,
2978
+ output_tokens: outputTokens,
2979
+ cache_read_tokens: cacheReadTokens,
2980
+ cache_create_tokens: 0,
2981
+ cost_usd: costUsd,
2982
+ cost_basis: defaultCostBasisForAgent("codex"),
2983
+ duration_ms: 0,
2984
+ timestamp,
2985
+ source_request_id: requestId,
2986
+ machine_id: machineId
2987
+ }, account));
2988
+ requests++;
2989
+ });
2990
+ rollupSession(db, sessionId);
2991
+ setIngestState(db, source.stateSource, thread.id, stateValue);
2992
+ ingested++;
2993
+ if (verbose)
2994
+ console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2995
+ }
2996
+ } finally {
2997
+ codexDb?.close();
2963
2998
  }
2964
- } finally {
2965
- codexDb?.close();
2966
2999
  }
2967
3000
  return { sessions: ingested, requests };
2968
3001
  }
2969
- var DEFAULT_CODEX_DB_PATH, DEFAULT_CODEX_CONFIG_PATH, CODEX_INGEST_VERSION = "rollout-aggregate-v3";
3002
+ var DEFAULT_CODEX_DB_PATH, DEFAULT_CODEX_CONFIG_PATH, DEFAULT_CODEWITH_DB_PATH, DEFAULT_CODEWITH_CONFIG_PATH, CODEX_INGEST_VERSION = "rollout-aggregate-v3";
2970
3003
  var init_codex = __esm(() => {
2971
3004
  init_database();
2972
3005
  init_pricing();
2973
3006
  init_accounts();
2974
3007
  DEFAULT_CODEX_DB_PATH = join7(homedir4(), ".codex", "state_5.sqlite");
2975
3008
  DEFAULT_CODEX_CONFIG_PATH = join7(homedir4(), ".codex", "config.toml");
3009
+ DEFAULT_CODEWITH_DB_PATH = join7(homedir4(), ".codewith", "state_5.sqlite");
3010
+ DEFAULT_CODEWITH_CONFIG_PATH = join7(homedir4(), ".codewith", "config.toml");
2976
3011
  });
2977
3012
 
2978
3013
  // src/ingest/gemini.ts
package/dist/index.js CHANGED
@@ -2496,6 +2496,8 @@ import { join as join4, basename as basename2 } from "path";
2496
2496
  import { Database as BunDatabase2 } from "bun:sqlite";
2497
2497
  var DEFAULT_CODEX_DB_PATH = join4(homedir3(), ".codex", "state_5.sqlite");
2498
2498
  var DEFAULT_CODEX_CONFIG_PATH = join4(homedir3(), ".codex", "config.toml");
2499
+ var DEFAULT_CODEWITH_DB_PATH = join4(homedir3(), ".codewith", "state_5.sqlite");
2500
+ var DEFAULT_CODEWITH_CONFIG_PATH = join4(homedir3(), ".codewith", "config.toml");
2499
2501
  var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
2500
2502
  function codexDbPath() {
2501
2503
  return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
@@ -2503,8 +2505,40 @@ function codexDbPath() {
2503
2505
  function codexConfigPath() {
2504
2506
  return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
2505
2507
  }
2506
- function readCodexModel() {
2507
- const configPath = codexConfigPath();
2508
+ function codewithDbPath() {
2509
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
2510
+ }
2511
+ function codewithConfigPath() {
2512
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
2513
+ }
2514
+ function codexSources() {
2515
+ const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
2516
+ const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
2517
+ const sources = [{
2518
+ label: "Codex",
2519
+ dbPath: codexDbPath(),
2520
+ configPath: codexConfigPath(),
2521
+ sessionPrefix: "codex",
2522
+ stateSource: "codex"
2523
+ }];
2524
+ if (!explicitCodexPath || explicitCodewithPath) {
2525
+ sources.push({
2526
+ label: "Codewith",
2527
+ dbPath: codewithDbPath(),
2528
+ configPath: codewithConfigPath(),
2529
+ sessionPrefix: "codex-codewith",
2530
+ stateSource: "codex-codewith"
2531
+ });
2532
+ }
2533
+ const seen = new Set;
2534
+ return sources.filter((source) => {
2535
+ if (seen.has(source.dbPath))
2536
+ return false;
2537
+ seen.add(source.dbPath);
2538
+ return true;
2539
+ });
2540
+ }
2541
+ function readCodexModel(configPath = codexConfigPath()) {
2508
2542
  if (!existsSync5(configPath))
2509
2543
  return "gpt-5-codex";
2510
2544
  try {
@@ -2622,84 +2656,85 @@ function fallbackEvents(totalTokens) {
2622
2656
  }];
2623
2657
  }
2624
2658
  async function ingestCodex(db, verbose = false) {
2625
- const dbPath = codexDbPath();
2626
- if (!existsSync5(dbPath)) {
2627
- if (verbose)
2628
- console.log("Codex DB not found:", dbPath);
2629
- return { sessions: 0, requests: 0 };
2630
- }
2631
2659
  const machineId = getMachineId();
2632
- let codexDb = null;
2633
2660
  let ingested = 0;
2634
2661
  let requests = 0;
2635
2662
  const account = await resolveAccountForAgent("codex");
2636
- try {
2637
- codexDb = openCodexDb(dbPath, verbose);
2638
- if (!codexDb)
2639
- return { sessions: 0, requests: 0 };
2640
- const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2641
- for (const thread of threads) {
2642
- const model = thread.model ?? readCodexModel();
2643
- const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2644
- const processed = getIngestState(db, "codex", thread.id);
2645
- if (processed === stateValue)
2663
+ for (const source of codexSources()) {
2664
+ if (!existsSync5(source.dbPath)) {
2665
+ if (verbose)
2666
+ console.log(`${source.label} DB not found:`, source.dbPath);
2667
+ continue;
2668
+ }
2669
+ let codexDb = null;
2670
+ try {
2671
+ codexDb = openCodexDb(source.dbPath, verbose);
2672
+ if (!codexDb)
2646
2673
  continue;
2647
- const projectPath = thread.cwd ?? "";
2648
- const projectName = projectPath ? basename2(projectPath) : "unknown";
2649
- const sessionId = `codex-${thread.id}`;
2650
- const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2651
- const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2652
- upsertSession(db, withAccount({
2653
- id: sessionId,
2654
- agent: "codex",
2655
- project_path: projectPath,
2656
- project_name: projectName,
2657
- started_at: startedAt,
2658
- ended_at: endedAt,
2659
- total_cost_usd: 0,
2660
- total_tokens: 0,
2661
- request_count: 0,
2662
- machine_id: machineId
2663
- }, account));
2664
- const events = readTokenEvents(thread.rollout_path);
2665
- const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2666
- const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2667
- db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2668
- tokenEvents.forEach((event, index) => {
2669
- const usage = event.usage;
2670
- const inputTotal = usage.input_tokens ?? 0;
2671
- const cacheReadTokens = usage.cached_input_tokens ?? 0;
2672
- const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2673
- const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2674
- const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2675
- const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2676
- const requestId = `${sessionId}-${index}`;
2677
- upsertRequest(db, withAccount({
2678
- id: requestId,
2674
+ const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2675
+ for (const thread of threads) {
2676
+ const model = thread.model ?? readCodexModel(source.configPath);
2677
+ const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2678
+ const processed = getIngestState(db, source.stateSource, thread.id);
2679
+ if (processed === stateValue)
2680
+ continue;
2681
+ const projectPath = thread.cwd ?? "";
2682
+ const projectName = projectPath ? basename2(projectPath) : "unknown";
2683
+ const sessionId = `${source.sessionPrefix}-${thread.id}`;
2684
+ const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2685
+ const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2686
+ upsertSession(db, withAccount({
2687
+ id: sessionId,
2679
2688
  agent: "codex",
2680
- session_id: sessionId,
2681
- model,
2682
- input_tokens: inputTokens,
2683
- output_tokens: outputTokens,
2684
- cache_read_tokens: cacheReadTokens,
2685
- cache_create_tokens: 0,
2686
- cost_usd: costUsd,
2687
- cost_basis: defaultCostBasisForAgent("codex"),
2688
- duration_ms: 0,
2689
- timestamp,
2690
- source_request_id: requestId,
2689
+ project_path: projectPath,
2690
+ project_name: projectName,
2691
+ started_at: startedAt,
2692
+ ended_at: endedAt,
2693
+ total_cost_usd: 0,
2694
+ total_tokens: 0,
2695
+ request_count: 0,
2691
2696
  machine_id: machineId
2692
2697
  }, account));
2693
- requests++;
2694
- });
2695
- rollupSession(db, sessionId);
2696
- setIngestState(db, "codex", thread.id, stateValue);
2697
- ingested++;
2698
- if (verbose)
2699
- console.log(`Codex session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2698
+ const events = readTokenEvents(thread.rollout_path);
2699
+ const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2700
+ const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2701
+ db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2702
+ tokenEvents.forEach((event, index) => {
2703
+ const usage = event.usage;
2704
+ const inputTotal = usage.input_tokens ?? 0;
2705
+ const cacheReadTokens = usage.cached_input_tokens ?? 0;
2706
+ const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2707
+ const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2708
+ const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2709
+ const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2710
+ const requestId = `${sessionId}-${index}`;
2711
+ upsertRequest(db, withAccount({
2712
+ id: requestId,
2713
+ agent: "codex",
2714
+ session_id: sessionId,
2715
+ model,
2716
+ input_tokens: inputTokens,
2717
+ output_tokens: outputTokens,
2718
+ cache_read_tokens: cacheReadTokens,
2719
+ cache_create_tokens: 0,
2720
+ cost_usd: costUsd,
2721
+ cost_basis: defaultCostBasisForAgent("codex"),
2722
+ duration_ms: 0,
2723
+ timestamp,
2724
+ source_request_id: requestId,
2725
+ machine_id: machineId
2726
+ }, account));
2727
+ requests++;
2728
+ });
2729
+ rollupSession(db, sessionId);
2730
+ setIngestState(db, source.stateSource, thread.id, stateValue);
2731
+ ingested++;
2732
+ if (verbose)
2733
+ console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2734
+ }
2735
+ } finally {
2736
+ codexDb?.close();
2700
2737
  }
2701
- } finally {
2702
- codexDb?.close();
2703
2738
  }
2704
2739
  return { sessions: ingested, requests };
2705
2740
  }
@@ -1,5 +1,5 @@
1
1
  import type { SqliteAdapter as Database } from '@hasna/cloud';
2
- declare function readCodexModel(): string;
2
+ declare function readCodexModel(configPath?: string): string;
3
3
  export declare function ingestCodex(db: Database, verbose?: boolean): Promise<{
4
4
  sessions: number;
5
5
  requests: number;
@@ -1 +1 @@
1
- {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/ingest/codex.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA6C7D,iBAAS,cAAc,IAAI,MAAM,CAUhC;AAwGD,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA8FhH;AAED,OAAO,EAAE,cAAc,EAAE,CAAA"}
1
+ {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/ingest/codex.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA4F7D,iBAAS,cAAc,CAAC,UAAU,SAAoB,GAAG,MAAM,CAS9D;AAwGD,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA+FhH;AAED,OAAO,EAAE,cAAc,EAAE,CAAA"}
package/dist/mcp/index.js CHANGED
@@ -2240,6 +2240,8 @@ import { join as join3, basename as basename2 } from "path";
2240
2240
  import { Database as BunDatabase } from "bun:sqlite";
2241
2241
  var DEFAULT_CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
2242
2242
  var DEFAULT_CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
2243
+ var DEFAULT_CODEWITH_DB_PATH = join3(homedir3(), ".codewith", "state_5.sqlite");
2244
+ var DEFAULT_CODEWITH_CONFIG_PATH = join3(homedir3(), ".codewith", "config.toml");
2243
2245
  var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
2244
2246
  function codexDbPath() {
2245
2247
  return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
@@ -2247,8 +2249,40 @@ function codexDbPath() {
2247
2249
  function codexConfigPath() {
2248
2250
  return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
2249
2251
  }
2250
- function readCodexModel() {
2251
- const configPath = codexConfigPath();
2252
+ function codewithDbPath() {
2253
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
2254
+ }
2255
+ function codewithConfigPath() {
2256
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
2257
+ }
2258
+ function codexSources() {
2259
+ const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
2260
+ const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
2261
+ const sources = [{
2262
+ label: "Codex",
2263
+ dbPath: codexDbPath(),
2264
+ configPath: codexConfigPath(),
2265
+ sessionPrefix: "codex",
2266
+ stateSource: "codex"
2267
+ }];
2268
+ if (!explicitCodexPath || explicitCodewithPath) {
2269
+ sources.push({
2270
+ label: "Codewith",
2271
+ dbPath: codewithDbPath(),
2272
+ configPath: codewithConfigPath(),
2273
+ sessionPrefix: "codex-codewith",
2274
+ stateSource: "codex-codewith"
2275
+ });
2276
+ }
2277
+ const seen = new Set;
2278
+ return sources.filter((source) => {
2279
+ if (seen.has(source.dbPath))
2280
+ return false;
2281
+ seen.add(source.dbPath);
2282
+ return true;
2283
+ });
2284
+ }
2285
+ function readCodexModel(configPath = codexConfigPath()) {
2252
2286
  if (!existsSync3(configPath))
2253
2287
  return "gpt-5-codex";
2254
2288
  try {
@@ -2366,84 +2400,85 @@ function fallbackEvents(totalTokens) {
2366
2400
  }];
2367
2401
  }
2368
2402
  async function ingestCodex(db, verbose = false) {
2369
- const dbPath = codexDbPath();
2370
- if (!existsSync3(dbPath)) {
2371
- if (verbose)
2372
- console.log("Codex DB not found:", dbPath);
2373
- return { sessions: 0, requests: 0 };
2374
- }
2375
2403
  const machineId = getMachineId();
2376
- let codexDb = null;
2377
2404
  let ingested = 0;
2378
2405
  let requests = 0;
2379
2406
  const account = await resolveAccountForAgent("codex");
2380
- try {
2381
- codexDb = openCodexDb(dbPath, verbose);
2382
- if (!codexDb)
2383
- return { sessions: 0, requests: 0 };
2384
- const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2385
- for (const thread of threads) {
2386
- const model = thread.model ?? readCodexModel();
2387
- const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2388
- const processed = getIngestState(db, "codex", thread.id);
2389
- if (processed === stateValue)
2407
+ for (const source of codexSources()) {
2408
+ if (!existsSync3(source.dbPath)) {
2409
+ if (verbose)
2410
+ console.log(`${source.label} DB not found:`, source.dbPath);
2411
+ continue;
2412
+ }
2413
+ let codexDb = null;
2414
+ try {
2415
+ codexDb = openCodexDb(source.dbPath, verbose);
2416
+ if (!codexDb)
2390
2417
  continue;
2391
- const projectPath = thread.cwd ?? "";
2392
- const projectName = projectPath ? basename2(projectPath) : "unknown";
2393
- const sessionId = `codex-${thread.id}`;
2394
- const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2395
- const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2396
- upsertSession(db, withAccount({
2397
- id: sessionId,
2398
- agent: "codex",
2399
- project_path: projectPath,
2400
- project_name: projectName,
2401
- started_at: startedAt,
2402
- ended_at: endedAt,
2403
- total_cost_usd: 0,
2404
- total_tokens: 0,
2405
- request_count: 0,
2406
- machine_id: machineId
2407
- }, account));
2408
- const events = readTokenEvents(thread.rollout_path);
2409
- const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2410
- const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2411
- db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2412
- tokenEvents.forEach((event, index) => {
2413
- const usage = event.usage;
2414
- const inputTotal = usage.input_tokens ?? 0;
2415
- const cacheReadTokens = usage.cached_input_tokens ?? 0;
2416
- const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2417
- const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2418
- const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2419
- const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2420
- const requestId = `${sessionId}-${index}`;
2421
- upsertRequest(db, withAccount({
2422
- id: requestId,
2418
+ const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2419
+ for (const thread of threads) {
2420
+ const model = thread.model ?? readCodexModel(source.configPath);
2421
+ const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2422
+ const processed = getIngestState(db, source.stateSource, thread.id);
2423
+ if (processed === stateValue)
2424
+ continue;
2425
+ const projectPath = thread.cwd ?? "";
2426
+ const projectName = projectPath ? basename2(projectPath) : "unknown";
2427
+ const sessionId = `${source.sessionPrefix}-${thread.id}`;
2428
+ const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2429
+ const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2430
+ upsertSession(db, withAccount({
2431
+ id: sessionId,
2423
2432
  agent: "codex",
2424
- session_id: sessionId,
2425
- model,
2426
- input_tokens: inputTokens,
2427
- output_tokens: outputTokens,
2428
- cache_read_tokens: cacheReadTokens,
2429
- cache_create_tokens: 0,
2430
- cost_usd: costUsd,
2431
- cost_basis: defaultCostBasisForAgent("codex"),
2432
- duration_ms: 0,
2433
- timestamp,
2434
- source_request_id: requestId,
2433
+ project_path: projectPath,
2434
+ project_name: projectName,
2435
+ started_at: startedAt,
2436
+ ended_at: endedAt,
2437
+ total_cost_usd: 0,
2438
+ total_tokens: 0,
2439
+ request_count: 0,
2435
2440
  machine_id: machineId
2436
2441
  }, account));
2437
- requests++;
2438
- });
2439
- rollupSession(db, sessionId);
2440
- setIngestState(db, "codex", thread.id, stateValue);
2441
- ingested++;
2442
- if (verbose)
2443
- console.log(`Codex session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2442
+ const events = readTokenEvents(thread.rollout_path);
2443
+ const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2444
+ const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2445
+ db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2446
+ tokenEvents.forEach((event, index) => {
2447
+ const usage = event.usage;
2448
+ const inputTotal = usage.input_tokens ?? 0;
2449
+ const cacheReadTokens = usage.cached_input_tokens ?? 0;
2450
+ const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2451
+ const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2452
+ const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2453
+ const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2454
+ const requestId = `${sessionId}-${index}`;
2455
+ upsertRequest(db, withAccount({
2456
+ id: requestId,
2457
+ agent: "codex",
2458
+ session_id: sessionId,
2459
+ model,
2460
+ input_tokens: inputTokens,
2461
+ output_tokens: outputTokens,
2462
+ cache_read_tokens: cacheReadTokens,
2463
+ cache_create_tokens: 0,
2464
+ cost_usd: costUsd,
2465
+ cost_basis: defaultCostBasisForAgent("codex"),
2466
+ duration_ms: 0,
2467
+ timestamp,
2468
+ source_request_id: requestId,
2469
+ machine_id: machineId
2470
+ }, account));
2471
+ requests++;
2472
+ });
2473
+ rollupSession(db, sessionId);
2474
+ setIngestState(db, source.stateSource, thread.id, stateValue);
2475
+ ingested++;
2476
+ if (verbose)
2477
+ console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2478
+ }
2479
+ } finally {
2480
+ codexDb?.close();
2444
2481
  }
2445
- } finally {
2446
- codexDb?.close();
2447
2482
  }
2448
2483
  return { sessions: ingested, requests };
2449
2484
  }
@@ -2698,6 +2698,8 @@ import { join as join3, basename as basename2 } from "path";
2698
2698
  import { Database as BunDatabase } from "bun:sqlite";
2699
2699
  var DEFAULT_CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
2700
2700
  var DEFAULT_CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
2701
+ var DEFAULT_CODEWITH_DB_PATH = join3(homedir3(), ".codewith", "state_5.sqlite");
2702
+ var DEFAULT_CODEWITH_CONFIG_PATH = join3(homedir3(), ".codewith", "config.toml");
2701
2703
  var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
2702
2704
  function codexDbPath() {
2703
2705
  return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
@@ -2705,8 +2707,40 @@ function codexDbPath() {
2705
2707
  function codexConfigPath() {
2706
2708
  return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
2707
2709
  }
2708
- function readCodexModel() {
2709
- const configPath = codexConfigPath();
2710
+ function codewithDbPath() {
2711
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
2712
+ }
2713
+ function codewithConfigPath() {
2714
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
2715
+ }
2716
+ function codexSources() {
2717
+ const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
2718
+ const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
2719
+ const sources = [{
2720
+ label: "Codex",
2721
+ dbPath: codexDbPath(),
2722
+ configPath: codexConfigPath(),
2723
+ sessionPrefix: "codex",
2724
+ stateSource: "codex"
2725
+ }];
2726
+ if (!explicitCodexPath || explicitCodewithPath) {
2727
+ sources.push({
2728
+ label: "Codewith",
2729
+ dbPath: codewithDbPath(),
2730
+ configPath: codewithConfigPath(),
2731
+ sessionPrefix: "codex-codewith",
2732
+ stateSource: "codex-codewith"
2733
+ });
2734
+ }
2735
+ const seen = new Set;
2736
+ return sources.filter((source) => {
2737
+ if (seen.has(source.dbPath))
2738
+ return false;
2739
+ seen.add(source.dbPath);
2740
+ return true;
2741
+ });
2742
+ }
2743
+ function readCodexModel(configPath = codexConfigPath()) {
2710
2744
  if (!existsSync3(configPath))
2711
2745
  return "gpt-5-codex";
2712
2746
  try {
@@ -2824,84 +2858,85 @@ function fallbackEvents(totalTokens) {
2824
2858
  }];
2825
2859
  }
2826
2860
  async function ingestCodex(db, verbose = false) {
2827
- const dbPath = codexDbPath();
2828
- if (!existsSync3(dbPath)) {
2829
- if (verbose)
2830
- console.log("Codex DB not found:", dbPath);
2831
- return { sessions: 0, requests: 0 };
2832
- }
2833
2861
  const machineId = getMachineId();
2834
- let codexDb = null;
2835
2862
  let ingested = 0;
2836
2863
  let requests = 0;
2837
2864
  const account = await resolveAccountForAgent("codex");
2838
- try {
2839
- codexDb = openCodexDb(dbPath, verbose);
2840
- if (!codexDb)
2841
- return { sessions: 0, requests: 0 };
2842
- const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2843
- for (const thread of threads) {
2844
- const model = thread.model ?? readCodexModel();
2845
- const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2846
- const processed = getIngestState(db, "codex", thread.id);
2847
- if (processed === stateValue)
2865
+ for (const source of codexSources()) {
2866
+ if (!existsSync3(source.dbPath)) {
2867
+ if (verbose)
2868
+ console.log(`${source.label} DB not found:`, source.dbPath);
2869
+ continue;
2870
+ }
2871
+ let codexDb = null;
2872
+ try {
2873
+ codexDb = openCodexDb(source.dbPath, verbose);
2874
+ if (!codexDb)
2848
2875
  continue;
2849
- const projectPath = thread.cwd ?? "";
2850
- const projectName = projectPath ? basename2(projectPath) : "unknown";
2851
- const sessionId = `codex-${thread.id}`;
2852
- const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2853
- const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2854
- upsertSession(db, withAccount({
2855
- id: sessionId,
2856
- agent: "codex",
2857
- project_path: projectPath,
2858
- project_name: projectName,
2859
- started_at: startedAt,
2860
- ended_at: endedAt,
2861
- total_cost_usd: 0,
2862
- total_tokens: 0,
2863
- request_count: 0,
2864
- machine_id: machineId
2865
- }, account));
2866
- const events = readTokenEvents(thread.rollout_path);
2867
- const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2868
- const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2869
- db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2870
- tokenEvents.forEach((event, index) => {
2871
- const usage = event.usage;
2872
- const inputTotal = usage.input_tokens ?? 0;
2873
- const cacheReadTokens = usage.cached_input_tokens ?? 0;
2874
- const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2875
- const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2876
- const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2877
- const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2878
- const requestId = `${sessionId}-${index}`;
2879
- upsertRequest(db, withAccount({
2880
- id: requestId,
2876
+ const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2877
+ for (const thread of threads) {
2878
+ const model = thread.model ?? readCodexModel(source.configPath);
2879
+ const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2880
+ const processed = getIngestState(db, source.stateSource, thread.id);
2881
+ if (processed === stateValue)
2882
+ continue;
2883
+ const projectPath = thread.cwd ?? "";
2884
+ const projectName = projectPath ? basename2(projectPath) : "unknown";
2885
+ const sessionId = `${source.sessionPrefix}-${thread.id}`;
2886
+ const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2887
+ const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2888
+ upsertSession(db, withAccount({
2889
+ id: sessionId,
2881
2890
  agent: "codex",
2882
- session_id: sessionId,
2883
- model,
2884
- input_tokens: inputTokens,
2885
- output_tokens: outputTokens,
2886
- cache_read_tokens: cacheReadTokens,
2887
- cache_create_tokens: 0,
2888
- cost_usd: costUsd,
2889
- cost_basis: defaultCostBasisForAgent("codex"),
2890
- duration_ms: 0,
2891
- timestamp,
2892
- source_request_id: requestId,
2891
+ project_path: projectPath,
2892
+ project_name: projectName,
2893
+ started_at: startedAt,
2894
+ ended_at: endedAt,
2895
+ total_cost_usd: 0,
2896
+ total_tokens: 0,
2897
+ request_count: 0,
2893
2898
  machine_id: machineId
2894
2899
  }, account));
2895
- requests++;
2896
- });
2897
- rollupSession(db, sessionId);
2898
- setIngestState(db, "codex", thread.id, stateValue);
2899
- ingested++;
2900
- if (verbose)
2901
- console.log(`Codex session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2900
+ const events = readTokenEvents(thread.rollout_path);
2901
+ const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2902
+ const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2903
+ db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2904
+ tokenEvents.forEach((event, index) => {
2905
+ const usage = event.usage;
2906
+ const inputTotal = usage.input_tokens ?? 0;
2907
+ const cacheReadTokens = usage.cached_input_tokens ?? 0;
2908
+ const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2909
+ const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2910
+ const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2911
+ const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2912
+ const requestId = `${sessionId}-${index}`;
2913
+ upsertRequest(db, withAccount({
2914
+ id: requestId,
2915
+ agent: "codex",
2916
+ session_id: sessionId,
2917
+ model,
2918
+ input_tokens: inputTokens,
2919
+ output_tokens: outputTokens,
2920
+ cache_read_tokens: cacheReadTokens,
2921
+ cache_create_tokens: 0,
2922
+ cost_usd: costUsd,
2923
+ cost_basis: defaultCostBasisForAgent("codex"),
2924
+ duration_ms: 0,
2925
+ timestamp,
2926
+ source_request_id: requestId,
2927
+ machine_id: machineId
2928
+ }, account));
2929
+ requests++;
2930
+ });
2931
+ rollupSession(db, sessionId);
2932
+ setIngestState(db, source.stateSource, thread.id, stateValue);
2933
+ ingested++;
2934
+ if (verbose)
2935
+ console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2936
+ }
2937
+ } finally {
2938
+ codexDb?.close();
2902
2939
  }
2903
- } finally {
2904
- codexDb?.close();
2905
2940
  }
2906
2941
  return { sessions: ingested, requests };
2907
2942
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/economy",
3
- "version": "0.2.36",
3
+ "version": "0.2.37",
4
4
  "description": "AI coding cost tracker — CLI + MCP server + REST API + web dashboard for Claude Code, Codex, Gemini, OpenCode, Cursor, Pi, and Hermes",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",