@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/README.md CHANGED
@@ -106,6 +106,12 @@ Full sync also imports active project metadata from `@hasna/projects` when the r
106
106
 
107
107
  Account attribution is automatic when `@hasna/accounts` has a matching active, applied, or env-dir profile for the agent. You can also force attribution for a process with `ECONOMY_ACCOUNT=tool:name` or agent-specific overrides such as `ECONOMY_CODEX_ACCOUNT=codex:work`.
108
108
 
109
+ Session drilldown can be scoped to an account key, account name, or email:
110
+
111
+ ```bash
112
+ economy sessions --account work@example.com
113
+ ```
114
+
109
115
  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.
110
116
 
111
117
  Subscription plans can be configured locally and are used by savings calculations:
@@ -180,7 +186,7 @@ Common endpoints:
180
186
 
181
187
  - `GET /health`
182
188
  - `GET /api/summary?period=today`
183
- - `GET /api/sessions?agent=codex&limit=20`
189
+ - `GET /api/sessions?agent=codex&account=work@example.com&limit=20`
184
190
  - `GET /api/sessions/:id/requests`
185
191
  - `GET /api/models`
186
192
  - `GET /api/projects?period=month`
@@ -206,11 +212,11 @@ Common endpoints:
206
212
 
207
213
  Budget, goal, and subscription mutation endpoints validate agent scopes against `claude`, `takumi`, `codex`, `gemini`, `opencode`, `cursor`, `pi`, and `hermes`.
208
214
 
209
- The server also serves the built dashboard when `dashboard/dist` is present.
215
+ The server also serves the built dashboard when `dashboard/dist` is present. The dashboard includes account-scoped session filtering, subscription plan create/update/delete controls in Savings, and savings/usage/account tables for subscription-aware cost analysis.
210
216
 
211
217
  ## Native macOS Menubar
212
218
 
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`.
219
+ 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, active subscription plans, subscription savings, multi-agent usage snapshots, recent sessions, and fleet status. The default server URL is `http://127.0.0.1:3456`.
214
220
 
215
221
  Build it on macOS:
216
222
 
@@ -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));
@@ -6678,7 +6790,7 @@ program.command("month").description("Cost summary for this month").action(async
6678
6790
  await autoSync();
6679
6791
  printSummary("This Month", "month");
6680
6792
  });
6681
- program.command("sessions").description("List coding sessions with costs").option("--agent <agent>", "Filter by agent (claude|takumi|codex|gemini)").option("--project <path>", "Filter by project path").option("--machine <id>", "Filter by machine hostname (e.g. spark01, apple01)").option("--limit <n>", "Number of sessions", "20").option("--format <fmt>", "Output format: table|compact|csv|json", "table").option("--since <date>", "Filter sessions since date or relative (e.g. 2026-03-01, 7d, 30d)").option("--search <query>", "Search by project name, session id prefix, or agent").action(async (opts) => {
6793
+ program.command("sessions").description("List coding sessions with costs").option("--agent <agent>", "Filter by agent (claude|takumi|codex|gemini)").option("--project <path>", "Filter by project path").option("--account <query>", "Filter by account key, name, or email").option("--machine <id>", "Filter by machine hostname (e.g. spark01, apple01)").option("--limit <n>", "Number of sessions", "20").option("--format <fmt>", "Output format: table|compact|csv|json", "table").option("--since <date>", "Filter sessions since date or relative (e.g. 2026-03-01, 7d, 30d)").option("--search <query>", "Search by project name, session id prefix, or agent").action(async (opts) => {
6682
6794
  const limit = parsePositiveCliInteger(opts.limit ?? "20", "--limit");
6683
6795
  const agent = parseOptionalCliAgent(opts.agent);
6684
6796
  await autoSync();
@@ -6687,6 +6799,7 @@ program.command("sessions").description("List coding sessions with costs").optio
6687
6799
  let sessions = querySessions(db, {
6688
6800
  agent,
6689
6801
  project: opts.project,
6802
+ account: opts.account,
6690
6803
  machine: opts.machine,
6691
6804
  limit,
6692
6805
  since: sinceDate,
@@ -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"}