@hasna/economy 0.2.22 → 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/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);
@@ -1343,6 +1372,12 @@ function upsertSubscription(db, sub) {
1343
1372
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1344
1373
  `).run(sub.id, sub.agent, sub.provider, sub.plan, sub.monthly_fee_usd, sub.included_usage_usd, sub.billing_cycle_start, sub.reset_policy, sub.active, sub.created_at, sub.updated_at);
1345
1374
  }
1375
+ function listSubscriptions(db) {
1376
+ return db.prepare(`SELECT * FROM subscriptions ORDER BY provider, plan`).all();
1377
+ }
1378
+ function deleteSubscription(db, id) {
1379
+ db.prepare(`DELETE FROM subscriptions WHERE id = ?`).run(id);
1380
+ }
1346
1381
  function upsertUsageSnapshot(db, snap) {
1347
1382
  const now = snap.updated_at ?? new Date().toISOString();
1348
1383
  const id = snap.id ?? `${snap.agent}-${snap.date}-${snap.metric}-${snap.machine_id}`;
@@ -1626,6 +1661,10 @@ function prorateMonthlyFee(monthlyFee, period) {
1626
1661
  return monthlyFee;
1627
1662
  }
1628
1663
  }
1664
+ function proratedIncludedConsumed(includedUsage, includedCap, period) {
1665
+ const cap = prorateMonthlyFee(includedCap, period);
1666
+ return cap > 0 ? Math.min(includedUsage, cap) : includedUsage;
1667
+ }
1629
1668
  function computeSavedUsd(apiEquivalent, onDemand, subscriptionFee) {
1630
1669
  return Math.max(0, apiEquivalent - onDemand - subscriptionFee);
1631
1670
  }
@@ -1653,24 +1692,74 @@ function querySavingsSummary(db, period, agent) {
1653
1692
  AND metric = 'on_demand_usd'
1654
1693
  `).get(...params);
1655
1694
  const subs = db.prepare(`
1656
- 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
1657
1698
  FROM subscriptions
1658
- WHERE active = 1${agent ? " AND agent = ?" : ""}
1699
+ WHERE active = 1${agent ? " AND (agent = ? OR agent IS NULL)" : ""}
1659
1700
  `).get(...agent ? [agent] : []);
1660
- const subscriptionFee = prorateMonthlyFee(subs.total, period);
1701
+ const subscriptionFee = prorateMonthlyFee(subs.fee, period);
1661
1702
  const apiEquivalent = apiRow.total + includedRow.total;
1703
+ const includedConsumed = proratedIncludedConsumed(includedRow.total, subs.included, period);
1662
1704
  const onDemand = onDemandRow.total;
1663
1705
  const saved = computeSavedUsd(apiEquivalent, onDemand, subscriptionFee);
1664
1706
  const byAgent = {};
1665
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;
1719
+ for (const row of db.prepare(`
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);
1666
1744
  for (const row of db.prepare(`
1667
- SELECT agent, COALESCE(SUM(cost_usd), 0) as api_eq
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
1668
1748
  FROM requests WHERE ${where}
1669
1749
  GROUP BY agent
1670
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;
1671
1757
  byAgent[row.agent] = {
1672
1758
  api_equivalent_usd: row.api_eq,
1673
- 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)
1674
1763
  };
1675
1764
  }
1676
1765
  }
