@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 +85 -57
- package/dist/db/database.d.ts +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +83 -55
- package/dist/mcp/index.js +83 -55
- package/dist/otel/index.js +35 -43
- package/dist/server/index.js +84 -56
- package/package.json +1 -1
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
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
815
|
-
db.exec(`ALTER TABLE requests ADD COLUMN ${column} TEXT DEFAULT ''`);
|
|
816
|
-
}
|
|
821
|
+
addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
|
|
817
822
|
}
|
|
818
|
-
|
|
819
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
1435
|
-
|
|
1436
|
-
COALESCE((
|
|
1437
|
-
COALESCE(SUM(
|
|
1438
|
-
MAX(
|
|
1439
|
-
FROM
|
|
1440
|
-
|
|
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));
|
package/dist/db/database.d.ts
CHANGED
|
@@ -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":"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,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;
|
|
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
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
773
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
1385
|
-
|
|
1386
|
-
COALESCE((
|
|
1387
|
-
COALESCE(SUM(
|
|
1388
|
-
MAX(
|
|
1389
|
-
FROM
|
|
1390
|
-
|
|
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
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
774
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
1356
|
-
|
|
1357
|
-
COALESCE((
|
|
1358
|
-
COALESCE(SUM(
|
|
1359
|
-
MAX(
|
|
1360
|
-
FROM
|
|
1361
|
-
|
|
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
|
}
|
package/dist/otel/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
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
774
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
package/dist/server/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
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
774
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
1377
|
-
|
|
1378
|
-
COALESCE((
|
|
1379
|
-
COALESCE(SUM(
|
|
1380
|
-
MAX(
|
|
1381
|
-
FROM
|
|
1382
|
-
|
|
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.
|
|
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",
|