@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 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
- return db.prepare(`
477
- WITH labeled AS (
478
- SELECT
479
- s.id,
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":"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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
@@ -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
- return db.prepare(`
435
- WITH labeled AS (
436
- SELECT
437
- s.id,
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
- return db.prepare(`
436
- WITH labeled AS (
437
- SELECT
438
- s.id,
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(`
@@ -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
- return db.prepare(`
437
- WITH labeled AS (
438
- SELECT
439
- s.id,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/economy",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "description": "AI coding cost tracker — CLI + MCP server + REST API + web dashboard for Claude Code, Codex, and Gemini",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",