@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.
@@ -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;AA6CnC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0R/D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkC5D"}
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 where = sessionPeriodWhere(period);
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 ${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, totalCost: 0, lastActive: "" };
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
- FROM requests WHERE session_id IN (${placeholders})
1139
- `).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
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: g.sessionIds.length,
1144
- requests: reqStats.requests,
1145
- total_tokens: reqStats.total_tokens,
1146
- cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
1147
- last_active: g.lastActive
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 sWhere = sessionPeriodWhere(period);
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 ${sWhere}
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
- FROM requests WHERE session_id IN (${placeholders})
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 hasRequestCosts = reqStats.requests > 0;
1209
- const apiEquivalentUsd = hasRequestCosts ? reqStats.cost_usd : group.totalCost;
1210
- const estimatedUsd = hasRequestCosts ? reqStats.estimated_usd : group.totalCost;
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: group.sessionIds.length,
1219
- requests: reqStats.requests || group.requests,
1220
- total_tokens: reqStats.total_tokens || group.totalTokens,
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: group.lastActive
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 COALESCE(SUM(monthly_fee_usd), 0) as total
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.total, period);
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, COALESCE(SUM(cost_usd), 0) as api_eq
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
- saved_usd: row.api_eq
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: includedRow.total,
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, { agent: agent && isAgent(agent) ? agent : undefined, since }),
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 since = period === "today" ? new Date().toISOString().substring(0, 10) : period === "week" ? new Date(Date.now() - 7 * 86400000).toISOString().substring(0, 10) : period === "month" ? new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().substring(0, 10) : undefined;
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":"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"}
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 where = sessionPeriodWhere(period);
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 ${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, totalCost: 0, lastActive: "" };
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
- FROM requests WHERE session_id IN (${placeholders})
1089
- `).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
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: g.sessionIds.length,
1094
- requests: reqStats.requests,
1095
- total_tokens: reqStats.total_tokens,
1096
- cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
1097
- last_active: g.lastActive
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 sWhere = sessionPeriodWhere(period);
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 ${sWhere}
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
- FROM requests WHERE session_id IN (${placeholders})
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 hasRequestCosts = reqStats.requests > 0;
1159
- const apiEquivalentUsd = hasRequestCosts ? reqStats.cost_usd : group.totalCost;
1160
- const estimatedUsd = hasRequestCosts ? reqStats.estimated_usd : group.totalCost;
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: group.sessionIds.length,
1169
- requests: reqStats.requests || group.requests,
1170
- total_tokens: reqStats.total_tokens || group.totalTokens,
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: group.lastActive
1207
+ last_active: lastActive
1179
1208
  });
1180
1209
  }
1181
1210
  result.sort((a, b) => b.cost_usd - a.cost_usd);
@@ -0,0 +1,6 @@
1
+ import type { Period } from '../types/index.js';
2
+ export declare function usageSnapshotFilterForPeriod(period: Period): {
3
+ date?: string;
4
+ since?: string;
5
+ };
6
+ //# sourceMappingURL=periods.d.ts.map
@@ -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;AA+BD,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,CA6DhB;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAIhE"}
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 where = sessionPeriodWhere(period);
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 ${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, totalCost: 0, lastActive: "" };
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
- FROM requests WHERE session_id IN (${placeholders})
1090
- `).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
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: g.sessionIds.length,
1095
- requests: reqStats.requests,
1096
- total_tokens: reqStats.total_tokens,
1097
- cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
1098
- last_active: g.lastActive
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 sWhere = sessionPeriodWhere(period);
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 ${sWhere}
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
- FROM requests WHERE session_id IN (${placeholders})
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 hasRequestCosts = reqStats.requests > 0;
1160
- const apiEquivalentUsd = hasRequestCosts ? reqStats.cost_usd : group.totalCost;
1161
- const estimatedUsd = hasRequestCosts ? reqStats.estimated_usd : group.totalCost;
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: group.sessionIds.length,
1170
- requests: reqStats.requests || group.requests,
1171
- total_tokens: reqStats.total_tokens || group.totalTokens,
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: group.lastActive
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 COALESCE(SUM(monthly_fee_usd), 0) as total
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.total, period);
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, COALESCE(SUM(cost_usd), 0) as api_eq
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
- saved_usd: row.api_eq
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: includedRow.total,
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
  });
@@ -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 where = sessionPeriodWhere(period);
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 ${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, totalCost: 0, lastActive: "" };
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
- FROM requests WHERE session_id IN (${placeholders})
1090
- `).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
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: g.sessionIds.length,
1095
- requests: reqStats.requests,
1096
- total_tokens: reqStats.total_tokens,
1097
- cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
1098
- last_active: g.lastActive
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 sWhere = sessionPeriodWhere(period);
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 ${sWhere}
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
- FROM requests WHERE session_id IN (${placeholders})
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 hasRequestCosts = reqStats.requests > 0;
1160
- const apiEquivalentUsd = hasRequestCosts ? reqStats.cost_usd : group.totalCost;
1161
- const estimatedUsd = hasRequestCosts ? reqStats.estimated_usd : group.totalCost;
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: group.sessionIds.length,
1170
- requests: reqStats.requests || group.requests,
1171
- total_tokens: reqStats.total_tokens || group.totalTokens,
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: group.lastActive
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 COALESCE(SUM(monthly_fee_usd), 0) as total
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.total, period);
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, COALESCE(SUM(cost_usd), 0) as api_eq
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
- saved_usd: row.api_eq
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: includedRow.total,
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, { agent: agent && isAgent(agent) ? agent : undefined, since }),
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;AAsC7D,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,CAgW/D;AAED,wBAAgB,WAAW,CAAC,IAAI,SAAO,EAAE,OAAO,GAAE,kBAAuB,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAcvG"}
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.23",
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",