@hasna/economy 0.2.27 → 0.2.28

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
@@ -615,6 +615,26 @@ function openDatabase(dbPath, skipSeed = false) {
615
615
  }
616
616
  return db;
617
617
  }
618
+ function quoteSqlIdent(identifier) {
619
+ return `"${identifier.replace(/"/g, '""')}"`;
620
+ }
621
+ function hasColumn(db, table, column) {
622
+ const columns = db.prepare(`PRAGMA table_info(${quoteSqlIdent(table)})`).all();
623
+ return columns.some((c) => c.name === column);
624
+ }
625
+ function addColumnIfMissing(db, table, column, definition) {
626
+ if (hasColumn(db, table, column))
627
+ return false;
628
+ try {
629
+ db.exec(`ALTER TABLE ${quoteSqlIdent(table)} ADD COLUMN ${quoteSqlIdent(column)} ${definition}`);
630
+ return true;
631
+ } catch (error) {
632
+ const message = error instanceof Error ? error.message : String(error);
633
+ if (/duplicate column name/i.test(message))
634
+ return true;
635
+ throw error;
636
+ }
637
+ }
618
638
  function initSchema(db) {
619
639
  db.exec(`
620
640
  CREATE TABLE IF NOT EXISTS requests (
@@ -785,59 +805,31 @@ function initSchema(db) {
785
805
  CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
786
806
  CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
787
807
  `);
788
- const cols = db.prepare(`PRAGMA table_info(requests)`).all();
789
- if (!cols.some((c) => c.name === "machine_id")) {
790
- db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
791
- db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
792
- }
793
- if (!cols.some((c) => c.name === "cache_create_5m_tokens")) {
794
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_5m_tokens INTEGER DEFAULT 0`);
808
+ addColumnIfMissing(db, "requests", "machine_id", `TEXT DEFAULT ''`);
809
+ addColumnIfMissing(db, "sessions", "machine_id", `TEXT DEFAULT ''`);
810
+ if (addColumnIfMissing(db, "requests", "cache_create_5m_tokens", "INTEGER DEFAULT 0")) {
795
811
  db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
796
812
  }
797
- if (!cols.some((c) => c.name === "cache_create_1h_tokens")) {
798
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_1h_tokens INTEGER DEFAULT 0`);
799
- }
800
- if (!cols.some((c) => c.name === "cost_basis")) {
801
- db.exec(`ALTER TABLE requests ADD COLUMN cost_basis TEXT DEFAULT 'estimated'`);
802
- }
803
- if (!cols.some((c) => c.name === "attribution_tag")) {
804
- db.exec(`ALTER TABLE requests ADD COLUMN attribution_tag TEXT DEFAULT ''`);
805
- }
806
- if (!cols.some((c) => c.name === "updated_at")) {
807
- db.exec(`ALTER TABLE requests ADD COLUMN updated_at TEXT DEFAULT ''`);
813
+ addColumnIfMissing(db, "requests", "cache_create_1h_tokens", "INTEGER DEFAULT 0");
814
+ addColumnIfMissing(db, "requests", "cost_basis", `TEXT DEFAULT 'estimated'`);
815
+ addColumnIfMissing(db, "requests", "attribution_tag", `TEXT DEFAULT ''`);
816
+ if (addColumnIfMissing(db, "requests", "updated_at", `TEXT DEFAULT ''`)) {
808
817
  db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
809
818
  }
810
- if (!cols.some((c) => c.name === "synced_at")) {
811
- db.exec(`ALTER TABLE requests ADD COLUMN synced_at TEXT DEFAULT ''`);
812
- }
819
+ addColumnIfMissing(db, "requests", "synced_at", `TEXT DEFAULT ''`);
813
820
  for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
814
- if (!cols.some((c) => c.name === column)) {
815
- db.exec(`ALTER TABLE requests ADD COLUMN ${column} TEXT DEFAULT ''`);
816
- }
821
+ addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
817
822
  }
818
- const sessionCols = db.prepare(`PRAGMA table_info(sessions)`).all();
819
- if (!sessionCols.some((c) => c.name === "attribution_tag")) {
820
- db.exec(`ALTER TABLE sessions ADD COLUMN attribution_tag TEXT DEFAULT ''`);
821
- }
822
- if (!sessionCols.some((c) => c.name === "updated_at")) {
823
- db.exec(`ALTER TABLE sessions ADD COLUMN updated_at TEXT DEFAULT ''`);
823
+ addColumnIfMissing(db, "sessions", "attribution_tag", `TEXT DEFAULT ''`);
824
+ if (addColumnIfMissing(db, "sessions", "updated_at", `TEXT DEFAULT ''`)) {
824
825
  db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
825
826
  }
826
- if (!sessionCols.some((c) => c.name === "synced_at")) {
827
- db.exec(`ALTER TABLE sessions ADD COLUMN synced_at TEXT DEFAULT ''`);
828
- }
827
+ addColumnIfMissing(db, "sessions", "synced_at", `TEXT DEFAULT ''`);
829
828
  for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
830
- if (!sessionCols.some((c) => c.name === column)) {
831
- db.exec(`ALTER TABLE sessions ADD COLUMN ${column} TEXT DEFAULT ''`);
832
- }
833
- }
834
- const pricingCols = db.prepare(`PRAGMA table_info(model_pricing)`).all();
835
- if (!pricingCols.some((c) => c.name === "cache_write_1h_per_1m")) {
836
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_write_1h_per_1m REAL NOT NULL DEFAULT 0`);
837
- }
838
- if (!pricingCols.some((c) => c.name === "cache_storage_per_1m_hour")) {
839
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0`);
829
+ addColumnIfMissing(db, "sessions", column, `TEXT DEFAULT ''`);
840
830
  }
