@hasna/economy 0.2.28 → 0.2.30
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 +332 -132
- package/dist/db/database.d.ts +9 -4
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +185 -107
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/mcp/http.d.ts +14 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/index.js +765 -615
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/server/index.js +200 -114
- 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,
|
|
@@ -1020,8 +1021,10 @@ function queryModelBreakdown(db) {
|
|
|
1020
1021
|
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
1021
1022
|
`).all();
|
|
1022
1023
|
}
|
|
1023
|
-
function queryAgentBreakdown(db, period = "all") {
|
|
1024
|
+
function queryAgentBreakdown(db, period = "all", machine) {
|
|
1024
1025
|
const requestWhere = requestPeriodWhere(period);
|
|
1026
|
+
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
1027
|
+
const machineParams = machine ? [machine] : [];
|
|
1025
1028
|
const groups = new Map;
|
|
1026
1029
|
const requestRows = db.prepare(`
|
|
1027
1030
|
SELECT agent,
|
|
@@ -1037,10 +1040,10 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
1037
1040
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1038
1041
|
MAX(timestamp) as last_active
|
|
1039
1042
|
FROM requests
|
|
1040
|
-
WHERE ${requestWhere}
|
|
1043
|
+
WHERE ${requestWhere}${machineClause}
|
|
1041
1044
|
GROUP BY agent
|
|
1042
1045
|
ORDER BY api_equivalent_usd DESC
|
|
1043
|
-
`).all();
|
|
1046
|
+
`).all(...machineParams);
|
|
1044
1047
|
for (const row of requestRows) {
|
|
1045
1048
|
groups.set(row.agent, row);
|
|
1046
1049
|
}
|
|
@@ -1053,10 +1056,10 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
1053
1056
|
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1054
1057
|
MAX(started_at) as last_active
|
|
1055
1058
|
FROM sessions
|
|
1056
|
-
WHERE ${sessionWhere}
|
|
1059
|
+
WHERE ${sessionWhere}${machineClause}
|
|
1057
1060
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1058
1061
|
GROUP BY agent
|
|
1059
|
-
`).all();
|
|
1062
|
+
`).all(...machineParams);
|
|
1060
1063
|
for (const row of sessionOnlyRows) {
|
|
1061
1064
|
const existing = groups.get(row.agent) ?? {
|
|
1062
1065
|
agent: row.agent,
|
|
@@ -1133,14 +1136,20 @@ function labelForPath(projectPath, projectName) {
|
|
|
1133
1136
|
function groupKeyForPath(projectPath, projectName) {
|
|
1134
1137
|
return labelForPath(projectPath, projectName).trim().toLowerCase();
|
|
1135
1138
|
}
|
|
1136
|
-
function queryProjectBreakdown(db, period = "all") {
|
|
1139
|
+
function queryProjectBreakdown(db, period = "all", machine) {
|
|
1137
1140
|
const requestWhere = requestPeriodWhere(period);
|
|
1138
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] : [];
|
|
1139
1148
|
const sessions = db.prepare(`
|
|
1140
1149
|
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
1141
1150
|
FROM sessions
|
|
1142
|
-
WHERE project_path != '' OR project_name != ''
|
|
1143
|
-
`).all();
|
|
1151
|
+
WHERE (project_path != '' OR project_name != '')${sessionMachineClause}
|
|
1152
|
+
`).all(...sessionMachineParams);
|
|
1144
1153
|
const groups = new Map;
|
|
1145
1154
|
for (const s of sessions) {
|
|
1146
1155
|
const label = labelForPath(s.project_path, s.project_name);
|
|
@@ -1166,7 +1175,8 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1166
1175
|
FROM requests
|
|
1167
1176
|
WHERE session_id IN (${placeholders})
|
|
1168
1177
|
AND ${requestWhere}
|
|
1169
|
-
|
|
1178
|
+
${requestMachineClause}
|
|
1179
|
+
`).get(...g.sessionIds, ...requestMachineParams) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
|
|
1170
1180
|
const sessionOnlyStats = placeholders.length ? db.prepare(`
|
|
1171
1181
|
SELECT
|
|
1172
1182
|
COUNT(*) as sessions,
|
|
@@ -1177,8 +1187,9 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1177
1187
|
FROM sessions
|
|
1178
1188
|
WHERE id IN (${placeholders})
|
|
1179
1189
|
AND ${sessionWhere}
|
|
1190
|
+
${sessionOnlyMachineClause}
|
|
1180
1191
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1181
|
-
`).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 };
|
|
1182
1193
|
const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1183
1194
|
if (totalSessions === 0)
|
|
1184
1195
|
continue;
|
|
@@ -1196,107 +1207,167 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1196
1207
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1197
1208
|
return result;
|
|
1198
1209
|
}
|
|
1199
|
-
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) {
|
|
1200
1275
|
const requestWhere = requestPeriodWhere(period);
|
|
1201
1276
|
const sessionWhere = sessionPeriodWhere(period);
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
WHERE account_key != '' OR account_tool != '' OR account_name != '' OR account_email != ''
|
|
1207
|
-
`).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] : [];
|
|
1208
1281
|
const groups = new Map;
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
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
|
-
account_email: group.account_email,
|
|
1275
|
-
account_source: group.account_source,
|
|
1276
|
-
sessions: sessionsTotal,
|
|
1277
|
-
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1278
|
-
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1279
|
-
api_equivalent_usd: apiEquivalentUsd,
|
|
1280
|
-
billable_usd: billableUsd,
|
|
1281
|
-
metered_api_usd: reqStats.metered_api_usd,
|
|
1282
|
-
subscription_included_usd: reqStats.subscription_included_usd,
|
|
1283
|
-
estimated_usd: estimatedUsd,
|
|
1284
|
-
unknown_usd: reqStats.unknown_usd,
|
|
1285
|
-
cost_usd: apiEquivalentUsd,
|
|
1286
|
-
last_active: lastActive
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
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
|
+
}));
|
|
1289
1347
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1290
1348
|
return result;
|
|
1291
1349
|
}
|
|
1292
|
-
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}`];
|
|
1293
1353
|
return db.prepare(`
|
|
1294
1354
|
SELECT DATE(timestamp) as date, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
1295
1355
|
FROM requests
|
|
1296
|
-
WHERE timestamp >= DATE('now', ? || ' days')
|
|
1356
|
+
WHERE timestamp >= DATE('now', ? || ' days')${machineClause}
|
|
1297
1357
|
GROUP BY DATE(timestamp), agent
|
|
1298
1358
|
ORDER BY date ASC
|
|
1299
|
-
`).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);
|
|
1300
1371
|
}
|
|
1301
1372
|
function upsertProject(db, project) {
|
|
1302
1373
|
db.prepare(`
|
|
@@ -2359,18 +2430,22 @@ function agentPaths() {
|
|
|
2359
2430
|
var init_paths = () => {};
|
|
2360
2431
|
|
|
2361
2432
|
// src/lib/accounts.ts
|
|
2362
|
-
function
|
|
2363
|
-
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}`;
|
|
2364
2439
|
}
|
|
2365
2440
|
function normalizeDir(value) {
|
|
2366
2441
|
return value.replace(/\/+$/, "");
|
|
2367
2442
|
}
|
|
2368
2443
|
function fromProfile(profile, source) {
|
|
2369
2444
|
return {
|
|
2370
|
-
account_key: accountKey(profile.tool, profile.name),
|
|
2445
|
+
account_key: accountKey(profile.tool, profile.name, profile.email),
|
|
2371
2446
|
account_tool: profile.tool,
|
|
2372
2447
|
account_name: profile.name,
|
|
2373
|
-
...profile.email ? { account_email: profile.email } : {},
|
|
2448
|
+
...profile.email ? { account_email: normalizeEmail(profile.email) } : {},
|
|
2374
2449
|
account_source: source
|
|
2375
2450
|
};
|
|
2376
2451
|
}
|
|
@@ -2382,10 +2457,12 @@ function fromOverride(raw, agent) {
|
|
|
2382
2457
|
const [tool, name] = value.includes(":") ? value.split(":", 2) : [candidateTool, value];
|
|
2383
2458
|
if (!tool || !name)
|
|
2384
2459
|
return null;
|
|
2460
|
+
const email = name.includes("@") ? normalizeEmail(name) : undefined;
|
|
2385
2461
|
return {
|
|
2386
|
-
account_key: accountKey(tool, name),
|
|
2462
|
+
account_key: accountKey(tool, name, email),
|
|
2387
2463
|
account_tool: tool,
|
|
2388
2464
|
account_name: name,
|
|
2465
|
+
...email ? { account_email: email } : {},
|
|
2389
2466
|
account_source: "override"
|
|
2390
2467
|
};
|
|
2391
2468
|
}
|
|
@@ -2398,11 +2475,12 @@ function envOverride(agent, env) {
|
|
|
2398
2475
|
const name = env[`ECONOMY_${agentPrefix}_ACCOUNT_NAME`] ?? env["ECONOMY_ACCOUNT_NAME"];
|
|
2399
2476
|
if (!tool || !name)
|
|
2400
2477
|
return null;
|
|
2478
|
+
const email = normalizeEmail(env[`ECONOMY_${agentPrefix}_ACCOUNT_EMAIL`] ?? env["ECONOMY_ACCOUNT_EMAIL"]);
|
|
2401
2479
|
return {
|
|
2402
|
-
account_key: accountKey(tool, name),
|
|
2480
|
+
account_key: accountKey(tool, name, email),
|
|
2403
2481
|
account_tool: tool,
|
|
2404
2482
|
account_name: name,
|
|
2405
|
-
account_email:
|
|
2483
|
+
...email ? { account_email: email } : {},
|
|
2406
2484
|
account_source: "override"
|
|
2407
2485
|
};
|
|
2408
2486
|
}
|
|
@@ -4541,8 +4619,9 @@ function createHandler(db) {
|
|
|
4541
4619
|
}
|
|
4542
4620
|
if (path === "/api/fleet" && method === "GET") {
|
|
4543
4621
|
const period = url.searchParams.get("period") ?? "month";
|
|
4622
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
4544
4623
|
return ok({
|
|
4545
|
-
summary: querySummary(db, period,
|
|
4624
|
+
summary: querySummary(db, period, machine),
|
|
4546
4625
|
machines: listMachines(db, period),
|
|
4547
4626
|
registry: listMachineRegistry(db),
|
|
4548
4627
|
current_machine: getMachineId()
|
|
@@ -4550,7 +4629,12 @@ function createHandler(db) {
|
|
|
4550
4629
|
}
|
|
4551
4630
|
if (path === "/api/daily" && method === "GET") {
|
|
4552
4631
|
const days = Number(url.searchParams.get("days") ?? 30);
|
|
4553
|
-
|
|
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));
|
|
4554
4638
|
}
|
|
4555
4639
|
if (path === "/api/sessions" && method === "GET") {
|
|
4556
4640
|
const agent = url.searchParams.get("agent");
|
|
@@ -4620,21 +4704,24 @@ function createHandler(db) {
|
|
|
4620
4704
|
}
|
|
4621
4705
|
if (path === "/api/projects" && method === "GET") {
|
|
4622
4706
|
const period = url.searchParams.get("period") ?? "all";
|
|
4623
|
-
|
|
4707
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
4708
|
+
return ok(queryProjectBreakdown(db, period, machine));
|
|
4624
4709
|
}
|
|
4625
4710
|
if (path === "/api/accounts" && method === "GET") {
|
|
4626
4711
|
const period = url.searchParams.get("period") ?? "all";
|
|
4627
|
-
|
|
4712
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
4713
|
+
return ok(queryAccountBreakdown(db, period, machine));
|
|
4628
4714
|
}
|
|
4629
4715
|
if (path === "/api/breakdown" && method === "GET") {
|
|
4630
4716
|
const by = url.searchParams.get("by") ?? "model";
|
|
4631
4717
|
const period = url.searchParams.get("period") ?? "all";
|
|
4718
|
+
const machine = url.searchParams.get("machine") ?? undefined;
|
|
4632
4719
|
if (by === "project")
|
|
4633
|
-
return ok(queryProjectBreakdown(db, period));
|
|
4720
|
+
return ok(queryProjectBreakdown(db, period, machine));
|
|
4634
4721
|
if (by === "agent")
|
|
4635
|
-
return ok(queryAgentBreakdown(db, period));
|
|
4722
|
+
return ok(queryAgentBreakdown(db, period, machine));
|
|
4636
4723
|
if (by === "account")
|
|
4637
|
-
return ok(queryAccountBreakdown(db, period));
|
|
4724
|
+
return ok(queryAccountBreakdown(db, period, machine));
|
|
4638
4725
|
return ok(queryModelBreakdown(db));
|
|
4639
4726
|
}
|
|
4640
4727
|
if (path === "/api/budgets" && method === "GET") {
|
|
@@ -6245,6 +6332,8 @@ var TOP_LEVEL = [
|
|
|
6245
6332
|
"doctor",
|
|
6246
6333
|
"init",
|
|
6247
6334
|
"estimate",
|
|
6335
|
+
"accounts",
|
|
6336
|
+
"breakdown",
|
|
6248
6337
|
"fleet",
|
|
6249
6338
|
"merge-db",
|
|
6250
6339
|
"todos",
|
|
@@ -7015,6 +7104,22 @@ function printTable(headers, rows) {
|
|
|
7015
7104
|
}
|
|
7016
7105
|
console.log(`\u2514${sep2.replace(/\u253C/g, "\u2534")}\u2518`);
|
|
7017
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
|
+
}
|
|
7018
7123
|
function parseSinceDate(since) {
|
|
7019
7124
|
const relMatch = since.match(/^(\d+)d$/);
|
|
7020
7125
|
if (relMatch) {
|
|
@@ -7274,29 +7379,106 @@ program.command("breakdown").description("Cost breakdown by model, agent, projec
|
|
|
7274
7379
|
]));
|
|
7275
7380
|
} else if (opts.by === "account") {
|
|
7276
7381
|
const rows = sinceDate ? db.prepare(`
|
|
7277
|
-
|
|
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,
|
|
7278
7465
|
COUNT(DISTINCT session_id) as sessions,
|
|
7279
|
-
|
|
7280
|
-
COALESCE(SUM(
|
|
7466
|
+
COALESCE(SUM(requests), 0) as requests,
|
|
7467
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
7281
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,
|
|
7282
7470
|
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
|
|
7283
7471
|
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
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
|
|
7289
7479
|
ORDER BY api_equivalent_usd DESC
|
|
7290
|
-
`).all(sinceDate) : queryAccountBreakdown(db);
|
|
7291
|
-
|
|
7292
|
-
chalk7.white(r.account_key || r.account_name || chalk7.dim("unknown")),
|
|
7293
|
-
String(r.sessions),
|
|
7294
|
-
String(r.requests),
|
|
7295
|
-
chalk7.cyan(fmtTokens(r.total_tokens)),
|
|
7296
|
-
fmt4(r.api_equivalent_usd),
|
|
7297
|
-
fmt4("billable_usd" in r ? Number(r.billable_usd) : r.metered_api_usd),
|
|
7298
|
-
fmt4(r.subscription_included_usd)
|
|
7299
|
-
]));
|
|
7480
|
+
`).all(sinceDate, sinceDate) : queryAccountBreakdown(db);
|
|
7481
|
+
printAccountBreakdown(rows);
|
|
7300
7482
|
} else {
|
|
7301
7483
|
const rows = sinceDate ? db.prepare(`
|
|
7302
7484
|
SELECT model, agent,
|
|
@@ -7318,6 +7500,24 @@ program.command("breakdown").description("Cost breakdown by model, agent, projec
|
|
|
7318
7500
|
}
|
|
7319
7501
|
console.log();
|
|
7320
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
|
+
});
|
|
7321
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) => {
|
|
7322
7522
|
const { watchCosts: watchCosts2 } = await Promise.resolve().then(() => (init_watch(), exports_watch));
|
|
7323
7523
|
await watchCosts2({
|