@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/README.md CHANGED
@@ -1,15 +1,16 @@
1
1
  # @hasna/economy
2
2
 
3
- AI coding cost tracker for Claude Code, Takumi, Codex, and Gemini. It ships as a CLI, MCP server, REST API, web dashboard, and native macOS menu bar app.
3
+ AI coding cost tracker for Claude Code, Takumi, Codex, Gemini, OpenCode, Cursor, Pi, and Hermes. It ships as a CLI, MCP server, REST API, web dashboard, and native macOS menu bar app.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@hasna/economy)](https://www.npmjs.com/package/@hasna/economy)
6
6
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
7
7
 
8
8
  ## Features
9
9
 
10
- - Ingests local Claude Code, Takumi, Codex, and Gemini CLI usage.
10
+ - Ingests local Claude Code, Takumi, Codex, Gemini, OpenCode, Cursor, Pi, and Hermes usage.
11
11
  - Tracks sessions, requests, projects, machines, models, cache tokens, budgets, goals, and provider billing.
12
12
  - Attributes usage to `@hasna/accounts` profiles when agents run under managed account/profile config dirs.
13
+ - Breaks down API-equivalent, metered API, subscription-included, estimated, and unknown cost by account and coding agent.
13
14
  - Seeds editable model pricing with input, output, cache-read, 5-minute cache-write, 1-hour cache-write, and context-cache storage rates.
14
15
  - Handles tiered pricing such as Gemini long-prompt rates and OpenAI long-context rates.
15
16
  - Reconciles estimates against Anthropic, OpenAI, and Gemini billing sources.
@@ -70,7 +71,7 @@ Gemini settings:
70
71
  }
71
72
  ```
72
73
 
73
- The MCP server exposes read tools for summaries, sessions, machines, pricing, daily spend, budgets, goals, and provider billing. It also exposes mutation tools for budgets, pricing rows, and goals so Claude Code, Codex, and Gemini can manage Economy data through the same validated surface as the CLI and REST API.
74
+ The MCP server exposes read tools for summaries, sessions, machines, pricing, daily spend, budgets, goals, provider billing, usage snapshots, savings, project/account/agent breakdowns, and subscriptions. It also exposes mutation tools for budgets, pricing rows, goals, and subscriptions so coding agents can manage Economy data through the same validated surface as the CLI and REST API.
74
75
 
75
76
  ## Ingest
76
77
 
@@ -87,6 +88,10 @@ economy sync --claude
87
88
  economy sync --codex
88
89
  economy sync --gemini
89
90
  economy sync --takumi
91
+ economy sync --opencode
92
+ economy sync --cursor
93
+ economy sync --pi
94
+ economy sync --hermes
90
95
  ```
91
96
 
92
97
  Useful repair options:
@@ -103,6 +108,15 @@ Account attribution is automatic when `@hasna/accounts` has a matching active, a
103
108
 
104
109
  Account breakdowns report `api_equivalent_usd` for the API list-price value of the usage, plus `billable_usd`/`metered_api_usd` for known direct API spend and `subscription_included_usd` for usage covered by a subscription.
105
110
 
111
+ Subscription plans can be configured locally and are used by savings calculations:
112
+
113
+ ```bash
114
+ economy subscriptions set --provider cursor --plan pro --fee 20 --included 20 --agent cursor
115
+ economy subscriptions list
116
+ economy savings month
117
+ economy usage month --agent cursor
118
+ ```
119
+
106
120
  ## Pricing
107
121
 
108
122
  Default pricing is seeded into SQLite and can be edited locally:
@@ -150,7 +164,7 @@ economy config set webhook-url https://example.com/economy-webhook
150
164
  economy config webhook-test