831
+ addColumnIfMissing(db, "model_pricing", "cache_write_1h_per_1m", "REAL NOT NULL DEFAULT 0");
832
+ addColumnIfMissing(db, "model_pricing", "cache_storage_per_1m_hour", "REAL NOT NULL DEFAULT 0");
841
833
  db.exec(`
842
834
  CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
843
835
  CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
@@ -998,17 +990,22 @@ function querySummary(db, period, machine, allMachines = false) {
998
990
  const codexTotals = db.prepare(`
999
991
  SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
1000
992
  COALESCE(SUM(total_tokens), 0) as tokens,
993
+ COALESCE(SUM(request_count), 0) as requests,
1001
994
  COUNT(*) as sessions
1002
995
  FROM sessions
1003
996
  WHERE ${sWhere}${machineClause}
1004
997
  AND id NOT IN (SELECT DISTINCT session_id FROM requests)
1005
998
  `).get();
1006
- const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}${machineClause}`).get();
999
+ const requestSessionCount = db.prepare(`
1000
+ SELECT COUNT(DISTINCT session_id) as sessions
1001
+ FROM requests
1002
+ WHERE ${rWhere}${machineClause}
1003
+ `).get();
1007
1004
  return {
1008
1005
  total_usd: r.total_usd + codexTotals.cost_usd,
1009
- requests: r.requests,
1006
+ requests: r.requests + codexTotals.requests,
1010
1007
  tokens: r.tokens + codexTotals.tokens,
1011
- sessions: sessionCount.sessions,
1008
+ sessions: requestSessionCount.sessions + codexTotals.sessions,
1012
1009
  period
1013
1010
  };
1014
1011
  }
@@ -1428,17 +1425,48 @@ function queryBillingSummary(db, period) {
1428
1425
  }
1429
1426
  return { total_usd: total, by_provider };
1430
1427
  }
