@hasna/economy 0.2.26 → 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 +355 -24
- 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 +361 -31
- package/dist/lib/peer-sync.d.ts +21 -0
- package/dist/lib/peer-sync.d.ts.map +1 -0
- package/dist/mcp/index.js +49 -13
- package/dist/server/index.js +49 -13
- package/package.json +1 -1
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;
|
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",
|