@hasna/economy 0.2.27 → 0.2.29
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/LICENSE +2 -1
- package/README.md +16 -1
- package/dist/cli/index.js +417 -189
- package/dist/db/database.d.ts +10 -5
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +268 -162
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/mcp/http.d.ts +13 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/index.js +846 -670
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/otel/index.js +35 -43
- package/dist/server/index.js +284 -170
- package/dist/server/serve.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -534,6 +534,7 @@ __export(exports_database, {
|
|
|
534
534
|
queryRequestsSince: () => queryRequestsSince,
|
|
535
535
|
queryProjectBreakdown: () => queryProjectBreakdown,
|
|
536
536
|
queryModelBreakdown: () => queryModelBreakdown,
|
|
537
|
+
queryHourlyBreakdown: () => queryHourlyBreakdown,
|
|
537
538
|
queryDailyBreakdown: () => queryDailyBreakdown,
|
|
538
539
|
queryBillingSummary: () => queryBillingSummary,
|
|
539
540
|
queryAgentBreakdown: () => queryAgentBreakdown,
|
|
@@ -615,6 +616,26 @@ function openDatabase(dbPath, skipSeed = false) {
|
|
|
615
616
|
}
|
|
616
617
|
return db;
|
|
617
618
|
}
|
|
619
|
+
function quoteSqlIdent(identifier) {
|
|
620
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
621
|
+
}
|
|
622
|
+
function hasColumn(db, table, column) {
|
|
623
|
+
const columns = db.prepare(`PRAGMA table_info(${quoteSqlIdent(table)})`).all();
|
|
624
|
+
return columns.some((c) => c.name === column);
|
|
625
|
+
}
|
|
626
|
+
function addColumnIfMissing(db, table, column, definition) {
|
|
627
|
+
if (hasColumn(db, table, column))
|
|
628
|
+
return false;
|
|
629
|
+
try {
|
|
630
|
+
db.exec(`ALTER TABLE ${quoteSqlIdent(table)} ADD COLUMN ${quoteSqlIdent(column)} ${definition}`);
|
|
631
|
+
return true;
|
|
632
|
+
} catch (error) {
|
|
633
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
634
|
+
if (/duplicate column name/i.test(message))
|
|
635
|
+
return true;
|
|
636
|
+
throw error;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
618
639
|
function initSchema(db) {
|
|
619
640
|
db.exec(`
|
|
620
641
|
CREATE TABLE IF NOT EXISTS requests (
|
|
@@ -785,59 +806,31 @@ function initSchema(db) {
|
|
|
785
806
|
CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
|
|
786
807
|
CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
|
|
787
808
|
`);
|
|
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`);
|
|
809
|
+
addColumnIfMissing(db, "requests", "machine_id", `TEXT DEFAULT ''`);
|
|
810
|
+
addColumnIfMissing(db, "sessions", "machine_id", `TEXT DEFAULT ''`);
|
|
811
|
+
if (addColumnIfMissing(db, "requests", "cache_create_5m_tokens", "INTEGER DEFAULT 0")) {
|
|
795
812
|
db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
|
|
796
813
|
}
|
|
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 ''`);
|
|
814
|
+
addColumnIfMissing(db, "requests", "cache_create_1h_tokens", "INTEGER DEFAULT 0");
|
|
815
|
+
addColumnIfMissing(db, "requests", "cost_basis", `TEXT DEFAULT 'estimated'`);
|
|
816
|
+
addColumnIfMissing(db, "requests", "attribution_tag", `TEXT DEFAULT ''`);
|
|
817
|
+
if (addColumnIfMissing(db, "requests", "updated_at", `TEXT DEFAULT ''`)) {
|
|
808
818
|
db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
|
|
809
819
|
}
|
|
810
|
-
|
|
811
|
-
db.exec(`ALTER TABLE requests ADD COLUMN synced_at TEXT DEFAULT ''`);
|
|
812
|
-
}
|
|
820
|
+
addColumnIfMissing(db, "requests", "synced_at", `TEXT DEFAULT ''`);
|
|
813
821
|
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
|
-
}
|
|
822
|
+
addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
|
|
817
823
|
}
|
|
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 ''`);
|
|
824
|
+
addColumnIfMissing(db, "sessions", "attribution_tag", `TEXT DEFAULT ''`);
|
|
825
|
+
if (addColumnIfMissing(db, "sessions", "updated_at", `TEXT DEFAULT ''`)) {
|
|
824
826
|
db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
|
|
825
827
|
}
|
|
826
|
-
|
|
827
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN synced_at TEXT DEFAULT ''`);
|
|
828
|
-
}
|
|
828
|
+
addColumnIfMissing(db, "sessions", "synced_at", `TEXT DEFAULT ''`);
|
|
829
829
|
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`);
|
|
830
|
+
addColumnIfMissing(db, "sessions", column, `TEXT DEFAULT ''`);
|
|
840
831
|
}
|
|
832
|
+
addColumnIfMissing(db, "model_pricing", "cache_write_1h_per_1m", "REAL NOT NULL DEFAULT 0");
|
|
833
|
+
addColumnIfMissing(db, "model_pricing", "cache_storage_per_1m_hour", "REAL NOT NULL DEFAULT 0");
|
|
841
834
|
db.exec(`
|
|
842
835
|
CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
|
|
843
836
|
CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
|
|
@@ -998,17 +991,22 @@ function querySummary(db, period, machine, allMachines = false) {
|
|
|
998
991
|
const codexTotals = db.prepare(`
|
|
999
992
|
SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1000
993
|
COALESCE(SUM(total_tokens), 0) as tokens,
|
|
994
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1001
995
|
COUNT(*) as sessions
|
|
1002
996
|
FROM sessions
|
|
1003
997
|
WHERE ${sWhere}${machineClause}
|
|
1004
998
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1005
999
|
`).get();
|
|
1006
|
-
const
|
|
1000
|
+
const requestSessionCount = db.prepare(`
|
|
1001
|
+
SELECT COUNT(DISTINCT session_id) as sessions
|
|
1002
|
+
FROM requests
|
|
1003
|
+
WHERE ${rWhere}${machineClause}
|
|
1004
|
+
`).get();
|
|
1007
1005
|
return {
|
|
1008
1006
|
total_usd: r.total_usd + codexTotals.cost_usd,
|
|
1009
|
-
requests: r.requests,
|
|
1007
|
+
requests: r.requests + codexTotals.requests,
|
|
1010
1008
|
tokens: r.tokens + codexTotals.tokens,
|
|
1011
|
-
sessions:
|
|
1009
|
+
sessions: requestSessionCount.sessions + codexTotals.sessions,
|
|
1012
1010
|
period
|
|
1013
1011
|
};
|
|
1014
1012
|
}
|
|
@@ -1023,8 +1021,10 @@ function queryModelBreakdown(db) {
|
|
|
1023
1021
|
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
1024
1022
|
`).all();
|
|
1025
1023
|
}
|
|
1026
|
-
function queryAgentBreakdown(db, period = "all") {
|
|
1024
|
+
function queryAgentBreakdown(db, period = "all", machine) {
|
|
1027
1025
|
const requestWhere = requestPeriodWhere(period);
|
|
1026
|
+
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
1027
|
+
const machineParams = machine ? [machine] : [];
|
|
1028
1028
|
const groups = new Map;
|
|
1029
1029
|
const requestRows = db.prepare(`
|
|
1030
1030
|
SELECT agent,
|
|
@@ -1040,10 +1040,10 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
1040
1040
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1041
1041
|
MAX(timestamp) as last_active
|
|
1042
1042
|
FROM requests
|
|
1043
|
-
WHERE ${requestWhere}
|
|
1043
|
+
WHERE ${requestWhere}${machineClause}
|
|
1044
1044
|
GROUP BY agent
|
|
1045
1045
|
ORDER BY api_equivalent_usd DESC
|
|
1046
|
-
`).all();
|
|
1046
|
+
`).all(...machineParams);
|
|
1047
1047
|
for (const row of requestRows) {
|
|
1048
1048
|
groups.set(row.agent, row);
|
|
1049
1049
|
}
|
|
@@ -1056,10 +1056,10 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
1056
1056
|
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1057
1057
|
MAX(started_at) as last_active
|
|
1058
1058
|
FROM sessions
|
|
1059
|
-
WHERE ${sessionWhere}
|
|
1059
|
+
WHERE ${sessionWhere}${machineClause}
|
|
1060
1060
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1061
1061
|
GROUP BY agent
|
|
1062
|
-
`).all();
|
|
1062
|
+
`).all(...machineParams);
|
|
1063
1063
|
for (const row of sessionOnlyRows) {
|
|
1064
1064
|
const existing = groups.get(row.agent) ?? {
|
|
1065
1065
|
agent: row.agent,
|
|
@@ -1136,14 +1136,20 @@ function labelForPath(projectPath, projectName) {
|
|
|
1136
1136
|
function groupKeyForPath(projectPath, projectName) {
|
|
1137
1137
|
return labelForPath(projectPath, projectName).trim().toLowerCase();
|
|
1138
1138
|
}
|
|
1139
|
-
function queryProjectBreakdown(db, period = "all") {
|
|
1139
|
+
function queryProjectBreakdown(db, period = "all", machine) {
|
|
1140
1140
|
const requestWhere = requestPeriodWhere(period);
|
|
1141
1141
|
const sessionWhere = sessionPeriodWhere(period);
|
|
1142
|
+
const sessionMachineClause = machine ? " AND (machine_id = ? OR id IN (SELECT DISTINCT session_id FROM requests WHERE machine_id = ?))" : "";
|
|
1143
|
+
const requestMachineClause = machine ? " AND machine_id = ?" : "";
|
|
1144
|
+
const sessionMachineParams = machine ? [machine, machine] : [];
|
|
1145
|
+
const requestMachineParams = machine ? [machine] : [];
|
|
1146
|
+
const sessionOnlyMachineClause = machine ? " AND machine_id = ?" : "";
|
|
1147
|
+
const sessionOnlyMachineParams = machine ? [machine] : [];
|
|
1142
1148
|
const sessions = db.prepare(`
|
|
1143
1149
|
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
1144
1150
|
FROM sessions
|
|
1145
|
-
WHERE project_path != '' OR project_name != ''
|
|
1146
|
-
`).all();
|
|
1151
|
+
WHERE (project_path != '' OR project_name != '')${sessionMachineClause}
|
|
1152
|
+
`).all(...sessionMachineParams);
|
|
1147
1153
|
const groups = new Map;
|
|
1148
1154
|
for (const s of sessions) {
|
|
1149
1155
|
const label = labelForPath(s.project_path, s.project_name);
|
|
@@ -1169,7 +1175,8 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1169
1175
|
FROM requests
|
|
1170
1176
|
WHERE session_id IN (${placeholders})
|
|
1171
1177
|
AND ${requestWhere}
|
|
1172
|
-
|
|
1178
|
+
${requestMachineClause}
|
|
1179
|
+
`).get(...g.sessionIds, ...requestMachineParams) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
|
|
1173
1180
|
const sessionOnlyStats = placeholders.length ? db.prepare(`
|
|
1174
1181
|
SELECT
|
|
1175
1182
|
COUNT(*) as sessions,
|
|
@@ -1180,8 +1187,9 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1180
1187
|
FROM sessions
|
|
1181
1188
|
WHERE id IN (${placeholders})
|
|
1182
1189
|
AND ${sessionWhere}
|
|
1190
|
+
${sessionOnlyMachineClause}
|
|
1183
1191
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1184
|
-
`).get(...g.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1192
|
+
`).get(...g.sessionIds, ...sessionOnlyMachineParams) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1185
1193
|
const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1186
1194
|
if (totalSessions === 0)
|
|
1187
1195
|
continue;
|
|
@@ -1199,107 +1207,167 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1199
1207
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1200
1208
|
return result;
|
|
1201
1209
|
}
|
|
1202
|
-
function
|
|
1210
|
+
function normalizeAccountEmail(email) {
|
|
1211
|
+
return (email ?? "").trim().toLowerCase();
|
|
1212
|
+
}
|
|
1213
|
+
function accountIdentityKey(agent, accountKey, accountName, accountEmail) {
|
|
1214
|
+
const identityAgent = (agent || "").trim();
|
|
1215
|
+
const normalizedEmail = normalizeAccountEmail(accountEmail);
|
|
1216
|
+
if (identityAgent && normalizedEmail)
|
|
1217
|
+
return `${identityAgent}:${normalizedEmail}`;
|
|
1218
|
+
if (identityAgent && accountName)
|
|
1219
|
+
return `${identityAgent}:${accountName}`;
|
|
1220
|
+
if (accountKey)
|
|
1221
|
+
return accountKey;
|
|
1222
|
+
return identityAgent ? `${identityAgent}:unknown` : "";
|
|
1223
|
+
}
|
|
1224
|
+
function addAccountBreakdownRow(groups, row, sessionOnly) {
|
|
1225
|
+
const agent = row.agent || row.account_tool;
|
|
1226
|
+
const email = normalizeAccountEmail(row.account_email);
|
|
1227
|
+
const accountName = row.account_name || email || row.account_key;
|
|
1228
|
+
const key = accountIdentityKey(agent, row.account_key, accountName, email);
|
|
1229
|
+
if (!key)
|
|
1230
|
+
return;
|
|
1231
|
+
const group = groups.get(key) ?? {
|
|
1232
|
+
account_key: key,
|
|
1233
|
+
account_tool: agent,
|
|
1234
|
+
account_name: accountName,
|
|
1235
|
+
account_email: email || null,
|
|
1236
|
+
account_source: row.account_source || "unknown",
|
|
1237
|
+
sessionIds: new Set,
|
|
1238
|
+
requests: 0,
|
|
1239
|
+
total_tokens: 0,
|
|
1240
|
+
api_equivalent_usd: 0,
|
|
1241
|
+
metered_api_usd: 0,
|
|
1242
|
+
subscription_included_usd: 0,
|
|
1243
|
+
estimated_usd: 0,
|
|
1244
|
+
unknown_usd: 0,
|
|
1245
|
+
last_active: ""
|
|
1246
|
+
};
|
|
1247
|
+
if (!group.account_email && email)
|
|
1248
|
+
group.account_email = email;
|
|
1249
|
+
if (!group.account_name && accountName)
|
|
1250
|
+
group.account_name = accountName;
|
|
1251
|
+
if ((!group.account_source || group.account_source === "unknown") && row.account_source && row.account_source !== "unknown") {
|
|
1252
|
+
group.account_source = row.account_source;
|
|
1253
|
+
}
|
|
1254
|
+
if (row.session_id)
|
|
1255
|
+
group.sessionIds.add(row.session_id);
|
|
1256
|
+
group.requests += row.requests;
|
|
1257
|
+
group.total_tokens += row.total_tokens;
|
|
1258
|
+
group.api_equivalent_usd += row.cost_usd;
|
|
1259
|
+
if (sessionOnly) {
|
|
1260
|
+
group.estimated_usd += row.cost_usd;
|
|
1261
|
+
} else if (row.cost_basis === "metered_api") {
|
|
1262
|
+
group.metered_api_usd += row.cost_usd;
|
|
1263
|
+
} else if (row.cost_basis === "subscription_included") {
|
|
1264
|
+
group.subscription_included_usd += row.cost_usd;
|
|
1265
|
+
} else if (row.cost_basis === "unknown") {
|
|
1266
|
+
group.unknown_usd += row.cost_usd;
|
|
1267
|
+
} else {
|
|
1268
|
+
group.estimated_usd += row.cost_usd;
|
|
1269
|
+
}
|
|
1270
|
+
if (!group.last_active || row.last_active > group.last_active)
|
|
1271
|
+
group.last_active = row.last_active;
|
|
1272
|
+
groups.set(key, group);
|
|
1273
|
+
}
|
|
1274
|
+
function queryAccountBreakdown(db, period = "all", machine) {
|
|
1203
1275
|
const requestWhere = requestPeriodWhere(period);
|
|
1204
1276
|
const sessionWhere = sessionPeriodWhere(period);
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
WHERE account_key != '' OR account_tool != '' OR account_name != '' OR account_email != ''
|
|
1210
|
-
`).all();
|
|
1277
|
+
const requestMachineClause = machine ? " AND r.machine_id = ?" : "";
|
|
1278
|
+
const sessionMachineClause = machine ? " AND s.machine_id = ?" : "";
|
|
1279
|
+
const requestMachineParams = machine ? [machine] : [];
|
|
1280
|
+
const sessionMachineParams = machine ? [machine] : [];
|
|
1211
1281
|
const groups = new Map;
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
account_email: group.account_email,
|
|
1278
|
-
account_source: group.account_source,
|
|
1279
|
-
sessions: sessionsTotal,
|
|
1280
|
-
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1281
|
-
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1282
|
-
api_equivalent_usd: apiEquivalentUsd,
|
|
1283
|
-
billable_usd: billableUsd,
|
|
1284
|
-
metered_api_usd: reqStats.metered_api_usd,
|
|
1285
|
-
subscription_included_usd: reqStats.subscription_included_usd,
|
|
1286
|
-
estimated_usd: estimatedUsd,
|
|
1287
|
-
unknown_usd: reqStats.unknown_usd,
|
|
1288
|
-
cost_usd: apiEquivalentUsd,
|
|
1289
|
-
last_active: lastActive
|
|
1290
|
-
});
|
|
1291
|
-
}
|
|
1282
|
+
const requestRows = db.prepare(`
|
|
1283
|
+
SELECT
|
|
1284
|
+
r.session_id as session_id,
|
|
1285
|
+
COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), '') as agent,
|
|
1286
|
+
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as account_key,
|
|
1287
|
+
COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') as account_tool,
|
|
1288
|
+
COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as account_name,
|
|
1289
|
+
COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') as account_email,
|
|
1290
|
+
COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
1291
|
+
1 as requests,
|
|
1292
|
+
COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
|
|
1293
|
+
COALESCE(r.cost_usd, 0) as cost_usd,
|
|
1294
|
+
COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
|
|
1295
|
+
r.timestamp as last_active
|
|
1296
|
+
FROM requests r
|
|
1297
|
+
LEFT JOIN sessions s ON s.id = r.session_id
|
|
1298
|
+
WHERE ${requestWhere}${requestMachineClause}
|
|
1299
|
+
AND (
|
|
1300
|
+
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') != ''
|
|
1301
|
+
OR COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') != ''
|
|
1302
|
+
OR COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') != ''
|
|
1303
|
+
OR COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') != ''
|
|
1304
|
+
)
|
|
1305
|
+
`).all(...requestMachineParams);
|
|
1306
|
+
for (const row of requestRows)
|
|
1307
|
+
addAccountBreakdownRow(groups, row, false);
|
|
1308
|
+
const sessionOnlyRows = db.prepare(`
|
|
1309
|
+
SELECT
|
|
1310
|
+
s.id as session_id,
|
|
1311
|
+
s.agent as agent,
|
|
1312
|
+
s.account_key as account_key,
|
|
1313
|
+
s.account_tool as account_tool,
|
|
1314
|
+
s.account_name as account_name,
|
|
1315
|
+
s.account_email as account_email,
|
|
1316
|
+
COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
1317
|
+
COALESCE(s.request_count, 0) as requests,
|
|
1318
|
+
COALESCE(s.total_tokens, 0) as total_tokens,
|
|
1319
|
+
COALESCE(s.total_cost_usd, 0) as cost_usd,
|
|
1320
|
+
'estimated' as cost_basis,
|
|
1321
|
+
s.started_at as last_active
|
|
1322
|
+
FROM sessions s
|
|
1323
|
+
WHERE ${sessionWhere}${sessionMachineClause}
|
|
1324
|
+
AND s.id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1325
|
+
AND (s.account_key != '' OR s.account_tool != '' OR s.account_name != '' OR s.account_email != '')
|
|
1326
|
+
`).all(...sessionMachineParams);
|
|
1327
|
+
for (const row of sessionOnlyRows)
|
|
1328
|
+
addAccountBreakdownRow(groups, row, true);
|
|
1329
|
+
const result = [...groups.values()].map((group) => ({
|
|
1330
|
+
account_key: group.account_key,
|
|
1331
|
+
account_tool: group.account_tool,
|
|
1332
|
+
account_name: group.account_name,
|
|
1333
|
+
account_email: group.account_email,
|
|
1334
|
+
account_source: group.account_source,
|
|
1335
|
+
sessions: group.sessionIds.size,
|
|
1336
|
+
requests: group.requests,
|
|
1337
|
+
total_tokens: group.total_tokens,
|
|
1338
|
+
api_equivalent_usd: group.api_equivalent_usd,
|
|
1339
|
+
billable_usd: group.metered_api_usd,
|
|
1340
|
+
metered_api_usd: group.metered_api_usd,
|
|
1341
|
+
subscription_included_usd: group.subscription_included_usd,
|
|
1342
|
+
estimated_usd: group.estimated_usd,
|
|
1343
|
+
unknown_usd: group.unknown_usd,
|
|
1344
|
+
cost_usd: group.api_equivalent_usd,
|
|
1345
|
+
last_active: group.last_active
|
|
1346
|
+
}));
|
|
1292
1347
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1293
1348
|
return result;
|
|
1294
1349
|
}
|
|
1295
|
-
function queryDailyBreakdown(db, days = 30) {
|
|
1350
|
+
function queryDailyBreakdown(db, days = 30, machine) {
|
|
1351
|
+
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
1352
|
+
const params = machine ? [`-${days}`, machine] : [`-${days}`];
|
|
1296
1353
|
return db.prepare(`
|
|
1297
1354
|
SELECT DATE(timestamp) as date, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
1298
1355
|
FROM requests
|
|
1299
|
-
WHERE timestamp >= DATE('now', ? || ' days')
|
|
1356
|
+
WHERE timestamp >= DATE('now', ? || ' days')${machineClause}
|
|
1300
1357
|
GROUP BY DATE(timestamp), agent
|
|
1301
1358
|
ORDER BY date ASC
|
|
1302
|
-
`).all(
|
|
1359
|
+
`).all(...params);
|
|
1360
|
+
}
|
|
1361
|
+
function queryHourlyBreakdown(db, machine) {
|
|
1362
|
+
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
1363
|
+
const params = machine ? [machine] : [];
|
|
1364
|
+
return db.prepare(`
|
|
1365
|
+
SELECT STRFTIME('%H', timestamp) as hour, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
1366
|
+
FROM requests
|
|
1367
|
+
WHERE DATE(timestamp) = DATE('now')${machineClause}
|
|
1368
|
+
GROUP BY STRFTIME('%H', timestamp), agent
|
|
1369
|
+
ORDER BY hour ASC
|
|
1370
|
+
`).all(...params);
|
|
1303
1371
|
}
|
|
1304
1372
|
function upsertProject(db, project) {
|
|
1305
1373
|
db.prepare(`
|
|
@@ -1428,17 +1496,48 @@ function queryBillingSummary(db, period) {
|
|
|
1428
1496
|
}
|
|
1429
1497
|
return { total_usd: total, by_provider };
|
|
1430
1498
|
}
|
|
1431
|
-
function listMachines(db) {
|
|
1499
|
+
function listMachines(db, period = "all") {
|
|
1500
|
+
const rWhere = requestPeriodWhere(period);
|
|
1501
|
+
const sWhere = sessionPeriodWhere(period);
|
|
1432
1502
|
return db.prepare(`
|
|
1503
|
+
WITH request_stats AS (
|
|
1504
|
+
SELECT
|
|
1505
|
+
machine_id,
|
|
1506
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
1507
|
+
COUNT(*) as requests,
|
|
1508
|
+
COALESCE(SUM(cost_usd), 0) as total_cost_usd,
|
|
1509
|
+
MAX(timestamp) as last_active
|
|
1510
|
+
FROM requests
|
|
1511
|
+
WHERE machine_id != ''
|
|
1512
|
+
AND ${rWhere}
|
|
1513
|
+
GROUP BY machine_id
|
|
1514
|
+
),
|
|
1515
|
+
session_only_stats AS (
|
|
1516
|
+
SELECT
|
|
1517
|
+
machine_id,
|
|
1518
|
+
COUNT(*) as sessions,
|
|
1519
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1520
|
+
COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
|
|
1521
|
+
MAX(started_at) as last_active
|
|
1522
|
+
FROM sessions
|
|
1523
|
+
WHERE machine_id != ''
|
|
1524
|
+
AND ${sWhere}
|
|
1525
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1526
|
+
GROUP BY machine_id
|
|
1527
|
+
),
|
|
1528
|
+
combined AS (
|
|
1529
|
+
SELECT * FROM request_stats
|
|
1530
|
+
UNION ALL
|
|
1531
|
+
SELECT * FROM session_only_stats
|
|
1532
|
+
)
|
|
1433
1533
|
SELECT
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
COALESCE((
|
|
1437
|
-
COALESCE(SUM(
|
|
1438
|
-
MAX(
|
|
1439
|
-
FROM
|
|
1440
|
-
|
|
1441
|
-
GROUP BY s.machine_id
|
|
1534
|
+
machine_id,
|
|
1535
|
+
COALESCE(SUM(sessions), 0) as sessions,
|
|
1536
|
+
COALESCE(SUM(requests), 0) as requests,
|
|
1537
|
+
COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
|
|
1538
|
+
MAX(last_active) as last_active
|
|
1539
|
+
FROM combined
|
|
1540
|
+
GROUP BY machine_id
|
|
1442
1541
|
ORDER BY total_cost_usd DESC
|
|
1443
1542
|
`).all();
|
|
1444
1543
|
}
|
|
@@ -2331,18 +2430,22 @@ function agentPaths() {
|
|
|
2331
2430
|
var init_paths = () => {};
|
|
2332
2431
|
|
|
2333
2432
|
// src/lib/accounts.ts
|
|
2334
|
-
function
|
|
2335
|
-
return
|
|
2433
|
+
function normalizeEmail(email) {
|
|
2434
|
+
return (email ?? "").trim().toLowerCase();
|
|
2435
|
+
}
|
|
2436
|
+
function accountKey(tool, name, email) {
|
|
2437
|
+
const normalizedEmail = normalizeEmail(email);
|
|
2438
|
+
return `${tool}:${normalizedEmail || name}`;
|
|
2336
2439
|
}
|
|
2337
2440
|
function normalizeDir(value) {
|
|
2338
2441
|
return value.replace(/\/+$/, "");
|
|
2339
2442
|
}
|
|
2340
2443
|
function fromProfile(profile, source) {
|
|
2341
2444
|
return {
|
|
2342
|
-
account_key: accountKey(profile.tool, profile.name),
|
|
2445
|
+
account_key: accountKey(profile.tool, profile.name, profile.email),
|
|
2343
2446
|
account_tool: profile.tool,
|
|
2344
2447
|
account_name: profile.name,
|
|
2345
|
-
...profile.email ? { account_email: profile.email } : {},
|
|
2448
|
+
...profile.email ? { account_email: normalizeEmail(profile.email) } : {},
|
|
2346
2449
|
account_source: source
|
|
2347
2450
|
};
|
|
2348
2451
|
}
|
|
@@ -2354,10 +2457,12 @@ function fromOverride(raw, agent) {
|
|
|
2354
2457
|
const [tool, name] = value.includes(":") ? value.split(":", 2) : [candidateTool, value];
|
|
2355
2458
|
if (!tool || !name)
|
|
2356
2459
|
return null;
|
|
2460
|
+
const email = name.includes("@") ? normalizeEmail(name) : undefined;
|
|
2357
2461
|
return {
|
|
2358
|
-
account_key: accountKey(tool, name),
|
|
2462
|
+
account_key: accountKey(tool, name, email),
|
|
2359
2463
|
account_tool: tool,
|
|
2360
2464
|
account_name: name,
|
|
2465
|
+
...email ? { account_email: email } : {},
|
|
2361
2466
|
account_source: "override"
|
|
2362
2467
|
};
|
|
2363
2468
|
}
|
|
@@ -2370,11 +2475,12 @@ function envOverride(agent, env) {
|
|
|
2370
2475
|
const name = env[`ECONOMY_${agentPrefix}_ACCOUNT_NAME`] ?? env["ECONOMY_ACCOUNT_NAME"];
|
|
2371
2476
|
if (!tool || !name)
|
|
2372
2477
|
return null;
|
|
2478
|
+
const email = normalizeEmail(env[`ECONOMY_${agentPrefix}_ACCOUNT_EMAIL`] ?? env["ECONOMY_ACCOUNT_EMAIL"]);
|
|
2373
2479
|
return {
|
|
2374
|
-
account_key: accountKey(tool, name),
|
|
2480
|
+
account_key: accountKey(tool, name, email),
|
|
2375
2481
|
account_tool: tool,
|
|
2376
2482
|
account_name: name,
|
|
2377
|
-
account_email:
|
|
2483
|
+
...email ? { account_email: email } : {},
|
|
2378
2484
|
account_source: "override"
|
|
2379
2485
|
};
|
|
2380
2486
|
}
|
|
@@ -4513,16 +4619,22 @@ function createHandler(db) {
|
|
|
4513
4619
|
}
|
|
4514
4620
|
if (path === "/api/fleet" && method === "GET") {
|
|
4515
4621
|
const period = url.searchParams.get("period") ?? "month";
|
|
4622
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
4516
4623
|
return ok({
|
|
4517
|
-
summary: querySummary(db, period,
|
|
4518
|
-
machines: listMachines(db),
|
|
4624
|
+
summary: querySummary(db, period, machine),
|
|
4625
|
+
machines: listMachines(db, period),
|
|
4519
4626
|
registry: listMachineRegistry(db),
|
|
4520
4627
|
current_machine: getMachineId()
|
|
4521
4628
|
});
|
|
4522
4629
|
}
|
|
4523
4630
|
if (path === "/api/daily" && method === "GET") {
|
|
4524
4631
|
const days = Number(url.searchParams.get("days") ?? 30);
|
|
4525
|
-
|
|
4632
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
4633
|
+
return ok(queryDailyBreakdown(db, days, machine));
|
|
4634
|
+
}
|
|
4635
|
+
if (path === "/api/hourly" && method === "GET") {
|
|
4636
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
4637
|
+
return ok(queryHourlyBreakdown(db, machine));
|
|
4526
4638
|
}
|
|
4527
4639
|
if (path === "/api/sessions" && method === "GET") {
|
|
4528
4640
|
const agent = url.searchParams.get("agent");
|
|
@@ -4592,21 +4704,24 @@ function createHandler(db) {
|
|
|
4592
4704
|
}
|
|
4593
4705
|
if (path === "/api/projects" && method === "GET") {
|
|
4594
4706
|
const period = url.searchParams.get("period") ?? "all";
|
|
4595
|
-
|
|
4707
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
4708
|
+
return ok(queryProjectBreakdown(db, period, machine));
|
|
4596
4709
|
}
|
|
4597
4710
|
if (path === "/api/accounts" && method === "GET") {
|
|
4598
4711
|
const period = url.searchParams.get("period") ?? "all";
|
|
4599
|
-
|
|
4712
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
4713
|
+
return ok(queryAccountBreakdown(db, period, machine));
|
|
4600
4714
|
}
|
|
4601
4715
|
if (path === "/api/breakdown" && method === "GET") {
|
|
4602
4716
|
const by = url.searchParams.get("by") ?? "model";
|
|
4603
4717
|
const period = url.searchParams.get("period") ?? "all";
|
|
4718
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
4604
4719
|
if (by === "project")
|
|
4605
|
-
return ok(queryProjectBreakdown(db, period));
|
|
4720
|
+
return ok(queryProjectBreakdown(db, period, machine));
|
|
4606
4721
|
if (by === "agent")
|
|
4607
|
-
return ok(queryAgentBreakdown(db, period));
|
|
4722
|
+
return ok(queryAgentBreakdown(db, period, machine));
|
|
4608
4723
|
if (by === "account")
|
|
4609
|
-
return ok(queryAccountBreakdown(db, period));
|
|
4724
|
+
return ok(queryAccountBreakdown(db, period, machine));
|
|
4610
4725
|
return ok(queryModelBreakdown(db));
|
|
4611
4726
|
}
|
|
4612
4727
|
if (path === "/api/budgets" && method === "GET") {
|
|
@@ -6217,6 +6332,8 @@ var TOP_LEVEL = [
|
|
|
6217
6332
|
"doctor",
|
|
6218
6333
|
"init",
|
|
6219
6334
|
"estimate",
|
|
6335
|
+
"accounts",
|
|
6336
|
+
"breakdown",
|
|
6220
6337
|
"fleet",
|
|
6221
6338
|
"merge-db",
|
|
6222
6339
|
"todos",
|
|
@@ -6548,7 +6665,7 @@ function registerFleetCommands(program) {
|
|
|
6548
6665
|
const db = openDatabase();
|
|
6549
6666
|
const period = parsePeriod(opts.period, "today");
|
|
6550
6667
|
const summary = querySummary(db, period, undefined, true);
|
|
6551
|
-
const machines = listMachines(db);
|
|
6668
|
+
const machines = listMachines(db, period);
|
|
6552
6669
|
const registry = listMachineRegistry(db);
|
|
6553
6670
|
if (opts.json) {
|
|
6554
6671
|
console.log(JSON.stringify({ period, summary, machines, registry }, null, 2));
|
|
@@ -6987,6 +7104,22 @@ function printTable(headers, rows) {
|
|
|
6987
7104
|
}
|
|
6988
7105
|
console.log(`\u2514${sep2.replace(/\u253C/g, "\u2534")}\u2518`);
|
|
6989
7106
|
}
|
|
7107
|
+
function accountDisplayName(row) {
|
|
7108
|
+
return row.account_email || row.account_name || row.account_key || "unknown";
|
|
7109
|
+
}
|
|
7110
|
+
function printAccountBreakdown(rows) {
|
|
7111
|
+
printTable(["Account", "Agent", "Source", "Sessions", "Requests", "Tokens", "API Eq", "Billable", "Included"], rows.map((r) => [
|
|
7112
|
+
chalk7.white(accountDisplayName(r)),
|
|
7113
|
+
fmtAgent(r.account_tool),
|
|
7114
|
+
chalk7.dim(r.account_source || "unknown"),
|
|
7115
|
+
String(r.sessions),
|
|
7116
|
+
String(r.requests),
|
|
7117
|
+
chalk7.cyan(fmtTokens(r.total_tokens)),
|
|
7118
|
+
fmt4(r.api_equivalent_usd),
|
|
7119
|
+
fmt4(r.billable_usd),
|
|
7120
|
+
fmt4(r.subscription_included_usd)
|
|
7121
|
+
]));
|
|
7122
|
+
}
|
|
6990
7123
|
function parseSinceDate(since) {
|
|
6991
7124
|
const relMatch = since.match(/^(\d+)d$/);
|
|
6992
7125
|
if (relMatch) {
|
|
@@ -7246,29 +7379,106 @@ program.command("breakdown").description("Cost breakdown by model, agent, projec
|
|
|
7246
7379
|
]));
|
|
7247
7380
|
} else if (opts.by === "account") {
|
|
7248
7381
|
const rows = sinceDate ? db.prepare(`
|
|
7249
|
-
|
|
7382
|
+
WITH request_rows AS (
|
|
7383
|
+
SELECT
|
|
7384
|
+
r.session_id as session_id,
|
|
7385
|
+
COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), 'unknown') as account_agent,
|
|
7386
|
+
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as raw_account_key,
|
|
7387
|
+
COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as raw_account_name,
|
|
7388
|
+
LOWER(TRIM(COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), ''))) as raw_account_email,
|
|
7389
|
+
COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
7390
|
+
1 as requests,
|
|
7391
|
+
COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
|
|
7392
|
+
COALESCE(r.cost_usd, 0) as cost_usd,
|
|
7393
|
+
COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
|
|
7394
|
+
r.timestamp as last_active
|
|
7395
|
+
FROM requests r
|
|
7396
|
+
LEFT JOIN sessions s ON s.id = r.session_id
|
|
7397
|
+
WHERE r.timestamp >= ?
|
|
7398
|
+
AND (
|
|
7399
|
+
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') != ''
|
|
7400
|
+
OR COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') != ''
|
|
7401
|
+
OR COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') != ''
|
|
7402
|
+
OR COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') != ''
|
|
7403
|
+
)
|
|
7404
|
+
),
|
|
7405
|
+
session_only_rows AS (
|
|
7406
|
+
SELECT
|
|
7407
|
+
s.id as session_id,
|
|
7408
|
+
COALESCE(NULLIF(s.agent, ''), NULLIF(s.account_tool, ''), 'unknown') as account_agent,
|
|
7409
|
+
s.account_key as raw_account_key,
|
|
7410
|
+
s.account_name as raw_account_name,
|
|
7411
|
+
LOWER(TRIM(COALESCE(s.account_email, ''))) as raw_account_email,
|
|
7412
|
+
COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
7413
|
+
COALESCE(s.request_count, 0) as requests,
|
|
7414
|
+
COALESCE(s.total_tokens, 0) as total_tokens,
|
|
7415
|
+
COALESCE(s.total_cost_usd, 0) as cost_usd,
|
|
7416
|
+
'estimated' as cost_basis,
|
|
7417
|
+
s.started_at as last_active
|
|
7418
|
+
FROM sessions s
|
|
7419
|
+
WHERE s.started_at >= ?
|
|
7420
|
+
AND s.id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
7421
|
+
AND (s.account_key != '' OR s.account_tool != '' OR s.account_name != '' OR s.account_email != '')
|
|
7422
|
+
),
|
|
7423
|
+
normalized AS (
|
|
7424
|
+
SELECT
|
|
7425
|
+
CASE
|
|
7426
|
+
WHEN raw_account_email != '' THEN account_agent || ':' || raw_account_email
|
|
7427
|
+
WHEN raw_account_name != '' THEN account_agent || ':' || raw_account_name
|
|
7428
|
+
ELSE raw_account_key
|
|
7429
|
+
END as account_key,
|
|
7430
|
+
account_agent as account_tool,
|
|
7431
|
+
raw_account_name as account_name,
|
|
7432
|
+
raw_account_email as account_email,
|
|
7433
|
+
account_source,
|
|
7434
|
+
session_id,
|
|
7435
|
+
requests,
|
|
7436
|
+
total_tokens,
|
|
7437
|
+
cost_usd,
|
|
7438
|
+
cost_basis,
|
|
7439
|
+
last_active
|
|
7440
|
+
FROM request_rows
|
|
7441
|
+
UNION ALL
|
|
7442
|
+
SELECT
|
|
7443
|
+
CASE
|
|
7444
|
+
WHEN raw_account_email != '' THEN account_agent || ':' || raw_account_email
|
|
7445
|
+
WHEN raw_account_name != '' THEN account_agent || ':' || raw_account_name
|
|
7446
|
+
ELSE raw_account_key
|
|
7447
|
+
END as account_key,
|
|
7448
|
+
account_agent as account_tool,
|
|
7449
|
+
raw_account_name as account_name,
|
|
7450
|
+
raw_account_email as account_email,
|
|
7451
|
+
account_source,
|
|
7452
|
+
session_id,
|
|
7453
|
+
requests,
|
|
7454
|
+
total_tokens,
|
|
7455
|
+
cost_usd,
|
|
7456
|
+
cost_basis,
|
|
7457
|
+
last_active
|
|
7458
|
+
FROM session_only_rows
|
|
7459
|
+
)
|
|
7460
|
+
SELECT account_key,
|
|
7461
|
+
account_tool,
|
|
7462
|
+
COALESCE(MAX(NULLIF(account_name, '')), MAX(NULLIF(account_email, '')), account_key) as account_name,
|
|
7463
|
+
NULLIF(account_email, '') as account_email,
|
|
7464
|
+
COALESCE(MAX(NULLIF(account_source, 'unknown')), 'unknown') as account_source,
|
|
7250
7465
|
COUNT(DISTINCT session_id) as sessions,
|
|
7251
|
-
|
|
7252
|
-
COALESCE(SUM(
|
|
7466
|
+
COALESCE(SUM(requests), 0) as requests,
|
|
7467
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
7253
7468
|
COALESCE(SUM(cost_usd), 0) as api_equivalent_usd,
|
|
7469
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as billable_usd,
|
|
7254
7470
|
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
|
|
7255
7471
|
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7472
|
+
COALESCE(SUM(CASE WHEN cost_basis NOT IN ('metered_api', 'subscription_included', 'unknown') THEN cost_usd ELSE 0 END), 0) as estimated_usd,
|
|
7473
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd,
|
|
7474
|
+
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
7475
|
+
MAX(last_active) as last_active
|
|
7476
|
+
FROM normalized
|
|
7477
|
+
WHERE account_key != ''
|
|
7478
|
+
GROUP BY account_key, account_tool, account_email
|
|
7261
7479
|
ORDER BY api_equivalent_usd DESC
|
|
7262
|
-
`).all(sinceDate) : queryAccountBreakdown(db);
|
|
7263
|
-
|
|
7264
|
-
chalk7.white(r.account_key || r.account_name || chalk7.dim("unknown")),
|
|
7265
|
-
String(r.sessions),
|
|
7266
|
-
String(r.requests),
|
|
7267
|
-
chalk7.cyan(fmtTokens(r.total_tokens)),
|
|
7268
|
-
fmt4(r.api_equivalent_usd),
|
|
7269
|
-
fmt4("billable_usd" in r ? Number(r.billable_usd) : r.metered_api_usd),
|
|
7270
|
-
fmt4(r.subscription_included_usd)
|
|
7271
|
-
]));
|
|
7480
|
+
`).all(sinceDate, sinceDate) : queryAccountBreakdown(db);
|
|
7481
|
+
printAccountBreakdown(rows);
|
|
7272
7482
|
} else {
|
|
7273
7483
|
const rows = sinceDate ? db.prepare(`
|
|
7274
7484
|
SELECT model, agent,
|
|
@@ -7290,6 +7500,24 @@ program.command("breakdown").description("Cost breakdown by model, agent, projec
|
|
|
7290
7500
|
}
|
|
7291
7501
|
console.log();
|
|
7292
7502
|
});
|
|
7503
|
+
var ACCOUNT_PERIODS = ["today", "week", "month", "year", "all"];
|
|
7504
|
+
program.command("accounts [period]").description("List account usage by email address and coding agent").option("--json", "Output JSON").action((periodArg, opts) => {
|
|
7505
|
+
const period = requireCliChoice(periodArg, "period", ACCOUNT_PERIODS);
|
|
7506
|
+
const rows = queryAccountBreakdown(openDatabase(), period);
|
|
7507
|
+
if (opts.json) {
|
|
7508
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
7509
|
+
return;
|
|
7510
|
+
}
|
|
7511
|
+
if (rows.length === 0) {
|
|
7512
|
+
console.log(chalk7.yellow("No account-attributed sessions yet. Run `economy sync` first."));
|
|
7513
|
+
return;
|
|
7514
|
+
}
|
|
7515
|
+
console.log();
|
|
7516
|
+
console.log(chalk7.bold.cyan(` Accounts \u2014 ${period}`));
|
|
7517
|
+
console.log();
|
|
7518
|
+
printAccountBreakdown(rows);
|
|
7519
|
+
console.log();
|
|
7520
|
+
});
|
|
7293
7521
|
program.command("watch").description("Live stream of incoming costs").option("--interval <seconds>", "Poll interval in seconds", "10").option("--daemon", "Watch agent data directories and sync on change").option("--agent <agent>", "Filter by agent").option("--notify <amount>", "Fire macOS notification when cumulative cost crosses this USD threshold").action(async (opts) => {
|
|
7294
7522
|
const { watchCosts: watchCosts2 } = await Promise.resolve().then(() => (init_watch(), exports_watch));
|
|
7295
7523
|
await watchCosts2({
|