1431
- function listMachines(db) {
1428
+ function listMachines(db, period = "all") {
1429
+ const rWhere = requestPeriodWhere(period);
1430
+ const sWhere = sessionPeriodWhere(period);
1432
1431
  return db.prepare(`
1432
+ WITH request_stats AS (
1433
+ SELECT
1434
+ machine_id,
1435
+ COUNT(DISTINCT session_id) as sessions,
1436
+ COUNT(*) as requests,
1437
+ COALESCE(SUM(cost_usd), 0) as total_cost_usd,
1438
+ MAX(timestamp) as last_active
1439
+ FROM requests
1440
+ WHERE machine_id != ''
1441
+ AND ${rWhere}
1442
+ GROUP BY machine_id
1443
+ ),
1444
+ session_only_stats AS (
1445
+ SELECT
1446
+ machine_id,
1447
+ COUNT(*) as sessions,
1448
+ COALESCE(SUM(request_count), 0) as requests,
1449
+ COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
1450
+ MAX(started_at) as last_active
1451
+ FROM sessions
1452
+ WHERE machine_id != ''
1453
+ AND ${sWhere}
1454
+ AND id NOT IN (SELECT DISTINCT session_id FROM requests)
1455
+ GROUP BY machine_id
1456
+ ),
1457
+ combined AS (
1458
+ SELECT * FROM request_stats
1459
+ UNION ALL
1460
+ SELECT * FROM session_only_stats
1461
+ )
1433
1462
  SELECT
1434
- s.machine_id,
1435
- COUNT(DISTINCT s.id) as sessions,
1436
- COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
1437
- COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
1438
- MAX(s.started_at) as last_active
1439
- FROM sessions s
1440
- WHERE s.machine_id != ''
1441
- GROUP BY s.machine_id
1463
+ machine_id,
1464
+ COALESCE(SUM(sessions), 0) as sessions,
1465
+ COALESCE(SUM(requests), 0) as requests,
1466
+ COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
1467
+ MAX(last_active) as last_active
1468
+ FROM combined
1469
+ GROUP BY machine_id
1442
1470
  ORDER BY total_cost_usd DESC
1443
1471
  `).all();
1444
1472
  }
@@ -4515,7 +4543,7 @@ function createHandler(db) {
4515
4543
  const period = url.searchParams.get("period") ?? "month";
4516
4544
  return ok({
4517
4545
  summary: querySummary(db, period, undefined, true),
4518
- machines: listMachines(db),
4546
+ machines: listMachines(db, period),
4519
4547
  registry: listMachineRegistry(db),
4520
4548
  current_machine: getMachineId()
4521
4549
  });
@@ -6548,7 +6576,7 @@ function registerFleetCommands(program) {
6548
6576
  const db = openDatabase();
6549
6577
  const period = parsePeriod(opts.period, "today");
6550
6578
  const summary = querySummary(db, period, undefined, true);
6551
- const machines = listMachines(db);
6579
+ const machines = listMachines(db, period);
6552
6580
  const registry = listMachineRegistry(db);
6553
6581
  if (opts.json) {
6554
6582
  console.log(JSON.stringify({ period, summary, machines, registry }, null, 2));
@@ -70,7 +70,7 @@ export interface MachineInfo {
70
70
  total_cost_usd: number;
71
71
  last_active: string;
72
72
  }
73
- export declare function listMachines(db: Database): MachineInfo[];
73
+ export declare function listMachines(db: Database, period?: Period): MachineInfo[];
74
74
  export interface DbModelPricing {
75
75
  model: string;
76
76
  input_per_1m: number;
@@ -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
@@ -565,6 +565,26 @@ function openDatabase(dbPath, skipSeed = false) {
565
565
  }
566
566
  return db;
567
567
  }
568
+ function quoteSqlIdent(identifier) {
569
+ return `"${identifier.replace(/"/g, '""')}"`;
570
+ }
571
+ function hasColumn(db, table, column) {
572
+ const columns = db.prepare(`PRAGMA table_info(${quoteSqlIdent(table)})`).all();
573
+ return columns.some((c) => c.name === column);
574
+ }
575
+ function addColumnIfMissing(db, table, column, definition) {
576
+ if (hasColumn(db, table, column))
577
+ return false;
578
+ try {
579
+ db.exec(`ALTER TABLE ${quoteSqlIdent(table)} ADD COLUMN ${quoteSqlIdent(column)} ${definition}`);
580
+ return true;
581
+ } catch (error) {
582
+ const message = error instanceof Error ? error.message : String(error);
583
+ if (/duplicate column name/i.test(message))
584
+ return true;
585
+ throw error;
586
+ }
587
+ }
568
588
  function initSchema(db) {
569
589
  db.exec(`
570
590
  CREATE TABLE IF NOT EXISTS requests (
@@ -735,59 +755,31 @@ function initSchema(db) {
735
755
  CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
736
756
  CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
737
757
  `);
738
- const cols = db.prepare(`PRAGMA table_info(requests)`).all();
739
- if (!cols.some((c) => c.name === "machine_id")) {
740
- db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
741
- db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
742
- }
743
- if (!cols.some((c) => c.name === "cache_create_5m_tokens")) {
744
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_5m_tokens INTEGER DEFAULT 0`);
758
+ addColumnIfMissing(db, "requests", "machine_id", `TEXT DEFAULT ''`);
759
+ addColumnIfMissing(db, "sessions", "machine_id", `TEXT DEFAULT ''`);
760
+ if (addColumnIfMissing(db, "requests", "cache_create_5m_tokens", "INTEGER DEFAULT 0")) {
745
761
  db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
746
762
  }
747
- if (!cols.some((c) => c.name === "cache_create_1h_tokens")) {
748
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_1h_tokens INTEGER DEFAULT 0`);
749
- }
750
- if (!cols.some((c) => c.name === "cost_basis")) {
751
- db.exec(`ALTER TABLE requests ADD COLUMN cost_basis TEXT DEFAULT 'estimated'`);
752
- }
753
- if (!cols.some((c) => c.name === "attribution_tag")) {
754
- db.exec(`ALTER TABLE requests ADD COLUMN attribution_tag TEXT DEFAULT ''`);
755
- }
756
- if (!cols.some((c) => c.name === "updated_at")) {
757
- db.exec(`ALTER TABLE requests ADD COLUMN updated_at TEXT DEFAULT ''`);
763
+ addColumnIfMissing(db, "requests", "cache_create_1h_tokens", "INTEGER DEFAULT 0");
764
+ addColumnIfMissing(db, "requests", "cost_basis", `TEXT DEFAULT 'estimated'`);
765
+ addColumnIfMissing(db, "requests", "attribution_tag", `TEXT DEFAULT ''`);
766
+ if (addColumnIfMissing(db, "requests", "updated_at", `TEXT DEFAULT ''`)) {
758
767
  db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
759
768
  }
760
- if (!cols.some((c) => c.name === "synced_at")) {
761
- db.exec(`ALTER TABLE requests ADD COLUMN synced_at TEXT DEFAULT ''`);
762
- }
769
+ addColumnIfMissing(db, "requests", "synced_at", `TEXT DEFAULT ''`);
763
770
  for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
764
- if (!cols.some((c) => c.name === column)) {
765
- db.exec(`ALTER TABLE requests ADD COLUMN ${column} TEXT DEFAULT ''`);
766
- }
767
- }
768
- const sessionCols = db.prepare(`PRAGMA table_info(sessions)`).all();
769
- if (!sessionCols.some((c) => c.name === "attribution_tag")) {
770
- db.exec(`ALTER TABLE sessions ADD COLUMN attribution_tag TEXT DEFAULT ''`);
771
+ addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
771
772
  }
772
- if (!sessionCols.some((c) => c.name === "updated_at")) {
773
- db.exec(`ALTER TABLE sessions ADD COLUMN updated_at TEXT DEFAULT ''`);
773
+ addColumnIfMissing(db, "sessions", "attribution_tag", `TEXT DEFAULT ''`);
774
+ if (addColumnIfMissing(db, "sessions", "updated_at", `TEXT DEFAULT ''`)) {
774
775
  db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
775
776
  }
776
- if (!sessionCols.some((c) => c.name === "synced_at")) {
777
- db.exec(`ALTER TABLE sessions ADD COLUMN synced_at TEXT DEFAULT ''`);
778
- }
777
+ addColumnIfMissing(db, "sessions", "synced_at", `TEXT DEFAULT ''`);
779
778
  for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
780
- if (!sessionCols.some((c) => c.name === column)) {
781
- db.exec(`ALTER TABLE sessions ADD COLUMN ${column} TEXT DEFAULT ''`);
782
- }
783
- }
784
- const pricingCols = db.prepare(`PRAGMA table_info(model_pricing)`).all();
785
- if (!pricingCols.some((c) => c.name === "cache_write_1h_per_1m")) {
786
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_write_1h_per_1m REAL NOT NULL DEFAULT 0`);
787
- }
788
- if (!pricingCols.some((c) => c.name === "cache_storage_per_1m_hour")) {
789
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0`);
779
+ addColumnIfMissing(db, "sessions", column, `TEXT DEFAULT ''`);
790
780
  }
781
+ addColumnIfMissing(db, "model_pricing", "cache_write_1h_per_1m", "REAL NOT NULL DEFAULT 0");
782
+ addColumnIfMissing(db, "model_pricing", "cache_storage_per_1m_hour", "REAL NOT NULL DEFAULT 0");
791
783
  db.exec(`
792
784
  CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
793
785
  CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
@@ -948,17 +940,22 @@ function querySummary(db, period, machine, allMachines = false) {
948
940
  const codexTotals = db.prepare(`
949
941
  SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
950
942
  COALESCE(SUM(total_tokens), 0) as tokens,
943
+ COALESCE(SUM(request_count), 0) as requests,
951
944
  COUNT(*) as sessions
952
945
  FROM sessions
953
946
  WHERE ${sWhere}${machineClause}
954
947
  AND id NOT IN (SELECT DISTINCT session_id FROM requests)
955
948
  `).get();
956
- const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}${machineClause}`).get();
949
+ const requestSessionCount = db.prepare(`
950
+ SELECT COUNT(DISTINCT session_id) as sessions
951
+ FROM requests
952
+ WHERE ${rWhere}${machineClause}
953
+ `).get();
957
954
  return {
958
955
  total_usd: r.total_usd + codexTotals.cost_usd,
959
- requests: r.requests,
956
+ requests: r.requests + codexTotals.requests,
960
957
  tokens: r.tokens + codexTotals.tokens,
961
- sessions: sessionCount.sessions,
958
+ sessions: requestSessionCount.sessions + codexTotals.sessions,
962
959
  period
963
960
  };
964
961
  }
@@ -1378,17 +1375,48 @@ function queryBillingSummary(db, period) {
1378
1375
  }
1379
1376
  return { total_usd: total, by_provider };
1380
1377
  }
1381
- function listMachines(db) {
1378
+ function listMachines(db, period = "all") {
1379
+ const rWhere = requestPeriodWhere(period);
1380
+ const sWhere = sessionPeriodWhere(period);
1382
1381
  return db.prepare(`
1382
+ WITH request_stats AS (
1383
+ SELECT
1384
+ machine_id,
1385
+ COUNT(DISTINCT session_id) as sessions,
1386
+ COUNT(*) as requests,
1387
+ COALESCE(SUM(cost_usd), 0) as total_cost_usd,
1388
+ MAX(timestamp) as last_active
1389
+ FROM requests
1390
+ WHERE machine_id != ''
1391
+ AND ${rWhere}
1392
+ GROUP BY machine_id
1393
+ ),
1394
+ session_only_stats AS (
1395
+ SELECT
1396
+ machine_id,
1397
+ COUNT(*) as sessions,
1398
+ COALESCE(SUM(request_count), 0) as requests,
1399
+ COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
1400
+ MAX(started_at) as last_active
1401
+ FROM sessions
1402
+ WHERE machine_id != ''
1403
+ AND ${sWhere}
1404
+ AND id NOT IN (SELECT DISTINCT session_id FROM requests)
1405
+ GROUP BY machine_id
1406
+ ),
1407
+ combined AS (
1408
+ SELECT * FROM request_stats
1409
+ UNION ALL
1410
+ SELECT * FROM session_only_stats
1411
+ )
1383
1412
  SELECT
1384
- s.machine_id,
1385
- COUNT(DISTINCT s.id) as sessions,
1386
- COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
1387
- COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
1388
- MAX(s.started_at) as last_active
1389
- FROM sessions s
1390
- WHERE s.machine_id != ''
1391
- GROUP BY s.machine_id
1413
+ machine_id,
1414
+ COALESCE(SUM(sessions), 0) as sessions,
1415
+ COALESCE(SUM(requests), 0) as requests,
1416
+ COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
1417
+ MAX(last_active) as last_active
1418
+ FROM combined
1419
+ GROUP BY machine_id
1392
1420
  ORDER BY total_cost_usd DESC
1393
1421
  `).all();
1394
1422
  }
package/dist/mcp/index.js CHANGED
@@ -566,6 +566,26 @@ function openDatabase(dbPath, skipSeed = false) {
566
566
  }
567
567
  return db;
568
568
  }
569
+ function quoteSqlIdent(identifier) {
570
+ return `"${identifier.replace(/"/g, '""')}"`;
571
+ }
572
+ function hasColumn(db, table, column) {
573
+ const columns = db.prepare(`PRAGMA table_info(${quoteSqlIdent(table)})`).all();
574
+ return columns.some((c) => c.name === column);
575
+ }
576
+ function addColumnIfMissing(db, table, column, definition) {
577
+ if (hasColumn(db, table, column))
578
+ return false;
579
+ try {
580
+ db.exec(`ALTER TABLE ${quoteSqlIdent(table)} ADD COLUMN ${quoteSqlIdent(column)} ${definition}`);
581
+ return true;
582
+ } catch (error) {
583
+ const message = error instanceof Error ? error.message : String(error);
584
+ if (/duplicate column name/i.test(message))
585
+ return true;
586
+ throw error;
587
+ }
588
+ }
569
589
  function initSchema(db) {
570
590
  db.exec(`
571
591
  CREATE TABLE IF NOT EXISTS requests (
@@ -736,59 +756,31 @@ function initSchema(db) {
736
756
  CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
737
757
  CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
738
758
  `);
739
- const cols = db.prepare(`PRAGMA table_info(requests)`).all();
740
- if (!cols.some((c) => c.name === "machine_id")) {
741
- db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
742
- db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
743
- }
744
- if (!cols.some((c) => c.name === "cache_create_5m_tokens")) {
745
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_5m_tokens INTEGER DEFAULT 0`);
759
+ addColumnIfMissing(db, "requests", "machine_id", `TEXT DEFAULT ''`);
760
+ addColumnIfMissing(db, "sessions", "machine_id", `TEXT DEFAULT ''`);
761
+ if (addColumnIfMissing(db, "requests", "cache_create_5m_tokens", "INTEGER DEFAULT 0")) {
746
762
  db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
747
763
  }
748
- if (!cols.some((c) => c.name === "cache_create_1h_tokens")) {
749
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_1h_tokens INTEGER DEFAULT 0`);
750
- }
751
- if (!cols.some((c) => c.name === "cost_basis")) {
752
- db.exec(`ALTER TABLE requests ADD COLUMN cost_basis TEXT DEFAULT 'estimated'`);
753
- }
754
- if (!cols.some((c) => c.name === "attribution_tag")) {
755
- db.exec(`ALTER TABLE requests ADD COLUMN attribution_tag TEXT DEFAULT ''`);
756
- }
757
- if (!cols.some((c) => c.name === "updated_at")) {
758
- db.exec(`ALTER TABLE requests ADD COLUMN updated_at TEXT DEFAULT ''`);
764
+ addColumnIfMissing(db, "requests", "cache_create_1h_tokens", "INTEGER DEFAULT 0");
765
+ addColumnIfMissing(db, "requests", "cost_basis", `TEXT DEFAULT 'estimated'`);
766
+ addColumnIfMissing(db, "requests", "attribution_tag", `TEXT DEFAULT ''`);
767
+ if (addColumnIfMissing(db, "requests", "updated_at", `TEXT DEFAULT ''`)) {
759
768
  db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
760
769
  }
761
- if (!cols.some((c) => c.name === "synced_at")) {
762
- db.exec(`ALTER TABLE requests ADD COLUMN synced_at TEXT DEFAULT ''`);
763
- }
770
+ addColumnIfMissing(db, "requests", "synced_at", `TEXT DEFAULT ''`);
764
771
  for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
765
- if (!cols.some((c) => c.name === column)) {
766
- db.exec(`ALTER TABLE requests ADD COLUMN ${column} TEXT DEFAULT ''`);
767
- }
768
- }
769
- const sessionCols = db.prepare(`PRAGMA table_info(sessions)`).all();
770
- if (!sessionCols.some((c) => c.name === "attribution_tag")) {
771
- db.exec(`ALTER TABLE sessions ADD COLUMN attribution_tag TEXT DEFAULT ''`);
772
+ addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
772
773
  }
773
- if (!sessionCols.some((c) => c.name === "updated_at")) {
774
- db.exec(`ALTER TABLE sessions ADD COLUMN updated_at TEXT DEFAULT ''`);
774
+ addColumnIfMissing(db, "sessions", "attribution_tag", `TEXT DEFAULT ''`);
775
+ if (addColumnIfMissing(db, "sessions", "updated_at", `TEXT DEFAULT ''`)) {
775
776
  db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
776
777
  }
777
- if (!sessionCols.some((c) => c.name === "synced_at")) {
778
- db.exec(`ALTER TABLE sessions ADD COLUMN synced_at TEXT DEFAULT ''`);
779
- }
778
+ addColumnIfMissing(db, "sessions", "synced_at", `TEXT DEFAULT ''`);
780
779
  for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
781
- if (!sessionCols.some((c) => c.name === column)) {
782
- db.exec(`ALTER TABLE sessions ADD COLUMN ${column} TEXT DEFAULT ''`);
783
- }
784
- }
785
- const pricingCols = db.prepare(`PRAGMA table_info(model_pricing)`).all();
786
- if (!pricingCols.some((c) => c.name === "cache_write_1h_per_1m")) {
787
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_write_1h_per_1m REAL NOT NULL DEFAULT 0`);
788
- }
789
- if (!pricingCols.some((c) => c.name === "cache_storage_per_1m_hour")) {
790
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0`);
780
+ addColumnIfMissing(db, "sessions", column, `TEXT DEFAULT ''`);
791
781
  }
