@hasna/economy 0.2.23 → 0.2.25

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/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();
@@ -3339,7 +3450,7 @@ var TOOL_NAMES = [
3339
3450
  ];
3340
3451
  var TOOL_DESCRIPTIONS = {
3341
3452
  get_cost_summary: "period(today|week|month|year|all), machine?(hostname) -> {total_usd, sessions, requests, tokens, summary}",
3342
- get_sessions: `agent(${AGENTS.join("|")}), project(partial), machine?(hostname), limit(20) -> compact session table`,
3453
+ get_sessions: `agent(${AGENTS.join("|")}), project(partial), account?(key/name/email), machine?(hostname), limit(20) -> compact session table`,
3343
3454
  get_top_sessions: `n(10), agent(${AGENTS.join("|")}) -> top sessions by cost`,
3344
3455
  list_machines: "no params -> machine_id, sessions, requests, cost, last_active",
3345
3456
  get_model_breakdown: "no params -> model, requests, tokens, cost",
@@ -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`,
@@ -3413,15 +3524,17 @@ server.tool("get_cost_summary", "Cost summary (total_usd, sessions, requests, to
3413
3524
  ].join(`
3414
3525
  `));
3415
3526
  });
3416
- server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, machine, limit(20)", {
3527
+ server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, account, machine, limit(20)", {
3417
3528
  agent: z.enum(AGENTS).optional(),
3418
3529
  project: z.string().optional(),
3530
+ account: z.string().optional(),
3419
3531
  machine: z.string().optional(),
3420
3532
  limit: z.number().int().positive().max(100).optional()
3421
- }, async ({ agent, project, machine, limit }) => {
3533
+ }, async ({ agent, project, account, machine, limit }) => {
3422
3534
  const sessions = querySessions(db, {
3423
3535
  agent,
3424
3536
  project,
3537
+ account,
3425
3538
  machine,
3426
3539
  limit: limit ?? 20
3427
3540
  });
@@ -3611,9 +3724,9 @@ server.tool("sync", `Ingest new cost data. sources: all|${AGENTS.join("|")}`, {
3611
3724
  const result = await syncAll(db, opts);
3612
3725
  return text(JSON.stringify(result, null, 2));
3613
3726
  });
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 }) => {
3727
+ 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
3728
  const p = period ?? "month";
3616
- const snaps = queryUsageSnapshots(db, { agent });
3729
+ const snaps = queryUsageSnapshots(db, { agent, ...usageSnapshotFilterForPeriod(p) });
3617
3730
  const summary = querySummary(db, p, undefined, true);
3618
3731
  return text(JSON.stringify({ snapshots: snaps, summary }, null, 2));
3619
3732
  });