151
165
  ```
152
166
 
153
- Budgets and goals can be global, project-scoped with `--project`, agent-scoped with `--agent`, or both. Valid agent scopes are `claude`, `takumi`, `codex`, and `gemini`.
167
+ Budgets and goals can be global, project-scoped with `--project`, agent-scoped with `--agent`, or both. Valid agent scopes are `claude`, `takumi`, `codex`, `gemini`, `opencode`, `cursor`, `pi`, and `hermes`.
154
168
 
155
169
  Budget webhooks fire after sync when the alert threshold is crossed. Failed webhook deliveries are not marked as fired, so the next sync can retry them.
156
170
 
@@ -169,7 +183,14 @@ Common endpoints:
169
183
  - `GET /api/sessions?agent=codex&limit=20`
170
184
  - `GET /api/sessions/:id/requests`
171
185
  - `GET /api/models`
172
- - `GET /api/projects`
186
+ - `GET /api/projects?period=month`
187
+ - `GET /api/breakdown?by=agent&period=month`
188
+ - `GET /api/accounts?period=month`
189
+ - `GET /api/usage?period=month`
190
+ - `GET /api/savings?period=month`
191
+ - `GET /api/subscriptions`
192
+ - `POST /api/subscriptions`
193
+ - `DELETE /api/subscriptions/:id`
173
194
  - `GET /api/budgets`
174
195
  - `POST /api/budgets`
175
196
  - `DELETE /api/budgets/:id`
@@ -183,13 +204,13 @@ Common endpoints:
183
204
  - `POST /api/sync`
184
205
  - `POST /api/billing/sync`
185
206
 
186
- Budget and goal mutation endpoints validate agent scopes against `claude`, `takumi`, `codex`, and `gemini`.
207
+ Budget, goal, and subscription mutation endpoints validate agent scopes against `claude`, `takumi`, `codex`, `gemini`, `opencode`, `cursor`, `pi`, and `hermes`.
187
208
 
188
209
  The server also serves the built dashboard when `dashboard/dist` is present.
189
210
 
190
211
  ## Native macOS Menubar
191
212
 
192
- The `menubar/` app is a native SwiftUI `MenuBarExtra` app, not Electron. It targets Swift 5.9+ and macOS 14+, and talks to the REST API exposed by `economy-serve`. The default server URL is `http://127.0.0.1:3456`.
213
+ The `menubar/` app is a native SwiftUI `MenuBarExtra` app, not Electron. It targets Swift 5.9+ and macOS 14+, and talks to the REST API exposed by `economy-serve`. It shows today/week/month spend, token and request counts, top agents, top accounts, top projects, subscription savings, usage snapshots, recent sessions, and fleet status. The default server URL is `http://127.0.0.1:3456`.
193
214
 
194
215
  Build it on macOS:
195
216
 
@@ -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
  }
@@ -4574,6 +4687,50 @@ function createHandler(db) {
4574
4687
  const agent = url.searchParams.get("agent") ?? undefined;
4575
4688
  return ok(querySavingsSummary(db, period, agent && isAgent(agent) ? agent : undefined));
4576
4689
  }
4690
+ if (path === "/api/subscriptions" && method === "GET") {
4691
+ return ok(listSubscriptions(db));
4692
+ }
4693
+ if (path === "/api/subscriptions" && method === "POST") {
4694
+ const body = await jsonBody(req);
4695
+ if (!body)
4696
+ return err("invalid JSON body");
4697
+ const provider = optionalString(body["provider"])?.trim();
4698
+ const plan = optionalString(body["plan"])?.trim();
4699
+ if (!provider)
4700
+ return err("provider is required");
4701
+ if (!plan)
4702
+ return err("plan is required");
4703
+ const monthlyFee = finiteNumber(body["monthly_fee_usd"] ?? body["fee_usd"] ?? 0);
4704
+ const includedUsage = finiteNumber(body["included_usage_usd"] ?? 0);
4705
+ if (monthlyFee == null || monthlyFee < 0)
4706
+ return err("monthly_fee_usd must be a non-negative number");
4707
+ if (includedUsage == null || includedUsage < 0)
4708
+ return err("included_usage_usd must be a non-negative number");
4709
+ const agent = optionalAgent(body["agent"]);
4710
+ if (agent === undefined)
4711
+ return err(AGENT_ERROR);
4712
+ const now = new Date().toISOString();
4713
+ const subscription = {
4714
+ id: optionalString(body["id"])?.trim() || randomUUID2(),
4715
+ agent,
4716
+ provider,
4717
+ plan,
4718
+ monthly_fee_usd: monthlyFee,
4719
+ included_usage_usd: includedUsage,
4720
+ billing_cycle_start: optionalString(body["billing_cycle_start"]),
4721
+ reset_policy: optionalString(body["reset_policy"]) ?? "monthly",
4722
+ active: body["active"] === false || body["active"] === 0 ? 0 : 1,
4723
+ created_at: optionalString(body["created_at"]) ?? now,
4724
+ updated_at: now
4725
+ };
4726
+ upsertSubscription(db, subscription);
4727
+ return ok(subscription);
4728
+ }
4729
+ const subscriptionMatch = path.match(/^\/api\/subscriptions\/(.+)$/);
4730
+ if (subscriptionMatch && method === "DELETE") {
4731
+ deleteSubscription(db, decodeURIComponent(subscriptionMatch[1]));
4732
+ return ok({ ok: true });
4733
+ }
4577
4734
  const sessionRequestsMatch = path.match(/^\/api\/sessions\/([^/]+)\/requests$/);
4578
4735
  if (sessionRequestsMatch && method === "GET") {
4579
4736
  const sessionId = decodeURIComponent(sessionRequestsMatch[1]);
@@ -6135,8 +6292,7 @@ function registerExtendedCommands(program) {
6135
6292
  const db = openDatabase();
6136
6293
  const period = parsePeriod(periodArg, "month");
6137
6294
  const agent = parseAgent(opts.agent, "--agent");
6138
- 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;
6139
- const snaps = queryUsageSnapshots(db, { agent, since });
6295
+ const snaps = queryUsageSnapshots(db, { agent, ...usageSnapshotFilterForPeriod(period) });
6140
6296
  const summary = querySummary(db, period, undefined, true);
6141
6297
  if (opts.json) {
6142
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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"}