782
+ addColumnIfMissing(db, "model_pricing", "cache_write_1h_per_1m", "REAL NOT NULL DEFAULT 0");
783
+ addColumnIfMissing(db, "model_pricing", "cache_storage_per_1m_hour", "REAL NOT NULL DEFAULT 0");
792
784
  db.exec(`
793
785
  CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
794
786
  CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
@@ -949,17 +941,22 @@ function querySummary(db, period, machine, allMachines = false) {
949
941
  const codexTotals = db.prepare(`
950
942
  SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
951
943
  COALESCE(SUM(total_tokens), 0) as tokens,
944
+ COALESCE(SUM(request_count), 0) as requests,
952
945
  COUNT(*) as sessions
953
946
  FROM sessions
954
947
  WHERE ${sWhere}${machineClause}
955
948
  AND id NOT IN (SELECT DISTINCT session_id FROM requests)
956
949
  `).get();
957
- const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}${machineClause}`).get();
950
+ const requestSessionCount = db.prepare(`
951
+ SELECT COUNT(DISTINCT session_id) as sessions
952
+ FROM requests
953
+ WHERE ${rWhere}${machineClause}
954
+ `).get();
958
955
  return {
959
956
  total_usd: r.total_usd + codexTotals.cost_usd,
960
- requests: r.requests,
957
+ requests: r.requests + codexTotals.requests,
961
958
  tokens: r.tokens + codexTotals.tokens,
962
- sessions: sessionCount.sessions,
959
+ sessions: requestSessionCount.sessions + codexTotals.sessions,
963
960
  period
964
961
  };
