@hasna/economy 0.2.25 → 0.2.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +377 -25
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +382 -31
- package/dist/ingest/codex.d.ts.map +1 -1
- package/dist/lib/peer-sync.d.ts +21 -0
- package/dist/lib/peer-sync.d.ts.map +1 -0
- package/dist/mcp/index.js +71 -14
- package/dist/server/index.js +71 -14
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -1038,9 +1038,7 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
1038
1038
|
}
|
|
1039
1039
|
return [...groups.values()].sort((a, b) => b.api_equivalent_usd - a.api_equivalent_usd);
|
|
1040
1040
|
}
|
|
1041
|
-
function
|
|
1042
|
-
if (projectName && projectName.trim() !== "")
|
|
1043
|
-
return projectName;
|
|
1041
|
+
function pathProjectLabel(projectPath) {
|
|
1044
1042
|
if (!projectPath)
|
|
1045
1043
|
return "";
|
|
1046
1044
|
const segments = projectPath.split("/").filter(Boolean);
|
|
@@ -1049,12 +1047,45 @@ function labelForPath(projectPath, projectName) {
|
|
|
1049
1047
|
if (projectPrefix.test(seg))
|
|
1050
1048
|
return seg;
|
|
1051
1049
|
}
|
|
1052
|
-
const generic = new Set([
|
|
1050
|
+
const generic = new Set([
|
|
1051
|
+
"web",
|
|
1052
|
+
"app",
|
|
1053
|
+
"apps",
|
|
1054
|
+
"packages",
|
|
1055
|
+
"src",
|
|
1056
|
+
"lib",
|
|
1057
|
+
"server",
|
|
1058
|
+
"client",
|
|
1059
|
+
"api",
|
|
1060
|
+
"frontend",
|
|
1061
|
+
"backend",
|
|
1062
|
+
"home",
|
|
1063
|
+
"users",
|
|
1064
|
+
"workspace",
|
|
1065
|
+
"workspaces",
|
|
1066
|
+
"hasna"
|
|
1067
|
+
]);
|
|
1053
1068
|
for (let i = segments.length - 1;i >= 0; i--) {
|
|
1054
1069
|
if (!generic.has(segments[i].toLowerCase()))
|
|
1055
1070
|
return segments[i];
|
|
1056
1071
|
}
|
|
1057
|
-
return
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
function isRepoLikeLabel(label) {
|
|
1075
|
+
return /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/.test(label) || label.includes("-");
|
|
1076
|
+
}
|
|
1077
|
+
function labelForPath(projectPath, projectName) {
|
|
1078
|
+
const pathLabel = pathProjectLabel(projectPath);
|
|
1079
|
+
if (pathLabel && (!projectName || projectName.trim() === "" || isRepoLikeLabel(pathLabel)))
|
|
1080
|
+
return pathLabel;
|
|
1081
|
+
if (projectName && projectName.trim() !== "")
|
|
1082
|
+
return projectName;
|
|
1083
|
+
if (pathLabel)
|
|
1084
|
+
return pathLabel;
|
|
1085
|
+
return projectPath;
|
|
1086
|
+
}
|
|
1087
|
+
function groupKeyForPath(projectPath, projectName) {
|
|
1088
|
+
return labelForPath(projectPath, projectName).trim().toLowerCase();
|
|
1058
1089
|
}
|
|
1059
1090
|
function queryProjectBreakdown(db, period = "all") {
|
|
1060
1091
|
const requestWhere = requestPeriodWhere(period);
|
|
@@ -1069,14 +1100,15 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1069
1100
|
const label = labelForPath(s.project_path, s.project_name);
|
|
1070
1101
|
if (!label)
|
|
1071
1102
|
continue;
|
|
1072
|
-
const
|
|
1103
|
+
const key = groupKeyForPath(s.project_path, s.project_name);
|
|
1104
|
+
const g = groups.get(key) ?? { label, sessionIds: [], samplePath: s.project_path };
|
|
1073
1105
|
g.sessionIds.push(s.id);
|
|
1074
1106
|
if (!g.samplePath)
|
|
1075
1107
|
g.samplePath = s.project_path;
|
|
1076
|
-
groups.set(
|
|
1108
|
+
groups.set(key, g);
|
|
1077
1109
|
}
|
|
1078
1110
|
const result = [];
|
|
1079
|
-
for (const
|
|
1111
|
+
for (const g of groups.values()) {
|
|
1080
1112
|
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
1081
1113
|
const reqStats = placeholders.length ? db.prepare(`
|
|
1082
1114
|
SELECT
|
|
@@ -1107,7 +1139,7 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1107
1139
|
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1108
1140
|
result.push({
|
|
1109
1141
|
project_path: g.samplePath,
|
|
1110
|
-
project_name: label,
|
|
1142
|
+
project_name: g.label,
|
|
1111
1143
|
sessions: totalSessions,
|
|
1112
1144
|
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1113
1145
|
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
@@ -1406,17 +1438,21 @@ function queryUsageSnapshots(db, opts = {}) {
|
|
|
1406
1438
|
}
|
|
1407
1439
|
function dedupeRequests(db) {
|
|
1408
1440
|
const dupes = db.prepare(`
|
|
1409
|
-
SELECT source_request_id, agent, MIN(id) as keep_id, COUNT(*) as cnt
|
|
1441
|
+
SELECT source_request_id, agent, COALESCE(machine_id, '') as machine_id, MIN(id) as keep_id, COUNT(*) as cnt
|
|
1410
1442
|
FROM requests
|
|
1411
1443
|
WHERE source_request_id != '' AND source_request_id IS NOT NULL
|
|
1412
|
-
GROUP BY source_request_id, agent
|
|
1444
|
+
GROUP BY source_request_id, agent, COALESCE(machine_id, '')
|
|
1413
1445
|
HAVING cnt > 1
|
|
1414
1446
|
`).all();
|
|
1415
1447
|
let removed = 0;
|
|
1416
1448
|
for (const row of dupes) {
|
|
1417
1449
|
const result = db.prepare(`
|
|
1418
|
-
DELETE FROM requests
|
|
1419
|
-
|
|
1450
|
+
DELETE FROM requests
|
|
1451
|
+
WHERE source_request_id = ?
|
|
1452
|
+
AND agent = ?
|
|
1453
|
+
AND COALESCE(machine_id, '') = ?
|
|
1454
|
+
AND id != ?
|
|
1455
|
+
`).run(row.source_request_id, row.agent, row.machine_id, row.keep_id);
|
|
1420
1456
|
removed += result.changes;
|
|
1421
1457
|
}
|
|
1422
1458
|
return removed;
|
|
@@ -2122,6 +2158,25 @@ function buildThreadQuery(codexDb) {
|
|
|
2122
2158
|
FROM threads WHERE tokens_used > 0
|
|
2123
2159
|
`;
|
|
2124
2160
|
}
|
|
2161
|
+
function openCodexDb(dbPath, verbose) {
|
|
2162
|
+
let lastError;
|
|
2163
|
+
for (const readonly of [true, false]) {
|
|
2164
|
+
let codexDb = null;
|
|
2165
|
+
try {
|
|
2166
|
+
codexDb = readonly ? new BunDatabase(dbPath, { readonly: true }) : new BunDatabase(dbPath);
|
|
2167
|
+
codexDb.prepare("PRAGMA schema_version").get();
|
|
2168
|
+
return codexDb;
|
|
2169
|
+
} catch (error) {
|
|
2170
|
+
lastError = error;
|
|
2171
|
+
codexDb?.close();
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
if (verbose) {
|
|
2175
|
+
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
2176
|
+
console.log("Codex DB unreadable:", dbPath, message);
|
|
2177
|
+
}
|
|
2178
|
+
return null;
|
|
2179
|
+
}
|
|
2125
2180
|
function readTokenEvents(rolloutPath) {
|
|
2126
2181
|
if (!rolloutPath || !existsSync3(rolloutPath))
|
|
2127
2182
|
return [];
|
|
@@ -2211,7 +2266,9 @@ async function ingestCodex(db, verbose = false) {
|
|
|
2211
2266
|
let requests = 0;
|
|
2212
2267
|
const account = await resolveAccountForAgent("codex");
|
|
2213
2268
|
try {
|
|
2214
|
-
codexDb =
|
|
2269
|
+
codexDb = openCodexDb(dbPath, verbose);
|
|
2270
|
+
if (!codexDb)
|
|
2271
|
+
return { sessions: 0, requests: 0 };
|
|
2215
2272
|
const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
|
|
2216
2273
|
for (const thread of threads) {
|
|
2217
2274
|
const model = thread.model ?? readCodexModel();
|
package/dist/server/index.js
CHANGED
|
@@ -1038,9 +1038,7 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
1038
1038
|
}
|
|
1039
1039
|
return [...groups.values()].sort((a, b) => b.api_equivalent_usd - a.api_equivalent_usd);
|
|
1040
1040
|
}
|
|
1041
|
-
function
|
|
1042
|
-
if (projectName && projectName.trim() !== "")
|
|
1043
|
-
return projectName;
|
|
1041
|
+
function pathProjectLabel(projectPath) {
|
|
1044
1042
|
if (!projectPath)
|
|
1045
1043
|
return "";
|
|
1046
1044
|
const segments = projectPath.split("/").filter(Boolean);
|
|
@@ -1049,12 +1047,45 @@ function labelForPath(projectPath, projectName) {
|
|
|
1049
1047
|
if (projectPrefix.test(seg))
|
|
1050
1048
|
return seg;
|
|
1051
1049
|
}
|
|
1052
|
-
const generic = new Set([
|
|
1050
|
+
const generic = new Set([
|
|
1051
|
+
"web",
|
|
1052
|
+
"app",
|
|
1053
|
+
"apps",
|
|
1054
|
+
"packages",
|
|
1055
|
+
"src",
|
|
1056
|
+
"lib",
|
|
1057
|
+
"server",
|
|
1058
|
+
"client",
|
|
1059
|
+
"api",
|
|
1060
|
+
"frontend",
|
|
1061
|
+
"backend",
|
|
1062
|
+
"home",
|
|
1063
|
+
"users",
|
|
1064
|
+
"workspace",
|
|
1065
|
+
"workspaces",
|
|
1066
|
+
"hasna"
|
|
1067
|
+
]);
|
|
1053
1068
|
for (let i = segments.length - 1;i >= 0; i--) {
|
|
1054
1069
|
if (!generic.has(segments[i].toLowerCase()))
|
|
1055
1070
|
return segments[i];
|
|
1056
1071
|
}
|
|
1057
|
-
return
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
function isRepoLikeLabel(label) {
|
|
1075
|
+
return /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/.test(label) || label.includes("-");
|
|
1076
|
+
}
|
|
1077
|
+
function labelForPath(projectPath, projectName) {
|
|
1078
|
+
const pathLabel = pathProjectLabel(projectPath);
|
|
1079
|
+
if (pathLabel && (!projectName || projectName.trim() === "" || isRepoLikeLabel(pathLabel)))
|
|
1080
|
+
return pathLabel;
|
|
1081
|
+
if (projectName && projectName.trim() !== "")
|
|
1082
|
+
return projectName;
|
|
1083
|
+
if (pathLabel)
|
|
1084
|
+
return pathLabel;
|
|
1085
|
+
return projectPath;
|
|
1086
|
+
}
|
|
1087
|
+
function groupKeyForPath(projectPath, projectName) {
|
|
1088
|
+
return labelForPath(projectPath, projectName).trim().toLowerCase();
|
|
1058
1089
|
}
|
|
1059
1090
|
function queryProjectBreakdown(db, period = "all") {
|
|
1060
1091
|
const requestWhere = requestPeriodWhere(period);
|
|
@@ -1069,14 +1100,15 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1069
1100
|
const label = labelForPath(s.project_path, s.project_name);
|
|
1070
1101
|
if (!label)
|
|
1071
1102
|
continue;
|
|
1072
|
-
const
|
|
1103
|
+
const key = groupKeyForPath(s.project_path, s.project_name);
|
|
1104
|
+
const g = groups.get(key) ?? { label, sessionIds: [], samplePath: s.project_path };
|
|
1073
1105
|
g.sessionIds.push(s.id);
|
|
1074
1106
|
if (!g.samplePath)
|
|
1075
1107
|
g.samplePath = s.project_path;
|
|
1076
|
-
groups.set(
|
|
1108
|
+
groups.set(key, g);
|
|
1077
1109
|
}
|
|
1078
1110
|
const result = [];
|
|
1079
|
-
for (const
|
|
1111
|
+
for (const g of groups.values()) {
|
|
1080
1112
|
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
1081
1113
|
const reqStats = placeholders.length ? db.prepare(`
|
|
1082
1114
|
SELECT
|
|
@@ -1107,7 +1139,7 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1107
1139
|
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1108
1140
|
result.push({
|
|
1109
1141
|
project_path: g.samplePath,
|
|
1110
|
-
project_name: label,
|
|
1142
|
+
project_name: g.label,
|
|
1111
1143
|
sessions: totalSessions,
|
|
1112
1144
|
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1113
1145
|
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
@@ -1430,17 +1462,21 @@ function listMachineRegistry(db) {
|
|
|
1430
1462
|
}
|
|
1431
1463
|
function dedupeRequests(db) {
|
|
1432
1464
|
const dupes = db.prepare(`
|
|
1433
|
-
SELECT source_request_id, agent, MIN(id) as keep_id, COUNT(*) as cnt
|
|
1465
|
+
SELECT source_request_id, agent, COALESCE(machine_id, '') as machine_id, MIN(id) as keep_id, COUNT(*) as cnt
|
|
1434
1466
|
FROM requests
|
|
1435
1467
|
WHERE source_request_id != '' AND source_request_id IS NOT NULL
|
|
1436
|
-
GROUP BY source_request_id, agent
|
|
1468
|
+
GROUP BY source_request_id, agent, COALESCE(machine_id, '')
|
|
1437
1469
|
HAVING cnt > 1
|
|
1438
1470
|
`).all();
|
|
1439
1471
|
let removed = 0;
|
|
1440
1472
|
for (const row of dupes) {
|
|
1441
1473
|
const result = db.prepare(`
|
|
1442
|
-
DELETE FROM requests
|
|
1443
|
-
|
|
1474
|
+
DELETE FROM requests
|
|
1475
|
+
WHERE source_request_id = ?
|
|
1476
|
+
AND agent = ?
|
|
1477
|
+
AND COALESCE(machine_id, '') = ?
|
|
1478
|
+
AND id != ?
|
|
1479
|
+
`).run(row.source_request_id, row.agent, row.machine_id, row.keep_id);
|
|
1444
1480
|
removed += result.changes;
|
|
1445
1481
|
}
|
|
1446
1482
|
return removed;
|
|
@@ -2549,6 +2585,25 @@ function buildThreadQuery(codexDb) {
|
|
|
2549
2585
|
FROM threads WHERE tokens_used > 0
|
|
2550
2586
|
`;
|
|
2551
2587
|
}
|
|
2588
|
+
function openCodexDb(dbPath, verbose) {
|
|
2589
|
+
let lastError;
|
|
2590
|
+
for (const readonly of [true, false]) {
|
|
2591
|
+
let codexDb = null;
|
|
2592
|
+
try {
|
|
2593
|
+
codexDb = readonly ? new BunDatabase(dbPath, { readonly: true }) : new BunDatabase(dbPath);
|
|
2594
|
+
codexDb.prepare("PRAGMA schema_version").get();
|
|
2595
|
+
return codexDb;
|
|
2596
|
+
} catch (error) {
|
|
2597
|
+
lastError = error;
|
|
2598
|
+
codexDb?.close();
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
if (verbose) {
|
|
2602
|
+
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
2603
|
+
console.log("Codex DB unreadable:", dbPath, message);
|
|
2604
|
+
}
|
|
2605
|
+
return null;
|
|
2606
|
+
}
|
|
2552
2607
|
function readTokenEvents(rolloutPath) {
|
|
2553
2608
|
if (!rolloutPath || !existsSync3(rolloutPath))
|
|
2554
2609
|
return [];
|
|
@@ -2638,7 +2693,9 @@ async function ingestCodex(db, verbose = false) {
|
|
|
2638
2693
|
let requests = 0;
|
|
2639
2694
|
const account = await resolveAccountForAgent("codex");
|
|
2640
2695
|
try {
|
|
2641
|
-
codexDb =
|
|
2696
|
+
codexDb = openCodexDb(dbPath, verbose);
|
|
2697
|
+
if (!codexDb)
|
|
2698
|
+
return { sessions: 0, requests: 0 };
|
|
2642
2699
|
const threads = codexDb.prepare(buildThreadQuery(codexDb)).all();
|
|
2643
2700
|
for (const thread of threads) {
|
|
2644
2701
|
const model = thread.model ?? readCodexModel();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.27",
|
|
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",
|