@@ -1678,7 +1767,7 @@ function querySavingsSummary(db, period, agent) {
1678
1767
  period,
1679
1768
  api_equivalent_usd: apiEquivalent,
1680
1769
  subscription_fee_usd: subscriptionFee,
1681
- included_consumed_usd: includedRow.total,
1770
+ included_consumed_usd: includedConsumed,
1682
1771
  on_demand_usd: onDemand,
1683
1772
  saved_usd: saved,
1684
1773
  by_agent: byAgent
@@ -3266,6 +3355,34 @@ var AGENTS = [
3266
3355
  "hermes"
3267
3356
  ];
3268
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
+
3269
3386
  // src/mcp/index.ts
3270
3387
  init_database();
3271
3388
  init_pricing();
@@ -3314,6 +3431,9 @@ var TOOL_NAMES = [
3314
3431
  "get_session_detail",
3315
3432
  "get_usage",
3316
3433
  "get_savings",
3434
+ "list_subscriptions",
3435
+ "set_subscription",
3436
+ "remove_subscription",
3317
3437
  "estimate_cost",
3318
3438
  "sync",
3319
3439
  "search_tools",
@@ -3346,8 +3466,11 @@ var TOOL_DESCRIPTIONS = {
3346
3466
  get_daily: "days(30) -> daily cost table grouped by date and agent",
3347
3467
  get_billing_summary: "period(today|yesterday|week|month|year|all) -> actual provider billing totals",
3348
3468
  get_session_detail: "session_id(prefix ok) -> per-request breakdown with model, tokens, cost",
3349
- 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`,
3350
3470
  get_savings: `period(today|week|month|year|all), agent?(${AGENTS.join("|")}) -> subscription/API-equivalent savings`,
3471
+ list_subscriptions: "no params -> configured subscription plans and included usage",
3472
+ set_subscription: `provider, plan, monthly_fee_usd?, included_usage_usd?, agent?(${AGENTS.join("|")}) -> create/update subscription plan`,
3473
+ remove_subscription: "id -> delete subscription plan",
3351
3474
  estimate_cost: "model, input_tokens?, output_tokens? -> pre-flight token cost estimate",
3352
3475
  sync: `sources(all|${AGENTS.join("|")}) -> ingest latest cost data`,
3353
3476
  search_tools: "query substring -> tool name list",
@@ -3599,15 +3722,66 @@ server.tool("sync", `Ingest new cost data. sources: all|${AGENTS.join("|")}`, {
3599
3722
  const result = await syncAll(db, opts);
3600
3723
  return text(JSON.stringify(result, null, 2));
3601
3724
  });
3602
- 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 }) => {
3603
3726
  const p = period ?? "month";
3604
- const snaps = queryUsageSnapshots(db, { agent });
3727
+ const snaps = queryUsageSnapshots(db, { agent, ...usageSnapshotFilterForPeriod(p) });
3605
3728
  const summary = querySummary(db, p, undefined, true);
3606
3729
  return text(JSON.stringify({ snapshots: snaps, summary }, null, 2));
3607
3730
  });
3608
3731
  server.tool("get_savings", "Subscription vs API savings summary", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), agent: z.enum(AGENTS).optional() }, async ({ period, agent }) => {
3609
3732
  return text(JSON.stringify(querySavingsSummary(db, period ?? "month", agent), null, 2));
3610
3733
  });
3734
+ server.tool("list_subscriptions", "List configured subscription plans and included usage caps. No params.", {}, async () => {
3735
+ const rows = listSubscriptions(db);
3736
+ if (rows.length === 0)
3737
+ return text("No subscriptions configured.");
3738
+ const lines = ["id provider plan agent fee included active"];
3739
+ for (const row of rows) {
3740
+ lines.push(`${String(row["id"]).slice(0, 8).padEnd(9)}` + `${String(row["provider"]).slice(0, 12).padEnd(13)}` + `${String(row["plan"]).slice(0, 10).padEnd(11)}` + `${String(row["agent"] ?? "all").slice(0, 10).padEnd(11)}` + `${fmtUsd(Number(row["monthly_fee_usd"] ?? 0)).padEnd(10)}` + `${fmtUsd(Number(row["included_usage_usd"] ?? 0)).padEnd(10)}` + `${Number(row["active"] ?? 0) ? "yes" : "no"}`);
3741
+ }
3742
+ return text(lines.join(`
3743
+ `));
3744
+ });
3745
+ server.tool("set_subscription", `Create or update a subscription plan. agent may be ${AGENTS.join("|")}.`, {
3746
+ id: z.string().optional(),
3747
+ provider: z.string(),
3748
+ plan: z.string(),
3749
+ agent: z.enum(AGENTS).optional(),
3750
+ monthly_fee_usd: z.number().optional(),
3751
+ included_usage_usd: z.number().optional(),
3752
+ billing_cycle_start: z.string().optional(),
3753
+ reset_policy: z.string().optional(),
3754
+ active: z.boolean().optional()
3755
+ }, async (input) => {
3756
+ if (input.monthly_fee_usd != null && input.monthly_fee_usd < 0)
3757
+ return text("monthly_fee_usd must be non-negative");
3758
+ if (input.included_usage_usd != null && input.included_usage_usd < 0)
3759
+ return text("included_usage_usd must be non-negative");
3760
+ const now = new Date().toISOString();
3761
+ const subscription = {
3762
+ id: input.id?.trim() || randomUUID(),
3763
+ agent: input.agent ?? null,
3764
+ provider: input.provider.trim(),
3765
+ plan: input.plan.trim(),
3766
+ monthly_fee_usd: input.monthly_fee_usd ?? 0,
3767
+ included_usage_usd: input.included_usage_usd ?? 0,
3768
+ billing_cycle_start: input.billing_cycle_start ?? null,
3769
+ reset_policy: input.reset_policy ?? "monthly",
3770
+ active: input.active === false ? 0 : 1,
3771
+ created_at: now,
3772
+ updated_at: now
3773
+ };
3774
+ if (!subscription.provider)
3775
+ return text("provider is required");
3776
+ if (!subscription.plan)
3777
+ return text("plan is required");
3778
+ upsertSubscription(db, subscription);
3779
+ return text(JSON.stringify(subscription, null, 2));
3780
+ });
3781
+ server.tool("remove_subscription", "Remove a subscription plan by id.", { id: z.string() }, async ({ id }) => {
3782
+ deleteSubscription(db, id);
3783
+ return text(`Removed subscription ${id}`);
3784
+ });
3611
3785
  server.tool("estimate_cost", "Pre-flight cost estimate for token counts", { model: z.string(), input_tokens: z.number().optional(), output_tokens: z.number().optional() }, async ({ model, input_tokens, output_tokens }) => {
3612
3786
  const cost = computeCostFromDb(db, model, input_tokens ?? 0, output_tokens ?? 0, 0, 0, 0);
3613
3787
  return text(`${model}: ${fmtUsd(cost)} (${input_tokens ?? 0} in / ${output_tokens ?? 0} out)`);