965
962
  }
@@ -1349,17 +1346,48 @@ function queryBillingSummary(db, period) {
1349
1346
  }
1350
1347
  return { total_usd: total, by_provider };
1351
1348
  }
1352
- function listMachines(db) {
1349
+ function listMachines(db, period = "all") {
1350
+ const rWhere = requestPeriodWhere(period);
1351
+ const sWhere = sessionPeriodWhere(period);
1353
1352
  return db.prepare(`
1353
+ WITH request_stats AS (
1354
+ SELECT
1355
+ machine_id,
1356
+ COUNT(DISTINCT session_id) as sessions,
1357
+ COUNT(*) as requests,
1358
+ COALESCE(SUM(cost_usd), 0) as total_cost_usd,
1359
+ MAX(timestamp) as last_active
1360
+ FROM requests
1361
+ WHERE machine_id != ''
1362
+ AND ${rWhere}
1363
+ GROUP BY machine_id
1364
+ ),
1365
+ session_only_stats AS (
1366
+ SELECT
1367
+ machine_id,
1368
+ COUNT(*) as sessions,
1369
+ COALESCE(SUM(request_count), 0) as requests,
1370
+ COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
1371
+ MAX(started_at) as last_active
1372
+ FROM sessions
1373
+ WHERE machine_id != ''
1374
+ AND ${sWhere}
1375
+ AND id NOT IN (SELECT DISTINCT session_id FROM requests)
1376
+ GROUP BY machine_id
1377
+ ),
1378
+ combined AS (
1379
+ SELECT * FROM request_stats
1380
+ UNION ALL
1381
+ SELECT * FROM session_only_stats
1382
+ )
1354
1383
  SELECT
1355
- s.machine_id,
1356
- COUNT(DISTINCT s.id) as sessions,
1357
- COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
1358
- COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
1359
- MAX(s.started_at) as last_active
1360
- FROM sessions s
1361
- WHERE s.machine_id != ''
1362
- GROUP BY s.machine_id
1384
+ machine_id,
1385
+ COALESCE(SUM(sessions), 0) as sessions,
1386
+ COALESCE(SUM(requests), 0) as requests,
1387
+ COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
1388
+ MAX(last_active) as last_active
1389
+ FROM combined
1390
+ GROUP BY machine_id
1363
1391
  ORDER BY total_cost_usd DESC
1364
1392
  `).all();
1365
1393
  }
@@ -566,6 +566,26 @@ function openDatabase(dbPath, skipSeed = false) {
566
566
  }
567
567
  return db;
568
568
  }
