@hasna/economy 0.2.36 → 0.2.38

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
@@ -565,17 +565,40 @@ __export(exports_database, {
565
565
  clearBillingRange: () => clearBillingRange
566
566
  });
567
567
  import { SqliteAdapter as Database } from "@hasna/cloud";
568
+ import { execFileSync } from "child_process";
568
569
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
569
- import { hostname } from "os";
570
+ import { hostname, platform } from "os";
570
571
  import { homedir } from "os";
571
572
  import { join } from "path";
573
+ function normalizeMachineId(value) {
574
+ const id = value?.trim().toLowerCase().split(".")[0];
575
+ return id && id.length > 0 ? id : null;
576
+ }
577
+ function macHostMachineId() {
578
+ if (platform() !== "darwin")
579
+ return null;
580
+ for (const key of ["LocalHostName", "ComputerName", "HostName"]) {
581
+ try {
582
+ const value = execFileSync("/usr/sbin/scutil", ["--get", key], {
583
+ encoding: "utf8",
584
+ stdio: ["ignore", "pipe", "ignore"],
585
+ timeout: 1000
586
+ });
587
+ const id = normalizeMachineId(value);
588
+ if (id && id !== "mac" && id !== "localhost")
589
+ return id;
590
+ } catch {}
591
+ }
592
+ return null;
593
+ }
572
594
  function getMachineId() {
573
- if (process.env["ECONOMY_MACHINE_ID"])
574
- return process.env["ECONOMY_MACHINE_ID"];
575
- const h = hostname().toLowerCase();
576
- if (h.startsWith("spark") || h.startsWith("apple"))
577
- return h.split(".")[0];
578
- return h.split(".")[0];
595
+ const envMachine = normalizeMachineId(process.env["ECONOMY_MACHINE_ID"]);
596
+ if (envMachine)
597
+ return envMachine;
598
+ const hostMachine = normalizeMachineId(hostname()) ?? "unknown";
599
+ if (hostMachine === "mac" || hostMachine === "localhost")
600
+ return macHostMachineId() ?? hostMachine;
601
+ return hostMachine;
579
602
  }
