@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 +28 -7
- package/dist/cli/commands/extras.d.ts.map +1 -1
- package/dist/cli/index.js +204 -48
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +67 -38
- package/dist/lib/periods.d.ts +6 -0
- package/dist/lib/periods.d.ts.map +1 -0
- package/dist/lib/savings.d.ts.map +1 -1
- package/dist/mcp/index.js +221 -47
- package/dist/server/index.js +209 -46
- package/dist/server/serve.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/server/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
|
|
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
|
|
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
|
|
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
|
-
|
|
1090
|
-
|
|
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:
|
|
1095
|
-
requests: reqStats.requests,
|
|
1096
|
-
total_tokens: reqStats.total_tokens,
|
|
1097
|
-
cost_usd: reqStats.cost_usd
|
|
1098
|
-
last_active:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
1160
|
-
|
|
1161
|
-
|
|
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:
|
|
1170
|
-
requests: reqStats.requests
|
|
1171
|
-
total_tokens: reqStats.total_tokens
|
|
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:
|
|
1208
|
+
last_active: lastActive
|
|
1180
1209
|
});
|
|
1181
1210
|
}
|
|
1182
1211
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
@@ -1364,6 +1393,12 @@ function upsertSubscription(db, sub) {
|
|
|
1364
1393
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1365
1394
|
`).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);
|
|
1366
1395
|
}
|
|
1396
|
+
function listSubscriptions(db) {
|
|
1397
|
+
return db.prepare(`SELECT * FROM subscriptions ORDER BY provider, plan`).all();
|
|
1398
|
+
}
|
|
1399
|
+
function deleteSubscription(db, id) {
|
|
1400
|
+
db.prepare(`DELETE FROM subscriptions WHERE id = ?`).run(id);
|
|
1401
|
+
}
|
|
1367
1402
|
function upsertUsageSnapshot(db, snap) {
|
|
1368
1403
|
const now = snap.updated_at ?? new Date().toISOString();
|
|
1369
1404
|
const id = snap.id ?? `${snap.agent}-${snap.date}-${snap.metric}-${snap.machine_id}`;
|
|
@@ -2053,6 +2088,10 @@ function prorateMonthlyFee(monthlyFee, period) {
|
|
|
2053
2088
|
return monthlyFee;
|
|
2054
2089
|
}
|
|
2055
2090
|
}
|
|
2091
|
+
function proratedIncludedConsumed(includedUsage, includedCap, period) {
|
|
2092
|
+
const cap = prorateMonthlyFee(includedCap, period);
|
|
2093
|
+
return cap > 0 ? Math.min(includedUsage, cap) : includedUsage;
|
|
2094
|
+
}
|
|
2056
2095
|
function computeSavedUsd(apiEquivalent, onDemand, subscriptionFee) {
|
|
2057
2096
|
return Math.max(0, apiEquivalent - onDemand - subscriptionFee);
|
|
2058
2097
|
}
|
|
@@ -2080,24 +2119,74 @@ function querySavingsSummary(db, period, agent) {
|
|
|
2080
2119
|
AND metric = 'on_demand_usd'
|
|
2081
2120
|
`).get(...params);
|
|
2082
2121
|
const subs = db.prepare(`
|
|
2083
|
-
SELECT
|
|
2122
|
+
SELECT
|
|
2123
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
2124
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
2084
2125
|
FROM subscriptions
|
|
2085
|
-
WHERE active = 1${agent ? " AND agent = ?" : ""}
|
|
2126
|
+
WHERE active = 1${agent ? " AND (agent = ? OR agent IS NULL)" : ""}
|
|
2086
2127
|
`).get(...agent ? [agent] : []);
|
|
2087
|
-
const subscriptionFee = prorateMonthlyFee(subs.
|
|
2128
|
+
const subscriptionFee = prorateMonthlyFee(subs.fee, period);
|
|
2088
2129
|
const apiEquivalent = apiRow.total + includedRow.total;
|
|
2130
|
+
const includedConsumed = proratedIncludedConsumed(includedRow.total, subs.included, period);
|
|
2089
2131
|
const onDemand = onDemandRow.total;
|
|
2090
2132
|
const saved = computeSavedUsd(apiEquivalent, onDemand, subscriptionFee);
|
|
2091
2133
|
const byAgent = {};
|
|
2092
2134
|
if (!agent) {
|
|
2135
|
+
const onDemandByAgent = new Map;
|
|
2136
|
+
for (const row of db.prepare(`
|
|
2137
|
+
SELECT agent, COALESCE(SUM(value), 0) as total
|
|
2138
|
+
FROM usage_snapshots
|
|
2139
|
+
WHERE ${subWhere}
|
|
2140
|
+
AND metric = 'on_demand_usd'
|
|
2141
|
+
GROUP BY agent
|
|
2142
|
+
`).all()) {
|
|
2143
|
+
onDemandByAgent.set(row.agent, row.total);
|
|
2144
|
+
}
|
|
2145
|
+
const subscriptionByAgent = new Map;
|
|
2146
|
+
for (const row of db.prepare(`
|
|
2147
|
+
SELECT agent,
|
|
2148
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
2149
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
2150
|
+
FROM subscriptions
|
|
2151
|
+
WHERE active = 1 AND agent IS NOT NULL
|
|
2152
|
+
GROUP BY agent
|
|
2153
|
+
`).all()) {
|
|
2154
|
+
subscriptionByAgent.set(row.agent, row);
|
|
2155
|
+
}
|
|
2156
|
+
const globalSubs = db.prepare(`
|
|
2157
|
+
SELECT
|
|
2158
|
+
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
2159
|
+
COALESCE(SUM(included_usage_usd), 0) as included
|
|
2160
|
+
FROM subscriptions
|
|
2161
|
+
WHERE active = 1 AND agent IS NULL
|
|
2162
|
+
`).get();
|
|
2163
|
+
const rows = db.prepare(`
|
|
2164
|
+
SELECT agent,
|
|
2165
|
+
COALESCE(SUM(cost_usd), 0) as api_eq,
|
|
2166
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as included
|
|
2167
|
+
FROM requests WHERE ${where}
|
|
2168
|
+
GROUP BY agent
|
|
2169
|
+
`).all();
|
|
2170
|
+
const totalAgentApiEq = rows.reduce((sum, row) => sum + row.api_eq, 0);
|
|
2093
2171
|
for (const row of db.prepare(`
|
|
2094
|
-
SELECT agent,
|
|
2172
|
+
SELECT agent,
|
|
2173
|
+
COALESCE(SUM(cost_usd), 0) as api_eq,
|
|
2174
|
+
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as included
|
|
2095
2175
|
FROM requests WHERE ${where}
|
|
2096
2176
|
GROUP BY agent
|
|
2097
2177
|
`).all()) {
|
|
2178
|
+
const agentSubs = subscriptionByAgent.get(row.agent) ?? { fee: 0, included: 0 };
|
|
2179
|
+
const globalShare = totalAgentApiEq > 0 ? row.api_eq / totalAgentApiEq : 0;
|
|
2180
|
+
const agentFee = prorateMonthlyFee(agentSubs.fee + globalSubs.fee * globalShare, period);
|
|
2181
|
+
const agentIncludedCap = agentSubs.included + globalSubs.included * globalShare;
|
|
2182
|
+
const agentIncludedConsumed = proratedIncludedConsumed(row.included, agentIncludedCap, period);
|
|
2183
|
+
const agentOnDemand = onDemandByAgent.get(row.agent) ?? 0;
|
|
2098
2184
|
byAgent[row.agent] = {
|
|
2099
2185
|
api_equivalent_usd: row.api_eq,
|
|
2100
|
-
|
|
2186
|
+
subscription_fee_usd: agentFee,
|
|
2187
|
+
included_consumed_usd: agentIncludedConsumed,
|
|
2188
|
+
on_demand_usd: agentOnDemand,
|
|
2189
|
+
saved_usd: computeSavedUsd(row.api_eq, agentOnDemand, agentFee)
|
|
2101
2190
|
};
|
|
2102
2191
|
}
|
|
2103
2192
|
}
|
|
@@ -2105,7 +2194,7 @@ function querySavingsSummary(db, period, agent) {
|
|
|
2105
2194
|
period,
|
|
2106
2195
|
api_equivalent_usd: apiEquivalent,
|
|
2107
2196
|
subscription_fee_usd: subscriptionFee,
|
|
2108
|
-
included_consumed_usd:
|
|
2197
|
+
included_consumed_usd: includedConsumed,
|
|
2109
2198
|
on_demand_usd: onDemand,
|
|
2110
2199
|
saved_usd: saved,
|
|
2111
2200
|
by_agent: byAgent
|
|
@@ -3681,6 +3770,34 @@ async function syncAll(db, opts = {}) {
|
|
|
3681
3770
|
return result;
|
|
3682
3771
|
}
|
|
3683
3772
|
|
|
3773
|
+
// src/lib/periods.ts
|
|
3774
|
+
function ymd(date) {
|
|
3775
|
+
return date.toISOString().substring(0, 10);
|
|
3776
|
+
}
|
|
3777
|
+
function usageSnapshotFilterForPeriod(period) {
|
|
3778
|
+
const now = new Date;
|
|
3779
|
+
switch (period) {
|
|
3780
|
+
case "today":
|
|
3781
|
+
return { date: ymd(now) };
|
|
3782
|
+
case "yesterday": {
|
|
3783
|
+
const yesterday = new Date(now);
|
|
3784
|
+
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
|
|
3785
|
+
return { date: ymd(yesterday) };
|
|
3786
|
+
}
|
|
3787
|
+
case "week": {
|
|
3788
|
+
const weekAgo = new Date(now);
|
|
3789
|
+
weekAgo.setUTCDate(weekAgo.getUTCDate() - 7);
|
|
3790
|
+
return { since: ymd(weekAgo) };
|
|
3791
|
+
}
|
|
3792
|
+
case "month":
|
|
3793
|
+
return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))) };
|
|
3794
|
+
case "year":
|
|
3795
|
+
return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), 0, 1))) };
|
|
3796
|
+
case "all":
|
|
3797
|
+
return {};
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3684
3801
|
// src/lib/billing-diff.ts
|
|
3685
3802
|
init_database();
|
|
3686
3803
|
var PROVIDER_TO_AGENT = {
|
|
@@ -4101,9 +4218,11 @@ function createHandler(db) {
|
|
|
4101
4218
|
if (path === "/api/usage" && method === "GET") {
|
|
4102
4219
|
const period = url.searchParams.get("period") ?? "month";
|
|
4103
4220
|
const agent = url.searchParams.get("agent") ?? undefined;
|
|
4104
|
-
const since = period === "month" ? new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().substring(0, 10) : undefined;
|
|
4105
4221
|
return ok({
|
|
4106
|
-
snapshots: queryUsageSnapshots(db, {
|
|
4222
|
+
snapshots: queryUsageSnapshots(db, {
|
|
4223
|
+
agent: agent && isAgent(agent) ? agent : undefined,
|
|
4224
|
+
...usageSnapshotFilterForPeriod(period)
|
|
4225
|
+
}),
|
|
4107
4226
|
summary: querySummary(db, period, undefined, true)
|
|
4108
4227
|
});
|
|
4109
4228
|
}
|
|
@@ -4112,6 +4231,50 @@ function createHandler(db) {
|
|
|
4112
4231
|
const agent = url.searchParams.get("agent") ?? undefined;
|
|
4113
4232
|
return ok(querySavingsSummary(db, period, agent && isAgent(agent) ? agent : undefined));
|
|
4114
4233
|
}
|
|
4234
|
+
if (path === "/api/subscriptions" && method === "GET") {
|
|
4235
|
+
return ok(listSubscriptions(db));
|
|
4236
|
+
}
|
|
4237
|
+
if (path === "/api/subscriptions" && method === "POST") {
|
|
4238
|
+
const body = await jsonBody(req);
|
|
4239
|
+
if (!body)
|
|
4240
|
+
return err("invalid JSON body");
|
|
4241
|
+
const provider = optionalString(body["provider"])?.trim();
|
|
4242
|
+
const plan = optionalString(body["plan"])?.trim();
|
|
4243
|
+
if (!provider)
|
|
4244
|
+
return err("provider is required");
|
|
4245
|
+
if (!plan)
|
|
4246
|
+
return err("plan is required");
|
|
4247
|
+
const monthlyFee = finiteNumber(body["monthly_fee_usd"] ?? body["fee_usd"] ?? 0);
|
|
4248
|
+
const includedUsage = finiteNumber(body["included_usage_usd"] ?? 0);
|
|
4249
|
+
if (monthlyFee == null || monthlyFee < 0)
|
|
4250
|
+
return err("monthly_fee_usd must be a non-negative number");
|
|
4251
|
+
if (includedUsage == null || includedUsage < 0)
|
|
4252
|
+
return err("included_usage_usd must be a non-negative number");
|
|
4253
|
+
const agent = optionalAgent(body["agent"]);
|
|
4254
|
+
if (agent === undefined)
|
|
4255
|
+
return err(AGENT_ERROR);
|
|
4256
|
+
const now = new Date().toISOString();
|
|
4257
|
+
const subscription = {
|
|
4258
|
+
id: optionalString(body["id"])?.trim() || randomUUID(),
|
|
4259
|
+
agent,
|
|
4260
|
+
provider,
|
|
4261
|
+
plan,
|
|
4262
|
+
monthly_fee_usd: monthlyFee,
|
|
4263
|
+
included_usage_usd: includedUsage,
|
|
4264
|
+
billing_cycle_start: optionalString(body["billing_cycle_start"]),
|
|
4265
|
+
reset_policy: optionalString(body["reset_policy"]) ?? "monthly",
|
|
4266
|
+
active: body["active"] === false || body["active"] === 0 ? 0 : 1,
|
|
4267
|
+
created_at: optionalString(body["created_at"]) ?? now,
|
|
4268
|
+
updated_at: now
|
|
4269
|
+
};
|
|
4270
|
+
upsertSubscription(db, subscription);
|
|
4271
|
+
return ok(subscription);
|
|
4272
|
+
}
|
|
4273
|
+
const subscriptionMatch = path.match(/^\/api\/subscriptions\/(.+)$/);
|
|
4274
|
+
if (subscriptionMatch && method === "DELETE") {
|
|
4275
|
+
deleteSubscription(db, decodeURIComponent(subscriptionMatch[1]));
|
|
4276
|
+
return ok({ ok: true });
|
|
4277
|
+
}
|
|
4115
4278
|
const sessionRequestsMatch = path.match(/^\/api\/sessions\/([^/]+)\/requests$/);
|
|
4116
4279
|
if (sessionRequestsMatch && method === "GET") {
|
|
4117
4280
|
const sessionId = decodeURIComponent(sessionRequestsMatch[1]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAuC7D,UAAU,kBAAkB;IAC1B,EAAE,CAAC,EAAE,QAAQ,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;AAqED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAAE,YAAY,SAAwB,IACzF,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAwB7D;AAQD,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,IACV,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAkW/D;AAED,wBAAgB,WAAW,CAAC,IAAI,SAAO,EAAE,OAAO,GAAE,kBAAuB,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAcvG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"description": "AI coding cost tracker — CLI + MCP server + REST API + web dashboard for Claude Code, Codex, Gemini, OpenCode, Cursor, Pi, and Hermes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|