569
+ function quoteSqlIdent(identifier) {
570
+ return `"${identifier.replace(/"/g, '""')}"`;
571
+ }
572
+ function hasColumn(db, table, column) {
573
+ const columns = db.prepare(`PRAGMA table_info(${quoteSqlIdent(table)})`).all();
574
+ return columns.some((c) => c.name === column);
575
+ }
576
+ function addColumnIfMissing(db, table, column, definition) {
577
+ if (hasColumn(db, table, column))
578
+ return false;
579
+ try {
580
+ db.exec(`ALTER TABLE ${quoteSqlIdent(table)} ADD COLUMN ${quoteSqlIdent(column)} ${definition}`);
581
+ return true;
582
+ } catch (error) {
583
+ const message = error instanceof Error ? error.message : String(error);
584
+ if (/duplicate column name/i.test(message))
585
+ return true;
586
+ throw error;
587
+ }
588
+ }
569
589
  function initSchema(db) {
570
590
  db.exec(`
571
591
  CREATE TABLE IF NOT EXISTS requests (
@@ -736,59 +756,31 @@ function initSchema(db) {
736
756
  CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
737
757
  CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
738
758
  `);
739
- const cols = db.prepare(`PRAGMA table_info(requests)`).all();
740
- if (!cols.some((c) => c.name === "machine_id")) {
741
- db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
742
- db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
743
- }
744
- if (!cols.some((c) => c.name === "cache_create_5m_tokens")) {
745
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_5m_tokens INTEGER DEFAULT 0`);
759
+ addColumnIfMissing(db, "requests", "machine_id", `TEXT DEFAULT ''`);
760
+ addColumnIfMissing(db, "sessions", "machine_id", `TEXT DEFAULT ''`);
761
+ if (addColumnIfMissing(db, "requests", "cache_create_5m_tokens", "INTEGER DEFAULT 0")) {
746
762
  db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
747
763
  }
748
- if (!cols.some((c) => c.name === "cache_create_1h_tokens")) {
749
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_1h_tokens INTEGER DEFAULT 0`);
750
- }
751
- if (!cols.some((c) => c.name === "cost_basis")) {
752
- db.exec(`ALTER TABLE requests ADD COLUMN cost_basis TEXT DEFAULT 'estimated'`);
753
- }
754
- if (!cols.some((c) => c.name === "attribution_tag")) {
755
- db.exec(`ALTER TABLE requests ADD COLUMN attribution_tag TEXT DEFAULT ''`);
756
- }
757
- if (!cols.some((c) => c.name === "updated_at")) {
758
- db.exec(`ALTER TABLE requests ADD COLUMN updated_at TEXT DEFAULT ''`);
764
+ addColumnIfMissing(db, "requests", "cache_create_1h_tokens", "INTEGER DEFAULT 0");
765
+ addColumnIfMissing(db, "requests", "cost_basis", `TEXT DEFAULT 'estimated'`);
766
+ addColumnIfMissing(db, "requests", "attribution_tag", `TEXT DEFAULT ''`);
767
+ if (addColumnIfMissing(db, "requests", "updated_at", `TEXT DEFAULT ''`)) {
759
768
  db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
760
769
  }
761
- if (!cols.some((c) => c.name === "synced_at")) {
762
- db.exec(`ALTER TABLE requests ADD COLUMN synced_at TEXT DEFAULT ''`);
763
- }
770
+ addColumnIfMissing(db, "requests", "synced_at", `TEXT DEFAULT ''`);
764
771
  for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
765
- if (!cols.some((c) => c.name === column)) {
766
- db.exec(`ALTER TABLE requests ADD COLUMN ${column} TEXT DEFAULT ''`);
767
- }
768
- }
769
- const sessionCols = db.prepare(`PRAGMA table_info(sessions)`).all();
770
- if (!sessionCols.some((c) => c.name === "attribution_tag")) {
771
- db.exec(`ALTER TABLE sessions ADD COLUMN attribution_tag TEXT DEFAULT ''`);
772
+ addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
772
773
  }
