@hasna/economy 0.2.16 → 0.2.17
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 +58 -35
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +58 -35
- package/dist/mcp/index.js +58 -35
- package/dist/server/index.js +58 -35
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -472,43 +472,66 @@ function queryModelBreakdown(db) {
|
|
|
472
472
|
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
473
473
|
`).all();
|
|
474
474
|
}
|
|
475
|
+
function labelForPath(projectPath, projectName) {
|
|
476
|
+
if (projectName && projectName.trim() !== "")
|
|
477
|
+
return projectName;
|
|
478
|
+
if (!projectPath)
|
|
479
|
+
return "";
|
|
480
|
+
const segments = projectPath.split("/").filter(Boolean);
|
|
481
|
+
const projectPrefix = /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/;
|
|
482
|
+
for (const seg of segments) {
|
|
483
|
+
if (projectPrefix.test(seg))
|
|
484
|
+
return seg;
|
|
485
|
+
}
|
|
486
|
+
const generic = new Set(["web", "app", "apps", "packages", "src", "lib", "server", "client", "api", "frontend", "backend"]);
|
|
487
|
+
for (let i = segments.length - 1;i >= 0; i--) {
|
|
488
|
+
if (!generic.has(segments[i].toLowerCase()))
|
|
489
|
+
return segments[i];
|
|
490
|
+
}
|
|
491
|
+
return segments[segments.length - 1] ?? projectPath;
|
|
492
|
+
}
|
|
475
493
|
function queryProjectBreakdown(db) {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
s.project_path,
|
|
481
|
-
s.total_cost_usd,
|
|
482
|
-
s.started_at,
|
|
483
|
-
COALESCE(
|
|
484
|
-
NULLIF(s.project_name, ''),
|
|
485
|
-
CASE
|
|
486
|
-
WHEN s.project_path LIKE '%/%'
|
|
487
|
-
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
488
|
-
ELSE s.project_path
|
|
489
|
-
END
|
|
490
|
-
) as label
|
|
491
|
-
FROM sessions s
|
|
492
|
-
WHERE s.project_path != '' OR s.project_name != ''
|
|
493
|
-
)
|
|
494
|
-
SELECT
|
|
495
|
-
MIN(l.project_path) as project_path,
|
|
496
|
-
l.label as project_name,
|
|
497
|
-
COUNT(DISTINCT l.id) as sessions,
|
|
498
|
-
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
499
|
-
COALESCE(
|
|
500
|
-
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
501
|
-
SUM(l.total_cost_usd)
|
|
502
|
-
) as cost_usd,
|
|
503
|
-
COALESCE(
|
|
504
|
-
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
505
|
-
0
|
|
506
|
-
) as total_tokens,
|
|
507
|
-
MAX(l.started_at) as last_active
|
|
508
|
-
FROM labeled l
|
|
509
|
-
GROUP BY l.label
|
|
510
|
-
ORDER BY cost_usd DESC
|
|
494
|
+
const sessions = db.prepare(`
|
|
495
|
+
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
496
|
+
FROM sessions
|
|
497
|
+
WHERE project_path != '' OR project_name != ''
|
|
511
498
|
`).all();
|
|
499
|
+
const groups = new Map;
|
|
500
|
+
for (const s of sessions) {
|
|
501
|
+
const label = labelForPath(s.project_path, s.project_name);
|
|
502
|
+
if (!label)
|
|
503
|
+
continue;
|
|
504
|
+
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path, totalCost: 0, lastActive: "" };
|
|
505
|
+
g.sessionIds.push(s.id);
|
|
506
|
+
g.totalCost += s.total_cost_usd || 0;
|
|
507
|
+
if (!g.lastActive || s.started_at > g.lastActive)
|
|
508
|
+
g.lastActive = s.started_at;
|
|
509
|
+
if (!g.samplePath)
|
|
510
|
+
g.samplePath = s.project_path;
|
|
511
|
+
groups.set(label, g);
|
|
512
|
+
}
|
|
513
|
+
const result = [];
|
|
514
|
+
for (const [label, g] of groups.entries()) {
|
|
515
|
+
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
516
|
+
const reqStats = placeholders.length ? db.prepare(`
|
|
517
|
+
SELECT
|
|
518
|
+
COUNT(*) as requests,
|
|
519
|
+
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
520
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
|
|
521
|
+
FROM requests WHERE session_id IN (${placeholders})
|
|
522
|
+
`).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
|
|
523
|
+
result.push({
|
|
524
|
+
project_path: g.samplePath,
|
|
525
|
+
project_name: label,
|
|
526
|
+
sessions: g.sessionIds.length,
|
|
527
|
+
requests: reqStats.requests,
|
|
528
|
+
total_tokens: reqStats.total_tokens,
|
|
529
|
+
cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
|
|
530
|
+
last_active: g.lastActive
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
534
|
+
return result;
|
|
512
535
|
}
|
|
513
536
|
function queryDailyBreakdown(db, days = 30) {
|
|
514
537
|
return db.prepare(`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAKxD,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,cAAc,EACd,MAAM,EACN,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,MAAM,EACN,aAAa,EACd,MAAM,mBAAmB,CAAA;AAE1B,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAkBnC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAIlC;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,QAAQ,CAgBxE;AAmJD,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAarE;AAID,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAYzE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAYnE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAE,aAAkB,GAAG,cAAc,EAAE,CAkBxF;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,SAAK,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAKvF;AAID,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CA8BxF;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,cAAc,EAAE,CAUlE;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -430,43 +430,66 @@ function queryModelBreakdown(db) {
|
|
|
430
430
|
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
431
431
|
`).all();
|
|
432
432
|
}
|
|
433
|
+
function labelForPath(projectPath, projectName) {
|
|
434
|
+
if (projectName && projectName.trim() !== "")
|
|
435
|
+
return projectName;
|
|
436
|
+
if (!projectPath)
|
|
437
|
+
return "";
|
|
438
|
+
const segments = projectPath.split("/").filter(Boolean);
|
|
439
|
+
const projectPrefix = /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/;
|
|
440
|
+
for (const seg of segments) {
|
|
441
|
+
if (projectPrefix.test(seg))
|
|
442
|
+
return seg;
|
|
443
|
+
}
|
|
444
|
+
const generic = new Set(["web", "app", "apps", "packages", "src", "lib", "server", "client", "api", "frontend", "backend"]);
|
|
445
|
+
for (let i = segments.length - 1;i >= 0; i--) {
|
|
446
|
+
if (!generic.has(segments[i].toLowerCase()))
|
|
447
|
+
return segments[i];
|
|
448
|
+
}
|
|
449
|
+
return segments[segments.length - 1] ?? projectPath;
|
|
450
|
+
}
|
|
433
451
|
function queryProjectBreakdown(db) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
s.project_path,
|
|
439
|
-
s.total_cost_usd,
|
|
440
|
-
s.started_at,
|
|
441
|
-
COALESCE(
|
|
442
|
-
NULLIF(s.project_name, ''),
|
|
443
|
-
CASE
|
|
444
|
-
WHEN s.project_path LIKE '%/%'
|
|
445
|
-
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
446
|
-
ELSE s.project_path
|
|
447
|
-
END
|
|
448
|
-
) as label
|
|
449
|
-
FROM sessions s
|
|
450
|
-
WHERE s.project_path != '' OR s.project_name != ''
|
|
451
|
-
)
|
|
452
|
-
SELECT
|
|
453
|
-
MIN(l.project_path) as project_path,
|
|
454
|
-
l.label as project_name,
|
|
455
|
-
COUNT(DISTINCT l.id) as sessions,
|
|
456
|
-
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
457
|
-
COALESCE(
|
|
458
|
-
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
459
|
-
SUM(l.total_cost_usd)
|
|
460
|
-
) as cost_usd,
|
|
461
|
-
COALESCE(
|
|
462
|
-
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
463
|
-
0
|
|
464
|
-
) as total_tokens,
|
|
465
|
-
MAX(l.started_at) as last_active
|
|
466
|
-
FROM labeled l
|
|
467
|
-
GROUP BY l.label
|
|
468
|
-
ORDER BY cost_usd DESC
|
|
452
|
+
const sessions = db.prepare(`
|
|
453
|
+
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
454
|
+
FROM sessions
|
|
455
|
+
WHERE project_path != '' OR project_name != ''
|
|
469
456
|
`).all();
|
|
457
|
+
const groups = new Map;
|
|
458
|
+
for (const s of sessions) {
|
|
459
|
+
const label = labelForPath(s.project_path, s.project_name);
|
|
460
|
+
if (!label)
|
|
461
|
+
continue;
|
|
462
|
+
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path, totalCost: 0, lastActive: "" };
|
|
463
|
+
g.sessionIds.push(s.id);
|
|
464
|
+
g.totalCost += s.total_cost_usd || 0;
|
|
465
|
+
if (!g.lastActive || s.started_at > g.lastActive)
|
|
466
|
+
g.lastActive = s.started_at;
|
|
467
|
+
if (!g.samplePath)
|
|
468
|
+
g.samplePath = s.project_path;
|
|
469
|
+
groups.set(label, g);
|
|
470
|
+
}
|
|
471
|
+
const result = [];
|
|
472
|
+
for (const [label, g] of groups.entries()) {
|
|
473
|
+
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
474
|
+
const reqStats = placeholders.length ? db.prepare(`
|
|
475
|
+
SELECT
|
|
476
|
+
COUNT(*) as requests,
|
|
477
|
+
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
478
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
|
|
479
|
+
FROM requests WHERE session_id IN (${placeholders})
|
|
480
|
+
`).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
|
|
481
|
+
result.push({
|
|
482
|
+
project_path: g.samplePath,
|
|
483
|
+
project_name: label,
|
|
484
|
+
sessions: g.sessionIds.length,
|
|
485
|
+
requests: reqStats.requests,
|
|
486
|
+
total_tokens: reqStats.total_tokens,
|
|
487
|
+
cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
|
|
488
|
+
last_active: g.lastActive
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
492
|
+
return result;
|
|
470
493
|
}
|
|
471
494
|
function queryDailyBreakdown(db, days = 30) {
|
|
472
495
|
return db.prepare(`
|
package/dist/mcp/index.js
CHANGED
|
@@ -431,43 +431,66 @@ function queryModelBreakdown(db) {
|
|
|
431
431
|
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
432
432
|
`).all();
|
|
433
433
|
}
|
|
434
|
+
function labelForPath(projectPath, projectName) {
|
|
435
|
+
if (projectName && projectName.trim() !== "")
|
|
436
|
+
return projectName;
|
|
437
|
+
if (!projectPath)
|
|
438
|
+
return "";
|
|
439
|
+
const segments = projectPath.split("/").filter(Boolean);
|
|
440
|
+
const projectPrefix = /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/;
|
|
441
|
+
for (const seg of segments) {
|
|
442
|
+
if (projectPrefix.test(seg))
|
|
443
|
+
return seg;
|
|
444
|
+
}
|
|
445
|
+
const generic = new Set(["web", "app", "apps", "packages", "src", "lib", "server", "client", "api", "frontend", "backend"]);
|
|
446
|
+
for (let i = segments.length - 1;i >= 0; i--) {
|
|
447
|
+
if (!generic.has(segments[i].toLowerCase()))
|
|
448
|
+
return segments[i];
|
|
449
|
+
}
|
|
450
|
+
return segments[segments.length - 1] ?? projectPath;
|
|
451
|
+
}
|
|
434
452
|
function queryProjectBreakdown(db) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
s.project_path,
|
|
440
|
-
s.total_cost_usd,
|
|
441
|
-
s.started_at,
|
|
442
|
-
COALESCE(
|
|
443
|
-
NULLIF(s.project_name, ''),
|
|
444
|
-
CASE
|
|
445
|
-
WHEN s.project_path LIKE '%/%'
|
|
446
|
-
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
447
|
-
ELSE s.project_path
|
|
448
|
-
END
|
|
449
|
-
) as label
|
|
450
|
-
FROM sessions s
|
|
451
|
-
WHERE s.project_path != '' OR s.project_name != ''
|
|
452
|
-
)
|
|
453
|
-
SELECT
|
|
454
|
-
MIN(l.project_path) as project_path,
|
|
455
|
-
l.label as project_name,
|
|
456
|
-
COUNT(DISTINCT l.id) as sessions,
|
|
457
|
-
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
458
|
-
COALESCE(
|
|
459
|
-
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
460
|
-
SUM(l.total_cost_usd)
|
|
461
|
-
) as cost_usd,
|
|
462
|
-
COALESCE(
|
|
463
|
-
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
464
|
-
0
|
|
465
|
-
) as total_tokens,
|
|
466
|
-
MAX(l.started_at) as last_active
|
|
467
|
-
FROM labeled l
|
|
468
|
-
GROUP BY l.label
|
|
469
|
-
ORDER BY cost_usd DESC
|
|
453
|
+
const sessions = db.prepare(`
|
|
454
|
+
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
455
|
+
FROM sessions
|
|
456
|
+
WHERE project_path != '' OR project_name != ''
|
|
470
457
|
`).all();
|
|
458
|
+
const groups = new Map;
|
|
459
|
+
for (const s of sessions) {
|
|
460
|
+
const label = labelForPath(s.project_path, s.project_name);
|
|
461
|
+
if (!label)
|
|
462
|
+
continue;
|
|
463
|
+
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path, totalCost: 0, lastActive: "" };
|
|
464
|
+
g.sessionIds.push(s.id);
|
|
465
|
+
g.totalCost += s.total_cost_usd || 0;
|
|
466
|
+
if (!g.lastActive || s.started_at > g.lastActive)
|
|
467
|
+
g.lastActive = s.started_at;
|
|
468
|
+
if (!g.samplePath)
|
|
469
|
+
g.samplePath = s.project_path;
|
|
470
|
+
groups.set(label, g);
|
|
471
|
+
}
|
|
472
|
+
const result = [];
|
|
473
|
+
for (const [label, g] of groups.entries()) {
|
|
474
|
+
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
475
|
+
const reqStats = placeholders.length ? db.prepare(`
|
|
476
|
+
SELECT
|
|
477
|
+
COUNT(*) as requests,
|
|
478
|
+
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
479
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
|
|
480
|
+
FROM requests WHERE session_id IN (${placeholders})
|
|
481
|
+
`).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
|
|
482
|
+
result.push({
|
|
483
|
+
project_path: g.samplePath,
|
|
484
|
+
project_name: label,
|
|
485
|
+
sessions: g.sessionIds.length,
|
|
486
|
+
requests: reqStats.requests,
|
|
487
|
+
total_tokens: reqStats.total_tokens,
|
|
488
|
+
cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
|
|
489
|
+
last_active: g.lastActive
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
493
|
+
return result;
|
|
471
494
|
}
|
|
472
495
|
function queryDailyBreakdown(db, days = 30) {
|
|
473
496
|
return db.prepare(`
|
package/dist/server/index.js
CHANGED
|
@@ -432,43 +432,66 @@ function queryModelBreakdown(db) {
|
|
|
432
432
|
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
433
433
|
`).all();
|
|
434
434
|
}
|
|
435
|
+
function labelForPath(projectPath, projectName) {
|
|
436
|
+
if (projectName && projectName.trim() !== "")
|
|
437
|
+
return projectName;
|
|
438
|
+
if (!projectPath)
|
|
439
|
+
return "";
|
|
440
|
+
const segments = projectPath.split("/").filter(Boolean);
|
|
441
|
+
const projectPrefix = /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/;
|
|
442
|
+
for (const seg of segments) {
|
|
443
|
+
if (projectPrefix.test(seg))
|
|
444
|
+
return seg;
|
|
445
|
+
}
|
|
446
|
+
const generic = new Set(["web", "app", "apps", "packages", "src", "lib", "server", "client", "api", "frontend", "backend"]);
|
|
447
|
+
for (let i = segments.length - 1;i >= 0; i--) {
|
|
448
|
+
if (!generic.has(segments[i].toLowerCase()))
|
|
449
|
+
return segments[i];
|
|
450
|
+
}
|
|
451
|
+
return segments[segments.length - 1] ?? projectPath;
|
|
452
|
+
}
|
|
435
453
|
function queryProjectBreakdown(db) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
s.project_path,
|
|
441
|
-
s.total_cost_usd,
|
|
442
|
-
s.started_at,
|
|
443
|
-
COALESCE(
|
|
444
|
-
NULLIF(s.project_name, ''),
|
|
445
|
-
CASE
|
|
446
|
-
WHEN s.project_path LIKE '%/%'
|
|
447
|
-
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
448
|
-
ELSE s.project_path
|
|
449
|
-
END
|
|
450
|
-
) as label
|
|
451
|
-
FROM sessions s
|
|
452
|
-
WHERE s.project_path != '' OR s.project_name != ''
|
|
453
|
-
)
|
|
454
|
-
SELECT
|
|
455
|
-
MIN(l.project_path) as project_path,
|
|
456
|
-
l.label as project_name,
|
|
457
|
-
COUNT(DISTINCT l.id) as sessions,
|
|
458
|
-
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
459
|
-
COALESCE(
|
|
460
|
-
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
461
|
-
SUM(l.total_cost_usd)
|
|
462
|
-
) as cost_usd,
|
|
463
|
-
COALESCE(
|
|
464
|
-
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
465
|
-
0
|
|
466
|
-
) as total_tokens,
|
|
467
|
-
MAX(l.started_at) as last_active
|
|
468
|
-
FROM labeled l
|
|
469
|
-
GROUP BY l.label
|
|
470
|
-
ORDER BY cost_usd DESC
|
|
454
|
+
const sessions = db.prepare(`
|
|
455
|
+
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
456
|
+
FROM sessions
|
|
457
|
+
WHERE project_path != '' OR project_name != ''
|
|
471
458
|
`).all();
|
|
459
|
+
const groups = new Map;
|
|
460
|
+
for (const s of sessions) {
|
|
461
|
+
const label = labelForPath(s.project_path, s.project_name);
|
|
462
|
+
if (!label)
|
|
463
|
+
continue;
|
|
464
|
+
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path, totalCost: 0, lastActive: "" };
|
|
465
|
+
g.sessionIds.push(s.id);
|
|
466
|
+
g.totalCost += s.total_cost_usd || 0;
|
|
467
|
+
if (!g.lastActive || s.started_at > g.lastActive)
|
|
468
|
+
g.lastActive = s.started_at;
|
|
469
|
+
if (!g.samplePath)
|
|
470
|
+
g.samplePath = s.project_path;
|
|
471
|
+
groups.set(label, g);
|
|
472
|
+
}
|
|
473
|
+
const result = [];
|
|
474
|
+
for (const [label, g] of groups.entries()) {
|
|
475
|
+
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
476
|
+
const reqStats = placeholders.length ? db.prepare(`
|
|
477
|
+
SELECT
|
|
478
|
+
COUNT(*) as requests,
|
|
479
|
+
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
480
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
|
|
481
|
+
FROM requests WHERE session_id IN (${placeholders})
|
|
482
|
+
`).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
|
|
483
|
+
result.push({
|
|
484
|
+
project_path: g.samplePath,
|
|
485
|
+
project_name: label,
|
|
486
|
+
sessions: g.sessionIds.length,
|
|
487
|
+
requests: reqStats.requests,
|
|
488
|
+
total_tokens: reqStats.total_tokens,
|
|
489
|
+
cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
|
|
490
|
+
last_active: g.lastActive
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
494
|
+
return result;
|
|
472
495
|
}
|
|
473
496
|
function queryDailyBreakdown(db, days = 30) {
|
|
474
497
|
return db.prepare(`
|
package/package.json
CHANGED