@hasna/economy 0.2.26 → 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 +440 -81
- package/dist/db/database.d.ts +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +444 -86
- package/dist/lib/peer-sync.d.ts +21 -0
- package/dist/lib/peer-sync.d.ts.map +1 -0
- package/dist/mcp/index.js +132 -68
- package/dist/otel/index.js +35 -43
- package/dist/server/index.js +133 -69
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SqliteAdapter as Database } from '@hasna/cloud';
|
|
2
|
+
export interface PeerTableMergeStats {
|
|
3
|
+
table: string;
|
|
4
|
+
inserted: number;
|
|
5
|
+
updated: number;
|
|
6
|
+
skipped: number;
|
|
7
|
+
collisions: number;
|
|
8
|
+
}
|
|
9
|
+
export interface PeerMergeResult {
|
|
10
|
+
source_path: string;
|
|
11
|
+
source_machine: string;
|
|
12
|
+
rows_written: number;
|
|
13
|
+
collisions: number;
|
|
14
|
+
deduped: number;
|
|
15
|
+
tables: PeerTableMergeStats[];
|
|
16
|
+
}
|
|
17
|
+
export declare function mergePeerDatabase(target: Database, sourcePath: string, opts?: {
|
|
18
|
+
sourceMachine?: string;
|
|
19
|
+
now?: string;
|
|
20
|
+
}): PeerMergeResult;
|
|
21
|
+
//# sourceMappingURL=peer-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer-sync.d.ts","sourceRoot":"","sources":["../../src/lib/peer-sync.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAc7D,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,mBAAmB,EAAE,CAAA;CAC9B;AAwQD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,QAAQ,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,eAAe,CA0CjB"}
|
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
|
-
}
|
|
772
|
+
addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
|
|
768
773
|
}
|
|
769
|
-
|
|
770
|
-
if (
|
|
771
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN attribution_tag TEXT DEFAULT ''`);
|
|
772
|
-
}
|
|
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
|
-
|
|
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
|
}
|
|
@@ -1038,9 +1035,7 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
1038
1035
|
}
|
|
1039
1036
|
return [...groups.values()].sort((a, b) => b.api_equivalent_usd - a.api_equivalent_usd);
|
|
1040
1037
|
}
|
|
1041
|
-
function
|
|
1042
|
-
if (projectName && projectName.trim() !== "")
|
|
1043
|
-
return projectName;
|
|
1038
|
+
function pathProjectLabel(projectPath) {
|
|
1044
1039
|
if (!projectPath)
|
|
1045
1040
|
return "";
|
|
1046
1041
|
const segments = projectPath.split("/").filter(Boolean);
|
|
@@ -1049,12 +1044,45 @@ function labelForPath(projectPath, projectName) {
|
|
|
1049
1044
|
if (projectPrefix.test(seg))
|
|
1050
1045
|
return seg;
|
|
1051
1046
|
}
|
|
1052
|
-
const generic = new Set([
|
|
1047
|
+
const generic = new Set([
|
|
1048
|
+
"web",
|
|
1049
|
+
"app",
|
|
1050
|
+
"apps",
|
|
1051
|
+
"packages",
|
|
1052
|
+
"src",
|
|
1053
|
+
"lib",
|
|
1054
|
+
"server",
|
|
1055
|
+
"client",
|
|
1056
|
+
"api",
|
|
1057
|
+
"frontend",
|
|
1058
|
+
"backend",
|
|
1059
|
+
"home",
|
|
1060
|
+
"users",
|
|
1061
|
+
"workspace",
|
|
1062
|
+
"workspaces",
|
|
1063
|
+
"hasna"
|
|
1064
|
+
]);
|
|
1053
1065
|
for (let i = segments.length - 1;i >= 0; i--) {
|
|
1054
1066
|
if (!generic.has(segments[i].toLowerCase()))
|
|
1055
1067
|
return segments[i];
|
|
1056
1068
|
}
|
|
1057
|
-
return
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
function isRepoLikeLabel(label) {
|
|
1072
|
+
return /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/.test(label) || label.includes("-");
|
|
1073
|
+
}
|
|
1074
|
+
function labelForPath(projectPath, projectName) {
|
|
1075
|
+
const pathLabel = pathProjectLabel(projectPath);
|
|
1076
|
+
if (pathLabel && (!projectName || projectName.trim() === "" || isRepoLikeLabel(pathLabel)))
|
|
1077
|
+
return pathLabel;
|
|
1078
|
+
if (projectName && projectName.trim() !== "")
|
|
1079
|
+
return projectName;
|
|
1080
|
+
if (pathLabel)
|
|
1081
|
+
return pathLabel;
|
|
1082
|
+
return projectPath;
|
|
1083
|
+
}
|
|
1084
|
+
function groupKeyForPath(projectPath, projectName) {
|
|
1085
|
+
return labelForPath(projectPath, projectName).trim().toLowerCase();
|
|
1058
1086
|
}
|
|
1059
1087
|
function queryProjectBreakdown(db, period = "all") {
|
|
1060
1088
|
const requestWhere = requestPeriodWhere(period);
|
|
@@ -1069,14 +1097,15 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1069
1097
|
const label = labelForPath(s.project_path, s.project_name);
|
|
1070
1098
|
if (!label)
|
|
1071
1099
|
continue;
|
|
1072
|
-
const
|
|
1100
|
+
const key = groupKeyForPath(s.project_path, s.project_name);
|
|
1101
|
+
const g = groups.get(key) ?? { label, sessionIds: [], samplePath: s.project_path };
|
|
1073
1102
|
g.sessionIds.push(s.id);
|
|
1074
1103
|
if (!g.samplePath)
|
|
1075
1104
|
g.samplePath = s.project_path;
|
|
1076
|
-
groups.set(
|
|
1105
|
+
groups.set(key, g);
|
|
1077
1106
|
}
|
|
1078
1107
|
const result = [];
|
|
1079
|
-
for (const
|
|
1108
|
+
for (const g of groups.values()) {
|
|
1080
1109
|
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
1081
1110
|
const reqStats = placeholders.length ? db.prepare(`
|
|
1082
1111
|
SELECT
|
|
@@ -1107,7 +1136,7 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1107
1136
|
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1108
1137
|
result.push({
|
|
1109
1138
|
project_path: g.samplePath,
|
|
1110
|
-
project_name: label,
|
|
1139
|
+
project_name: g.label,
|
|
1111
1140
|
sessions: totalSessions,
|
|
1112
1141
|
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1113
1142
|
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
@@ -1317,17 +1346,48 @@ function queryBillingSummary(db, period) {
|
|
|
1317
1346
|
}
|
|
1318
1347
|
return { total_usd: total, by_provider };
|
|
1319
1348
|
}
|
|
1320
|
-
function listMachines(db) {
|
|
1349
|
+
function listMachines(db, period = "all") {
|
|
1350
|
+
const rWhere = requestPeriodWhere(period);
|
|
1351
|
+
const sWhere = sessionPeriodWhere(period);
|
|
1321
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
|
+
)
|
|
1322
1383
|
SELECT
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
COALESCE((
|
|
1326
|
-
COALESCE(SUM(
|
|
1327
|
-
MAX(
|
|
1328
|
-
FROM
|
|
1329
|
-
|
|
1330
|
-
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
|
|
1331
1391
|
ORDER BY total_cost_usd DESC
|
|
1332
1392
|
`).all();
|
|
1333
1393
|
}
|
|
@@ -1406,17 +1466,21 @@ function queryUsageSnapshots(db, opts = {}) {
|
|
|
1406
1466
|
}
|
|
1407
1467
|
function dedupeRequests(db) {
|
|
1408
1468
|
const dupes = db.prepare(`
|
|
1409
|
-
SELECT source_request_id, agent, MIN(id) as keep_id, COUNT(*) as cnt
|
|
1469
|
+
SELECT source_request_id, agent, COALESCE(machine_id, '') as machine_id, MIN(id) as keep_id, COUNT(*) as cnt
|
|
1410
1470
|
FROM requests
|
|
1411
1471
|
WHERE source_request_id != '' AND source_request_id IS NOT NULL
|
|
1412
|
-
GROUP BY source_request_id, agent
|
|
1472
|
+
GROUP BY source_request_id, agent, COALESCE(machine_id, '')
|
|
1413
1473
|
HAVING cnt > 1
|
|
1414
1474
|
`).all();
|
|
1415
1475
|
let removed = 0;
|
|
1416
1476
|
for (const row of dupes) {
|
|
1417
1477
|
const result = db.prepare(`
|
|
1418
|
-
DELETE FROM requests
|
|
1419
|
-
|
|
1478
|
+
DELETE FROM requests
|
|
1479
|
+
WHERE source_request_id = ?
|
|
1480
|
+
AND agent = ?
|
|
1481
|
+
AND COALESCE(machine_id, '') = ?
|
|
1482
|
+
AND id != ?
|
|
1483
|
+
`).run(row.source_request_id, row.agent, row.machine_id, row.keep_id);
|
|
1420
1484
|
removed += result.changes;
|
|
1421
1485
|
}
|
|
1422
1486
|
return removed;
|
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);
|