773
- if (!sessionCols.some((c) => c.name === "updated_at")) {
774
- db.exec(`ALTER TABLE sessions ADD COLUMN updated_at TEXT DEFAULT ''`);
774
+ addColumnIfMissing(db, "sessions", "attribution_tag", `TEXT DEFAULT ''`);
775
+ if (addColumnIfMissing(db, "sessions", "updated_at", `TEXT DEFAULT ''`)) {
775
776
  db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
776
777
  }
777
- if (!sessionCols.some((c) => c.name === "synced_at")) {
778
- db.exec(`ALTER TABLE sessions ADD COLUMN synced_at TEXT DEFAULT ''`);
779
- }
778
+ addColumnIfMissing(db, "sessions", "synced_at", `TEXT DEFAULT ''`);
780
779
  for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
781
- if (!sessionCols.some((c) => c.name === column)) {
782
- db.exec(`ALTER TABLE sessions ADD COLUMN ${column} TEXT DEFAULT ''`);
783
- }
784
- }
785
- const pricingCols = db.prepare(`PRAGMA table_info(model_pricing)`).all();
786
- if (!pricingCols.some((c) => c.name === "cache_write_1h_per_1m")) {
787
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_write_1h_per_1m REAL NOT NULL DEFAULT 0`);
788
- }
789
- if (!pricingCols.some((c) => c.name === "cache_storage_per_1m_hour")) {
790
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0`);
780
+ addColumnIfMissing(db, "sessions", column, `TEXT DEFAULT ''`);
791
781
  }
