@hasna/economy 0.2.23 → 0.2.24
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/commands/extras.d.ts.map +1 -1
- package/dist/cli/index.js +160 -48
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +67 -38
- package/dist/lib/periods.d.ts +6 -0
- package/dist/lib/periods.d.ts.map +1 -0
- package/dist/lib/savings.d.ts.map +1 -1
- package/dist/mcp/index.js +158 -47
- package/dist/server/index.js +159 -46
- package/dist/server/serve.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extras.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/extras.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"extras.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/extras.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA8CnC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsR/D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkC5D"}
|
package/dist/cli/index.js
CHANGED
|
@@ -1106,23 +1106,20 @@ function labelForPath(projectPath, projectName) {
|
|
|
1106
1106
|
return segments[segments.length - 1] ?? projectPath;
|
|
1107
1107
|
}
|
|
1108
1108
|
function queryProjectBreakdown(db, period = "all") {
|
|
1109
|
-
const
|
|
1109
|
+
const requestWhere = requestPeriodWhere(period);
|
|
1110
|
+
const sessionWhere = sessionPeriodWhere(period);
|
|
1110
1111
|
const sessions = db.prepare(`
|
|
1111
1112
|
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
1112
1113
|
FROM sessions
|
|
1113
|
-
WHERE
|
|
1114
|
-
AND (project_path != '' OR project_name != '')
|
|
1114
|
+
WHERE project_path != '' OR project_name != ''
|
|
1115
1115
|
`).all();
|
|
1116
1116
|
const groups = new Map;
|
|
1117
1117
|
for (const s of sessions) {
|
|
1118
1118
|
const label = labelForPath(s.project_path, s.project_name);
|
|
1119
1119
|
if (!label)
|
|
1120
1120
|
continue;
|
|
1121
|
-
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path
|
|
1121
|
+
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path };
|
|
1122
1122
|
g.sessionIds.push(s.id);
|
|
1123
|
-
g.totalCost += s.total_cost_usd || 0;
|
|
1124
|
-
if (!g.lastActive || s.started_at > g.lastActive)
|
|
1125
|
-
g.lastActive = s.started_at;
|
|
1126
1123
|
if (!g.samplePath)
|
|
1127
1124
|
g.samplePath = s.project_path;
|
|
1128
1125
|
groups.set(label, g);
|
|
@@ -1132,32 +1129,52 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1132
1129
|
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
1133
1130
|
const reqStats = placeholders.length ? db.prepare(`
|
|
1134
1131
|
SELECT
|
|
1132
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
1135
1133
|
COUNT(*) as requests,
|
|
1136
1134
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1137
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
|
|
1138
|
-
|
|
1139
|
-
|
|
1135
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1136
|
+
MAX(timestamp) as last_active
|
|
1137
|
+
FROM requests
|
|
1138
|
+
WHERE session_id IN (${placeholders})
|
|
1139
|
+
AND ${requestWhere}
|
|
1140
|
+
`).get(...g.sessionIds) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
|
|
1141
|
+
const sessionOnlyStats = placeholders.length ? db.prepare(`
|
|
1142
|
+
SELECT
|
|
1143
|
+
COUNT(*) as sessions,
|
|
1144
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1145
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1146
|
+
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1147
|
+
MAX(started_at) as last_active
|
|
1148
|
+
FROM sessions
|
|
1149
|
+
WHERE id IN (${placeholders})
|
|
1150
|
+
AND ${sessionWhere}
|
|
1151
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1152
|
+
`).get(...g.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1153
|
+
const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1154
|
+
if (totalSessions === 0)
|
|
1155
|
+
continue;
|
|
1156
|
+
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1140
1157
|
result.push({
|
|
1141
1158
|
project_path: g.samplePath,
|
|
1142
1159
|
project_name: label,
|
|
1143
|
-
sessions:
|
|
1144
|
-
requests: reqStats.requests,
|
|
1145
|
-
total_tokens: reqStats.total_tokens,
|
|
1146
|
-
cost_usd: reqStats.cost_usd
|
|
1147
|
-
last_active:
|
|
1160
|
+
sessions: totalSessions,
|
|
1161
|
+
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1162
|
+
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1163
|
+
cost_usd: reqStats.cost_usd + sessionOnlyStats.cost_usd,
|
|
1164
|
+
last_active: lastActive
|
|
1148
1165
|
});
|
|
1149
1166
|
}
|
|
1150
1167
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1151
1168
|
return result;
|
|
1152
1169
|
}
|
|
1153
1170
|
function queryAccountBreakdown(db, period = "all") {
|
|
1154
|
-
const
|
|
1171
|
+
const requestWhere = requestPeriodWhere(period);
|
|
1172
|
+
const sessionWhere = sessionPeriodWhere(period);
|
|
1155
1173
|
const sessions = db.prepare(`
|
|
1156
1174
|
SELECT id, account_key, account_tool, account_name, account_email, account_source,
|
|
1157
1175
|
total_cost_usd, total_tokens, request_count, started_at
|
|
1158
1176
|
FROM sessions
|
|
1159
|
-
WHERE
|
|
1160
|
-
AND (account_key != '' OR account_tool != '' OR account_name != '' OR account_email != '')
|
|
1177
|
+
WHERE account_key != '' OR account_tool != '' OR account_name != '' OR account_email != ''
|
|
1161
1178
|
`).all();
|
|
1162
1179
|
const groups = new Map;
|
|
1163
1180
|
for (const session of sessions) {
|
|
@@ -1169,18 +1186,9 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1169
1186
|
account_tool: session.account_tool,
|
|
1170
1187
|
account_name: session.account_name,
|
|
1171
1188
|
account_email: session.account_email || null,
|
|
1172
|
-
account_source: session.account_source || "unknown"
|
|
1173
|
-
totalCost: 0,
|
|
1174
|
-
totalTokens: 0,
|
|
1175
|
-
requests: 0,
|
|
1176
|
-
lastActive: ""
|
|
1189
|
+
account_source: session.account_source || "unknown"
|
|
1177
1190
|
};
|
|
1178
1191
|
group.sessionIds.push(session.id);
|
|
1179
|
-
group.totalCost += session.total_cost_usd || 0;
|
|
1180
|
-
group.totalTokens += session.total_tokens || 0;
|
|
1181
|
-
group.requests += session.request_count || 0;
|
|
1182
|
-
if (!group.lastActive || session.started_at > group.lastActive)
|
|
1183
|
-
group.lastActive = session.started_at;
|
|
1184
1192
|
groups.set(key, group);
|
|
1185
1193
|
}
|
|
1186
1194
|
const result = [];
|
|
@@ -1188,36 +1196,57 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1188
1196
|
const placeholders = group.sessionIds.map(() => "?").join(",");
|
|
1189
1197
|
const reqStats = placeholders ? db.prepare(`
|
|
1190
1198
|
SELECT
|
|
1199
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
1191
1200
|
COUNT(*) as requests,
|
|
1192
1201
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1193
1202
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1194
1203
|
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
|
|
1195
1204
|
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
|
|
1196
1205
|
COALESCE(SUM(CASE WHEN COALESCE(cost_basis, 'estimated') = 'estimated' THEN cost_usd ELSE 0 END), 0) as estimated_usd,
|
|
1197
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd
|
|
1198
|
-
|
|
1206
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd,
|
|
1207
|
+
MAX(timestamp) as last_active
|
|
1208
|
+
FROM requests
|
|
1209
|
+
WHERE session_id IN (${placeholders})
|
|
1210
|
+
AND ${requestWhere}
|
|
1199
1211
|
`).get(...group.sessionIds) : {
|
|
1212
|
+
sessions: 0,
|
|
1200
1213
|
requests: 0,
|
|
1201
1214
|
cost_usd: 0,
|
|
1202
1215
|
total_tokens: 0,
|
|
1203
1216
|
metered_api_usd: 0,
|
|
1204
1217
|
subscription_included_usd: 0,
|
|
1205
1218
|
estimated_usd: 0,
|
|
1206
|
-
unknown_usd: 0
|
|
1219
|
+
unknown_usd: 0,
|
|
1220
|
+
last_active: null
|
|
1207
1221
|
};
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1222
|
+
const sessionOnlyStats = placeholders ? db.prepare(`
|
|
1223
|
+
SELECT
|
|
1224
|
+
COUNT(*) as sessions,
|
|
1225
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1226
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1227
|
+
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1228
|
+
MAX(started_at) as last_active
|
|
1229
|
+
FROM sessions
|
|
1230
|
+
WHERE id IN (${placeholders})
|
|
1231
|
+
AND ${sessionWhere}
|
|
1232
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1233
|
+
`).get(...group.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1234
|
+
const sessionsTotal = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1235
|
+
if (sessionsTotal === 0)
|
|
1236
|
+
continue;
|
|
1237
|
+
const apiEquivalentUsd = reqStats.cost_usd + sessionOnlyStats.cost_usd;
|
|
1238
|
+
const estimatedUsd = reqStats.estimated_usd + sessionOnlyStats.cost_usd;
|
|
1211
1239
|
const billableUsd = reqStats.metered_api_usd;
|
|
1240
|
+
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1212
1241
|
result.push({
|
|
1213
1242
|
account_key: key,
|
|
1214
1243
|
account_tool: group.account_tool,
|
|
1215
1244
|
account_name: group.account_name,
|
|
1216
1245
|
account_email: group.account_email,
|
|
1217
1246
|
account_source: group.account_source,
|
|
1218
|
-
sessions:
|
|
1219
|
-
requests: reqStats.requests
|
|
1220
|
-
total_tokens: reqStats.total_tokens
|
|
1247
|
+
sessions: sessionsTotal,
|
|
1248
|
+
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1249
|
+
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1221
1250
|
api_equivalent_usd: apiEquivalentUsd,
|
|
1222
1251
|
billable_usd: billableUsd,
|
|
1223
1252
|
metered_api_usd: reqStats.metered_api_usd,
|
|
@@ -1225,7 +1254,7 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1225
1254
|
estimated_usd: estimatedUsd,
|
|
1226
1255
|
unknown_usd: reqStats.unknown_usd,
|
|
1227
1256
|
cost_usd: apiEquivalentUsd,
|
|
1228
|
-
last_active:
|
|
1257
|
+
last_active: lastActive
|
|
1229
1258
|
});
|
|
1230
1259
|
}
|
|
1231
1260
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
@@ -1510,6 +1539,10 @@ function prorateMonthlyFee(monthlyFee, period) {
|
|
|
1510
1539
|
return monthlyFee;
|
|
1511
1540
|
}
|
|
1512
1541
|
}
|
|
1542
|
+
function proratedIncludedConsumed(includedUsage, includedCap, period) {
|
|
1543
|
+
const cap = prorateMonthlyFee(includedCap, period);
|
|
1544
|
+
return cap > 0 ? Math.min(includedUsage, cap) : includedUsage;
|
|
1545
|
+
}
|
|
1513
1546
|
function computeSavedUsd(apiEquivalent, onDemand, subscriptionFee) {
|
|
1514
1547
|
return Math.max(0, apiEquivalent - onDemand - subscriptionFee);
|
|
1515
1548
|
}
|
|
@@ -1537,24 +1570,74 @@ function querySavingsSummary(db, period, agent) {
|
|
|
1537
1570
|
AND metric = 'on_demand_usd'
|
|
1538
1571
|
`).get(...params);
|
|
1539
1572
|
const subs = db.prepare(`
|
|
1540
|
-
SELECT
|
|
1573
|
+
SELECT
|
|
1574
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
1575
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
1541
1576
|
FROM subscriptions
|
|
1542
|
-
WHERE active = 1${agent ? " AND agent = ?" : ""}
|
|
1577
|
+
WHERE active = 1${agent ? " AND (agent = ? OR agent IS NULL)" : ""}
|
|
1543
1578
|
`).get(...agent ? [agent] : []);
|
|
1544
|
-
const subscriptionFee = prorateMonthlyFee(subs.
|
|
1579
|
+
const subscriptionFee = prorateMonthlyFee(subs.fee, period);
|
|
1545
1580
|
const apiEquivalent = apiRow.total + includedRow.total;
|
|
1581
|
+
const includedConsumed = proratedIncludedConsumed(includedRow.total, subs.included, period);
|
|
1546
1582
|
const onDemand = onDemandRow.total;
|
|
1547
1583
|
const saved = computeSavedUsd(apiEquivalent, onDemand, subscriptionFee);
|
|
1548
1584
|
const byAgent = {};
|
|
1549
1585
|
if (!agent) {
|
|
1586
|
+
const onDemandByAgent = new Map;
|
|
1587
|
+
for (const row of db.prepare(`
|
|
1588
|
+
SELECT agent, COALESCE(SUM(value), 0) as total
|
|
1589
|
+
FROM usage_snapshots
|
|
1590
|
+
WHERE ${subWhere}
|
|
1591
|
+
AND metric = 'on_demand_usd'
|
|
1592
|
+
GROUP BY agent
|
|
1593
|
+
`).all()) {
|
|
1594
|
+
onDemandByAgent.set(row.agent, row.total);
|
|
1595
|
+
}
|
|
1596
|
+
const subscriptionByAgent = new Map;
|
|
1597
|
+
for (const row of db.prepare(`
|
|
1598
|
+
SELECT agent,
|
|
1599
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
1600
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
1601
|
+
FROM subscriptions
|
|
1602
|
+
WHERE active = 1 AND agent IS NOT NULL
|
|
1603
|
+
GROUP BY agent
|
|
1604
|
+
`).all()) {
|
|
1605
|
+
subscriptionByAgent.set(row.agent, row);
|
|
1606
|
+
}
|
|
1607
|
+
const globalSubs = db.prepare(`
|
|
1608
|
+
SELECT
|
|
1609
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
1610
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
1611
|
+
FROM subscriptions
|
|
1612
|
+
WHERE active = 1 AND agent IS NULL
|
|
1613
|
+
`).get();
|
|
1614
|
+
const rows = db.prepare(`
|
|
1615
|
+
SELECT agent,
|
|
1616
|
+
COALESCE(SUM(cost_usd), 0) as api_eq,
|
|
1617
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as included
|
|
1618
|
+
FROM requests WHERE ${where}
|
|
1619
|
+
GROUP BY agent
|
|
1620
|
+
`).all();
|
|
1621
|
+
const totalAgentApiEq = rows.reduce((sum, row) => sum + row.api_eq, 0);
|
|
1550
1622
|
for (const row of db.prepare(`
|
|
1551
|
-
SELECT agent,
|
|
1623
|
+
SELECT agent,
|
|
1624
|
+
COALESCE(SUM(cost_usd), 0) as api_eq,
|
|
1625
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as included
|
|
1552
1626
|
FROM requests WHERE ${where}
|
|
1553
1627
|
GROUP BY agent
|
|
1554
1628
|
`).all()) {
|
|
1629
|
+
const agentSubs = subscriptionByAgent.get(row.agent) ?? { fee: 0, included: 0 };
|
|
1630
|
+
const globalShare = totalAgentApiEq > 0 ? row.api_eq / totalAgentApiEq : 0;
|
|
1631
|
+
const agentFee = prorateMonthlyFee(agentSubs.fee + globalSubs.fee * globalShare, period);
|
|
1632
|
+
const agentIncludedCap = agentSubs.included + globalSubs.included * globalShare;
|
|
1633
|
+
const agentIncludedConsumed = proratedIncludedConsumed(row.included, agentIncludedCap, period);
|
|
1634
|
+
const agentOnDemand = onDemandByAgent.get(row.agent) ?? 0;
|
|
1555
1635
|
byAgent[row.agent] = {
|
|
1556
1636
|
api_equivalent_usd: row.api_eq,
|
|
1557
|
-
|
|
1637
|
+
subscription_fee_usd: agentFee,
|
|
1638
|
+
included_consumed_usd: agentIncludedConsumed,
|
|
1639
|
+
on_demand_usd: agentOnDemand,
|
|
1640
|
+
saved_usd: computeSavedUsd(row.api_eq, agentOnDemand, agentFee)
|
|
1558
1641
|
};
|
|
1559
1642
|
}
|
|
1560
1643
|
}
|
|
@@ -1562,7 +1645,7 @@ function querySavingsSummary(db, period, agent) {
|
|
|
1562
1645
|
period,
|
|
1563
1646
|
api_equivalent_usd: apiEquivalent,
|
|
1564
1647
|
subscription_fee_usd: subscriptionFee,
|
|
1565
|
-
included_consumed_usd:
|
|
1648
|
+
included_consumed_usd: includedConsumed,
|
|
1566
1649
|
on_demand_usd: onDemand,
|
|
1567
1650
|
saved_usd: saved,
|
|
1568
1651
|
by_agent: byAgent
|
|
@@ -1640,6 +1723,34 @@ var init_billing_diff = __esm(() => {
|
|
|
1640
1723
|
};
|
|
1641
1724
|
});
|
|
1642
1725
|
|
|
1726
|
+
// src/lib/periods.ts
|
|
1727
|
+
function ymd(date) {
|
|
1728
|
+
return date.toISOString().substring(0, 10);
|
|
1729
|
+
}
|
|
1730
|
+
function usageSnapshotFilterForPeriod(period) {
|
|
1731
|
+
const now = new Date;
|
|
1732
|
+
switch (period) {
|
|
1733
|
+
case "today":
|
|
1734
|
+
return { date: ymd(now) };
|
|
1735
|
+
case "yesterday": {
|
|
1736
|
+
const yesterday = new Date(now);
|
|
1737
|
+
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
|
|
1738
|
+
return { date: ymd(yesterday) };
|
|
1739
|
+
}
|
|
1740
|
+
case "week": {
|
|
1741
|
+
const weekAgo = new Date(now);
|
|
1742
|
+
weekAgo.setUTCDate(weekAgo.getUTCDate() - 7);
|
|
1743
|
+
return { since: ymd(weekAgo) };
|
|
1744
|
+
}
|
|
1745
|
+
case "month":
|
|
1746
|
+
return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))) };
|
|
1747
|
+
case "year":
|
|
1748
|
+
return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), 0, 1))) };
|
|
1749
|
+
case "all":
|
|
1750
|
+
return {};
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1643
1754
|
// src/lib/package-metadata.ts
|
|
1644
1755
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1645
1756
|
function getPackageMetadata() {
|
|
@@ -4563,9 +4674,11 @@ function createHandler(db) {
|
|
|
4563
4674
|
if (path === "/api/usage" && method === "GET") {
|
|
4564
4675
|
const period = url.searchParams.get("period") ?? "month";
|
|
4565
4676
|
const agent = url.searchParams.get("agent") ?? undefined;
|
|
4566
|
-
const since = period === "month" ? new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().substring(0, 10) : undefined;
|
|
4567
4677
|
return ok({
|
|
4568
|
-
snapshots: queryUsageSnapshots(db, {
|
|
4678
|
+
snapshots: queryUsageSnapshots(db, {
|
|
4679
|
+
agent: agent && isAgent(agent) ? agent : undefined,
|
|
4680
|
+
...usageSnapshotFilterForPeriod(period)
|
|
4681
|
+
}),
|
|
4569
4682
|
summary: querySummary(db, period, undefined, true)
|
|
4570
4683
|
});
|
|
4571
4684
|
}
|
|
@@ -6179,8 +6292,7 @@ function registerExtendedCommands(program) {
|
|
|
6179
6292
|
const db = openDatabase();
|
|
6180
6293
|
const period = parsePeriod(periodArg, "month");
|
|
6181
6294
|
const agent = parseAgent(opts.agent, "--agent");
|
|
6182
|
-
const
|
|
6183
|
-
const snaps = queryUsageSnapshots(db, { agent, since });
|
|
6295
|
+
const snaps = queryUsageSnapshots(db, { agent, ...usageSnapshotFilterForPeriod(period) });
|
|
6184
6296
|
const summary = querySummary(db, period, undefined, true);
|
|
6185
6297
|
if (opts.json) {
|
|
6186
6298
|
console.log(JSON.stringify({ period, agent: agent ?? "all", snapshots: snaps, summary }, null, 2));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAKxD,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,cAAc,EACd,MAAM,EACN,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,MAAM,EACN,aAAa,EACd,MAAM,mBAAmB,CAAA;AAE1B,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAkBnC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAIlC;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,QAAQ,CAgBxE;AAkRD,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAuBrE;AAID,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAkBzE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CA2BnE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAE,aAAkB,GAAG,cAAc,EAAE,CAuBxF;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,SAAK,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAKvF;AAID,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,WAAW,CA8B7G;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,cAAc,EAAE,CAUlE;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAE,MAAc,GAAG,cAAc,EAAE,CA0E1F;AA0BD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAE,MAAc,GAAG,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1056,23 +1056,20 @@ function labelForPath(projectPath, projectName) {
|
|
|
1056
1056
|
return segments[segments.length - 1] ?? projectPath;
|
|
1057
1057
|
}
|
|
1058
1058
|
function queryProjectBreakdown(db, period = "all") {
|
|
1059
|
-
const
|
|
1059
|
+
const requestWhere = requestPeriodWhere(period);
|
|
1060
|
+
const sessionWhere = sessionPeriodWhere(period);
|
|
1060
1061
|
const sessions = db.prepare(`
|
|
1061
1062
|
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
1062
1063
|
FROM sessions
|
|
1063
|
-
WHERE
|
|
1064
|
-
AND (project_path != '' OR project_name != '')
|
|
1064
|
+
WHERE project_path != '' OR project_name != ''
|
|
1065
1065
|
`).all();
|
|
1066
1066
|
const groups = new Map;
|
|
1067
1067
|
for (const s of sessions) {
|
|
1068
1068
|
const label = labelForPath(s.project_path, s.project_name);
|
|
1069
1069
|
if (!label)
|
|
1070
1070
|
continue;
|
|
1071
|
-
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path
|
|
1071
|
+
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path };
|
|
1072
1072
|
g.sessionIds.push(s.id);
|
|
1073
|
-
g.totalCost += s.total_cost_usd || 0;
|
|
1074
|
-
if (!g.lastActive || s.started_at > g.lastActive)
|
|
1075
|
-
g.lastActive = s.started_at;
|
|
1076
1073
|
if (!g.samplePath)
|
|
1077
1074
|
g.samplePath = s.project_path;
|
|
1078
1075
|
groups.set(label, g);
|
|
@@ -1082,32 +1079,52 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1082
1079
|
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
1083
1080
|
const reqStats = placeholders.length ? db.prepare(`
|
|
1084
1081
|
SELECT
|
|
1082
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
1085
1083
|
COUNT(*) as requests,
|
|
1086
1084
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1087
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
|
|
1088
|
-
|
|
1089
|
-
|
|
1085
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1086
|
+
MAX(timestamp) as last_active
|
|
1087
|
+
FROM requests
|
|
1088
|
+
WHERE session_id IN (${placeholders})
|
|
1089
|
+
AND ${requestWhere}
|
|
1090
|
+
`).get(...g.sessionIds) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
|
|
1091
|
+
const sessionOnlyStats = placeholders.length ? db.prepare(`
|
|
1092
|
+
SELECT
|
|
1093
|
+
COUNT(*) as sessions,
|
|
1094
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1095
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1096
|
+
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1097
|
+
MAX(started_at) as last_active
|
|
1098
|
+
FROM sessions
|
|
1099
|
+
WHERE id IN (${placeholders})
|
|
1100
|
+
AND ${sessionWhere}
|
|
1101
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1102
|
+
`).get(...g.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1103
|
+
const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1104
|
+
if (totalSessions === 0)
|
|
1105
|
+
continue;
|
|
1106
|
+
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1090
1107
|
result.push({
|
|
1091
1108
|
project_path: g.samplePath,
|
|
1092
1109
|
project_name: label,
|
|
1093
|
-
sessions:
|
|
1094
|
-
requests: reqStats.requests,
|
|
1095
|
-
total_tokens: reqStats.total_tokens,
|
|
1096
|
-
cost_usd: reqStats.cost_usd
|
|
1097
|
-
last_active:
|
|
1110
|
+
sessions: totalSessions,
|
|
1111
|
+
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1112
|
+
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1113
|
+
cost_usd: reqStats.cost_usd + sessionOnlyStats.cost_usd,
|
|
1114
|
+
last_active: lastActive
|
|
1098
1115
|
});
|
|
1099
1116
|
}
|
|
1100
1117
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1101
1118
|
return result;
|
|
1102
1119
|
}
|
|
1103
1120
|
function queryAccountBreakdown(db, period = "all") {
|
|
1104
|
-
const
|
|
1121
|
+
const requestWhere = requestPeriodWhere(period);
|
|
1122
|
+
const sessionWhere = sessionPeriodWhere(period);
|
|
1105
1123
|
const sessions = db.prepare(`
|
|
1106
1124
|
SELECT id, account_key, account_tool, account_name, account_email, account_source,
|
|
1107
1125
|
total_cost_usd, total_tokens, request_count, started_at
|
|
1108
1126
|
FROM sessions
|
|
1109
|
-
WHERE
|
|
1110
|
-
AND (account_key != '' OR account_tool != '' OR account_name != '' OR account_email != '')
|
|
1127
|
+
WHERE account_key != '' OR account_tool != '' OR account_name != '' OR account_email != ''
|
|
1111
1128
|
`).all();
|
|
1112
1129
|
const groups = new Map;
|
|
1113
1130
|
for (const session of sessions) {
|
|
@@ -1119,18 +1136,9 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1119
1136
|
account_tool: session.account_tool,
|
|
1120
1137
|
account_name: session.account_name,
|
|
1121
1138
|
account_email: session.account_email || null,
|
|
1122
|
-
account_source: session.account_source || "unknown"
|
|
1123
|
-
totalCost: 0,
|
|
1124
|
-
totalTokens: 0,
|
|
1125
|
-
requests: 0,
|
|
1126
|
-
lastActive: ""
|
|
1139
|
+
account_source: session.account_source || "unknown"
|
|
1127
1140
|
};
|
|
1128
1141
|
group.sessionIds.push(session.id);
|
|
1129
|
-
group.totalCost += session.total_cost_usd || 0;
|
|
1130
|
-
group.totalTokens += session.total_tokens || 0;
|
|
1131
|
-
group.requests += session.request_count || 0;
|
|
1132
|
-
if (!group.lastActive || session.started_at > group.lastActive)
|
|
1133
|
-
group.lastActive = session.started_at;
|
|
1134
1142
|
groups.set(key, group);
|
|
1135
1143
|
}
|
|
1136
1144
|
const result = [];
|
|
@@ -1138,36 +1146,57 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1138
1146
|
const placeholders = group.sessionIds.map(() => "?").join(",");
|
|
1139
1147
|
const reqStats = placeholders ? db.prepare(`
|
|
1140
1148
|
SELECT
|
|
1149
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
1141
1150
|
COUNT(*) as requests,
|
|
1142
1151
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1143
1152
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1144
1153
|
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
|
|
1145
1154
|
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
|
|
1146
1155
|
COALESCE(SUM(CASE WHEN COALESCE(cost_basis, 'estimated') = 'estimated' THEN cost_usd ELSE 0 END), 0) as estimated_usd,
|
|
1147
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd
|
|
1148
|
-
|
|
1156
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd,
|
|
1157
|
+
MAX(timestamp) as last_active
|
|
1158
|
+
FROM requests
|
|
1159
|
+
WHERE session_id IN (${placeholders})
|
|
1160
|
+
AND ${requestWhere}
|
|
1149
1161
|
`).get(...group.sessionIds) : {
|
|
1162
|
+
sessions: 0,
|
|
1150
1163
|
requests: 0,
|
|
1151
1164
|
cost_usd: 0,
|
|
1152
1165
|
total_tokens: 0,
|
|
1153
1166
|
metered_api_usd: 0,
|
|
1154
1167
|
subscription_included_usd: 0,
|
|
1155
1168
|
estimated_usd: 0,
|
|
1156
|
-
unknown_usd: 0
|
|
1169
|
+
unknown_usd: 0,
|
|
1170
|
+
last_active: null
|
|
1157
1171
|
};
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
1172
|
+
const sessionOnlyStats = placeholders ? db.prepare(`
|
|
1173
|
+
SELECT
|
|
1174
|
+
COUNT(*) as sessions,
|
|
1175
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1176
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1177
|
+
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1178
|
+
MAX(started_at) as last_active
|
|
1179
|
+
FROM sessions
|
|
1180
|
+
WHERE id IN (${placeholders})
|
|
1181
|
+
AND ${sessionWhere}
|
|
1182
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1183
|
+
`).get(...group.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1184
|
+
const sessionsTotal = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1185
|
+
if (sessionsTotal === 0)
|
|
1186
|
+
continue;
|
|
1187
|
+
const apiEquivalentUsd = reqStats.cost_usd + sessionOnlyStats.cost_usd;
|
|
1188
|
+
const estimatedUsd = reqStats.estimated_usd + sessionOnlyStats.cost_usd;
|
|
1161
1189
|
const billableUsd = reqStats.metered_api_usd;
|
|
1190
|
+
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1162
1191
|
result.push({
|
|
1163
1192
|
account_key: key,
|
|
1164
1193
|
account_tool: group.account_tool,
|
|
1165
1194
|
account_name: group.account_name,
|
|
1166
1195
|
account_email: group.account_email,
|
|
1167
1196
|
account_source: group.account_source,
|
|
1168
|
-
sessions:
|
|
1169
|
-
requests: reqStats.requests
|
|
1170
|
-
total_tokens: reqStats.total_tokens
|
|
1197
|
+
sessions: sessionsTotal,
|
|
1198
|
+
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1199
|
+
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1171
1200
|
api_equivalent_usd: apiEquivalentUsd,
|
|
1172
1201
|
billable_usd: billableUsd,
|
|
1173
1202
|
metered_api_usd: reqStats.metered_api_usd,
|
|
@@ -1175,7 +1204,7 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1175
1204
|
estimated_usd: estimatedUsd,
|
|
1176
1205
|
unknown_usd: reqStats.unknown_usd,
|
|
1177
1206
|
cost_usd: apiEquivalentUsd,
|
|
1178
|
-
last_active:
|
|
1207
|
+
last_active: lastActive
|
|
1179
1208
|
});
|
|
1180
1209
|
}
|
|
1181
1210
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"periods.d.ts","sourceRoot":"","sources":["../../src/lib/periods.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAM/C,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAsB9F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"savings.d.ts","sourceRoot":"","sources":["../../src/lib/savings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,kBAAkB,EAAE,MAAM,CAAA;IAC1B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,qBAAqB,EAAE,MAAM,CAAA;IAC7B,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAA;CAClD;
|
|
1
|
+
{"version":3,"file":"savings.d.ts","sourceRoot":"","sources":["../../src/lib/savings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,kBAAkB,EAAE,MAAM,CAAA;IAC1B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,qBAAqB,EAAE,MAAM,CAAA;IAC7B,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAA;CAClD;AAoCD,6EAA6E;AAC7E,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,MAAM,CAER;AAED,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,KAAK,GACZ,cAAc,CAmHhB;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAIhE"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -1057,23 +1057,20 @@ function labelForPath(projectPath, projectName) {
|
|
|
1057
1057
|
return segments[segments.length - 1] ?? projectPath;
|
|
1058
1058
|
}
|
|
1059
1059
|
function queryProjectBreakdown(db, period = "all") {
|
|
1060
|
-
const
|
|
1060
|
+
const requestWhere = requestPeriodWhere(period);
|
|
1061
|
+
const sessionWhere = sessionPeriodWhere(period);
|
|
1061
1062
|
const sessions = db.prepare(`
|
|
1062
1063
|
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
1063
1064
|
FROM sessions
|
|
1064
|
-
WHERE
|
|
1065
|
-
AND (project_path != '' OR project_name != '')
|
|
1065
|
+
WHERE project_path != '' OR project_name != ''
|
|
1066
1066
|
`).all();
|
|
1067
1067
|
const groups = new Map;
|
|
1068
1068
|
for (const s of sessions) {
|
|
1069
1069
|
const label = labelForPath(s.project_path, s.project_name);
|
|
1070
1070
|
if (!label)
|
|
1071
1071
|
continue;
|
|
1072
|
-
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path
|
|
1072
|
+
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path };
|
|
1073
1073
|
g.sessionIds.push(s.id);
|
|
1074
|
-
g.totalCost += s.total_cost_usd || 0;
|
|
1075
|
-
if (!g.lastActive || s.started_at > g.lastActive)
|
|
1076
|
-
g.lastActive = s.started_at;
|
|
1077
1074
|
if (!g.samplePath)
|
|
1078
1075
|
g.samplePath = s.project_path;
|
|
1079
1076
|
groups.set(label, g);
|
|
@@ -1083,32 +1080,52 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1083
1080
|
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
1084
1081
|
const reqStats = placeholders.length ? db.prepare(`
|
|
1085
1082
|
SELECT
|
|
1083
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
1086
1084
|
COUNT(*) as requests,
|
|
1087
1085
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1088
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
|
|
1089
|
-
|
|
1090
|
-
|
|
1086
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1087
|
+
MAX(timestamp) as last_active
|
|
1088
|
+
FROM requests
|
|
1089
|
+
WHERE session_id IN (${placeholders})
|
|
1090
|
+
AND ${requestWhere}
|
|
1091
|
+
`).get(...g.sessionIds) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
|
|
1092
|
+
const sessionOnlyStats = placeholders.length ? db.prepare(`
|
|
1093
|
+
SELECT
|
|
1094
|
+
COUNT(*) as sessions,
|
|
1095
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1096
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1097
|
+
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1098
|
+
MAX(started_at) as last_active
|
|
1099
|
+
FROM sessions
|
|
1100
|
+
WHERE id IN (${placeholders})
|
|
1101
|
+
AND ${sessionWhere}
|
|
1102
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1103
|
+
`).get(...g.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1104
|
+
const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1105
|
+
if (totalSessions === 0)
|
|
1106
|
+
continue;
|
|
1107
|
+
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1091
1108
|
result.push({
|
|
1092
1109
|
project_path: g.samplePath,
|
|
1093
1110
|
project_name: label,
|
|
1094
|
-
sessions:
|
|
1095
|
-
requests: reqStats.requests,
|
|
1096
|
-
total_tokens: reqStats.total_tokens,
|
|
1097
|
-
cost_usd: reqStats.cost_usd
|
|
1098
|
-
last_active:
|
|
1111
|
+
sessions: totalSessions,
|
|
1112
|
+
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1113
|
+
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1114
|
+
cost_usd: reqStats.cost_usd + sessionOnlyStats.cost_usd,
|
|
1115
|
+
last_active: lastActive
|
|
1099
1116
|
});
|
|
1100
1117
|
}
|
|
1101
1118
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1102
1119
|
return result;
|
|
1103
1120
|
}
|
|
1104
1121
|
function queryAccountBreakdown(db, period = "all") {
|
|
1105
|
-
const
|
|
1122
|
+
const requestWhere = requestPeriodWhere(period);
|
|
1123
|
+
const sessionWhere = sessionPeriodWhere(period);
|
|
1106
1124
|
const sessions = db.prepare(`
|
|
1107
1125
|
SELECT id, account_key, account_tool, account_name, account_email, account_source,
|
|
1108
1126
|
total_cost_usd, total_tokens, request_count, started_at
|
|
1109
1127
|
FROM sessions
|
|
1110
|
-
WHERE
|
|
1111
|
-
AND (account_key != '' OR account_tool != '' OR account_name != '' OR account_email != '')
|
|
1128
|
+
WHERE account_key != '' OR account_tool != '' OR account_name != '' OR account_email != ''
|
|
1112
1129
|
`).all();
|
|
1113
1130
|
const groups = new Map;
|
|
1114
1131
|
for (const session of sessions) {
|
|
@@ -1120,18 +1137,9 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1120
1137
|
account_tool: session.account_tool,
|
|
1121
1138
|
account_name: session.account_name,
|
|
1122
1139
|
account_email: session.account_email || null,
|
|
1123
|
-
account_source: session.account_source || "unknown"
|
|
1124
|
-
totalCost: 0,
|
|
1125
|
-
totalTokens: 0,
|
|
1126
|
-
requests: 0,
|
|
1127
|
-
lastActive: ""
|
|
1140
|
+
account_source: session.account_source || "unknown"
|
|
1128
1141
|
};
|
|
1129
1142
|
group.sessionIds.push(session.id);
|
|
1130
|
-
group.totalCost += session.total_cost_usd || 0;
|
|
1131
|
-
group.totalTokens += session.total_tokens || 0;
|
|
1132
|
-
group.requests += session.request_count || 0;
|
|
1133
|
-
if (!group.lastActive || session.started_at > group.lastActive)
|
|
1134
|
-
group.lastActive = session.started_at;
|
|
1135
1143
|
groups.set(key, group);
|
|
1136
1144
|
}
|
|
1137
1145
|
const result = [];
|
|
@@ -1139,36 +1147,57 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1139
1147
|
const placeholders = group.sessionIds.map(() => "?").join(",");
|
|
1140
1148
|
const reqStats = placeholders ? db.prepare(`
|
|
1141
1149
|
SELECT
|
|
1150
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
1142
1151
|
COUNT(*) as requests,
|
|
1143
1152
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1144
1153
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1145
1154
|
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
|
|
1146
1155
|
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
|
|
1147
1156
|
COALESCE(SUM(CASE WHEN COALESCE(cost_basis, 'estimated') = 'estimated' THEN cost_usd ELSE 0 END), 0) as estimated_usd,
|
|
1148
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd
|
|
1149
|
-
|
|
1157
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd,
|
|
1158
|
+
MAX(timestamp) as last_active
|
|
1159
|
+
FROM requests
|
|
1160
|
+
WHERE session_id IN (${placeholders})
|
|
1161
|
+
AND ${requestWhere}
|
|
1150
1162
|
`).get(...group.sessionIds) : {
|
|
1163
|
+
sessions: 0,
|
|
1151
1164
|
requests: 0,
|
|
1152
1165
|
cost_usd: 0,
|
|
1153
1166
|
total_tokens: 0,
|
|
1154
1167
|
metered_api_usd: 0,
|
|
1155
1168
|
subscription_included_usd: 0,
|
|
1156
1169
|
estimated_usd: 0,
|
|
1157
|
-
unknown_usd: 0
|
|
1170
|
+
unknown_usd: 0,
|
|
1171
|
+
last_active: null
|
|
1158
1172
|
};
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1173
|
+
const sessionOnlyStats = placeholders ? db.prepare(`
|
|
1174
|
+
SELECT
|
|
1175
|
+
COUNT(*) as sessions,
|
|
1176
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1177
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1178
|
+
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1179
|
+
MAX(started_at) as last_active
|
|
1180
|
+
FROM sessions
|
|
1181
|
+
WHERE id IN (${placeholders})
|
|
1182
|
+
AND ${sessionWhere}
|
|
1183
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1184
|
+
`).get(...group.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1185
|
+
const sessionsTotal = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1186
|
+
if (sessionsTotal === 0)
|
|
1187
|
+
continue;
|
|
1188
|
+
const apiEquivalentUsd = reqStats.cost_usd + sessionOnlyStats.cost_usd;
|
|
1189
|
+
const estimatedUsd = reqStats.estimated_usd + sessionOnlyStats.cost_usd;
|
|
1162
1190
|
const billableUsd = reqStats.metered_api_usd;
|
|
1191
|
+
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1163
1192
|
result.push({
|
|
1164
1193
|
account_key: key,
|
|
1165
1194
|
account_tool: group.account_tool,
|
|
1166
1195
|
account_name: group.account_name,
|
|
1167
1196
|
account_email: group.account_email,
|
|
1168
1197
|
account_source: group.account_source,
|
|
1169
|
-
sessions:
|
|
1170
|
-
requests: reqStats.requests
|
|
1171
|
-
total_tokens: reqStats.total_tokens
|
|
1198
|
+
sessions: sessionsTotal,
|
|
1199
|
+
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1200
|
+
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1172
1201
|
api_equivalent_usd: apiEquivalentUsd,
|
|
1173
1202
|
billable_usd: billableUsd,
|
|
1174
1203
|
metered_api_usd: reqStats.metered_api_usd,
|
|
@@ -1176,7 +1205,7 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1176
1205
|
estimated_usd: estimatedUsd,
|
|
1177
1206
|
unknown_usd: reqStats.unknown_usd,
|
|
1178
1207
|
cost_usd: apiEquivalentUsd,
|
|
1179
|
-
last_active:
|
|
1208
|
+
last_active: lastActive
|
|
1180
1209
|
});
|
|
1181
1210
|
}
|
|
1182
1211
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
@@ -1632,6 +1661,10 @@ function prorateMonthlyFee(monthlyFee, period) {
|
|
|
1632
1661
|
return monthlyFee;
|
|
1633
1662
|
}
|
|
1634
1663
|
}
|
|
1664
|
+
function proratedIncludedConsumed(includedUsage, includedCap, period) {
|
|
1665
|
+
const cap = prorateMonthlyFee(includedCap, period);
|
|
1666
|
+
return cap > 0 ? Math.min(includedUsage, cap) : includedUsage;
|
|
1667
|
+
}
|
|
1635
1668
|
function computeSavedUsd(apiEquivalent, onDemand, subscriptionFee) {
|
|
1636
1669
|
return Math.max(0, apiEquivalent - onDemand - subscriptionFee);
|
|
1637
1670
|
}
|
|
@@ -1659,24 +1692,74 @@ function querySavingsSummary(db, period, agent) {
|
|
|
1659
1692
|
AND metric = 'on_demand_usd'
|
|
1660
1693
|
`).get(...params);
|
|
1661
1694
|
const subs = db.prepare(`
|
|
1662
|
-
SELECT
|
|
1695
|
+
SELECT
|
|
1696
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
1697
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
1663
1698
|
FROM subscriptions
|
|
1664
|
-
WHERE active = 1${agent ? " AND agent = ?" : ""}
|
|
1699
|
+
WHERE active = 1${agent ? " AND (agent = ? OR agent IS NULL)" : ""}
|
|
1665
1700
|
`).get(...agent ? [agent] : []);
|
|
1666
|
-
const subscriptionFee = prorateMonthlyFee(subs.
|
|
1701
|
+
const subscriptionFee = prorateMonthlyFee(subs.fee, period);
|
|
1667
1702
|
const apiEquivalent = apiRow.total + includedRow.total;
|
|
1703
|
+
const includedConsumed = proratedIncludedConsumed(includedRow.total, subs.included, period);
|
|
1668
1704
|
const onDemand = onDemandRow.total;
|
|
1669
1705
|
const saved = computeSavedUsd(apiEquivalent, onDemand, subscriptionFee);
|
|
1670
1706
|
const byAgent = {};
|
|
1671
1707
|
if (!agent) {
|
|
1708
|
+
const onDemandByAgent = new Map;
|
|
1709
|
+
for (const row of db.prepare(`
|
|
1710
|
+
SELECT agent, COALESCE(SUM(value), 0) as total
|
|
1711
|
+
FROM usage_snapshots
|
|
1712
|
+
WHERE ${subWhere}
|
|
1713
|
+
AND metric = 'on_demand_usd'
|
|
1714
|
+
GROUP BY agent
|
|
1715
|
+
`).all()) {
|
|
1716
|
+
onDemandByAgent.set(row.agent, row.total);
|
|
1717
|
+
}
|
|
1718
|
+
const subscriptionByAgent = new Map;
|
|
1672
1719
|
for (const row of db.prepare(`
|
|
1673
|
-
SELECT agent,
|
|
1720
|
+
SELECT agent,
|
|
1721
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
1722
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
1723
|
+
FROM subscriptions
|
|
1724
|
+
WHERE active = 1 AND agent IS NOT NULL
|
|
1725
|
+
GROUP BY agent
|
|
1726
|
+
`).all()) {
|
|
1727
|
+
subscriptionByAgent.set(row.agent, row);
|
|
1728
|
+
}
|
|
1729
|
+
const globalSubs = db.prepare(`
|
|
1730
|
+
SELECT
|
|
1731
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
1732
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
1733
|
+
FROM subscriptions
|
|
1734
|
+
WHERE active = 1 AND agent IS NULL
|
|
1735
|
+
`).get();
|
|
1736
|
+
const rows = db.prepare(`
|
|
1737
|
+
SELECT agent,
|
|
1738
|
+
COALESCE(SUM(cost_usd), 0) as api_eq,
|
|
1739
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as included
|
|
1740
|
+
FROM requests WHERE ${where}
|
|
1741
|
+
GROUP BY agent
|
|
1742
|
+
`).all();
|
|
1743
|
+
const totalAgentApiEq = rows.reduce((sum, row) => sum + row.api_eq, 0);
|
|
1744
|
+
for (const row of db.prepare(`
|
|
1745
|
+
SELECT agent,
|
|
1746
|
+
COALESCE(SUM(cost_usd), 0) as api_eq,
|
|
1747
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as included
|
|
1674
1748
|
FROM requests WHERE ${where}
|
|
1675
1749
|
GROUP BY agent
|
|
1676
1750
|
`).all()) {
|
|
1751
|
+
const agentSubs = subscriptionByAgent.get(row.agent) ?? { fee: 0, included: 0 };
|
|
1752
|
+
const globalShare = totalAgentApiEq > 0 ? row.api_eq / totalAgentApiEq : 0;
|
|
1753
|
+
const agentFee = prorateMonthlyFee(agentSubs.fee + globalSubs.fee * globalShare, period);
|
|
1754
|
+
const agentIncludedCap = agentSubs.included + globalSubs.included * globalShare;
|
|
1755
|
+
const agentIncludedConsumed = proratedIncludedConsumed(row.included, agentIncludedCap, period);
|
|
1756
|
+
const agentOnDemand = onDemandByAgent.get(row.agent) ?? 0;
|
|
1677
1757
|
byAgent[row.agent] = {
|
|
1678
1758
|
api_equivalent_usd: row.api_eq,
|
|
1679
|
-
|
|
1759
|
+
subscription_fee_usd: agentFee,
|
|
1760
|
+
included_consumed_usd: agentIncludedConsumed,
|
|
1761
|
+
on_demand_usd: agentOnDemand,
|
|
1762
|
+
saved_usd: computeSavedUsd(row.api_eq, agentOnDemand, agentFee)
|
|
1680
1763
|
};
|
|
1681
1764
|
}
|
|
1682
1765
|
}
|
|
@@ -1684,7 +1767,7 @@ function querySavingsSummary(db, period, agent) {
|
|
|
1684
1767
|
period,
|
|
1685
1768
|
api_equivalent_usd: apiEquivalent,
|
|
1686
1769
|
subscription_fee_usd: subscriptionFee,
|
|
1687
|
-
included_consumed_usd:
|
|
1770
|
+
included_consumed_usd: includedConsumed,
|
|
1688
1771
|
on_demand_usd: onDemand,
|
|
1689
1772
|
saved_usd: saved,
|
|
1690
1773
|
by_agent: byAgent
|
|
@@ -3272,6 +3355,34 @@ var AGENTS = [
|
|
|
3272
3355
|
"hermes"
|
|
3273
3356
|
];
|
|
3274
3357
|
|
|
3358
|
+
// src/lib/periods.ts
|
|
3359
|
+
function ymd(date) {
|
|
3360
|
+
return date.toISOString().substring(0, 10);
|
|
3361
|
+
}
|
|
3362
|
+
function usageSnapshotFilterForPeriod(period) {
|
|
3363
|
+
const now = new Date;
|
|
3364
|
+
switch (period) {
|
|
3365
|
+
case "today":
|
|
3366
|
+
return { date: ymd(now) };
|
|
3367
|
+
case "yesterday": {
|
|
3368
|
+
const yesterday = new Date(now);
|
|
3369
|
+
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
|
|
3370
|
+
return { date: ymd(yesterday) };
|
|
3371
|
+
}
|
|
3372
|
+
case "week": {
|
|
3373
|
+
const weekAgo = new Date(now);
|
|
3374
|
+
weekAgo.setUTCDate(weekAgo.getUTCDate() - 7);
|
|
3375
|
+
return { since: ymd(weekAgo) };
|
|
3376
|
+
}
|
|
3377
|
+
case "month":
|
|
3378
|
+
return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))) };
|
|
3379
|
+
case "year":
|
|
3380
|
+
return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), 0, 1))) };
|
|
3381
|
+
case "all":
|
|
3382
|
+
return {};
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3275
3386
|
// src/mcp/index.ts
|
|
3276
3387
|
init_database();
|
|
3277
3388
|
init_pricing();
|
|
@@ -3355,7 +3466,7 @@ var TOOL_DESCRIPTIONS = {
|
|
|
3355
3466
|
get_daily: "days(30) -> daily cost table grouped by date and agent",
|
|
3356
3467
|
get_billing_summary: "period(today|yesterday|week|month|year|all) -> actual provider billing totals",
|
|
3357
3468
|
get_session_detail: "session_id(prefix ok) -> per-request breakdown with model, tokens, cost",
|
|
3358
|
-
get_usage: `period(today|week|month), agent?(${AGENTS.join("|")}) -> usage snapshots and all-machine summary`,
|
|
3469
|
+
get_usage: `period(today|week|month|year|all), agent?(${AGENTS.join("|")}) -> usage snapshots and all-machine summary`,
|
|
3359
3470
|
get_savings: `period(today|week|month|year|all), agent?(${AGENTS.join("|")}) -> subscription/API-equivalent savings`,
|
|
3360
3471
|
list_subscriptions: "no params -> configured subscription plans and included usage",
|
|
3361
3472
|
set_subscription: `provider, plan, monthly_fee_usd?, included_usage_usd?, agent?(${AGENTS.join("|")}) -> create/update subscription plan`,
|
|
@@ -3611,9 +3722,9 @@ server.tool("sync", `Ingest new cost data. sources: all|${AGENTS.join("|")}`, {
|
|
|
3611
3722
|
const result = await syncAll(db, opts);
|
|
3612
3723
|
return text(JSON.stringify(result, null, 2));
|
|
3613
3724
|
});
|
|
3614
|
-
server.tool("get_usage", "Usage snapshots and fleet summary. period: today|week|month", { period: z.enum(["today", "week", "month"]).optional(), agent: z.enum(AGENTS).optional() }, async ({ period, agent }) => {
|
|
3725
|
+
server.tool("get_usage", "Usage snapshots and fleet summary. period: today|week|month|year|all", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), agent: z.enum(AGENTS).optional() }, async ({ period, agent }) => {
|
|
3615
3726
|
const p = period ?? "month";
|
|
3616
|
-
const snaps = queryUsageSnapshots(db, { agent });
|
|
3727
|
+
const snaps = queryUsageSnapshots(db, { agent, ...usageSnapshotFilterForPeriod(p) });
|
|
3617
3728
|
const summary = querySummary(db, p, undefined, true);
|
|
3618
3729
|
return text(JSON.stringify({ snapshots: snaps, summary }, null, 2));
|
|
3619
3730
|
});
|
package/dist/server/index.js
CHANGED
|
@@ -1057,23 +1057,20 @@ function labelForPath(projectPath, projectName) {
|
|
|
1057
1057
|
return segments[segments.length - 1] ?? projectPath;
|
|
1058
1058
|
}
|
|
1059
1059
|
function queryProjectBreakdown(db, period = "all") {
|
|
1060
|
-
const
|
|
1060
|
+
const requestWhere = requestPeriodWhere(period);
|
|
1061
|
+
const sessionWhere = sessionPeriodWhere(period);
|
|
1061
1062
|
const sessions = db.prepare(`
|
|
1062
1063
|
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
1063
1064
|
FROM sessions
|
|
1064
|
-
WHERE
|
|
1065
|
-
AND (project_path != '' OR project_name != '')
|
|
1065
|
+
WHERE project_path != '' OR project_name != ''
|
|
1066
1066
|
`).all();
|
|
1067
1067
|
const groups = new Map;
|
|
1068
1068
|
for (const s of sessions) {
|
|
1069
1069
|
const label = labelForPath(s.project_path, s.project_name);
|
|
1070
1070
|
if (!label)
|
|
1071
1071
|
continue;
|
|
1072
|
-
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path
|
|
1072
|
+
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path };
|
|
1073
1073
|
g.sessionIds.push(s.id);
|
|
1074
|
-
g.totalCost += s.total_cost_usd || 0;
|
|
1075
|
-
if (!g.lastActive || s.started_at > g.lastActive)
|
|
1076
|
-
g.lastActive = s.started_at;
|
|
1077
1074
|
if (!g.samplePath)
|
|
1078
1075
|
g.samplePath = s.project_path;
|
|
1079
1076
|
groups.set(label, g);
|
|
@@ -1083,32 +1080,52 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1083
1080
|
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
1084
1081
|
const reqStats = placeholders.length ? db.prepare(`
|
|
1085
1082
|
SELECT
|
|
1083
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
1086
1084
|
COUNT(*) as requests,
|
|
1087
1085
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1088
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
|
|
1089
|
-
|
|
1090
|
-
|
|
1086
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1087
|
+
MAX(timestamp) as last_active
|
|
1088
|
+
FROM requests
|
|
1089
|
+
WHERE session_id IN (${placeholders})
|
|
1090
|
+
AND ${requestWhere}
|
|
1091
|
+
`).get(...g.sessionIds) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
|
|
1092
|
+
const sessionOnlyStats = placeholders.length ? db.prepare(`
|
|
1093
|
+
SELECT
|
|
1094
|
+
COUNT(*) as sessions,
|
|
1095
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1096
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1097
|
+
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1098
|
+
MAX(started_at) as last_active
|
|
1099
|
+
FROM sessions
|
|
1100
|
+
WHERE id IN (${placeholders})
|
|
1101
|
+
AND ${sessionWhere}
|
|
1102
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1103
|
+
`).get(...g.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1104
|
+
const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1105
|
+
if (totalSessions === 0)
|
|
1106
|
+
continue;
|
|
1107
|
+
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1091
1108
|
result.push({
|
|
1092
1109
|
project_path: g.samplePath,
|
|
1093
1110
|
project_name: label,
|
|
1094
|
-
sessions:
|
|
1095
|
-
requests: reqStats.requests,
|
|
1096
|
-
total_tokens: reqStats.total_tokens,
|
|
1097
|
-
cost_usd: reqStats.cost_usd
|
|
1098
|
-
last_active:
|
|
1111
|
+
sessions: totalSessions,
|
|
1112
|
+
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1113
|
+
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1114
|
+
cost_usd: reqStats.cost_usd + sessionOnlyStats.cost_usd,
|
|
1115
|
+
last_active: lastActive
|
|
1099
1116
|
});
|
|
1100
1117
|
}
|
|
1101
1118
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1102
1119
|
return result;
|
|
1103
1120
|
}
|
|
1104
1121
|
function queryAccountBreakdown(db, period = "all") {
|
|
1105
|
-
const
|
|
1122
|
+
const requestWhere = requestPeriodWhere(period);
|
|
1123
|
+
const sessionWhere = sessionPeriodWhere(period);
|
|
1106
1124
|
const sessions = db.prepare(`
|
|
1107
1125
|
SELECT id, account_key, account_tool, account_name, account_email, account_source,
|
|
1108
1126
|
total_cost_usd, total_tokens, request_count, started_at
|
|
1109
1127
|
FROM sessions
|
|
1110
|
-
WHERE
|
|
1111
|
-
AND (account_key != '' OR account_tool != '' OR account_name != '' OR account_email != '')
|
|
1128
|
+
WHERE account_key != '' OR account_tool != '' OR account_name != '' OR account_email != ''
|
|
1112
1129
|
`).all();
|
|
1113
1130
|
const groups = new Map;
|
|
1114
1131
|
for (const session of sessions) {
|
|
@@ -1120,18 +1137,9 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1120
1137
|
account_tool: session.account_tool,
|
|
1121
1138
|
account_name: session.account_name,
|
|
1122
1139
|
account_email: session.account_email || null,
|
|
1123
|
-
account_source: session.account_source || "unknown"
|
|
1124
|
-
totalCost: 0,
|
|
1125
|
-
totalTokens: 0,
|
|
1126
|
-
requests: 0,
|
|
1127
|
-
lastActive: ""
|
|
1140
|
+
account_source: session.account_source || "unknown"
|
|
1128
1141
|
};
|
|
1129
1142
|
group.sessionIds.push(session.id);
|
|
1130
|
-
group.totalCost += session.total_cost_usd || 0;
|
|
1131
|
-
group.totalTokens += session.total_tokens || 0;
|
|
1132
|
-
group.requests += session.request_count || 0;
|
|
1133
|
-
if (!group.lastActive || session.started_at > group.lastActive)
|
|
1134
|
-
group.lastActive = session.started_at;
|
|
1135
1143
|
groups.set(key, group);
|
|
1136
1144
|
}
|
|
1137
1145
|
const result = [];
|
|
@@ -1139,36 +1147,57 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1139
1147
|
const placeholders = group.sessionIds.map(() => "?").join(",");
|
|
1140
1148
|
const reqStats = placeholders ? db.prepare(`
|
|
1141
1149
|
SELECT
|
|
1150
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
1142
1151
|
COUNT(*) as requests,
|
|
1143
1152
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1144
1153
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1145
1154
|
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
|
|
1146
1155
|
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
|
|
1147
1156
|
COALESCE(SUM(CASE WHEN COALESCE(cost_basis, 'estimated') = 'estimated' THEN cost_usd ELSE 0 END), 0) as estimated_usd,
|
|
1148
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd
|
|
1149
|
-
|
|
1157
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd,
|
|
1158
|
+
MAX(timestamp) as last_active
|
|
1159
|
+
FROM requests
|
|
1160
|
+
WHERE session_id IN (${placeholders})
|
|
1161
|
+
AND ${requestWhere}
|
|
1150
1162
|
`).get(...group.sessionIds) : {
|
|
1163
|
+
sessions: 0,
|
|
1151
1164
|
requests: 0,
|
|
1152
1165
|
cost_usd: 0,
|
|
1153
1166
|
total_tokens: 0,
|
|
1154
1167
|
metered_api_usd: 0,
|
|
1155
1168
|
subscription_included_usd: 0,
|
|
1156
1169
|
estimated_usd: 0,
|
|
1157
|
-
unknown_usd: 0
|
|
1170
|
+
unknown_usd: 0,
|
|
1171
|
+
last_active: null
|
|
1158
1172
|
};
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1173
|
+
const sessionOnlyStats = placeholders ? db.prepare(`
|
|
1174
|
+
SELECT
|
|
1175
|
+
COUNT(*) as sessions,
|
|
1176
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1177
|
+
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1178
|
+
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1179
|
+
MAX(started_at) as last_active
|
|
1180
|
+
FROM sessions
|
|
1181
|
+
WHERE id IN (${placeholders})
|
|
1182
|
+
AND ${sessionWhere}
|
|
1183
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1184
|
+
`).get(...group.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1185
|
+
const sessionsTotal = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1186
|
+
if (sessionsTotal === 0)
|
|
1187
|
+
continue;
|
|
1188
|
+
const apiEquivalentUsd = reqStats.cost_usd + sessionOnlyStats.cost_usd;
|
|
1189
|
+
const estimatedUsd = reqStats.estimated_usd + sessionOnlyStats.cost_usd;
|
|
1162
1190
|
const billableUsd = reqStats.metered_api_usd;
|
|
1191
|
+
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1163
1192
|
result.push({
|
|
1164
1193
|
account_key: key,
|
|
1165
1194
|
account_tool: group.account_tool,
|
|
1166
1195
|
account_name: group.account_name,
|
|
1167
1196
|
account_email: group.account_email,
|
|
1168
1197
|
account_source: group.account_source,
|
|
1169
|
-
sessions:
|
|
1170
|
-
requests: reqStats.requests
|
|
1171
|
-
total_tokens: reqStats.total_tokens
|
|
1198
|
+
sessions: sessionsTotal,
|
|
1199
|
+
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1200
|
+
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1172
1201
|
api_equivalent_usd: apiEquivalentUsd,
|
|
1173
1202
|
billable_usd: billableUsd,
|
|
1174
1203
|
metered_api_usd: reqStats.metered_api_usd,
|
|
@@ -1176,7 +1205,7 @@ function queryAccountBreakdown(db, period = "all") {
|
|
|
1176
1205
|
estimated_usd: estimatedUsd,
|
|
1177
1206
|
unknown_usd: reqStats.unknown_usd,
|
|
1178
1207
|
cost_usd: apiEquivalentUsd,
|
|
1179
|
-
last_active:
|
|
1208
|
+
last_active: lastActive
|
|
1180
1209
|
});
|
|
1181
1210
|
}
|
|
1182
1211
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
@@ -2059,6 +2088,10 @@ function prorateMonthlyFee(monthlyFee, period) {
|
|
|
2059
2088
|
return monthlyFee;
|
|
2060
2089
|
}
|
|
2061
2090
|
}
|
|
2091
|
+
function proratedIncludedConsumed(includedUsage, includedCap, period) {
|
|
2092
|
+
const cap = prorateMonthlyFee(includedCap, period);
|
|
2093
|
+
return cap > 0 ? Math.min(includedUsage, cap) : includedUsage;
|
|
2094
|
+
}
|
|
2062
2095
|
function computeSavedUsd(apiEquivalent, onDemand, subscriptionFee) {
|
|
2063
2096
|
return Math.max(0, apiEquivalent - onDemand - subscriptionFee);
|
|
2064
2097
|
}
|
|
@@ -2086,24 +2119,74 @@ function querySavingsSummary(db, period, agent) {
|
|
|
2086
2119
|
AND metric = 'on_demand_usd'
|
|
2087
2120
|
`).get(...params);
|
|
2088
2121
|
const subs = db.prepare(`
|
|
2089
|
-
SELECT
|
|
2122
|
+
SELECT
|
|
2123
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
2124
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
2090
2125
|
FROM subscriptions
|
|
2091
|
-
WHERE active = 1${agent ? " AND agent = ?" : ""}
|
|
2126
|
+
WHERE active = 1${agent ? " AND (agent = ? OR agent IS NULL)" : ""}
|
|
2092
2127
|
`).get(...agent ? [agent] : []);
|
|
2093
|
-
const subscriptionFee = prorateMonthlyFee(subs.
|
|
2128
|
+
const subscriptionFee = prorateMonthlyFee(subs.fee, period);
|
|
2094
2129
|
const apiEquivalent = apiRow.total + includedRow.total;
|
|
2130
|
+
const includedConsumed = proratedIncludedConsumed(includedRow.total, subs.included, period);
|
|
2095
2131
|
const onDemand = onDemandRow.total;
|
|
2096
2132
|
const saved = computeSavedUsd(apiEquivalent, onDemand, subscriptionFee);
|
|
2097
2133
|
const byAgent = {};
|
|
2098
2134
|
if (!agent) {
|
|
2135
|
+
const onDemandByAgent = new Map;
|
|
2136
|
+
for (const row of db.prepare(`
|
|
2137
|
+
SELECT agent, COALESCE(SUM(value), 0) as total
|
|
2138
|
+
FROM usage_snapshots
|
|
2139
|
+
WHERE ${subWhere}
|
|
2140
|
+
AND metric = 'on_demand_usd'
|
|
2141
|
+
GROUP BY agent
|
|
2142
|
+
`).all()) {
|
|
2143
|
+
onDemandByAgent.set(row.agent, row.total);
|
|
2144
|
+
}
|
|
2145
|
+
const subscriptionByAgent = new Map;
|
|
2099
2146
|
for (const row of db.prepare(`
|
|
2100
|
-
SELECT agent,
|
|
2147
|
+
SELECT agent,
|
|
2148
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
2149
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
2150
|
+
FROM subscriptions
|
|
2151
|
+
WHERE active = 1 AND agent IS NOT NULL
|
|
2152
|
+
GROUP BY agent
|
|
2153
|
+
`).all()) {
|
|
2154
|
+
subscriptionByAgent.set(row.agent, row);
|
|
2155
|
+
}
|
|
2156
|
+
const globalSubs = db.prepare(`
|
|
2157
|
+
SELECT
|
|
2158
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
2159
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
2160
|
+
FROM subscriptions
|
|
2161
|
+
WHERE active = 1 AND agent IS NULL
|
|
2162
|
+
`).get();
|
|
2163
|
+
const rows = db.prepare(`
|
|
2164
|
+
SELECT agent,
|
|
2165
|
+
COALESCE(SUM(cost_usd), 0) as api_eq,
|
|
2166
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as included
|
|
2167
|
+
FROM requests WHERE ${where}
|
|
2168
|
+
GROUP BY agent
|
|
2169
|
+
`).all();
|
|
2170
|
+
const totalAgentApiEq = rows.reduce((sum, row) => sum + row.api_eq, 0);
|
|
2171
|
+
for (const row of db.prepare(`
|
|
2172
|
+
SELECT agent,
|
|
2173
|
+
COALESCE(SUM(cost_usd), 0) as api_eq,
|
|
2174
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as included
|
|
2101
2175
|
FROM requests WHERE ${where}
|
|
2102
2176
|
GROUP BY agent
|
|
2103
2177
|
`).all()) {
|
|
2178
|
+
const agentSubs = subscriptionByAgent.get(row.agent) ?? { fee: 0, included: 0 };
|
|
2179
|
+
const globalShare = totalAgentApiEq > 0 ? row.api_eq / totalAgentApiEq : 0;
|
|
2180
|
+
const agentFee = prorateMonthlyFee(agentSubs.fee + globalSubs.fee * globalShare, period);
|
|
2181
|
+
const agentIncludedCap = agentSubs.included + globalSubs.included * globalShare;
|
|
2182
|
+
const agentIncludedConsumed = proratedIncludedConsumed(row.included, agentIncludedCap, period);
|
|
2183
|
+
const agentOnDemand = onDemandByAgent.get(row.agent) ?? 0;
|
|
2104
2184
|
byAgent[row.agent] = {
|
|
2105
2185
|
api_equivalent_usd: row.api_eq,
|
|
2106
|
-
|
|
2186
|
+
subscription_fee_usd: agentFee,
|
|
2187
|
+
included_consumed_usd: agentIncludedConsumed,
|
|
2188
|
+
on_demand_usd: agentOnDemand,
|
|
2189
|
+
saved_usd: computeSavedUsd(row.api_eq, agentOnDemand, agentFee)
|
|
2107
2190
|
};
|
|
2108
2191
|
}
|
|
2109
2192
|
}
|
|
@@ -2111,7 +2194,7 @@ function querySavingsSummary(db, period, agent) {
|
|
|
2111
2194
|
period,
|
|
2112
2195
|
api_equivalent_usd: apiEquivalent,
|
|
2113
2196
|
subscription_fee_usd: subscriptionFee,
|
|
2114
|
-
included_consumed_usd:
|
|
2197
|
+
included_consumed_usd: includedConsumed,
|
|
2115
2198
|
on_demand_usd: onDemand,
|
|
2116
2199
|
saved_usd: saved,
|
|
2117
2200
|
by_agent: byAgent
|
|
@@ -3687,6 +3770,34 @@ async function syncAll(db, opts = {}) {
|
|
|
3687
3770
|
return result;
|
|
3688
3771
|
}
|
|
3689
3772
|
|
|
3773
|
+
// src/lib/periods.ts
|
|
3774
|
+
function ymd(date) {
|
|
3775
|
+
return date.toISOString().substring(0, 10);
|
|
3776
|
+
}
|
|
3777
|
+
function usageSnapshotFilterForPeriod(period) {
|
|
3778
|
+
const now = new Date;
|
|
3779
|
+
switch (period) {
|
|
3780
|
+
case "today":
|
|
3781
|
+
return { date: ymd(now) };
|
|
3782
|
+
case "yesterday": {
|
|
3783
|
+
const yesterday = new Date(now);
|
|
3784
|
+
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
|
|
3785
|
+
return { date: ymd(yesterday) };
|
|
3786
|
+
}
|
|
3787
|
+
case "week": {
|
|
3788
|
+
const weekAgo = new Date(now);
|
|
3789
|
+
weekAgo.setUTCDate(weekAgo.getUTCDate() - 7);
|
|
3790
|
+
return { since: ymd(weekAgo) };
|
|
3791
|
+
}
|
|
3792
|
+
case "month":
|
|
3793
|
+
return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))) };
|
|
3794
|
+
case "year":
|
|
3795
|
+
return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), 0, 1))) };
|
|
3796
|
+
case "all":
|
|
3797
|
+
return {};
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3690
3801
|
// src/lib/billing-diff.ts
|
|
3691
3802
|
init_database();
|
|
3692
3803
|
var PROVIDER_TO_AGENT = {
|
|
@@ -4107,9 +4218,11 @@ function createHandler(db) {
|
|
|
4107
4218
|
if (path === "/api/usage" && method === "GET") {
|
|
4108
4219
|
const period = url.searchParams.get("period") ?? "month";
|
|
4109
4220
|
const agent = url.searchParams.get("agent") ?? undefined;
|
|
4110
|
-
const since = period === "month" ? new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().substring(0, 10) : undefined;
|
|
4111
4221
|
return ok({
|
|
4112
|
-
snapshots: queryUsageSnapshots(db, {
|
|
4222
|
+
snapshots: queryUsageSnapshots(db, {
|
|
4223
|
+
agent: agent && isAgent(agent) ? agent : undefined,
|
|
4224
|
+
...usageSnapshotFilterForPeriod(period)
|
|
4225
|
+
}),
|
|
4113
4226
|
summary: querySummary(db, period, undefined, true)
|
|
4114
4227
|
});
|
|
4115
4228
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAuC7D,UAAU,kBAAkB;IAC1B,EAAE,CAAC,EAAE,QAAQ,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;AAqED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAAE,YAAY,SAAwB,IACzF,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAwB7D;AAQD,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,IACV,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAkW/D;AAED,wBAAgB,WAAW,CAAC,IAAI,SAAO,EAAE,OAAO,GAAE,kBAAuB,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAcvG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"description": "AI coding cost tracker — CLI + MCP server + REST API + web dashboard for Claude Code, Codex, Gemini, OpenCode, Cursor, Pi, and Hermes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|