580
603
  function getDataDir() {
581
604
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
@@ -2766,8 +2789,40 @@ function codexDbPath() {
2766
2789
  function codexConfigPath() {
2767
2790
  return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
2768
2791
  }
2769
- function readCodexModel() {
2770
- const configPath = codexConfigPath();
2792
+ function codewithDbPath() {
2793
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
2794
+ }
2795
+ function codewithConfigPath() {
2796
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
2797
+ }
2798
+ function codexSources() {
2799
+ const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
2800
+ const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
2801
+ const sources = [{
2802
+ label: "Codex",
2803
+ dbPath: codexDbPath(),
2804
+ configPath: codexConfigPath(),
2805
+ sessionPrefix: "codex",
2806
+ stateSource: "codex"
2807
+ }];
2808
+ if (!explicitCodexPath || explicitCodewithPath) {
2809
+ sources.push({
2810
+ label: "Codewith",
2811
+ dbPath: codewithDbPath(),
2812
+ configPath: codewithConfigPath(),
2813
+ sessionPrefix: "codex-codewith",
2814
+ stateSource: "codex-codewith"
2815
+ });
2816
+ }
2817
+ const seen = new Set;
2818
+ return sources.filter((source) => {
2819
+ if (seen.has(source.dbPath))
2820
+ return false;
2821
+ seen.add(source.dbPath);
2822
+ return true;
2823
+ });
2824
+ }
2825
+ function readCodexModel(configPath = codexConfigPath()) {
2771
2826
  if (!existsSync5(configPath))
2772
2827
  return "gpt-5-codex";
2773
2828
  try {
@@ -2885,94 +2940,97 @@ function fallbackEvents(totalTokens) {
2885
2940
  }];
2886
2941
  }
2887
2942
  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
2943
  const machineId = getMachineId();
2895
- let codexDb = null;
2896
2944
  let ingested = 0;
2897
2945
  let requests = 0;
2898
2946
  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)
2947
+ for (const source of codexSources()) {
2948
+ if (!existsSync5(source.dbPath)) {
2949
+ if (verbose)
2950
+ console.log(`${source.label} DB not found:`, source.dbPath);
2951
+ continue;
2952
+ }
2953
+ let codexDb = null;
2954
+ try {
2955
+ codexDb = openCodexDb(source.dbPath, verbose);
2956
+ if (!codexDb)
2909
2957
  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,
2958
+ const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2959
+ for (const thread of threads) {
2960
+ const model = thread.model ?? readCodexModel(source.configPath);
2961
+ const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2962
+ const processed = getIngestState(db, source.stateSource, thread.id);
2963
+ if (processed === stateValue)
2964
+ continue;
2965
+ const projectPath = thread.cwd ?? "";
2966
+ const projectName = projectPath ? basename2(projectPath) : "unknown";
2967
+ const sessionId = `${source.sessionPrefix}-${thread.id}`;
2968
+ const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2969
+ const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2970
+ upsertSession(db, withAccount({
2971
+ id: sessionId,
2942
2972
  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,
2973
+ project_path: projectPath,
2974
+ project_name: projectName,
2975
+ started_at: startedAt,
2976
+ ended_at: endedAt,
2977
+ total_cost_usd: 0,
2978
+ total_tokens: 0,
2979
+ request_count: 0,
2954
2980
  machine_id: machineId
2955
2981
  }, 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}`);
2982
+ const events = readTokenEvents(thread.rollout_path);
2983
+ const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2984
+ const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2985
+ db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2986
+ tokenEvents.forEach((event, index) => {
2987
+ const usage = event.usage;
2988
+ const inputTotal = usage.input_tokens ?? 0;
2989
+ const cacheReadTokens = usage.cached_input_tokens ?? 0;
2990
+ const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2991
+ const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2992
+ const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2993
+ const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2994
+ const requestId = `${sessionId}-${index}`;
2995
+ upsertRequest(db, withAccount({
2996
+ id: requestId,
2997
+ agent: "codex",
2998
+ session_id: sessionId,
2999
+ model,
3000
+ input_tokens: inputTokens,
3001
+ output_tokens: outputTokens,
3002
+ cache_read_tokens: cacheReadTokens,
3003
+ cache_create_tokens: 0,
3004
+ cost_usd: costUsd,
3005
+ cost_basis: defaultCostBasisForAgent("codex"),
3006
+ duration_ms: 0,
3007
+ timestamp,
3008
+ source_request_id: requestId,
3009
+ machine_id: machineId
3010
+ }, account));
3011
+ requests++;
3012
+ });
3013
+ rollupSession(db, sessionId);
3014
+ setIngestState(db, source.stateSource, thread.id, stateValue);
3015
+ ingested++;
3016
+ if (verbose)
3017
+ console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
3018
+ }
3019
+ } finally {
3020
+ codexDb?.close();
2963
3021
  }
2964
- } finally {
2965
- codexDb?.close();
2966
3022
  }
2967
3023
  return { sessions: ingested, requests };
2968
3024
  }
2969
- var DEFAULT_CODEX_DB_PATH, DEFAULT_CODEX_CONFIG_PATH, CODEX_INGEST_VERSION = "rollout-aggregate-v3";
3025
+ var DEFAULT_CODEX_DB_PATH, DEFAULT_CODEX_CONFIG_PATH, DEFAULT_CODEWITH_DB_PATH, DEFAULT_CODEWITH_CONFIG_PATH, CODEX_INGEST_VERSION = "rollout-aggregate-v3";
2970
3026
  var init_codex = __esm(() => {
2971
3027
  init_database();
2972
3028
  init_pricing();
2973
3029
  init_accounts();
2974
3030
  DEFAULT_CODEX_DB_PATH = join7(homedir4(), ".codex", "state_5.sqlite");
2975
3031
  DEFAULT_CODEX_CONFIG_PATH = join7(homedir4(), ".codex", "config.toml");
3032
+ DEFAULT_CODEWITH_DB_PATH = join7(homedir4(), ".codewith", "state_5.sqlite");
3033
+ DEFAULT_CODEWITH_CONFIG_PATH = join7(homedir4(), ".codewith", "config.toml");
2976
3034
  });
2977
3035
 
2978
3036
  // src/ingest/gemini.ts
@@ -4390,14 +4448,14 @@ var init_watch_paths = __esm(() => {
4390
4448
  });
4391
4449
 
4392
4450
  // src/cli/commands/notification.ts
4393
- import { execFileSync } from "child_process";
4451
+ import { execFileSync as execFileSync2 } from "child_process";
4394
4452
  function appleScriptString(value) {
4395
4453
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
4396
4454
  }
4397
4455
  function notificationScript(title, body) {
4398
4456
  return `display notification ${appleScriptString(body)} with title ${appleScriptString(title)}`;
4399
4457
  }
4400
- function sendNotification(title, body, execFile = execFileSync) {
4458
+ function sendNotification(title, body, execFile = execFileSync2) {
4401
4459
  try {
4402
4460
  execFile("osascript", ["-e", notificationScript(title, body)], { stdio: "ignore" });
4403
4461
  } catch {}
@@ -5038,7 +5096,7 @@ __export(exports_menubar, {
5038
5096
  menubarInstall: () => menubarInstall
5039
5097
  });
5040
5098
  import chalk6 from "chalk";
5041
- import { execFileSync as execFileSync2 } from "child_process";
5099
+ import { execFileSync as execFileSync3 } from "child_process";
5042
5100
  import { cpSync, existsSync as existsSync16, mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
5043
5101
  import { tmpdir, arch } from "os";
5044
5102
  import { join as join13 } from "path";
@@ -5050,7 +5108,7 @@ function isInstalled() {
5050
5108
  }
5051
5109
  function isRunning() {
5052
5110
  try {
5053
- const result = execFileSync2("pgrep", ["-x", "EconomyBar"], { stdio: "pipe" }).toString().trim();
5111
+ const result = execFileSync3("pgrep", ["-x", "EconomyBar"], { stdio: "pipe" }).toString().trim();
5054
5112
  return result.length > 0;
5055
5113
  } catch {
5056
5114
  return false;
@@ -5101,12 +5159,12 @@ async function menubarInstall(opts) {
5101
5159
  try {
5102
5160
  rmSync(extractDir, { recursive: true, force: true });
5103
5161
  mkdirSync4(extractDir, { recursive: true });
5104
- execFileSync2("unzip", ["-q", zipPath, "-d", extractDir], { stdio: "ignore" });
5162
+ execFileSync3("unzip", ["-q", zipPath, "-d", extractDir], { stdio: "ignore" });
5105
5163
  if (isInstalled())
5106
5164
  rmSync(APP_PATH, { recursive: true, force: true });
5107
5165
  cpSync(join13(extractDir, "Economy Bar.app"), APP_PATH, { recursive: true });
5108
5166
  try {
5109
- execFileSync2("xattr", ["-rd", "com.apple.quarantine", APP_PATH], { stdio: "ignore" });
5167
+ execFileSync3("xattr", ["-rd", "com.apple.quarantine", APP_PATH], { stdio: "ignore" });
5110
5168
  } catch {}
5111
5169
  rmSync(zipPath, { force: true });
5112
5170
  rmSync(extractDir, { recursive: true, force: true });
@@ -5117,7 +5175,7 @@ async function menubarInstall(opts) {
5117
5175
  }
5118
5176
  console.log(chalk6.cyan("\u2192 Launching Economy Bar..."));
5119
5177
  try {
5120
- execFileSync2("open", [APP_PATH], { stdio: "ignore" });
5178
+ execFileSync3("open", [APP_PATH], { stdio: "ignore" });
5121
5179
  console.log(chalk6.bold.green(`
5122
5180
  \u2713 Economy Bar is running in your menu bar!`));
5123
5181
  console.log(chalk6.dim(" Make sure economy serve is running: economy serve"));
@@ -5132,8 +5190,8 @@ function menubarUninstall() {
5132
5190
  }
5133
5191
  if (isRunning()) {
5134
5192
  try {
5135
- execFileSync2("osascript", ["-e", 'quit app "Economy Bar"'], { stdio: "ignore" });
5136
- execFileSync2("sleep", ["1"], { stdio: "ignore" });
5193
+ execFileSync3("osascript", ["-e", 'quit app "Economy Bar"'], { stdio: "ignore" });
5194
+ execFileSync3("sleep", ["1"], { stdio: "ignore" });
5137
5195
  } catch {}
5138
5196
  }
5139
5197
  rmSync(APP_PATH, { recursive: true, force: true });
@@ -5144,7 +5202,7 @@ function menubarStart() {
5144
5202
  console.error(chalk6.red("Economy Bar is not installed. Run: economy menubar install"));
5145
5203
  process.exit(1);
5146
5204
  }
5147
- execFileSync2("open", [APP_PATH], { stdio: "ignore" });
5205
+ execFileSync3("open", [APP_PATH], { stdio: "ignore" });
5148
5206
  console.log(chalk6.green("\u2713 Economy Bar launched"));
5149
5207
  }
5150
5208
  function menubarStop() {
@@ -5153,7 +5211,7 @@ function menubarStop() {
5153
5211
  return;
5154
5212
  }
5155
5213
  try {
5156
- execFileSync2("osascript", ["-e", 'quit app "Economy Bar"'], { stdio: "ignore" });
5214
+ execFileSync3("osascript", ["-e", 'quit app "Economy Bar"'], { stdio: "ignore" });
5157
5215
  console.log(chalk6.green("\u2713 Economy Bar stopped"));
5158
5216
  } catch {
5159
5217
  console.log(chalk6.yellow("Could not quit Economy Bar gracefully"));
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -514,17 +514,40 @@ var init_pricing = __esm(() => {
514
514
 
515
515
  // src/db/database.ts
516
516
  import { SqliteAdapter as Database } from "@hasna/cloud";
517
+ import { execFileSync } from "child_process";
517
518
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
518
- import { hostname } from "os";
519
+ import { hostname, platform } from "os";
519
520
  import { homedir } from "os";
520
521
  import { join } from "path";
522
+ function normalizeMachineId(value) {
523
+ const id = value?.trim().toLowerCase().split(".")[0];
524
+ return id && id.length > 0 ? id : null;
525
+ }
526
+ function macHostMachineId() {
527
+ if (platform() !== "darwin")
528
+ return null;
529
+ for (const key of ["LocalHostName", "ComputerName", "HostName"]) {
530
+ try {
531
+ const value = execFileSync("/usr/sbin/scutil", ["--get", key], {
532
+ encoding: "utf8",
533
+ stdio: ["ignore", "pipe", "ignore"],
534
+ timeout: 1000
535
+ });
536
+ const id = normalizeMachineId(value);
537
+ if (id && id !== "mac" && id !== "localhost")
538
+ return id;
539
+ } catch {}
540
+ }
541
+ return null;
542
+ }
521
543
  function getMachineId() {
522
- if (process.env["ECONOMY_MACHINE_ID"])
523
- return process.env["ECONOMY_MACHINE_ID"];
524
- const h = hostname().toLowerCase();
525
- if (h.startsWith("spark") || h.startsWith("apple"))
526
- return h.split(".")[0];
527
- return h.split(".")[0];
544
+ const envMachine = normalizeMachineId(process.env["ECONOMY_MACHINE_ID"]);
545
+ if (envMachine)
546
+ return envMachine;
547
+ const hostMachine = normalizeMachineId(hostname()) ?? "unknown";
548
+ if (hostMachine === "mac" || hostMachine === "localhost")
549
+ return macHostMachineId() ?? hostMachine;
550
+ return hostMachine;
528
551
  }
529
552
  function getDataDir() {
530
553
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
@@ -2496,6 +2519,8 @@ import { join as join4, basename as basename2 } from "path";
2496
2519
  import { Database as BunDatabase2 } from "bun:sqlite";
2497
2520
  var DEFAULT_CODEX_DB_PATH = join4(homedir3(), ".codex", "state_5.sqlite");
2498
2521
  var DEFAULT_CODEX_CONFIG_PATH = join4(homedir3(), ".codex", "config.toml");
2522
+ var DEFAULT_CODEWITH_DB_PATH = join4(homedir3(), ".codewith", "state_5.sqlite");
2523
+ var DEFAULT_CODEWITH_CONFIG_PATH = join4(homedir3(), ".codewith", "config.toml");
2499
2524
  var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
2500
2525
  function codexDbPath() {
2501
2526
  return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
@@ -2503,8 +2528,40 @@ function codexDbPath() {
2503
2528
  function codexConfigPath() {
2504
2529
  return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
2505
2530
  }
2506
- function readCodexModel() {
2507
- const configPath = codexConfigPath();
2531
+ function codewithDbPath() {
2532
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
2533
+ }
2534
+ function codewithConfigPath() {
2535
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
2536
+ }
2537
+ function codexSources() {
2538
+ const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
2539
+ const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
2540
+ const sources = [{
2541
+ label: "Codex",
2542
+ dbPath: codexDbPath(),
2543
+ configPath: codexConfigPath(),
2544
+ sessionPrefix: "codex",
2545
+ stateSource: "codex"
2546
+ }];
2547
+ if (!explicitCodexPath || explicitCodewithPath) {
2548
+ sources.push({
2549
+ label: "Codewith",
2550
+ dbPath: codewithDbPath(),
2551
+ configPath: codewithConfigPath(),
2552
+ sessionPrefix: "codex-codewith",
2553
+ stateSource: "codex-codewith"
2554
+ });
2555
+ }
2556
+ const seen = new Set;
2557
+ return sources.filter((source) => {
2558
+ if (seen.has(source.dbPath))
2559
+ return false;
2560
+ seen.add(source.dbPath);
2561
+ return true;
2562
+ });
2563
+ }
2564
+ function readCodexModel(configPath = codexConfigPath()) {
2508
2565
  if (!existsSync5(configPath))
2509
2566
  return "gpt-5-codex";
2510
2567
  try {
@@ -2622,84 +2679,85 @@ function fallbackEvents(totalTokens) {
2622
2679
  }];
2623
2680
  }
2624
2681
  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
2682
  const machineId = getMachineId();
2632
- let codexDb = null;
2633
2683
  let ingested = 0;
2634
2684
  let requests = 0;
2635
2685
  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)
2686
+ for (const source of codexSources()) {
2687
+ if (!existsSync5(source.dbPath)) {
2688
+ if (verbose)
2689
+ console.log(`${source.label} DB not found:`, source.dbPath);
2690
+ continue;
2691
+ }
2692
+ let codexDb = null;
2693
+ try {
2694
+ codexDb = openCodexDb(source.dbPath, verbose);
2695
+ if (!codexDb)
2646
2696
  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,
2697
+ const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2698
+ for (const thread of threads) {
2699
+ const model = thread.model ?? readCodexModel(source.configPath);
2700
+ const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2701
+ const processed = getIngestState(db, source.stateSource, thread.id);
2702
+ if (processed === stateValue)
2703
+ continue;
2704
+ const projectPath = thread.cwd ?? "";
2705
+ const projectName = projectPath ? basename2(projectPath) : "unknown";
2706
+ const sessionId = `${source.sessionPrefix}-${thread.id}`;
2707
+ const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2708
+ const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2709
+ upsertSession(db, withAccount({
2710
+ id: sessionId,
2679
2711
  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,
2712
+ project_path: projectPath,
2713
+ project_name: projectName,
2714
+ started_at: startedAt,
2715
+ ended_at: endedAt,
2716
+ total_cost_usd: 0,
2717
+ total_tokens: 0,
2718
+ request_count: 0,
2691
2719
  machine_id: machineId
2692
2720
  }, 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}`);
2721
+ const events = readTokenEvents(thread.rollout_path);
2722
+ const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2723
+ const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2724
+ db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2725
+ tokenEvents.forEach((event, index) => {
2726
+ const usage = event.usage;
2727
+ const inputTotal = usage.input_tokens ?? 0;
2728
+ const cacheReadTokens = usage.cached_input_tokens ?? 0;
2729
+ const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2730
+ const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2731
+ const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2732
+ const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2733
+ const requestId = `${sessionId}-${index}`;
2734
+ upsertRequest(db, withAccount({
2735
+ id: requestId,
2736
+ agent: "codex",
2737
+ session_id: sessionId,
2738
+ model,
2739
+ input_tokens: inputTokens,
2740
+ output_tokens: outputTokens,
2741
+ cache_read_tokens: cacheReadTokens,
2742
+ cache_create_tokens: 0,
2743
+ cost_usd: costUsd,
2744
+ cost_basis: defaultCostBasisForAgent("codex"),
2745
+ duration_ms: 0,
2746
+ timestamp,
2747
+ source_request_id: requestId,
2748
+ machine_id: machineId
2749
+ }, account));
2750
+ requests++;
2751
+ });
2752
+ rollupSession(db, sessionId);
2753
+ setIngestState(db, source.stateSource, thread.id, stateValue);
2754
+ ingested++;
2755
+ if (verbose)
2756
+ console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2757
+ }
2758
+ } finally {
2759
+ codexDb?.close();
2700
2760
  }
2701
- } finally {
2702
- codexDb?.close();
2703
2761
  }
2704
2762
  return { sessions: ingested, requests };
2705
2763
  }
@@ -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
@@ -515,17 +515,40 @@ var init_pricing = __esm(() => {
515
515
 
516
516
  // src/db/database.ts
517
517
  import { SqliteAdapter as Database } from "@hasna/cloud";
518
+ import { execFileSync } from "child_process";
518
519
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
519
- import { hostname } from "os";
520
+ import { hostname, platform } from "os";
520
521
  import { homedir } from "os";
521
522
  import { join } from "path";
523
+ function normalizeMachineId(value) {
524
+ const id = value?.trim().toLowerCase().split(".")[0];
525
+ return id && id.length > 0 ? id : null;
526
+ }
527
+ function macHostMachineId() {
528
+ if (platform() !== "darwin")
529
+ return null;
530
+ for (const key of ["LocalHostName", "ComputerName", "HostName"]) {
531
+ try {
532
+ const value = execFileSync("/usr/sbin/scutil", ["--get", key], {
533
+ encoding: "utf8",
534
+ stdio: ["ignore", "pipe", "ignore"],
535
+ timeout: 1000
536
+ });
537
+ const id = normalizeMachineId(value);
538
+ if (id && id !== "mac" && id !== "localhost")
539
+ return id;
540
+ } catch {}
541
+ }
542
+ return null;
543
+ }
522
544
  function getMachineId() {
523
- if (process.env["ECONOMY_MACHINE_ID"])
524
- return process.env["ECONOMY_MACHINE_ID"];
525
- const h = hostname().toLowerCase();
526
- if (h.startsWith("spark") || h.startsWith("apple"))
527
- return h.split(".")[0];
528
- return h.split(".")[0];
545
+ const envMachine = normalizeMachineId(process.env["ECONOMY_MACHINE_ID"]);
546
+ if (envMachine)
547
+ return envMachine;
548
+ const hostMachine = normalizeMachineId(hostname()) ?? "unknown";
549
+ if (hostMachine === "mac" || hostMachine === "localhost")
550
+ return macHostMachineId() ?? hostMachine;
551
+ return hostMachine;
529
552
  }
530
553
  function getDataDir() {
531
554
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
@@ -2240,6 +2263,8 @@ import { join as join3, basename as basename2 } from "path";
2240
2263
  import { Database as BunDatabase } from "bun:sqlite";
2241
2264
  var DEFAULT_CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
2242
2265
  var DEFAULT_CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
2266
+ var DEFAULT_CODEWITH_DB_PATH = join3(homedir3(), ".codewith", "state_5.sqlite");
2267
+ var DEFAULT_CODEWITH_CONFIG_PATH = join3(homedir3(), ".codewith", "config.toml");
2243
2268
  var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
2244
2269
  function codexDbPath() {
2245
2270
  return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
@@ -2247,8 +2272,40 @@ function codexDbPath() {
2247
2272
  function codexConfigPath() {
2248
2273
  return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
2249
2274
  }
2250
- function readCodexModel() {
2251
- const configPath = codexConfigPath();
2275
+ function codewithDbPath() {
2276
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
2277
+ }
2278
+ function codewithConfigPath() {
2279
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
2280
+ }
2281
+ function codexSources() {
2282
+ const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
2283
+ const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
2284
+ const sources = [{
2285
+ label: "Codex",
2286
+ dbPath: codexDbPath(),
2287
+ configPath: codexConfigPath(),
2288
+ sessionPrefix: "codex",
2289
+ stateSource: "codex"
2290
+ }];
2291
+ if (!explicitCodexPath || explicitCodewithPath) {
2292
+ sources.push({
2293
+ label: "Codewith",
2294
+ dbPath: codewithDbPath(),
2295
+ configPath: codewithConfigPath(),
2296
+ sessionPrefix: "codex-codewith",
2297
+ stateSource: "codex-codewith"
2298
+ });
2299
+ }
2300
+ const seen = new Set;
2301
+ return sources.filter((source) => {
2302
+ if (seen.has(source.dbPath))
2303
+ return false;
2304
+ seen.add(source.dbPath);
2305
+ return true;
2306
+ });
2307
+ }
2308
+ function readCodexModel(configPath = codexConfigPath()) {
2252
2309
  if (!existsSync3(configPath))
2253
2310
  return "gpt-5-codex";
2254
2311
  try {
@@ -2366,84 +2423,85 @@ function fallbackEvents(totalTokens) {
2366
2423
  }];
2367
2424
  }
2368
2425
  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
2426
  const machineId = getMachineId();
2376
- let codexDb = null;
2377
2427
  let ingested = 0;
2378
2428
  let requests = 0;
2379
2429
  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)
2430
+ for (const source of codexSources()) {
2431
+ if (!existsSync3(source.dbPath)) {
2432
+ if (verbose)
2433
+ console.log(`${source.label} DB not found:`, source.dbPath);
2434
+ continue;
2435
+ }
2436
+ let codexDb = null;
2437
+ try {
2438
+ codexDb = openCodexDb(source.dbPath, verbose);
2439
+ if (!codexDb)
2390
2440
  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,
2441
+ const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2442
+ for (const thread of threads) {
2443
+ const model = thread.model ?? readCodexModel(source.configPath);
2444
+ const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2445
+ const processed = getIngestState(db, source.stateSource, thread.id);
2446
+ if (processed === stateValue)
2447
+ continue;
2448
+ const projectPath = thread.cwd ?? "";
2449
+ const projectName = projectPath ? basename2(projectPath) : "unknown";
2450
+ const sessionId = `${source.sessionPrefix}-${thread.id}`;
2451
+ const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2452
+ const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2453
+ upsertSession(db, withAccount({
2454
+ id: sessionId,
2423
2455
  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,
2456
+ project_path: projectPath,
2457
+ project_name: projectName,
2458
+ started_at: startedAt,
2459
+ ended_at: endedAt,
2460
+ total_cost_usd: 0,
2461
+ total_tokens: 0,
2462
+ request_count: 0,
2435
2463
  machine_id: machineId
2436
2464
  }, 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}`);
2465
+ const events = readTokenEvents(thread.rollout_path);
2466
+ const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2467
+ const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2468
+ db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2469
+ tokenEvents.forEach((event, index) => {
2470
+ const usage = event.usage;
2471
+ const inputTotal = usage.input_tokens ?? 0;
2472
+ const cacheReadTokens = usage.cached_input_tokens ?? 0;
2473
+ const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2474
+ const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2475
+ const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2476
+ const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2477
+ const requestId = `${sessionId}-${index}`;
2478
+ upsertRequest(db, withAccount({
2479
+ id: requestId,
2480
+ agent: "codex",
2481
+ session_id: sessionId,
2482
+ model,
2483
+ input_tokens: inputTokens,
2484
+ output_tokens: outputTokens,
2485
+ cache_read_tokens: cacheReadTokens,
2486
+ cache_create_tokens: 0,
2487
+ cost_usd: costUsd,
2488
+ cost_basis: defaultCostBasisForAgent("codex"),
2489
+ duration_ms: 0,
2490
+ timestamp,
2491
+ source_request_id: requestId,
2492
+ machine_id: machineId
2493
+ }, account));
2494
+ requests++;
2495
+ });
2496
+ rollupSession(db, sessionId);
2497
+ setIngestState(db, source.stateSource, thread.id, stateValue);
2498
+ ingested++;
2499
+ if (verbose)
2500
+ console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2501
+ }
2502
+ } finally {
2503
+ codexDb?.close();
2444
2504
  }
2445
- } finally {
2446
- codexDb?.close();
2447
2505
  }
2448
2506
  return { sessions: ingested, requests };
2449
2507
  }
@@ -515,17 +515,40 @@ var init_pricing = __esm(() => {
515
515
 
516
516
  // src/db/database.ts
517
517
  import { SqliteAdapter as Database } from "@hasna/cloud";
518
+ import { execFileSync } from "child_process";
518
519
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
519
- import { hostname } from "os";
520
+ import { hostname, platform } from "os";
520
521
  import { homedir } from "os";
521
522
  import { join } from "path";
523
+ function normalizeMachineId(value) {
524
+ const id = value?.trim().toLowerCase().split(".")[0];
525
+ return id && id.length > 0 ? id : null;
526
+ }
527
+ function macHostMachineId() {
528
+ if (platform() !== "darwin")
529
+ return null;
530
+ for (const key of ["LocalHostName", "ComputerName", "HostName"]) {
531
+ try {
532
+ const value = execFileSync("/usr/sbin/scutil", ["--get", key], {
533
+ encoding: "utf8",
534
+ stdio: ["ignore", "pipe", "ignore"],
535
+ timeout: 1000
536
+ });
537
+ const id = normalizeMachineId(value);
538
+ if (id && id !== "mac" && id !== "localhost")
539
+ return id;
540
+ } catch {}
541
+ }
542
+ return null;
543
+ }
522
544
  function getMachineId() {
523
- if (process.env["ECONOMY_MACHINE_ID"])
524
- return process.env["ECONOMY_MACHINE_ID"];
525
- const h = hostname().toLowerCase();
526
- if (h.startsWith("spark") || h.startsWith("apple"))
527
- return h.split(".")[0];
528
- return h.split(".")[0];
545
+ const envMachine = normalizeMachineId(process.env["ECONOMY_MACHINE_ID"]);
546
+ if (envMachine)
547
+ return envMachine;
548
+ const hostMachine = normalizeMachineId(hostname()) ?? "unknown";
549
+ if (hostMachine === "mac" || hostMachine === "localhost")
550
+ return macHostMachineId() ?? hostMachine;
551
+ return hostMachine;
529
552
  }
530
553
  function getDataDir() {
531
554
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
@@ -515,17 +515,40 @@ var init_pricing = __esm(() => {
515
515
 
516
516
  // src/db/database.ts
517
517
  import { SqliteAdapter as Database } from "@hasna/cloud";
518
+ import { execFileSync } from "child_process";
518
519
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
519
- import { hostname } from "os";
520
+ import { hostname, platform } from "os";
520
521
  import { homedir } from "os";
521
522
  import { join } from "path";
523
+ function normalizeMachineId(value) {
524
+ const id = value?.trim().toLowerCase().split(".")[0];
525
+ return id && id.length > 0 ? id : null;
526
+ }
527
+ function macHostMachineId() {
528
+ if (platform() !== "darwin")
529
+ return null;
530
+ for (const key of ["LocalHostName", "ComputerName", "HostName"]) {
531
+ try {
532
+ const value = execFileSync("/usr/sbin/scutil", ["--get", key], {
533
+ encoding: "utf8",
534
+ stdio: ["ignore", "pipe", "ignore"],
535
+ timeout: 1000
536
+ });
537
+ const id = normalizeMachineId(value);
538
+ if (id && id !== "mac" && id !== "localhost")
539
+ return id;
540
+ } catch {}
541
+ }
542
+ return null;
543
+ }
522
544
  function getMachineId() {
523
- if (process.env["ECONOMY_MACHINE_ID"])
524
- return process.env["ECONOMY_MACHINE_ID"];
525
- const h = hostname().toLowerCase();
526
- if (h.startsWith("spark") || h.startsWith("apple"))
527
- return h.split(".")[0];
528
- return h.split(".")[0];
545
+ const envMachine = normalizeMachineId(process.env["ECONOMY_MACHINE_ID"]);
546
+ if (envMachine)
547
+ return envMachine;
548
+ const hostMachine = normalizeMachineId(hostname()) ?? "unknown";
549
+ if (hostMachine === "mac" || hostMachine === "localhost")
550
+ return macHostMachineId() ?? hostMachine;
551
+ return hostMachine;
529
552
  }
530
553
  function getDataDir() {
531
554
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
@@ -2698,6 +2721,8 @@ import { join as join3, basename as basename2 } from "path";
2698
2721
  import { Database as BunDatabase } from "bun:sqlite";
2699
2722
  var DEFAULT_CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
2700
2723
  var DEFAULT_CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
2724
+ var DEFAULT_CODEWITH_DB_PATH = join3(homedir3(), ".codewith", "state_5.sqlite");
2725
+ var DEFAULT_CODEWITH_CONFIG_PATH = join3(homedir3(), ".codewith", "config.toml");
2701
2726
  var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
2702
2727
  function codexDbPath() {
2703
2728
  return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
@@ -2705,8 +2730,40 @@ function codexDbPath() {
2705
2730
  function codexConfigPath() {
2706
2731
  return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
2707
2732
  }
2708
- function readCodexModel() {
2709
- const configPath = codexConfigPath();
2733
+ function codewithDbPath() {
2734
+ return process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"] ?? DEFAULT_CODEWITH_DB_PATH;
2735
+ }
2736
+ function codewithConfigPath() {
2737
+ return process.env["HASNA_ECONOMY_CODEWITH_CONFIG_PATH"] ?? DEFAULT_CODEWITH_CONFIG_PATH;
2738
+ }
2739
+ function codexSources() {
2740
+ const explicitCodexPath = process.env["HASNA_ECONOMY_CODEX_DB_PATH"];
2741
+ const explicitCodewithPath = process.env["HASNA_ECONOMY_CODEWITH_DB_PATH"];
2742
+ const sources = [{
2743
+ label: "Codex",
2744
+ dbPath: codexDbPath(),
2745
+ configPath: codexConfigPath(),
2746
+ sessionPrefix: "codex",
2747
+ stateSource: "codex"
2748
+ }];
2749
+ if (!explicitCodexPath || explicitCodewithPath) {
2750
+ sources.push({
2751
+ label: "Codewith",
2752
+ dbPath: codewithDbPath(),
2753
+ configPath: codewithConfigPath(),
2754
+ sessionPrefix: "codex-codewith",
2755
+ stateSource: "codex-codewith"
2756
+ });
2757
+ }
2758
+ const seen = new Set;
2759
+ return sources.filter((source) => {
2760
+ if (seen.has(source.dbPath))
2761
+ return false;
2762
+ seen.add(source.dbPath);
2763
+ return true;
2764
+ });
2765
+ }
2766
+ function readCodexModel(configPath = codexConfigPath()) {
2710
2767
  if (!existsSync3(configPath))
2711
2768
  return "gpt-5-codex";
2712
2769
  try {
@@ -2824,84 +2881,85 @@ function fallbackEvents(totalTokens) {
2824
2881
  }];
2825
2882
  }
2826
2883
  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
2884
  const machineId = getMachineId();
2834
- let codexDb = null;
2835
2885
  let ingested = 0;
2836
2886
  let requests = 0;
2837
2887
  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)
2888
+ for (const source of codexSources()) {
2889
+ if (!existsSync3(source.dbPath)) {
2890
+ if (verbose)
2891
+ console.log(`${source.label} DB not found:`, source.dbPath);
2892
+ continue;
2893
+ }
2894
+ let codexDb = null;
2895
+ try {
2896
+ codexDb = openCodexDb(source.dbPath, verbose);
2897
+ if (!codexDb)
2848
2898
  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,
2899
+ const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
2900
+ for (const thread of threads) {
2901
+ const model = thread.model ?? readCodexModel(source.configPath);
2902
+ const stateValue = `${CODEX_INGEST_VERSION}:${thread.updated_at}:${thread.tokens_used}:${model}`;
2903
+ const processed = getIngestState(db, source.stateSource, thread.id);
2904
+ if (processed === stateValue)
2905
+ continue;
2906
+ const projectPath = thread.cwd ?? "";
2907
+ const projectName = projectPath ? basename2(projectPath) : "unknown";
2908
+ const sessionId = `${source.sessionPrefix}-${thread.id}`;
2909
+ const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
2910
+ const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
2911
+ upsertSession(db, withAccount({
2912
+ id: sessionId,
2881
2913
  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,
2914
+ project_path: projectPath,
2915
+ project_name: projectName,
2916
+ started_at: startedAt,
2917
+ ended_at: endedAt,
2918
+ total_cost_usd: 0,
2919
+ total_tokens: 0,
2920
+ request_count: 0,
2893
2921
  machine_id: machineId
2894
2922
  }, 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}`);
2923
+ const events = readTokenEvents(thread.rollout_path);
2924
+ const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
2925
+ const ingestedTokens = tokenEvents.reduce((sum, event) => sum + tokenTotal(event.usage), 0);
2926
+ db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
2927
+ tokenEvents.forEach((event, index) => {
2928
+ const usage = event.usage;
2929
+ const inputTotal = usage.input_tokens ?? 0;
2930
+ const cacheReadTokens = usage.cached_input_tokens ?? 0;
2931
+ const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
2932
+ const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
2933
+ const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
2934
+ const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
2935
+ const requestId = `${sessionId}-${index}`;
2936
+ upsertRequest(db, withAccount({
2937
+ id: requestId,
2938
+ agent: "codex",
2939
+ session_id: sessionId,
2940
+ model,
2941
+ input_tokens: inputTokens,
2942
+ output_tokens: outputTokens,
2943
+ cache_read_tokens: cacheReadTokens,
2944
+ cache_create_tokens: 0,
2945
+ cost_usd: costUsd,
2946
+ cost_basis: defaultCostBasisForAgent("codex"),
2947
+ duration_ms: 0,
2948
+ timestamp,
2949
+ source_request_id: requestId,
2950
+ machine_id: machineId
2951
+ }, account));
2952
+ requests++;
2953
+ });
2954
+ rollupSession(db, sessionId);
2955
+ setIngestState(db, source.stateSource, thread.id, stateValue);
2956
+ ingested++;
2957
+ if (verbose)
2958
+ console.log(`${source.label} session ${thread.id}: ${ingestedTokens} tokens on ${model}`);
2959
+ }
2960
+ } finally {
2961
+ codexDb?.close();
2902
2962
  }
2903
- } finally {
2904
- codexDb?.close();
2905
2963
  }
2906
2964
  return { sessions: ingested, requests };
2907
2965
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/economy",
3
- "version": "0.2.36",
3
+ "version": "0.2.38",
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",