782
+ addColumnIfMissing(db, "model_pricing", "cache_write_1h_per_1m", "REAL NOT NULL DEFAULT 0");
783
+ addColumnIfMissing(db, "model_pricing", "cache_storage_per_1m_hour", "REAL NOT NULL DEFAULT 0");
792
784
  db.exec(`
793
785
  CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
794
786
  CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
@@ -566,6 +566,26 @@ function openDatabase(dbPath, skipSeed = false) {
566
566
  }
567
567
  return db;
568
568
  }
569
+ function quoteSqlIdent(identifier) {
570
+ return `"${identifier.replace(/"/g, '""')}"`;
571
+ }
572
+ function hasColumn(db, table, column) {
573
+ const columns = db.prepare(`PRAGMA table_info(${quoteSqlIdent(table)})`).all();
574
+ return columns.some((c) => c.name === column);
575
+ }
576
+ function addColumnIfMissing(db, table, column, definition) {
577
+ if (hasColumn(db, table, column))
578
+ return false;
579
+ try {
580
+ db.exec(`ALTER TABLE ${quoteSqlIdent(table)} ADD COLUMN ${quoteSqlIdent(column)} ${definition}`);
581
+ return true;
582
+ } catch (error) {
583
+ const message = error instanceof Error ? error.message : String(error);
584
+ if (/duplicate column name/i.test(message))
585
+ return true;
586
+ throw error;
587
+ }
588
+ }
569
589
  function initSchema(db) {
570
590
  db.exec(`
571
591
  CREATE TABLE IF NOT EXISTS requests (
@@ -736,59 +756,31 @@ function initSchema(db) {
736
756
  CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
737
757
  CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
738
758
  `);
739
- const cols = db.prepare(`PRAGMA table_info(requests)`).all();
740
- if (!cols.some((c) => c.name === "machine_id")) {
741
- db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
742
- db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
743
- }
744
- if (!cols.some((c) => c.name === "cache_create_5m_tokens")) {
745
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_5m_tokens INTEGER DEFAULT 0`);
759
+ addColumnIfMissing(db, "requests", "machine_id", `TEXT DEFAULT ''`);
760
+ addColumnIfMissing(db, "sessions", "machine_id", `TEXT DEFAULT ''`);
761
+ if (addColumnIfMissing(db, "requests", "cache_create_5m_tokens", "INTEGER DEFAULT 0")) {
746
762
  db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
747
763
  }
748
- if (!cols.some((c) => c.name === "cache_create_1h_tokens")) {
749
- db.exec(`ALTER TABLE requests ADD COLUMN cache_create_1h_tokens INTEGER DEFAULT 0`);
750
- }
751
- if (!cols.some((c) => c.name === "cost_basis")) {
752
- db.exec(`ALTER TABLE requests ADD COLUMN cost_basis TEXT DEFAULT 'estimated'`);
753
- }
754
- if (!cols.some((c) => c.name === "attribution_tag")) {
755
- db.exec(`ALTER TABLE requests ADD COLUMN attribution_tag TEXT DEFAULT ''`);
756
- }
757
- if (!cols.some((c) => c.name === "updated_at")) {
758
- db.exec(`ALTER TABLE requests ADD COLUMN updated_at TEXT DEFAULT ''`);
764
+ addColumnIfMissing(db, "requests", "cache_create_1h_tokens", "INTEGER DEFAULT 0");
765
+ addColumnIfMissing(db, "requests", "cost_basis", `TEXT DEFAULT 'estimated'`);
766
+ addColumnIfMissing(db, "requests", "attribution_tag", `TEXT DEFAULT ''`);
767
+ if (addColumnIfMissing(db, "requests", "updated_at", `TEXT DEFAULT ''`)) {
759
768
  db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
760
769
  }
761
- if (!cols.some((c) => c.name === "synced_at")) {
762
- db.exec(`ALTER TABLE requests ADD COLUMN synced_at TEXT DEFAULT ''`);
763
- }
770
+ addColumnIfMissing(db, "requests", "synced_at", `TEXT DEFAULT ''`);
764
771
  for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
765
- if (!cols.some((c) => c.name === column)) {
766
- db.exec(`ALTER TABLE requests ADD COLUMN ${column} TEXT DEFAULT ''`);
767
- }
768
- }
769
- const sessionCols = db.prepare(`PRAGMA table_info(sessions)`).all();
770
- if (!sessionCols.some((c) => c.name === "attribution_tag")) {
771
- db.exec(`ALTER TABLE sessions ADD COLUMN attribution_tag TEXT DEFAULT ''`);
772
+ addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
772
773
  }
773
- if (!sessionCols.some((c) => c.name === "updated_at")) {
774
- db.exec(`ALTER TABLE sessions ADD COLUMN updated_at TEXT DEFAULT ''`);
774
+ addColumnIfMissing(db, "sessions", "attribution_tag", `TEXT DEFAULT ''`);
775
+ if (addColumnIfMissing(db, "sessions", "updated_at", `TEXT DEFAULT ''`)) {
775
776
  db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
776
777
  }
777
- if (!sessionCols.some((c) => c.name === "synced_at")) {
778
- db.exec(`ALTER TABLE sessions ADD COLUMN synced_at TEXT DEFAULT ''`);
779
- }
778
+ addColumnIfMissing(db, "sessions", "synced_at", `TEXT DEFAULT ''`);
780
779
  for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
781
- if (!sessionCols.some((c) => c.name === column)) {
782
- db.exec(`ALTER TABLE sessions ADD COLUMN ${column} TEXT DEFAULT ''`);
783
- }
784
- }
785
- const pricingCols = db.prepare(`PRAGMA table_info(model_pricing)`).all();
786
- if (!pricingCols.some((c) => c.name === "cache_write_1h_per_1m")) {
787
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_write_1h_per_1m REAL NOT NULL DEFAULT 0`);
788
- }
789
- if (!pricingCols.some((c) => c.name === "cache_storage_per_1m_hour")) {
790
- db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0`);
780
+ addColumnIfMissing(db, "sessions", column, `TEXT DEFAULT ''`);
791
781
  }
782
+ addColumnIfMissing(db, "model_pricing", "cache_write_1h_per_1m", "REAL NOT NULL DEFAULT 0");
783
+ addColumnIfMissing(db, "model_pricing", "cache_storage_per_1m_hour", "REAL NOT NULL DEFAULT 0");
792
784
  db.exec(`
793
785
  CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
794
786
  CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
@@ -949,17 +941,22 @@ function querySummary(db, period, machine, allMachines = false) {
949
941
  const codexTotals = db.prepare(`
950
942
  SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
951
943
  COALESCE(SUM(total_tokens), 0) as tokens,
944
+ COALESCE(SUM(request_count), 0) as requests,
952
945
  COUNT(*) as sessions
953
946
  FROM sessions
954
947
  WHERE ${sWhere}${machineClause}
955
948
  AND id NOT IN (SELECT DISTINCT session_id FROM requests)
956
949
  `).get();
957
- const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}${machineClause}`).get();
950
+ const requestSessionCount = db.prepare(`
951
+ SELECT COUNT(DISTINCT session_id) as sessions
952
+ FROM requests
953
+ WHERE ${rWhere}${machineClause}
954
+ `).get();
958
955
  return {
959
956
  total_usd: r.total_usd + codexTotals.cost_usd,
960
- requests: r.requests,
957
+ requests: r.requests + codexTotals.requests,
961
958
  tokens: r.tokens + codexTotals.tokens,
962
- sessions: sessionCount.sessions,
959
+ sessions: requestSessionCount.sessions + codexTotals.sessions,
963
960
  period
964
961
  };
965
962
  }
@@ -1370,17 +1367,48 @@ function queryBillingSummary(db, period) {
1370
1367
  }
1371
1368
  return { total_usd: total, by_provider };
1372
1369
  }
1373
- function listMachines(db) {
1370
+ function listMachines(db, period = "all") {
1371
+ const rWhere = requestPeriodWhere(period);
1372
+ const sWhere = sessionPeriodWhere(period);
1374
1373
  return db.prepare(`
1374
+ WITH request_stats AS (
1375
+ SELECT
1376
+ machine_id,
1377
+ COUNT(DISTINCT session_id) as sessions,
1378
+ COUNT(*) as requests,
1379
+ COALESCE(SUM(cost_usd), 0) as total_cost_usd,
1380
+ MAX(timestamp) as last_active
1381
+ FROM requests
1382
+ WHERE machine_id != ''
1383
+ AND ${rWhere}
1384
+ GROUP BY machine_id
1385
+ ),
1386
+ session_only_stats AS (
1387
+ SELECT
1388
+ machine_id,
1389
+ COUNT(*) as sessions,
1390
+ COALESCE(SUM(request_count), 0) as requests,
1391
+ COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
1392
+ MAX(started_at) as last_active
1393
+ FROM sessions
1394
+ WHERE machine_id != ''
1395
+ AND ${sWhere}
1396
+ AND id NOT IN (SELECT DISTINCT session_id FROM requests)
1397
+ GROUP BY machine_id
1398
+ ),
1399
+ combined AS (
1400
+ SELECT * FROM request_stats
1401
+ UNION ALL
1402
+ SELECT * FROM session_only_stats
1403
+ )
1375
1404
  SELECT
1376
- s.machine_id,
1377
- COUNT(DISTINCT s.id) as sessions,
1378
- COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
1379
- COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
1380
- MAX(s.started_at) as last_active
1381
- FROM sessions s
1382
- WHERE s.machine_id != ''
1383
- GROUP BY s.machine_id
1405
+ machine_id,
1406
+ COALESCE(SUM(sessions), 0) as sessions,
1407
+ COALESCE(SUM(requests), 0) as requests,
1408
+ COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
1409
+ MAX(last_active) as last_active
1410
+ FROM combined
1411
+ GROUP BY machine_id
1384
1412
  ORDER BY total_cost_usd DESC
1385
1413
  `).all();
1386
1414
  }
@@ -4059,7 +4087,7 @@ function createHandler(db) {
4059
4087
  const period = url.searchParams.get("period") ?? "month";
4060
4088
  return ok({
4061
4089
  summary: querySummary(db, period, undefined, true),
4062
- machines: listMachines(db),
4090
+ machines: listMachines(db, period),
4063
4091
  registry: listMachineRegistry(db),
4064
4092
  current_machine: getMachineId()
4065
4093
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/economy",
3
- "version": "0.2.27",
3
+ "version": "0.2.28",
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",