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