@hasna/economy 0.2.28 → 0.2.30

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
@@ -971,8 +971,10 @@ function queryModelBreakdown(db) {
971
971
  FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
972
972
  `).all();
973
973
  }
974
- function queryAgentBreakdown(db, period = "all") {
974
+ function queryAgentBreakdown(db, period = "all", machine) {
975
975
  const requestWhere = requestPeriodWhere(period);
976
+ const machineClause = machine ? " AND machine_id = ?" : "";
977
+ const machineParams = machine ? [machine] : [];
976
978
  const groups = new Map;
977
979
  const requestRows = db.prepare(`
978
980
  SELECT agent,
@@ -988,10 +990,10 @@ function queryAgentBreakdown(db, period = "all") {
988
990
  COALESCE(SUM(cost_usd), 0) as cost_usd,
989
991
  MAX(timestamp) as last_active
990
992
  FROM requests
991
- WHERE ${requestWhere}
993
+ WHERE ${requestWhere}${machineClause}
992
994
  GROUP BY agent
993
995
  ORDER BY api_equivalent_usd DESC
994
- `).all();
996
+ `).all(...machineParams);
995
997
  for (const row of requestRows) {
996
998
  groups.set(row.agent, row);
997
999
  }
@@ -1004,10 +1006,10 @@ function queryAgentBreakdown(db, period = "all") {
1004
1006
  COALESCE(SUM(total_cost_usd), 0) as cost_usd,
1005
1007
  MAX(started_at) as last_active
1006
1008
  FROM sessions
1007
- WHERE ${sessionWhere}
1009
+ WHERE ${sessionWhere}${machineClause}
1008
1010
  AND id NOT IN (SELECT DISTINCT session_id FROM requests)
1009
1011
  GROUP BY agent
1010
- `).all();
1012
+ `).all(...machineParams);
1011
1013
  for (const row of sessionOnlyRows) {
1012
1014
  const existing = groups.get(row.agent) ?? {
1013
1015
  agent: row.agent,
@@ -1084,14 +1086,20 @@ function labelForPath(projectPath, projectName) {
1084
1086
  function groupKeyForPath(projectPath, projectName) {
1085
1087
  return labelForPath(projectPath, projectName).trim().toLowerCase();
1086
1088
  }
1087
- function queryProjectBreakdown(db, period = "all") {
1089
+ function queryProjectBreakdown(db, period = "all", machine) {
1088
1090
  const requestWhere = requestPeriodWhere(period);
1089
1091
  const sessionWhere = sessionPeriodWhere(period);
1092
+ const sessionMachineClause = machine ? " AND (machine_id = ? OR id IN (SELECT DISTINCT session_id FROM requests WHERE machine_id = ?))" : "";
1093
+ const requestMachineClause = machine ? " AND machine_id = ?" : "";
1094
+ const sessionMachineParams = machine ? [machine, machine] : [];
1095
+ const requestMachineParams = machine ? [machine] : [];
1096
+ const sessionOnlyMachineClause = machine ? " AND machine_id = ?" : "";
1097
+ const sessionOnlyMachineParams = machine ? [machine] : [];
1090
1098
  const sessions = db.prepare(`
1091
1099
  SELECT id, project_path, project_name, total_cost_usd, started_at
1092
1100
  FROM sessions
1093
- WHERE project_path != '' OR project_name != ''
1094
- `).all();
1101
+ WHERE (project_path != '' OR project_name != '')${sessionMachineClause}
1102
+ `).all(...sessionMachineParams);
1095
1103
  const groups = new Map;
1096
1104
  for (const s of sessions) {
1097
1105
  const label = labelForPath(s.project_path, s.project_name);
@@ -1117,7 +1125,8 @@ function queryProjectBreakdown(db, period = "all") {
1117
1125
  FROM requests
1118
1126
  WHERE session_id IN (${placeholders})
1119
1127
  AND ${requestWhere}
1120
- `).get(...g.sessionIds) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
1128
+ ${requestMachineClause}
1129
+ `).get(...g.sessionIds, ...requestMachineParams) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
1121
1130
  const sessionOnlyStats = placeholders.length ? db.prepare(`
1122
1131
  SELECT
1123
1132
  COUNT(*) as sessions,
@@ -1128,8 +1137,9 @@ function queryProjectBreakdown(db, period = "all") {
1128
1137
  FROM sessions
1129
1138
  WHERE id IN (${placeholders})
1130
1139
  AND ${sessionWhere}
1140
+ ${sessionOnlyMachineClause}
1131
1141
  AND id NOT IN (SELECT DISTINCT session_id FROM requests)
1132
- `).get(...g.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
1142
+ `).get(...g.sessionIds, ...sessionOnlyMachineParams) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
1133
1143
  const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
1134
1144
  if (totalSessions === 0)
1135
1145
  continue;
@@ -1147,107 +1157,156 @@ function queryProjectBreakdown(db, period = "all") {
1147
1157
  result.sort((a, b) => b.cost_usd - a.cost_usd);
1148
1158
  return result;
1149
1159
  }
1150
- function queryAccountBreakdown(db, period = "all") {
1160
+ function normalizeAccountEmail(email) {
1161
+ return (email ?? "").trim().toLowerCase();
1162
+ }
1163
+ function accountIdentityKey(agent, accountKey, accountName, accountEmail) {
1164
+ const identityAgent = (agent || "").trim();
1165
+ const normalizedEmail = normalizeAccountEmail(accountEmail);
1166
+ if (identityAgent && normalizedEmail)
1167
+ return `${identityAgent}:${normalizedEmail}`;
1168
+ if (identityAgent && accountName)
1169
+ return `${identityAgent}:${accountName}`;
1170
+ if (accountKey)
1171
+ return accountKey;
1172
+ return identityAgent ? `${identityAgent}:unknown` : "";
1173
+ }
1174
+ function addAccountBreakdownRow(groups, row, sessionOnly) {
1175
+ const agent = row.agent || row.account_tool;
1176
+ const email = normalizeAccountEmail(row.account_email);
1177
+ const accountName = row.account_name || email || row.account_key;
1178
+ const key = accountIdentityKey(agent, row.account_key, accountName, email);
1179
+ if (!key)
1180
+ return;
1181
+ const group = groups.get(key) ?? {
1182
+ account_key: key,
1183
+ account_tool: agent,
1184
+ account_name: accountName,
1185
+ account_email: email || null,
1186
+ account_source: row.account_source || "unknown",
1187
+ sessionIds: new Set,
1188
+ requests: 0,
1189
+ total_tokens: 0,
1190
+ api_equivalent_usd: 0,
1191
+ metered_api_usd: 0,
1192
+ subscription_included_usd: 0,
1193
+ estimated_usd: 0,
1194
+ unknown_usd: 0,
1195
+ last_active: ""
1196
+ };
1197
+ if (!group.account_email && email)
1198
+ group.account_email = email;
1199
+ if (!group.account_name && accountName)
1200
+ group.account_name = accountName;
1201
+ if ((!group.account_source || group.account_source === "unknown") && row.account_source && row.account_source !== "unknown") {
1202
+ group.account_source = row.account_source;
1203
+ }
1204
+ if (row.session_id)
1205
+ group.sessionIds.add(row.session_id);
1206
+ group.requests += row.requests;
1207
+ group.total_tokens += row.total_tokens;
1208
+ group.api_equivalent_usd += row.cost_usd;
1209
+ if (sessionOnly) {
1210
+ group.estimated_usd += row.cost_usd;
1211
+ } else if (row.cost_basis === "metered_api") {
1212
+ group.metered_api_usd += row.cost_usd;
1213
+ } else if (row.cost_basis === "subscription_included") {
1214
+ group.subscription_included_usd += row.cost_usd;
1215
+ } else if (row.cost_basis === "unknown") {
1216
+ group.unknown_usd += row.cost_usd;
1217
+ } else {
1218
+ group.estimated_usd += row.cost_usd;
1219
+ }
1220
+ if (!group.last_active || row.last_active > group.last_active)
1221
+ group.last_active = row.last_active;
1222
+ groups.set(key, group);
1223
+ }
1224
+ function queryAccountBreakdown(db, period = "all", machine) {
1151
1225
  const requestWhere = requestPeriodWhere(period);
1152
1226
  const sessionWhere = sessionPeriodWhere(period);
1153
- const sessions = db.prepare(`
1154
- SELECT id, account_key, account_tool, account_name, account_email, account_source,
1155
- total_cost_usd, total_tokens, request_count, started_at
1156
- FROM sessions
1157
- WHERE account_key != '' OR account_tool != '' OR account_name != '' OR account_email != ''
1158
- `).all();
1227
+ const requestMachineClause = machine ? " AND r.machine_id = ?" : "";
1228
+ const sessionMachineClause = machine ? " AND s.machine_id = ?" : "";
1229
+ const requestMachineParams = machine ? [machine] : [];
1230
+ const sessionMachineParams = machine ? [machine] : [];
1159
1231
  const groups = new Map;
1160
- for (const session of sessions) {
1161
- const key = session.account_key || `${session.account_tool}:${session.account_name}`;
1162
- if (!key || key === ":")
1163
- continue;
1164
- const group = groups.get(key) ?? {
1165
- sessionIds: [],
1166
- account_tool: session.account_tool,
1167
- account_name: session.account_name,
1168
- account_email: session.account_email || null,
1169
- account_source: session.account_source || "unknown"
1170
- };
1171
- group.sessionIds.push(session.id);
1172
- groups.set(key, group);
1173
- }
1174
- const result = [];
1175
- for (const [key, group] of groups.entries()) {
1176
- const placeholders = group.sessionIds.map(() => "?").join(",");
1177
- const reqStats = placeholders ? db.prepare(`
1178
- SELECT
1179
- COUNT(DISTINCT session_id) as sessions,
1180
- COUNT(*) as requests,
1181
- COALESCE(SUM(cost_usd), 0) as cost_usd,
1182
- COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
1183
- COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
1184
- COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
1185
- COALESCE(SUM(CASE WHEN COALESCE(cost_basis, 'estimated') = 'estimated' THEN cost_usd ELSE 0 END), 0) as estimated_usd,
1186
- COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd,
1187
- MAX(timestamp) as last_active
1188
- FROM requests
1189
- WHERE session_id IN (${placeholders})
1190
- AND ${requestWhere}
1191
- `).get(...group.sessionIds) : {
1192
- sessions: 0,
1193
- requests: 0,
1194
- cost_usd: 0,
1195
- total_tokens: 0,
1196
- metered_api_usd: 0,
1197
- subscription_included_usd: 0,
1198
- estimated_usd: 0,
1199
- unknown_usd: 0,
1200
- last_active: null
1201
- };
1202
- const sessionOnlyStats = placeholders ? db.prepare(`
1203
- SELECT
1204
- COUNT(*) as sessions,
1205
- COALESCE(SUM(request_count), 0) as requests,
1206
- COALESCE(SUM(total_tokens), 0) as total_tokens,
1207
- COALESCE(SUM(total_cost_usd), 0) as cost_usd,
1208
- MAX(started_at) as last_active
1209
- FROM sessions
1210
- WHERE id IN (${placeholders})
1211
- AND ${sessionWhere}
1212
- AND id NOT IN (SELECT DISTINCT session_id FROM requests)
1213
- `).get(...group.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
1214
- const sessionsTotal = reqStats.sessions + sessionOnlyStats.sessions;
1215
- if (sessionsTotal === 0)
1216
- continue;
1217
- const apiEquivalentUsd = reqStats.cost_usd + sessionOnlyStats.cost_usd;
1218
- const estimatedUsd = reqStats.estimated_usd + sessionOnlyStats.cost_usd;
1219
- const billableUsd = reqStats.metered_api_usd;
1220
- const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
1221
- result.push({
1222
- account_key: key,
1223
- account_tool: group.account_tool,
1224
- account_name: group.account_name,
1225
- account_email: group.account_email,
1226
- account_source: group.account_source,
1227
- sessions: sessionsTotal,
1228
- requests: reqStats.requests + sessionOnlyStats.requests,
1229
- total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
1230
- api_equivalent_usd: apiEquivalentUsd,
1231
- billable_usd: billableUsd,
1232
- metered_api_usd: reqStats.metered_api_usd,
1233
- subscription_included_usd: reqStats.subscription_included_usd,
1234
- estimated_usd: estimatedUsd,
1235
- unknown_usd: reqStats.unknown_usd,
1236
- cost_usd: apiEquivalentUsd,
1237
- last_active: lastActive
1238
- });
1239
- }
1232
+ const requestRows = db.prepare(`
1233
+ SELECT
1234
+ r.session_id as session_id,
1235
+ COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), '') as agent,
1236
+ COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as account_key,
1237
+ COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') as account_tool,
1238
+ COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as account_name,
1239
+ COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') as account_email,
1240
+ COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
1241
+ 1 as requests,
1242
+ COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
1243
+ COALESCE(r.cost_usd, 0) as cost_usd,
1244
+ COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
1245
+ r.timestamp as last_active
1246
+ FROM requests r
1247
+ LEFT JOIN sessions s ON s.id = r.session_id
1248
+ WHERE ${requestWhere}${requestMachineClause}
1249
+ AND (
1250
+ COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') != ''
1251
+ OR COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') != ''
1252
+ OR COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') != ''
1253
+ OR COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') != ''
1254
+ )
1255
+ `).all(...requestMachineParams);
1256
+ for (const row of requestRows)
1257
+ addAccountBreakdownRow(groups, row, false);
1258
+ const sessionOnlyRows = db.prepare(`
1259
+ SELECT
1260
+ s.id as session_id,
1261
+ s.agent as agent,
1262
+ s.account_key as account_key,
1263
+ s.account_tool as account_tool,
1264
+ s.account_name as account_name,
1265
+ s.account_email as account_email,
1266
+ COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
1267
+ COALESCE(s.request_count, 0) as requests,
1268
+ COALESCE(s.total_tokens, 0) as total_tokens,
1269
+ COALESCE(s.total_cost_usd, 0) as cost_usd,
1270
+ 'estimated' as cost_basis,
1271
+ s.started_at as last_active
1272
+ FROM sessions s
1273
+ WHERE ${sessionWhere}${sessionMachineClause}
1274
+ AND s.id NOT IN (SELECT DISTINCT session_id FROM requests)
1275
+ AND (s.account_key != '' OR s.account_tool != '' OR s.account_name != '' OR s.account_email != '')
1276
+ `).all(...sessionMachineParams);
1277
+ for (const row of sessionOnlyRows)
1278
+ addAccountBreakdownRow(groups, row, true);
1279
+ const result = [...groups.values()].map((group) => ({
1280
+ account_key: group.account_key,
1281
+ account_tool: group.account_tool,
1282
+ account_name: group.account_name,
1283
+ account_email: group.account_email,
1284
+ account_source: group.account_source,
1285
+ sessions: group.sessionIds.size,
1286
+ requests: group.requests,
1287
+ total_tokens: group.total_tokens,
1288
+ api_equivalent_usd: group.api_equivalent_usd,
1289
+ billable_usd: group.metered_api_usd,
1290
+ metered_api_usd: group.metered_api_usd,
1291
+ subscription_included_usd: group.subscription_included_usd,
1292
+ estimated_usd: group.estimated_usd,
1293
+ unknown_usd: group.unknown_usd,
1294
+ cost_usd: group.api_equivalent_usd,
1295
+ last_active: group.last_active
1296
+ }));
1240
1297
  result.sort((a, b) => b.cost_usd - a.cost_usd);
1241
1298
  return result;
1242
1299
  }
1243
- function queryDailyBreakdown(db, days = 30) {
1300
+ function queryDailyBreakdown(db, days = 30, machine) {
1301
+ const machineClause = machine ? " AND machine_id = ?" : "";
1302
+ const params = machine ? [`-${days}`, machine] : [`-${days}`];
1244
1303
  return db.prepare(`
1245
1304
  SELECT DATE(timestamp) as date, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
1246
1305
  FROM requests
1247
- WHERE timestamp >= DATE('now', ? || ' days')
1306
+ WHERE timestamp >= DATE('now', ? || ' days')${machineClause}
1248
1307
  GROUP BY DATE(timestamp), agent
1249
1308
  ORDER BY date ASC
1250
- `).all(`-${days}`);
1309
+ `).all(...params);
1251
1310
  }
1252
1311
  function upsertBudget(db, budget) {
1253
1312
  db.prepare(`
@@ -1676,18 +1735,36 @@ var init_pg_migrations = __esm(() => {
1676
1735
  });
1677
1736
 
1678
1737
  // src/mcp/index.ts
1738
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1739
+
1740
+ // src/lib/package-metadata.ts
1741
+ import { readFileSync } from "fs";
1742
+ var cachedMetadata = null;
1743
+ function getPackageMetadata() {
1744
+ if (cachedMetadata)
1745
+ return cachedMetadata;
1746
+ const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf8");
1747
+ const parsed = JSON.parse(raw);
1748
+ cachedMetadata = {
1749
+ name: parsed.name ?? "@hasna/economy",
1750
+ version: parsed.version ?? "0.0.0"
1751
+ };
1752
+ return cachedMetadata;
1753
+ }
1754
+ var packageMetadata = getPackageMetadata();
1755
+
1756
+ // src/mcp/server.ts
1679
1757
  init_database();
1680
1758
  init_pg_migrations();
1681
1759
  import { randomUUID } from "crypto";
1682
1760
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1683
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1684
1761
  import { registerCloudTools } from "@hasna/cloud";
1685
1762
  import { z } from "zod";
1686
1763
 
1687
1764
  // src/ingest/claude.ts
1688
1765
  init_database();
1689
1766
  init_pricing();
1690
- import { readdirSync as readdirSync2, readFileSync, existsSync as existsSync2, statSync as statSync2 } from "fs";
1767
+ import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
1691
1768
  import { homedir as homedir2 } from "os";
1692
1769
  import { join as join2, basename } from "path";
1693
1770
 
@@ -1856,18 +1933,22 @@ var AGENT_ACCOUNT_TOOLS = {
1856
1933
  pi: ["pi"],
1857
1934
  hermes: ["hermes"]
1858
1935
  };
1859
- function accountKey(tool, name) {
1860
- return `${tool}:${name}`;
1936
+ function normalizeEmail(email) {
1937
+ return (email ?? "").trim().toLowerCase();
1938
+ }
1939
+ function accountKey(tool, name, email) {
1940
+ const normalizedEmail = normalizeEmail(email);
1941
+ return `${tool}:${normalizedEmail || name}`;
1861
1942
  }
1862
1943
  function normalizeDir(value) {
1863
1944
  return value.replace(/\/+$/, "");
1864
1945
  }
1865
1946
  function fromProfile(profile, source) {
1866
1947
  return {
1867
- account_key: accountKey(profile.tool, profile.name),
1948
+ account_key: accountKey(profile.tool, profile.name, profile.email),
1868
1949
  account_tool: profile.tool,
1869
1950
  account_name: profile.name,
1870
- ...profile.email ? { account_email: profile.email } : {},
1951
+ ...profile.email ? { account_email: normalizeEmail(profile.email) } : {},
1871
1952
  account_source: source
1872
1953
  };
1873
1954
  }
@@ -1879,10 +1960,12 @@ function fromOverride(raw, agent) {
1879
1960
  const [tool, name] = value.includes(":") ? value.split(":", 2) : [candidateTool, value];
1880
1961
  if (!tool || !name)
1881
1962
  return null;
1963
+ const email = name.includes("@") ? normalizeEmail(name) : undefined;
1882
1964
  return {
1883
- account_key: accountKey(tool, name),
1965
+ account_key: accountKey(tool, name, email),
1884
1966
  account_tool: tool,
1885
1967
  account_name: name,
1968
+ ...email ? { account_email: email } : {},
1886
1969
  account_source: "override"
1887
1970
  };
1888
1971
  }
@@ -1895,11 +1978,12 @@ function envOverride(agent, env) {
1895
1978
  const name = env[`ECONOMY_${agentPrefix}_ACCOUNT_NAME`] ?? env["ECONOMY_ACCOUNT_NAME"];
1896
1979
  if (!tool || !name)
1897
1980
  return null;
1981
+ const email = normalizeEmail(env[`ECONOMY_${agentPrefix}_ACCOUNT_EMAIL`] ?? env["ECONOMY_ACCOUNT_EMAIL"]);
1898
1982
  return {
1899
- account_key: accountKey(tool, name),
1983
+ account_key: accountKey(tool, name, email),
1900
1984
  account_tool: tool,
1901
1985
  account_name: name,
1902
- account_email: env[`ECONOMY_${agentPrefix}_ACCOUNT_EMAIL`] ?? env["ECONOMY_ACCOUNT_EMAIL"],
1986
+ ...email ? { account_email: email } : {},
1903
1987
  account_source: "override"
1904
1988
  };
1905
1989
  }
@@ -2030,7 +2114,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
2030
2114
  continue;
2031
2115
  let lines;
2032
2116
  try {
2033
- lines = readFileSync(filePath, "utf-8").split(`
2117
+ lines = readFileSync2(filePath, "utf-8").split(`
2034
2118
  `).filter((l) => l.trim());
2035
2119
  } catch {
2036
2120
  continue;
@@ -2150,7 +2234,7 @@ function supportsClaudeDataResidencyPricing(model) {
2150
2234
  // src/ingest/codex.ts
2151
2235
  init_database();
2152
2236
  init_pricing();
2153
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2237
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
2154
2238
  import { homedir as homedir3 } from "os";
2155
2239
  import { join as join3, basename as basename2 } from "path";
2156
2240
  import { Database as BunDatabase } from "bun:sqlite";
@@ -2168,7 +2252,7 @@ function readCodexModel() {
2168
2252
  if (!existsSync3(configPath))
2169
2253
  return "gpt-5-codex";
2170
2254
  try {
2171
- const content = readFileSync2(configPath, "utf-8");
2255
+ const content = readFileSync3(configPath, "utf-8");
2172
2256
  const match = content.match(/^model\s*=\s*"([^"]+)"/m);
2173
2257
  return match?.[1] ?? "gpt-5-codex";
2174
2258
  } catch {
@@ -2211,7 +2295,7 @@ function readTokenEvents(rolloutPath) {
2211
2295
  const fallbackUsages = new Map;
2212
2296
  let fallbackTimestamp;
2213
2297
  let aggregate = null;
2214
- for (const line of readFileSync2(rolloutPath, "utf-8").split(`
2298
+ for (const line of readFileSync3(rolloutPath, "utf-8").split(`
2215
2299
  `)) {
2216
2300
  if (!line.trim())
2217
2301
  continue;
@@ -2367,7 +2451,7 @@ async function ingestCodex(db, verbose = false) {
2367
2451
  // src/ingest/gemini.ts
2368
2452
  init_database();
2369
2453
  init_pricing();
2370
- import { readdirSync as readdirSync3, readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync3 } from "fs";
2454
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync4, statSync as statSync3 } from "fs";
2371
2455
  import { homedir as homedir4 } from "os";
2372
2456
  import { join as join4, basename as basename3 } from "path";
2373
2457
  var DEFAULT_GEMINI_TMP_DIR = join4(homedir4(), ".gemini", "tmp");
@@ -2407,7 +2491,7 @@ function projectRoot(projectDir, chatData) {
2407
2491
  const rootFile = join4(projectDir, ".project_root");
2408
2492
  try {
2409
2493
  if (existsSync4(rootFile))
2410
- return readFileSync3(rootFile, "utf-8").trim();
2494
+ return readFileSync4(rootFile, "utf-8").trim();
2411
2495
  } catch {}
2412
2496
  return "";
2413
2497
  }
@@ -2448,7 +2532,7 @@ async function ingestGemini(db, verbose) {
2448
2532
  continue;
2449
2533
  let chatData;
2450
2534
  try {
2451
- chatData = JSON.parse(readFileSync3(filePath, "utf-8"));
2535
+ chatData = JSON.parse(readFileSync4(filePath, "utf-8"));
2452
2536
  } catch {
2453
2537
  continue;
2454
2538
  }
@@ -2526,7 +2610,7 @@ async function ingestGemini(db, verbose) {
2526
2610
  // src/ingest/opencode.ts
2527
2611
  init_database();
2528
2612
  init_pricing();
2529
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
2613
+ import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
2530
2614
  import { homedir as homedir5 } from "os";
2531
2615
  import { join as join5 } from "path";
2532
2616
  var OPENCODE_STORAGE = join5(homedir5(), ".local", "share", "opencode", "storage");
@@ -2569,7 +2653,7 @@ async function ingestOpenCode(db, verbose = false) {
2569
2653
  continue;
2570
2654
  let parsed;
2571
2655
  try {
2572
- parsed = JSON.parse(readFileSync4(file, "utf-8"));
2656
+ parsed = JSON.parse(readFileSync5(file, "utf-8"));
2573
2657
  } catch {
2574
2658
  continue;
2575
2659
  }
@@ -2756,7 +2840,7 @@ async function ingestCursor(db, verbose = false) {
2756
2840
 
2757
2841
  // src/ingest/pi.ts
2758
2842
  init_database();
2759
- import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
2843
+ import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
2760
2844
  import { homedir as homedir6 } from "os";
2761
2845
  import { join as join6 } from "path";
2762
2846
  var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join6(homedir6(), ".pi", "agent", "sessions");
@@ -2786,7 +2870,7 @@ async function ingestPi(db, verbose = false) {
2786
2870
  continue;
2787
2871
  let data;
2788
2872
  try {
2789
- data = JSON.parse(readFileSync5(file, "utf-8"));
2873
+ data = JSON.parse(readFileSync6(file, "utf-8"));
2790
2874
  } catch {
2791
2875
  continue;
2792
2876
  }
@@ -2941,7 +3025,7 @@ function statSyncSafe(path) {
2941
3025
 
2942
3026
  // src/ingest/claude-quota.ts
2943
3027
  init_database();
2944
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
3028
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
2945
3029
 
2946
3030
  // src/lib/paths.ts
2947
3031
  import { homedir as homedir8 } from "os";
@@ -2979,7 +3063,7 @@ function readClaudeToken() {
2979
3063
  if (!existsSync8(CREDENTIALS_PATH))
2980
3064
  return null;
2981
3065
  try {
2982
- const creds = JSON.parse(readFileSync6(CREDENTIALS_PATH, "utf-8"));
3066
+ const creds = JSON.parse(readFileSync7(CREDENTIALS_PATH, "utf-8"));
2983
3067
  const oauth = creds.claudeAiOauth;
2984
3068
  if (!oauth?.accessToken)
2985
3069
  return null;
@@ -3115,7 +3199,7 @@ async function ingestClaudeQuota(db, verbose = false) {
3115
3199
 
3116
3200
  // src/ingest/codex-quota.ts
3117
3201
  init_database();
3118
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
3202
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
3119
3203
  var WHAM_USAGE_URL = process.env["CODEX_USAGE_URL"] ?? "https://chatgpt.com/backend-api/wham/usage";
3120
3204
  function readCodexAuth() {
3121
3205
  const fromEnv = process.env["CODEX_OAUTH_TOKEN"];
@@ -3125,7 +3209,7 @@ function readCodexAuth() {
3125
3209
  if (!existsSync9(authPath))
3126
3210
  return null;
3127
3211
  try {
3128
- const auth = JSON.parse(readFileSync7(authPath, "utf-8"));
3212
+ const auth = JSON.parse(readFileSync8(authPath, "utf-8"));
3129
3213
  const token = auth.tokens?.access_token;
3130
3214
  if (!token)
3131
3215
  return null;
@@ -3257,24 +3341,6 @@ init_database();
3257
3341
 
3258
3342
  // src/lib/cloud-sync.ts
3259
3343
  init_database();
3260
-
3261
- // src/lib/package-metadata.ts
3262
- import { readFileSync as readFileSync8 } from "fs";
3263
- var cachedMetadata = null;
3264
- function getPackageMetadata() {
3265
- if (cachedMetadata)
3266
- return cachedMetadata;
3267
- const raw = readFileSync8(new URL("../../package.json", import.meta.url), "utf8");
3268
- const parsed = JSON.parse(raw);
3269
- cachedMetadata = {
3270
- name: parsed.name ?? "@hasna/economy",
3271
- version: parsed.version ?? "0.0.0"
3272
- };
3273
- return cachedMetadata;
3274
- }
3275
- var packageMetadata = getPackageMetadata();
3276
-
3277
- // src/lib/cloud-sync.ts
3278
3344
  var CLOUD_TABLES = [
3279
3345
  "requests",
3280
3346
  "sessions",
@@ -3440,6 +3506,9 @@ var AGENTS = [
3440
3506
  "hermes"
3441
3507
  ];
3442
3508
 
3509
+ // src/mcp/server.ts
3510
+ init_database();
3511
+
3443
3512
  // src/lib/periods.ts
3444
3513
  function ymd(date) {
3445
3514
  return date.toISOString().substring(0, 10);
@@ -3468,501 +3537,582 @@ function usageSnapshotFilterForPeriod(period) {
3468
3537
  }
3469
3538
  }
3470
3539
 
3471
- // src/mcp/index.ts
3472
- init_database();
3540
+ // src/mcp/server.ts
3473
3541
  init_pricing();
3474
3542
  init_pricing();
3475
- function printHelp() {
3476
- console.log(`Usage: economy-mcp [options]
3477
-
3478
- Runs the ${packageMetadata.name} MCP stdio server.
3479
-
3480
- Options:
3481
- -V, --version output the version number
3482
- -h, --help display help for command`);
3483
- }
3484
- var args = process.argv.slice(2);
3485
- if (args.includes("--help") || args.includes("-h")) {
3486
- printHelp();
3487
- process.exit(0);
3488
- }
3489
- if (args.includes("--version") || args.includes("-V")) {
3490
- console.log(packageMetadata.version);
3491
- process.exit(0);
3492
- }
3493
- var db = openDatabase();
3494
- ensurePricingSeeded(db);
3495
- var server = new McpServer({
3496
- name: "economy",
3497
- version: packageMetadata.version
3498
- });
3499
- var _econAgents = new Map;
3500
- var TOOL_NAMES = [
3501
- "get_cost_summary",
3502
- "get_sessions",
3503
- "get_top_sessions",
3504
- "get_model_breakdown",
3505
- "get_project_breakdown",
3506
- "get_agent_breakdown",
3507
- "get_account_breakdown",
3508
- "get_budget_status",
3509
- "set_budget",
3510
- "remove_budget",
3511
- "get_pricing",
3512
- "set_pricing",
3513
- "remove_pricing",
3514
- "get_daily",
3515
- "get_billing_summary",
3516
- "get_session_detail",
3517
- "get_usage",
3518
- "get_savings",
3519
- "list_subscriptions",
3520
- "set_subscription",
3521
- "remove_subscription",
3522
- "estimate_cost",
3523
- "sync",
3524
- "search_tools",
3525
- "describe_tools",
3526
- "get_goals",
3527
- "set_goal",
3528
- "remove_goal",
3529
- "list_machines",
3530
- "register_agent",
3531
- "heartbeat",
3532
- "set_focus",
3533
- "list_agents",
3534
- "send_feedback"
3535
- ];
3536
- var TOOL_DESCRIPTIONS = {
3537
- get_cost_summary: "period(today|week|month|year|all), machine?(hostname) -> {total_usd, sessions, requests, tokens, summary}",
3538
- get_sessions: `agent(${AGENTS.join("|")}), project(partial), account?(key/name/email), machine?(hostname), limit(20) -> compact session table`,
3539
- get_top_sessions: `n(10), agent(${AGENTS.join("|")}) -> top sessions by cost`,
3540
- list_machines: "no params -> machine_id, sessions, requests, cost, last_active",
3541
- get_model_breakdown: "no params -> model, requests, tokens, cost",
3542
- get_project_breakdown: "period?(today|week|month|year|all) -> project_name, sessions, tokens, cost",
3543
- get_agent_breakdown: "period?(today|week|month|year|all) -> agent, sessions, requests, tokens, api-equivalent, billable, included",
3544
- get_account_breakdown: "period?(today|week|month|year|all) -> account profile, sessions, requests, tokens, api-equivalent, billable, included",
3545
- get_budget_status: "no params -> budget limits, current spend, percent_used, is_over_alert",
3546
- set_budget: "period(daily|weekly|monthly), limit_usd, project_path?, agent?, alert_at_percent? -> create budget",
3547
- remove_budget: "id -> delete budget",
3548
- get_pricing: "no params -> model pricing rows with input, output, cache read/write, and cache storage rates",
3549
- set_pricing: "model, input_per_1m, output_per_1m, cache_read_per_1m?, cache_write_per_1m?, cache_write_1h_per_1m?, cache_storage_per_1m_hour? -> create/update pricing",
3550
- remove_pricing: "model -> delete pricing row",
3551
- get_daily: "days(30) -> daily cost table grouped by date and agent",
3552
- get_billing_summary: "period(today|yesterday|week|month|year|all) -> actual provider billing totals",
3553
- get_session_detail: "session_id(prefix ok) -> per-request breakdown with model, tokens, cost",
3554
- get_usage: `period(today|week|month|year|all), agent?(${AGENTS.join("|")}) -> usage snapshots and all-machine summary`,
3555
- get_savings: `period(today|week|month|year|all), agent?(${AGENTS.join("|")}) -> subscription/API-equivalent savings`,
3556
- list_subscriptions: "no params -> configured subscription plans and included usage",
3557
- set_subscription: `provider, plan, monthly_fee_usd?, included_usage_usd?, agent?(${AGENTS.join("|")}) -> create/update subscription plan`,
3558
- remove_subscription: "id -> delete subscription plan",
3559
- estimate_cost: "model, input_tokens?, output_tokens? -> pre-flight token cost estimate",
3560
- sync: `sources(all|${AGENTS.join("|")}) -> ingest latest cost data`,
3561
- search_tools: "query substring -> tool name list",
3562
- describe_tools: "names[] -> one-line parameter hints",
3563
- get_goals: "no params -> goal progress summary",
3564
- set_goal: "period(day|week|month|year), limit_usd, project_path?, agent? -> create goal",
3565
- remove_goal: "id -> delete goal",
3566
- register_agent: "name, session_id? -> register agent session",
3567
- heartbeat: "agent_id -> update last_seen_at",
3568
- set_focus: "agent_id, project_id? -> set active project context",
3569
- list_agents: "no params -> registered agent list",
3570
- send_feedback: "message, email?, category? -> save feedback locally"
3571
- };
3572
- var fmtUsd = (n) => "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
3573
- var fmtTok = (n) => n >= 1e9 ? `${(n / 1e9).toFixed(1)}B` : n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
3574
- function fmtSession(s) {
3575
- const id = String(s["id"] ?? "").slice(0, 8);
3576
- const agent = String(s["agent"] ?? "");
3577
- const proj = String(s["project_name"] || s["project_path"] || "\u2014").slice(0, 20);
3578
- const cost = fmtUsd(Number(s["total_cost_usd"] ?? 0));
3579
- const tok = fmtTok(Number(s["total_tokens"] ?? 0));
3580
- return `${id} ${agent.padEnd(9)} ${cost.padEnd(10)} ${tok.padEnd(8)} ${proj}`;
3581
- }
3582
- function text(text2) {
3583
- return { content: [{ type: "text", text: text2 }] };
3584
- }
3585
- function textError(message) {
3586
- return { content: [{ type: "text", text: message }], isError: true };
3587
- }
3588
- server.tool("search_tools", "List tool names matching query. Use first to find relevant tools.", { query: z.string().optional() }, async ({ query }) => {
3589
- const q = query?.toLowerCase();
3590
- const matches = q ? TOOL_NAMES.filter((name) => name.includes(q)) : [...TOOL_NAMES];
3591
- return text(matches.join(", "));
3592
- });
3593
- server.tool("describe_tools", "Get param hints for specific tools by name.", { names: z.array(z.string()) }, async ({ names }) => {
3594
- const result = names.map((name) => `${name}: ${TOOL_DESCRIPTIONS[name] ?? "see tool schema"}`).join(`
3543
+ var MCP_NAME = "economy";
3544
+ var DEFAULT_MCP_HTTP_PORT = 8860;
3545
+ function buildServer() {
3546
+ const db = openDatabase();
3547
+ ensurePricingSeeded(db);
3548
+ const server = new McpServer({
3549
+ name: MCP_NAME,
3550
+ version: packageMetadata.version
3551
+ });
3552
+ const _econAgents = new Map;
3553
+ const TOOL_NAMES = [
3554
+ "get_cost_summary",
3555
+ "get_sessions",
3556
+ "get_top_sessions",
3557
+ "get_model_breakdown",
3558
+ "get_project_breakdown",
3559
+ "get_agent_breakdown",
3560
+ "get_account_breakdown",
3561
+ "get_budget_status",
3562
+ "set_budget",
3563
+ "remove_budget",
3564
+ "get_pricing",
3565
+ "set_pricing",
3566
+ "remove_pricing",
3567
+ "get_daily",
3568
+ "get_billing_summary",
3569
+ "get_session_detail",
3570
+ "get_usage",
3571
+ "get_savings",
3572
+ "list_subscriptions",
3573
+ "set_subscription",
3574
+ "remove_subscription",
3575
+ "sync",
3576
+ "search_tools",
3577
+ "describe_tools",
3578
+ "get_goals",
3579
+ "set_goal",
3580
+ "remove_goal",
3581
+ "list_machines",
3582
+ "register_agent",
3583
+ "heartbeat",
3584
+ "set_focus",
3585
+ "list_agents",
3586
+ "send_feedback"
3587
+ ];
3588
+ const TOOL_DESCRIPTIONS = {
3589
+ get_cost_summary: "period(today|week|month|year|all), machine?(hostname) -> {total_usd, sessions, requests, tokens, summary}",
3590
+ get_sessions: `agent(${AGENTS.join("|")}), project(partial), account?(key/name/email), machine?(hostname), limit(20) -> compact session table`,
3591
+ get_top_sessions: `n(10), agent(${AGENTS.join("|")}) -> top sessions by cost`,
3592
+ list_machines: "no params -> machine_id, sessions, requests, cost, last_active",
3593
+ get_model_breakdown: "no params -> model, requests, tokens, cost",
3594
+ get_project_breakdown: "period?(today|week|month|year|all) -> project_name, sessions, tokens, cost",
3595
+ get_agent_breakdown: "period?(today|week|month|year|all) -> agent, sessions, requests, tokens, api-equivalent, billable, included",
3596
+ get_account_breakdown: "period?(today|week|month|year|all) -> account profile, sessions, requests, tokens, api-equivalent, billable, included",
3597
+ get_budget_status: "no params -> budget limits, current spend, percent_used, is_over_alert",
3598
+ set_budget: "period(daily|weekly|monthly), limit_usd, project_path?, agent?, alert_at_percent? -> create budget",
3599
+ remove_budget: "id -> delete budget",
3600
+ get_pricing: "no params -> model pricing rows with input, output, cache read/write, and cache storage rates",
3601
+ set_pricing: "model, input_per_1m, output_per_1m, cache_read_per_1m?, cache_write_per_1m?, cache_write_1h_per_1m?, cache_storage_per_1m_hour? -> create/update pricing",
3602
+ remove_pricing: "model -> delete pricing row",
3603
+ get_daily: "days(30) -> daily cost table grouped by date and agent",
3604
+ get_billing_summary: "period(today|yesterday|week|month|year|all) -> actual provider billing totals",
3605
+ get_session_detail: "session_id(prefix ok) -> per-request breakdown with model, tokens, cost",
3606
+ get_usage: `period(today|week|month|year|all), agent?(${AGENTS.join("|")}) -> usage snapshots and all-machine summary`,
3607
+ get_savings: `period(today|week|month|year|all), agent?(${AGENTS.join("|")}) -> subscription/API-equivalent savings`,
3608
+ list_subscriptions: "no params -> configured subscription plans and included usage",
3609
+ set_subscription: `provider, plan, monthly_fee_usd?, included_usage_usd?, agent?(${AGENTS.join("|")}) -> create/update subscription plan`,
3610
+ remove_subscription: "id -> delete subscription plan",
3611
+ sync: `sources(all|${AGENTS.join("|")}) -> ingest latest cost data`,
3612
+ search_tools: "query substring -> tool name list",
3613
+ describe_tools: "names[] -> one-line parameter hints",
3614
+ get_goals: "no params -> goal progress summary",
3615
+ set_goal: "period(day|week|month|year), limit_usd, project_path?, agent? -> create goal",
3616
+ remove_goal: "id -> delete goal",
3617
+ register_agent: "name, session_id? -> register agent session",
3618
+ heartbeat: "agent_id -> update last_seen_at",
3619
+ set_focus: "agent_id, project_id? -> set active project context",
3620
+ list_agents: "no params -> registered agent list",
3621
+ send_feedback: "message, email?, category? -> save feedback locally"
3622
+ };
3623
+ const fmtUsd = (n) => "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
3624
+ const fmtTok = (n) => n >= 1e9 ? `${(n / 1e9).toFixed(1)}B` : n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
3625
+ function fmtSession(s) {
3626
+ const id = String(s["id"] ?? "").slice(0, 8);
3627
+ const agent = String(s["agent"] ?? "");
3628
+ const proj = String(s["project_name"] || s["project_path"] || "\u2014").slice(0, 20);
3629
+ const cost = fmtUsd(Number(s["total_cost_usd"] ?? 0));
3630
+ const tok = fmtTok(Number(s["total_tokens"] ?? 0));
3631
+ return `${id} ${agent.padEnd(6)} ${cost.padEnd(10)} ${tok.padEnd(8)} ${proj}`;
3632
+ }
3633
+ function text(text2) {
3634
+ return { content: [{ type: "text", text: text2 }] };
3635
+ }
3636
+ function textError(message) {
3637
+ return { content: [{ type: "text", text: message }], isError: true };
3638
+ }
3639
+ server.tool("search_tools", "List tool names matching query. Use first to find relevant tools.", { query: z.string().optional() }, async ({ query }) => {
3640
+ const q = query?.toLowerCase();
3641
+ const matches = q ? TOOL_NAMES.filter((name) => name.includes(q)) : [...TOOL_NAMES];
3642
+ return text(matches.join(", "));
3643
+ });
3644
+ server.tool("describe_tools", "Get param hints for specific tools by name.", { names: z.array(z.string()) }, async ({ names }) => {
3645
+ const result = names.map((name) => `${name}: ${TOOL_DESCRIPTIONS[name] ?? "see tool schema"}`).join(`
3595
3646
  `);
3596
- return text(result);
3597
- });
3598
- server.tool("get_cost_summary", "Cost summary (total_usd, sessions, requests, tokens, human summary). period: today|week|month|year|all. machine: filter by hostname.", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), machine: z.string().optional() }, async ({ period, machine }) => {
3599
- const resolved = period ?? "today";
3600
- const s = querySummary(db, resolved, machine);
3601
- const machineLabel = machine ? ` on ${machine}` : "";
3602
- return text([
3603
- `period: ${resolved}${machineLabel}`,
3604
- `cost: ${fmtUsd(s.total_usd)}`,
3605
- `sessions: ${s.sessions}`,
3606
- `requests: ${s.requests.toLocaleString()}`,
3607
- `tokens: ${fmtTok(s.tokens)}`,
3608
- `summary: You've spent ${fmtUsd(s.total_usd)} ${resolved === "all" ? "total" : resolved}${machineLabel} across ${s.sessions} sessions (${s.requests.toLocaleString()} requests, ${fmtTok(s.tokens)} tokens)`
3609
- ].join(`
3647
+ return text(result);
3648
+ });
3649
+ server.tool("get_cost_summary", "Cost summary (total_usd, sessions, requests, tokens, human summary). period: today|week|month|year|all. machine: filter by hostname.", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), machine: z.string().optional() }, async ({ period, machine }) => {
3650
+ const resolved = period ?? "today";
3651
+ const s = querySummary(db, resolved, machine);
3652
+ const machineLabel = machine ? ` on ${machine}` : "";
3653
+ return text([
3654
+ `period: ${resolved}${machineLabel}`,
3655
+ `cost: ${fmtUsd(s.total_usd)}`,
3656
+ `sessions: ${s.sessions}`,
3657
+ `requests: ${s.requests.toLocaleString()}`,
3658
+ `tokens: ${fmtTok(s.tokens)}`,
3659
+ `summary: You've spent ${fmtUsd(s.total_usd)} ${resolved === "all" ? "total" : resolved}${machineLabel} across ${s.sessions} sessions (${s.requests.toLocaleString()} requests, ${fmtTok(s.tokens)} tokens)`
3660
+ ].join(`
3610
3661
  `));
3611
- });
3612
- server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, account, machine, limit(20)", {
3613
- agent: z.enum(AGENTS).optional(),
3614
- project: z.string().optional(),
3615
- account: z.string().optional(),
3616
- machine: z.string().optional(),
3617
- limit: z.number().int().positive().max(100).optional()
3618
- }, async ({ agent, project, account, machine, limit }) => {
3619
- const sessions = querySessions(db, {
3620
- agent,
3621
- project,
3622
- account,
3623
- machine,
3624
- limit: limit ?? 20
3625
3662
  });
3626
- const lines = ["id agent cost tokens project"];
3627
- for (const session of sessions)
3628
- lines.push(fmtSession(session));
3629
- return text(lines.join(`
3663
+ server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, account, machine, limit(20)", {
3664
+ agent: z.enum(AGENTS).optional(),
3665
+ project: z.string().optional(),
3666
+ account: z.string().optional(),
3667
+ machine: z.string().optional(),
3668
+ limit: z.number().int().positive().max(100).optional()
3669
+ }, async ({ agent, project, account, machine, limit }) => {
3670
+ const sessions = querySessions(db, {
3671
+ agent,
3672
+ project,
3673
+ account,
3674
+ machine,
3675
+ limit: limit ?? 20
3676
+ });
3677
+ const lines = ["id agent cost tokens project"];
3678
+ for (const session of sessions)
3679
+ lines.push(fmtSession(session));
3680
+ return text(lines.join(`
3630
3681
  `));
3631
- });
3632
- server.tool("get_top_sessions", "Top sessions by cost. Params: n(10), agent", {
3633
- n: z.number().int().positive().max(100).optional(),
3634
- agent: z.enum(AGENTS).optional()
3635
- }, async ({ n, agent }) => {
3636
- const sessions = queryTopSessions(db, n ?? 10, agent);
3637
- const lines = ["rank id agent cost tokens project"];
3638
- sessions.forEach((session, i) => lines.push(`${String(i + 1).padEnd(5)} ${fmtSession(session)}`));
3639
- return text(lines.join(`
3682
+ });
3683
+ server.tool("get_top_sessions", "Top sessions by cost. Params: n(10), agent", {
3684
+ n: z.number().int().positive().max(100).optional(),
3685
+ agent: z.enum(AGENTS).optional()
3686
+ }, async ({ n, agent }) => {
3687
+ const sessions = queryTopSessions(db, n ?? 10, agent);
3688
+ const lines = ["rank id agent cost tokens project"];
3689
+ sessions.forEach((session, i) => lines.push(`${String(i + 1).padEnd(5)} ${fmtSession(session)}`));
3690
+ return text(lines.join(`
3640
3691
  `));
3641
- });
3642
- server.tool("get_model_breakdown", "Cost per model. No params.", {}, async () => {
3643
- const rows = queryModelBreakdown(db);
3644
- const lines = ["model agent reqs tokens cost"];
3645
- for (const row of rows) {
3646
- lines.push(`${String(row["model"]).slice(0, 30).padEnd(31)}${String(row["agent"]).padEnd(10)}${String(row["requests"]).padEnd(8)}${fmtTok(Number(row["total_tokens"])).padEnd(9)}${fmtUsd(Number(row["cost_usd"]))}`);
3647
- }
3648
- return text(lines.join(`
3692
+ });
3693
+ server.tool("get_model_breakdown", "Cost per model. No params.", {}, async () => {
3694
+ const rows = queryModelBreakdown(db);
3695
+ const lines = ["model agent reqs tokens cost"];
3696
+ for (const row of rows) {
3697
+ lines.push(`${String(row["model"]).slice(0, 30).padEnd(31)}${String(row["agent"]).padEnd(10)}${String(row["requests"]).padEnd(8)}${fmtTok(Number(row["total_tokens"])).padEnd(9)}${fmtUsd(Number(row["cost_usd"]))}`);
3698
+ }
3699
+ return text(lines.join(`
3649
3700
  `));
3650
- });
3651
- server.tool("get_project_breakdown", "Cost per project. Params: period(today|week|month|year|all).", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
3652
- const rows = queryProjectBreakdown(db, period ?? "all");
3653
- const lines = ["project sessions tokens cost"];
3654
- for (const row of rows) {
3655
- const name = String(row["project_name"] || row["project_path"] || "\u2014").slice(0, 20);
3656
- lines.push(`${name.padEnd(21)}${String(row["sessions"]).padEnd(9)}${fmtTok(Number(row["total_tokens"])).padEnd(9)}${fmtUsd(Number(row["cost_usd"]))}`);
3657
- }
3658
- return text(lines.join(`
3701
+ });
3702
+ server.tool("get_project_breakdown", "Cost per project. Params: period(today|week|month|year|all).", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
3703
+ const rows = queryProjectBreakdown(db, period ?? "all");
3704
+ const lines = ["project sessions tokens cost"];
3705
+ for (const row of rows) {
3706
+ const name = String(row["project_name"] || row["project_path"] || "\u2014").slice(0, 20);
3707
+ lines.push(`${name.padEnd(21)}${String(row["sessions"]).padEnd(9)}${fmtTok(Number(row["total_tokens"])).padEnd(9)}${fmtUsd(Number(row["cost_usd"]))}`);
3708
+ }
3709
+ return text(lines.join(`
3659
3710
  `));
3660
- });
3661
- server.tool("get_agent_breakdown", "Cost per coding agent. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
3662
- const rows = queryAgentBreakdown(db, period ?? "all");
3663
- if (rows.length === 0)
3664
- return text("No agent usage yet.");
3665
- const lines = ["agent sessions requests tokens api_eq billable included"];
3666
- for (const row of rows) {
3667
- lines.push(`${String(row["agent"]).slice(0, 10).padEnd(11)}` + `${String(row["sessions"]).padEnd(9)}` + `${String(row["requests"]).padEnd(9)}` + `${fmtTok(Number(row["total_tokens"])).padEnd(9)}` + `${fmtUsd(Number(row["api_equivalent_usd"] ?? row["cost_usd"])).padEnd(10)}` + `${fmtUsd(Number(row["billable_usd"] ?? 0)).padEnd(10)}` + `${fmtUsd(Number(row["subscription_included_usd"] ?? 0))}`);
3668
- }
3669
- return text(lines.join(`
3711
+ });
3712
+ server.tool("get_agent_breakdown", "Cost per coding agent. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
3713
+ const rows = queryAgentBreakdown(db, period ?? "all");
3714
+ if (rows.length === 0)
3715
+ return text("No agent usage yet.");
3716
+ const lines = ["agent sessions requests tokens api_eq billable included"];
3717
+ for (const row of rows) {
3718
+ lines.push(`${String(row["agent"]).slice(0, 10).padEnd(11)}` + `${String(row["sessions"]).padEnd(9)}` + `${String(row["requests"]).padEnd(9)}` + `${fmtTok(Number(row["total_tokens"])).padEnd(9)}` + `${fmtUsd(Number(row["api_equivalent_usd"] ?? row["cost_usd"])).padEnd(10)}` + `${fmtUsd(Number(row["billable_usd"] ?? 0)).padEnd(10)}` + `${fmtUsd(Number(row["subscription_included_usd"] ?? 0))}`);
3719
+ }
3720
+ return text(lines.join(`
3670
3721
  `));
3671
- });
3672
- server.tool("get_account_breakdown", "Cost per account/profile. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
3673
- const rows = queryAccountBreakdown(db, period ?? "all");
3674
- if (rows.length === 0)
3675
- return text("No account-attributed sessions yet.");
3676
- const lines = ["account sessions requests tokens api_eq billable included"];
3677
- for (const row of rows) {
3678
- const label = String(row["account_key"] || row["account_name"] || "\u2014").slice(0, 20);
3679
- lines.push(`${label.padEnd(21)}` + `${String(row["sessions"]).padEnd(9)}` + `${String(row["requests"]).padEnd(9)}` + `${fmtTok(Number(row["total_tokens"])).padEnd(9)}` + `${fmtUsd(Number(row["api_equivalent_usd"] ?? row["cost_usd"])).padEnd(10)}` + `${fmtUsd(Number(row["billable_usd"] ?? 0)).padEnd(10)}` + `${fmtUsd(Number(row["subscription_included_usd"] ?? 0))}`);
3680
- }
3681
- return text(lines.join(`
3722
+ });
3723
+ server.tool("get_account_breakdown", "Cost per account/profile. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
3724
+ const rows = queryAccountBreakdown(db, period ?? "all");
3725
+ if (rows.length === 0)
3726
+ return text("No account-attributed sessions yet.");
3727
+ const lines = ["account agent sessions requests tokens api_eq billable included"];
3728
+ for (const row of rows) {
3729
+ const label = String(row["account_email"] || row["account_name"] || row["account_key"] || "\u2014").slice(0, 20);
3730
+ lines.push(`${label.padEnd(21)}` + `${String(row["account_tool"] ?? "").slice(0, 10).padEnd(11)}` + `${String(row["sessions"]).padEnd(9)}` + `${String(row["requests"]).padEnd(9)}` + `${fmtTok(Number(row["total_tokens"])).padEnd(9)}` + `${fmtUsd(Number(row["api_equivalent_usd"] ?? row["cost_usd"])).padEnd(10)}` + `${fmtUsd(Number(row["billable_usd"] ?? 0)).padEnd(10)}` + `${fmtUsd(Number(row["subscription_included_usd"] ?? 0))}`);
3731
+ }
3732
+ return text(lines.join(`
3682
3733
  `));
3683
- });
3684
- server.tool("get_budget_status", "Budget limits vs spend, percent used, alert flags. No params.", {}, async () => {
3685
- const budgets = getBudgetStatuses(db);
3686
- if (budgets.length === 0)
3687
- return text("No budgets set.");
3688
- const lines = ["scope period spent limit used% status"];
3689
- for (const budget of budgets) {
3690
- const scope = String(budget["project_path"] ?? "global").slice(0, 20);
3691
- const pct = Number(budget["percent_used"]).toFixed(1);
3692
- const status = budget["is_over_limit"] ? "OVER" : budget["is_over_alert"] ? "ALERT" : "OK";
3693
- lines.push(`${scope.padEnd(21)}${String(budget["period"]).padEnd(9)}${fmtUsd(Number(budget["current_spend_usd"])).padEnd(11)}${fmtUsd(Number(budget["limit_usd"])).padEnd(11)}${pct}%`.padEnd(49) + ` ${status}`);
3694
- }
3695
- return text(lines.join(`
3734
+ });
3735
+ server.tool("get_budget_status", "Budget limits vs spend, percent used, alert flags. No params.", {}, async () => {
3736
+ const budgets = getBudgetStatuses(db);
3737
+ if (budgets.length === 0)
3738
+ return text("No budgets set.");
3739
+ const lines = ["scope period spent limit used% status"];
3740
+ for (const budget of budgets) {
3741
+ const scope = String(budget["project_path"] ?? "global").slice(0, 20);
3742
+ const pct = Number(budget["percent_used"]).toFixed(1);
3743
+ const status = budget["is_over_limit"] ? "OVER" : budget["is_over_alert"] ? "ALERT" : "OK";
3744
+ lines.push(`${scope.padEnd(21)}${String(budget["period"]).padEnd(9)}${fmtUsd(Number(budget["current_spend_usd"])).padEnd(11)}${fmtUsd(Number(budget["limit_usd"])).padEnd(11)}${pct}%`.padEnd(49) + ` ${status}`);
3745
+ }
3746
+ return text(lines.join(`
3696
3747
  `));
3697
- });
3698
- server.tool("set_budget", "Create a spending budget. period: daily|weekly|monthly. limit_usd must be positive. alert_at_percent defaults to 80.", {
3699
- period: z.enum(["daily", "weekly", "monthly"]),
3700
- limit_usd: z.number().positive(),
3701
- project_path: z.string().optional(),
3702
- agent: z.enum(AGENTS).optional(),
3703
- alert_at_percent: z.number().positive().max(100).optional()
3704
- }, async ({ period, limit_usd, project_path, agent, alert_at_percent }) => {
3705
- const now = new Date().toISOString();
3706
- const id = randomUUID();
3707
- upsertBudget(db, {
3708
- id,
3709
- project_path: project_path ?? null,
3710
- agent: agent ?? null,
3711
- period,
3712
- limit_usd,
3713
- alert_at_percent: alert_at_percent ?? 80,
3714
- created_at: now,
3715
- updated_at: now
3716
3748
  });
3717
- return text(`Budget set: ${id}`);
3718
- });
3719
- server.tool("remove_budget", "Delete a budget by id.", { id: z.string() }, async ({ id }) => {
3720
- deleteBudget(db, id);
3721
- return text("Budget removed.");
3722
- });
3723
- server.tool("get_pricing", "Editable model pricing rows. Includes input/output/cache rates and context-cache storage.", {}, async () => {
3724
- const rows = listModelPricing(db);
3725
- const lines = ["model input output cache-r cache-w cache-1h storage-h"];
3726
- for (const row of rows) {
3727
- lines.push(`${row.model.slice(0, 30).padEnd(31)}` + `${fmtUsd(row.input_per_1m).padEnd(9)}` + `${fmtUsd(row.output_per_1m).padEnd(9)}` + `${fmtUsd(row.cache_read_per_1m).padEnd(9)}` + `${fmtUsd(row.cache_write_per_1m).padEnd(9)}` + `${fmtUsd(row.cache_write_1h_per_1m ?? 0).padEnd(9)}` + `${fmtUsd(row.cache_storage_per_1m_hour ?? 0)}`);
3728
- }
3729
- return text(lines.join(`
3749
+ server.tool("set_budget", "Create a spending budget. period: daily|weekly|monthly. limit_usd must be positive. alert_at_percent defaults to 80.", {
3750
+ period: z.enum(["daily", "weekly", "monthly"]),
3751
+ limit_usd: z.number().positive(),
3752
+ project_path: z.string().optional(),
3753
+ agent: z.enum(AGENTS).optional(),
3754
+ alert_at_percent: z.number().positive().max(100).optional()
3755
+ }, async ({ period, limit_usd, project_path, agent, alert_at_percent }) => {
3756
+ const now = new Date().toISOString();
3757
+ const id = randomUUID();
3758
+ upsertBudget(db, {
3759
+ id,
3760
+ project_path: project_path ?? null,
3761
+ agent: agent ?? null,
3762
+ period,
3763
+ limit_usd,
3764
+ alert_at_percent: alert_at_percent ?? 80,
3765
+ created_at: now,
3766
+ updated_at: now
3767
+ });
3768
+ return text(`Budget set: ${id}`);
3769
+ });
3770
+ server.tool("remove_budget", "Delete a budget by id.", { id: z.string() }, async ({ id }) => {
3771
+ deleteBudget(db, id);
3772
+ return text("Budget removed.");
3773
+ });
3774
+ server.tool("get_pricing", "Editable model pricing rows. Includes input/output/cache rates and context-cache storage.", {}, async () => {
3775
+ const rows = listModelPricing(db);
3776
+ const lines = ["model input output cache-r cache-w cache-1h storage-h"];
3777
+ for (const row of rows) {
3778
+ lines.push(`${row.model.slice(0, 30).padEnd(31)}` + `${fmtUsd(row.input_per_1m).padEnd(9)}` + `${fmtUsd(row.output_per_1m).padEnd(9)}` + `${fmtUsd(row.cache_read_per_1m).padEnd(9)}` + `${fmtUsd(row.cache_write_per_1m).padEnd(9)}` + `${fmtUsd(row.cache_write_1h_per_1m ?? 0).padEnd(9)}` + `${fmtUsd(row.cache_storage_per_1m_hour ?? 0)}`);
3779
+ }
3780
+ return text(lines.join(`
3730
3781
  `));
3731
- });
3732
- server.tool("set_pricing", "Create or update a model pricing row. Values are USD per 1M tokens except cache_storage_per_1m_hour.", {
3733
- model: z.string().min(1),
3734
- input_per_1m: z.number().nonnegative(),
3735
- output_per_1m: z.number().nonnegative(),
3736
- cache_read_per_1m: z.number().nonnegative().optional(),
3737
- cache_write_per_1m: z.number().nonnegative().optional(),
3738
- cache_write_1h_per_1m: z.number().nonnegative().optional(),
3739
- cache_storage_per_1m_hour: z.number().nonnegative().optional()
3740
- }, async (input) => {
3741
- const model = input.model.trim();
3742
- if (!model)
3743
- return textError("model is required");
3744
- upsertModelPricing(db, {
3745
- model,
3746
- input_per_1m: input.input_per_1m,
3747
- output_per_1m: input.output_per_1m,
3748
- cache_read_per_1m: input.cache_read_per_1m ?? 0,
3749
- cache_write_per_1m: input.cache_write_per_1m ?? 0,
3750
- cache_write_1h_per_1m: input.cache_write_1h_per_1m ?? 0,
3751
- cache_storage_per_1m_hour: input.cache_storage_per_1m_hour ?? 0,
3752
- updated_at: new Date().toISOString()
3753
3782
  });
3754
- return text(`Pricing set: ${model}`);
3755
- });
3756
- server.tool("remove_pricing", "Delete a model pricing row by model id.", { model: z.string() }, async ({ model }) => {
3757
- deleteModelPricing(db, model);
3758
- return text("Pricing removed.");
3759
- });
3760
- server.tool("get_daily", "Daily cost table by agent. Params: days(30)", { days: z.number().int().positive().max(365).optional() }, async ({ days }) => {
3761
- const rows = queryDailyBreakdown(db, days ?? 30);
3762
- const byDate = new Map;
3763
- for (const row of rows) {
3764
- const date = String(row["date"]);
3765
- const agent = String(row["agent"]);
3766
- const entry = byDate.get(date) ?? Object.fromEntries(AGENTS.map((name) => [name, 0]));
3767
- entry[agent] = (entry[agent] ?? 0) + Number(row["cost_usd"]);
3768
- byDate.set(date, entry);
3769
- }
3770
- const lines = [`date ${AGENTS.map((agent) => agent.slice(0, 8).padEnd(10)).join("")}total`];
3771
- for (const [date, costs] of [...byDate.entries()].sort()) {
3772
- const total = Object.values(costs).reduce((sum, value) => sum + value, 0);
3773
- lines.push(`${date} ${AGENTS.map((agent) => fmtUsd(costs[agent] ?? 0).padEnd(10)).join("")}${fmtUsd(total)}`);
3774
- }
3775
- return text(lines.join(`
3783
+ server.tool("set_pricing", "Create or update a model pricing row. Values are USD per 1M tokens except cache_storage_per_1m_hour.", {
3784
+ model: z.string().min(1),
3785
+ input_per_1m: z.number().nonnegative(),
3786
+ output_per_1m: z.number().nonnegative(),
3787
+ cache_read_per_1m: z.number().nonnegative().optional(),
3788
+ cache_write_per_1m: z.number().nonnegative().optional(),
3789
+ cache_write_1h_per_1m: z.number().nonnegative().optional(),
3790
+ cache_storage_per_1m_hour: z.number().nonnegative().optional()
3791
+ }, async (input) => {
3792
+ const model = input.model.trim();
3793
+ if (!model)
3794
+ return textError("model is required");
3795
+ upsertModelPricing(db, {
3796
+ model,
3797
+ input_per_1m: input.input_per_1m,
3798
+ output_per_1m: input.output_per_1m,
3799
+ cache_read_per_1m: input.cache_read_per_1m ?? 0,
3800
+ cache_write_per_1m: input.cache_write_per_1m ?? 0,
3801
+ cache_write_1h_per_1m: input.cache_write_1h_per_1m ?? 0,
3802
+ cache_storage_per_1m_hour: input.cache_storage_per_1m_hour ?? 0,
3803
+ updated_at: new Date().toISOString()
3804
+ });
3805
+ return text(`Pricing set: ${model}`);
3806
+ });
3807
+ server.tool("remove_pricing", "Delete a model pricing row by model id.", { model: z.string() }, async ({ model }) => {
3808
+ deleteModelPricing(db, model);
3809
+ return text("Pricing removed.");
3810
+ });
3811
+ server.tool("get_daily", "Daily cost table by agent. Params: days(30)", { days: z.number().int().positive().max(365).optional() }, async ({ days }) => {
3812
+ const rows = queryDailyBreakdown(db, days ?? 30);
3813
+ const byDate = new Map;
3814
+ for (const row of rows) {
3815
+ const date = String(row["date"]);
3816
+ const entry = byDate.get(date) ?? { claude: 0, takumi: 0, codex: 0, gemini: 0 };
3817
+ if (row["agent"] === "claude")
3818
+ entry.claude += Number(row["cost_usd"]);
3819
+ else if (row["agent"] === "takumi")
3820
+ entry.takumi += Number(row["cost_usd"]);
3821
+ else if (row["agent"] === "codex")
3822
+ entry.codex += Number(row["cost_usd"]);
3823
+ else if (row["agent"] === "gemini")
3824
+ entry.gemini += Number(row["cost_usd"]);
3825
+ byDate.set(date, entry);
3826
+ }
3827
+ const lines = ["date claude takumi codex gemini total"];
3828
+ for (const [date, costs] of [...byDate.entries()].sort()) {
3829
+ const total = costs.claude + costs.takumi + costs.codex + costs.gemini;
3830
+ lines.push(`${date} ${fmtUsd(costs.claude).padEnd(11)}${fmtUsd(costs.takumi).padEnd(11)}${fmtUsd(costs.codex).padEnd(11)}${fmtUsd(costs.gemini).padEnd(11)}${fmtUsd(total)}`);
3831
+ }
3832
+ return text(lines.join(`
3776
3833
  `));
3777
- });
3778
- server.tool("get_billing_summary", "Actual provider billing totals from admin API sync. Params: period(today|yesterday|week|month|year|all)", { period: z.enum(["today", "yesterday", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
3779
- const summary = queryBillingSummary(db, period ?? "month");
3780
- const lines = ["provider billed"];
3781
- for (const [provider, cost] of Object.entries(summary.by_provider)) {
3782
- lines.push(`${provider.padEnd(11)}${fmtUsd(cost)}`);
3783
- }
3784
- lines.push(`total ${fmtUsd(summary.total_usd)}`);
3785
- return text(lines.join(`
3834
+ });
3835
+ server.tool("get_billing_summary", "Actual provider billing totals from admin API sync. Params: period(today|yesterday|week|month|year|all)", { period: z.enum(["today", "yesterday", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
3836
+ const summary = queryBillingSummary(db, period ?? "month");
3837
+ const lines = ["provider billed"];
3838
+ for (const [provider, cost] of Object.entries(summary.by_provider)) {
3839
+ lines.push(`${provider.padEnd(11)}${fmtUsd(cost)}`);
3840
+ }
3841
+ lines.push(`total ${fmtUsd(summary.total_usd)}`);
3842
+ return text(lines.join(`
3786
3843
  `));
3787
- });
3788
- server.tool("get_session_detail", "Per-request breakdown of a single session. Params: session_id (prefix ok)", { session_id: z.string() }, async ({ session_id }) => {
3789
- const session = db.prepare(`SELECT * FROM sessions WHERE id = ? OR id LIKE ?`).get(session_id, `${session_id}%`);
3790
- if (!session)
3791
- return textError(`Session not found: ${session_id}`);
3792
- const requests = db.prepare(`SELECT * FROM requests WHERE session_id = ? ORDER BY timestamp ASC LIMIT 50`).all(session["id"]);
3793
- const lines = [
3794
- `session: ${String(session["id"]).slice(0, 16)}`,
3795
- `agent: ${session["agent"]} project: ${session["project_name"] || "\u2014"}`,
3796
- `cost: ${fmtUsd(Number(session["total_cost_usd"]))} tokens: ${fmtTok(Number(session["total_tokens"]))} requests: ${session["request_count"]}`,
3797
- "",
3798
- "time model input output cache-r cache-5m cache-1h cost"
3799
- ];
3800
- for (const request of requests) {
3801
- lines.push(`${String(request["timestamp"]).slice(11, 19)} ` + `${String(request["model"]).slice(0, 22).padEnd(23)}` + `${fmtTok(Number(request["input_tokens"])).padEnd(9)}` + `${fmtTok(Number(request["output_tokens"])).padEnd(9)}` + `${fmtTok(Number(request["cache_read_tokens"])).padEnd(9)}` + `${fmtTok(Number(request["cache_create_5m_tokens"] ?? request["cache_create_tokens"] ?? 0)).padEnd(9)}` + `${fmtTok(Number(request["cache_create_1h_tokens"] ?? 0)).padEnd(9)}` + `${fmtUsd(Number(request["cost_usd"]))}`);
3802
- }
3803
- return text(lines.join(`
3844
+ });
3845
+ server.tool("get_session_detail", "Per-request breakdown of a single session. Params: session_id (prefix ok)", { session_id: z.string() }, async ({ session_id }) => {
3846
+ const session = db.prepare(`SELECT * FROM sessions WHERE id = ? OR id LIKE ?`).get(session_id, `${session_id}%`);
3847
+ if (!session)
3848
+ return textError(`Session not found: ${session_id}`);
3849
+ const requests = db.prepare(`SELECT * FROM requests WHERE session_id = ? ORDER BY timestamp ASC LIMIT 50`).all(session["id"]);
3850
+ const lines = [
3851
+ `session: ${String(session["id"]).slice(0, 16)}`,
3852
+ `agent: ${session["agent"]} project: ${session["project_name"] || "\u2014"}`,
3853
+ `cost: ${fmtUsd(Number(session["total_cost_usd"]))} tokens: ${fmtTok(Number(session["total_tokens"]))} requests: ${session["request_count"]}`,
3854
+ "",
3855
+ "time model input output cache-r cache-5m cache-1h cost"
3856
+ ];
3857
+ for (const request of requests) {
3858
+ lines.push(`${String(request["timestamp"]).slice(11, 19)} ` + `${String(request["model"]).slice(0, 22).padEnd(23)}` + `${fmtTok(Number(request["input_tokens"])).padEnd(9)}` + `${fmtTok(Number(request["output_tokens"])).padEnd(9)}` + `${fmtTok(Number(request["cache_read_tokens"])).padEnd(9)}` + `${fmtTok(Number(request["cache_create_5m_tokens"] ?? request["cache_create_tokens"] ?? 0)).padEnd(9)}` + `${fmtTok(Number(request["cache_create_1h_tokens"] ?? 0)).padEnd(9)}` + `${fmtUsd(Number(request["cost_usd"]))}`);
3859
+ }
3860
+ return text(lines.join(`
3804
3861
  `));
3805
- });
3806
- server.tool("sync", `Ingest new cost data. sources: all|${AGENTS.join("|")}`, { sources: z.enum(["all", ...AGENTS]).optional() }, async ({ sources }) => {
3807
- const selected = sources ?? "all";
3808
- const opts = selected === "all" ? {} : { [selected]: true };
3809
- const result = await syncAll(db, opts);
3810
- return text(JSON.stringify(result, null, 2));
3811
- });
3812
- 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 }) => {
3813
- const p = period ?? "month";
3814
- const snaps = queryUsageSnapshots(db, { agent, ...usageSnapshotFilterForPeriod(p) });
3815
- const summary = querySummary(db, p, undefined, true);
3816
- return text(JSON.stringify({ snapshots: snaps, summary }, null, 2));
3817
- });
3818
- server.tool("get_savings", "Subscription vs API savings summary", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), agent: z.enum(AGENTS).optional() }, async ({ period, agent }) => {
3819
- return text(JSON.stringify(querySavingsSummary(db, period ?? "month", agent), null, 2));
3820
- });
3821
- server.tool("list_subscriptions", "List configured subscription plans and included usage caps. No params.", {}, async () => {
3822
- const rows = listSubscriptions(db);
3823
- if (rows.length === 0)
3824
- return text("No subscriptions configured.");
3825
- const lines = ["id provider plan agent fee included active"];
3826
- for (const row of rows) {
3827
- lines.push(`${String(row["id"]).slice(0, 8).padEnd(9)}` + `${String(row["provider"]).slice(0, 12).padEnd(13)}` + `${String(row["plan"]).slice(0, 10).padEnd(11)}` + `${String(row["agent"] ?? "all").slice(0, 10).padEnd(11)}` + `${fmtUsd(Number(row["monthly_fee_usd"] ?? 0)).padEnd(10)}` + `${fmtUsd(Number(row["included_usage_usd"] ?? 0)).padEnd(10)}` + `${Number(row["active"] ?? 0) ? "yes" : "no"}`);
3828
- }
3829
- return text(lines.join(`
3862
+ });
3863
+ server.tool("sync", `Ingest new cost data. sources: all|${AGENTS.join("|")}`, { sources: z.enum(["all", ...AGENTS]).optional() }, async ({ sources }) => {
3864
+ const selected = sources ?? "all";
3865
+ const opts = selected === "all" ? {} : { [selected]: true };
3866
+ const result = await syncAll(db, opts);
3867
+ return text(JSON.stringify(result, null, 2));
3868
+ });
3869
+ 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 }) => {
3870
+ const p = period ?? "month";
3871
+ const snaps = queryUsageSnapshots(db, { agent, ...usageSnapshotFilterForPeriod(p) });
3872
+ const summary = querySummary(db, p, undefined, true);
3873
+ return text(JSON.stringify({ snapshots: snaps, summary }, null, 2));
3874
+ });
3875
+ server.tool("get_savings", "Subscription vs API savings summary", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), agent: z.enum(AGENTS).optional() }, async ({ period, agent }) => {
3876
+ return text(JSON.stringify(querySavingsSummary(db, period ?? "month", agent), null, 2));
3877
+ });
3878
+ server.tool("list_subscriptions", "List configured subscription plans used by savings calculations.", {}, async () => {
3879
+ const rows = listSubscriptions(db);
3880
+ if (rows.length === 0)
3881
+ return text("No subscriptions configured.");
3882
+ const lines = ["id provider plan agent fee included active"];
3883
+ for (const row of rows) {
3884
+ lines.push(`${row.id.slice(0, 18).padEnd(19)}` + `${row.provider.slice(0, 10).padEnd(11)}` + `${row.plan.slice(0, 10).padEnd(11)}` + `${(row.agent ?? "all").slice(0, 10).padEnd(11)}` + `${fmtUsd(row.monthly_fee_usd).padEnd(10)}` + `${fmtUsd(row.included_usage_usd).padEnd(10)}` + `${row.active ? "yes" : "no"}`);
3885
+ }
3886
+ return text(lines.join(`
3830
3887
  `));
3831
- });
3832
- server.tool("set_subscription", `Create or update a subscription plan. agent may be ${AGENTS.join("|")}.`, {
3833
- id: z.string().optional(),
3834
- provider: z.string(),
3835
- plan: z.string(),
3836
- agent: z.enum(AGENTS).optional(),
3837
- monthly_fee_usd: z.number().optional(),
3838
- included_usage_usd: z.number().optional(),
3839
- billing_cycle_start: z.string().optional(),
3840
- reset_policy: z.string().optional(),
3841
- active: z.boolean().optional()
3842
- }, async (input) => {
3843
- if (input.monthly_fee_usd != null && input.monthly_fee_usd < 0)
3844
- return text("monthly_fee_usd must be non-negative");
3845
- if (input.included_usage_usd != null && input.included_usage_usd < 0)
3846
- return text("included_usage_usd must be non-negative");
3847
- const now = new Date().toISOString();
3848
- const subscription = {
3849
- id: input.id?.trim() || randomUUID(),
3850
- agent: input.agent ?? null,
3851
- provider: input.provider.trim(),
3852
- plan: input.plan.trim(),
3853
- monthly_fee_usd: input.monthly_fee_usd ?? 0,
3854
- included_usage_usd: input.included_usage_usd ?? 0,
3855
- billing_cycle_start: input.billing_cycle_start ?? null,
3856
- reset_policy: input.reset_policy ?? "monthly",
3857
- active: input.active === false ? 0 : 1,
3858
- created_at: now,
3859
- updated_at: now
3860
- };
3861
- if (!subscription.provider)
3862
- return text("provider is required");
3863
- if (!subscription.plan)
3864
- return text("plan is required");
3865
- upsertSubscription(db, subscription);
3866
- return text(JSON.stringify(subscription, null, 2));
3867
- });
3868
- server.tool("remove_subscription", "Remove a subscription plan by id.", { id: z.string() }, async ({ id }) => {
3869
- deleteSubscription(db, id);
3870
- return text(`Removed subscription ${id}`);
3871
- });
3872
- server.tool("estimate_cost", "Pre-flight cost estimate for token counts", { model: z.string(), input_tokens: z.number().optional(), output_tokens: z.number().optional() }, async ({ model, input_tokens, output_tokens }) => {
3873
- const cost = computeCostFromDb(db, model, input_tokens ?? 0, output_tokens ?? 0, 0, 0, 0);
3874
- return text(`${model}: ${fmtUsd(cost)} (${input_tokens ?? 0} in / ${output_tokens ?? 0} out)`);
3875
- });
3876
- server.tool("get_goals", "All spending goals with current progress. No params.", {}, async () => {
3877
- const goals = getGoalStatuses(db);
3878
- if (goals.length === 0)
3879
- return text("No goals set.");
3880
- const lines = ["period scope limit spent used% status"];
3881
- for (const goal of goals) {
3882
- const scope = String(goal["project_path"] ?? goal["agent"] ?? "global").slice(0, 20);
3883
- const pct = Number(goal["percent_used"]).toFixed(1);
3884
- const status = goal["is_over"] ? "OVER" : goal["is_at_risk"] ? "AT RISK" : "ON TRACK";
3885
- lines.push(`${String(goal["period"]).padEnd(9)}${scope.padEnd(21)}${fmtUsd(Number(goal["limit_usd"])).padEnd(11)}${fmtUsd(Number(goal["current_spend_usd"])).padEnd(11)}${pct}% ${status}`);
3886
- }
3887
- return text(lines.join(`
3888
+ });
3889
+ server.tool("set_subscription", "Create or update a subscription plan used by subscription-vs-API savings calculations.", {
3890
+ id: z.string().optional(),
3891
+ provider: z.string().min(1),
3892
+ plan: z.string().min(1),
3893
+ agent: z.enum(AGENTS).optional(),
3894
+ monthly_fee_usd: z.number().nonnegative().optional(),
3895
+ included_usage_usd: z.number().nonnegative().optional(),
3896
+ billing_cycle_start: z.string().optional(),
3897
+ reset_policy: z.string().optional(),
3898
+ active: z.boolean().optional()
3899
+ }, async (input) => {
3900
+ const now = new Date().toISOString();
3901
+ const row = {
3902
+ id: input.id ?? randomUUID(),
3903
+ provider: input.provider,
3904
+ plan: input.plan,
3905
+ agent: input.agent ?? null,
3906
+ monthly_fee_usd: input.monthly_fee_usd ?? 0,
3907
+ included_usage_usd: input.included_usage_usd ?? 0,
3908
+ billing_cycle_start: input.billing_cycle_start ?? null,
3909
+ reset_policy: input.reset_policy ?? "monthly",
3910
+ active: input.active === false ? 0 : 1,
3911
+ created_at: now,
3912
+ updated_at: now
3913
+ };
3914
+ upsertSubscription(db, row);
3915
+ return text(JSON.stringify(row, null, 2));
3916
+ });
3917
+ server.tool("remove_subscription", "Delete a subscription plan by id.", { id: z.string() }, async ({ id }) => {
3918
+ deleteSubscription(db, id);
3919
+ return text("Subscription removed.");
3920
+ });
3921
+ server.tool("estimate_cost", "Pre-flight cost estimate for token counts", { model: z.string(), input_tokens: z.number().optional(), output_tokens: z.number().optional() }, async ({ model, input_tokens, output_tokens }) => {
3922
+ const cost = computeCostFromDb(db, model, input_tokens ?? 0, output_tokens ?? 0, 0, 0, 0);
3923
+ return text(`${model}: ${fmtUsd(cost)} (${input_tokens ?? 0} in / ${output_tokens ?? 0} out)`);
3924
+ });
3925
+ server.tool("get_goals", "All spending goals with current progress. No params.", {}, async () => {
3926
+ const goals = getGoalStatuses(db);
3927
+ if (goals.length === 0)
3928
+ return text("No goals set.");
3929
+ const lines = ["period scope limit spent used% status"];
3930
+ for (const goal of goals) {
3931
+ const scope = String(goal["project_path"] ?? goal["agent"] ?? "global").slice(0, 20);
3932
+ const pct = Number(goal["percent_used"]).toFixed(1);
3933
+ const status = goal["is_over"] ? "OVER" : goal["is_at_risk"] ? "AT RISK" : "ON TRACK";
3934
+ lines.push(`${String(goal["period"]).padEnd(9)}${scope.padEnd(21)}${fmtUsd(Number(goal["limit_usd"])).padEnd(11)}${fmtUsd(Number(goal["current_spend_usd"])).padEnd(11)}${pct}% ${status}`);
3935
+ }
3936
+ return text(lines.join(`
3888
3937
  `));
3889
- });
3890
- server.tool("set_goal", "Create/update a spending goal. period(day|week|month|year), limit_usd, project_path?, agent?", {
3891
- period: z.enum(["day", "week", "month", "year"]),
3892
- limit_usd: z.number().positive(),
3893
- project_path: z.string().optional(),
3894
- agent: z.enum(AGENTS).optional()
3895
- }, async ({ period, limit_usd, project_path, agent }) => {
3896
- const now = new Date().toISOString();
3897
- upsertGoal(db, {
3898
- id: randomUUID(),
3899
- period,
3900
- project_path: project_path ?? null,
3901
- agent: agent ?? null,
3902
- limit_usd,
3903
- created_at: now,
3904
- updated_at: now
3905
3938
  });
3906
- return text(`Goal set: ${period} $${limit_usd}`);
3907
- });
3908
- server.tool("remove_goal", "Delete a goal by id.", { id: z.string() }, async ({ id }) => {
3909
- deleteGoal(db, id);
3910
- return text("Goal removed.");
3911
- });
3912
- server.tool("list_machines", "List all machines that have synced data. No params.", {}, async () => {
3913
- const machines = listMachines(db);
3914
- if (machines.length === 0)
3915
- return text(`No machine data yet. Current machine: ${getMachineId()}`);
3916
- const lines = ["machine sessions requests cost last_active"];
3917
- for (const m of machines) {
3918
- lines.push(`${m.machine_id.padEnd(17)}${String(m.sessions).padEnd(10)}${String(m.requests).padEnd(10)}${fmtUsd(m.total_cost_usd).padEnd(12)}${m.last_active?.substring(0, 16) ?? "\u2014"}`);
3919
- }
3920
- lines.push(`
3939
+ server.tool("set_goal", "Create/update a spending goal. period(day|week|month|year), limit_usd, project_path?, agent?", {
3940
+ period: z.enum(["day", "week", "month", "year"]),
3941
+ limit_usd: z.number().positive(),
3942
+ project_path: z.string().optional(),
3943
+ agent: z.string().optional()
3944
+ }, async ({ period, limit_usd, project_path, agent }) => {
3945
+ const now = new Date().toISOString();
3946
+ upsertGoal(db, {
3947
+ id: randomUUID(),
3948
+ period,
3949
+ project_path: project_path ?? null,
3950
+ agent: agent ?? null,
3951
+ limit_usd,
3952
+ created_at: now,
3953
+ updated_at: now
3954
+ });
3955
+ return text(`Goal set: ${period} $${limit_usd}`);
3956
+ });
3957
+ server.tool("remove_goal", "Delete a goal by id.", { id: z.string() }, async ({ id }) => {
3958
+ deleteGoal(db, id);
3959
+ return text("Goal removed.");
3960
+ });
3961
+ server.tool("list_machines", "List all machines that have synced data. No params.", {}, async () => {
3962
+ const machines = listMachines(db);
3963
+ if (machines.length === 0)
3964
+ return text(`No machine data yet. Current machine: ${getMachineId()}`);
3965
+ const lines = ["machine sessions requests cost last_active"];
3966
+ for (const m of machines) {
3967
+ lines.push(`${m.machine_id.padEnd(17)}${String(m.sessions).padEnd(10)}${String(m.requests).padEnd(10)}${fmtUsd(m.total_cost_usd).padEnd(12)}${m.last_active?.substring(0, 16) ?? "\u2014"}`);
3968
+ }
3969
+ lines.push(`
3921
3970
  current machine: ${getMachineId()}`);
3922
- return text(lines.join(`
3971
+ return text(lines.join(`
3923
3972
  `));
3924
- });
3925
- server.tool("register_agent", "Register agent session.", { name: z.string(), session_id: z.string().optional() }, async ({ name }) => {
3926
- const existing = [..._econAgents.values()].find((agent2) => agent2.name === name);
3927
- if (existing) {
3928
- existing.last_seen_at = new Date().toISOString();
3929
- return text(JSON.stringify(existing));
3930
- }
3931
- const id = Math.random().toString(36).slice(2, 10);
3932
- const agent = { id, name, last_seen_at: new Date().toISOString() };
3933
- _econAgents.set(id, agent);
3934
- return text(JSON.stringify(agent));
3935
- });
3936
- server.tool("heartbeat", "Update last_seen_at.", { agent_id: z.string() }, async ({ agent_id }) => {
3937
- const agent = _econAgents.get(agent_id);
3938
- if (!agent)
3939
- return textError("Agent not found");
3940
- agent.last_seen_at = new Date().toISOString();
3941
- return text(`\u2665 ${agent.name}`);
3942
- });
3943
- server.tool("set_focus", "Set active project context.", { agent_id: z.string(), project_id: z.string().optional().nullable() }, async ({ agent_id, project_id }) => {
3944
- const agent = _econAgents.get(agent_id);
3945
- if (!agent)
3946
- return textError("Agent not found");
3947
- agent.project_id = project_id ?? undefined;
3948
- return text(project_id ? `Focus: ${project_id}` : "Focus cleared");
3949
- });
3950
- server.tool("list_agents", "List all registered agents.", {}, async () => text(JSON.stringify([..._econAgents.values()])));
3951
- server.tool("send_feedback", "Send feedback about this service.", {
3952
- message: z.string(),
3953
- email: z.string().optional(),
3954
- category: z.enum(["bug", "feature", "general"]).optional()
3955
- }, async ({ message, email, category }) => {
3956
- try {
3957
- db.prepare("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)").run(message, email ?? null, category ?? "general", packageMetadata.version);
3958
- return text("Feedback saved. Thank you!");
3959
- } catch (error) {
3960
- return textError(String(error));
3973
+ });
3974
+ server.tool("register_agent", "Register agent session.", { name: z.string(), session_id: z.string().optional() }, async ({ name }) => {
3975
+ const existing = [..._econAgents.values()].find((agent2) => agent2.name === name);
3976
+ if (existing) {
3977
+ existing.last_seen_at = new Date().toISOString();
3978
+ return text(JSON.stringify(existing));
3979
+ }
3980
+ const id = Math.random().toString(36).slice(2, 10);
3981
+ const agent = { id, name, last_seen_at: new Date().toISOString() };
3982
+ _econAgents.set(id, agent);
3983
+ return text(JSON.stringify(agent));
3984
+ });
3985
+ server.tool("heartbeat", "Update last_seen_at.", { agent_id: z.string() }, async ({ agent_id }) => {
3986
+ const agent = _econAgents.get(agent_id);
3987
+ if (!agent)
3988
+ return textError("Agent not found");
3989
+ agent.last_seen_at = new Date().toISOString();
3990
+ return text(`\u2665 ${agent.name}`);
3991
+ });
3992
+ server.tool("set_focus", "Set active project context.", { agent_id: z.string(), project_id: z.string().optional().nullable() }, async ({ agent_id, project_id }) => {
3993
+ const agent = _econAgents.get(agent_id);
3994
+ if (!agent)
3995
+ return textError("Agent not found");
3996
+ agent.project_id = project_id ?? undefined;
3997
+ return text(project_id ? `Focus: ${project_id}` : "Focus cleared");
3998
+ });
3999
+ server.tool("list_agents", "List all registered agents.", {}, async () => text(JSON.stringify([..._econAgents.values()])));
4000
+ server.tool("send_feedback", "Send feedback about this service.", {
4001
+ message: z.string(),
4002
+ email: z.string().optional(),
4003
+ category: z.enum(["bug", "feature", "general"]).optional()
4004
+ }, async ({ message, email, category }) => {
4005
+ try {
4006
+ db.prepare("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)").run(message, email ?? null, category ?? "general", packageMetadata.version);
4007
+ return text("Feedback saved. Thank you!");
4008
+ } catch (error) {
4009
+ return textError(String(error));
4010
+ }
4011
+ });
4012
+ registerCloudTools(server, MCP_NAME, {
4013
+ dbPath: getDbPath(),
4014
+ migrations: PG_MIGRATIONS
4015
+ });
4016
+ return server;
4017
+ }
4018
+
4019
+ // src/mcp/http.ts
4020
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
4021
+ var MCP_HTTP_IDLE_TIMEOUT_SECONDS = 0;
4022
+ function isHttpMode(argv = process.argv.slice(2)) {
4023
+ return argv.includes("--http") || process.env["MCP_HTTP"] === "1";
4024
+ }
4025
+ function isStdioMode(argv = process.argv.slice(2)) {
4026
+ return argv.includes("--stdio") || process.env["MCP_STDIO"] === "1";
4027
+ }
4028
+ function resolveHttpPort(argv = process.argv.slice(2)) {
4029
+ for (let i = 0;i < argv.length; i++) {
4030
+ const arg = argv[i];
4031
+ if (arg === "--port" || arg === "-p") {
4032
+ const raw = argv[i + 1];
4033
+ if (!raw)
4034
+ throw new Error(`Invalid port: ${raw ?? ""}`);
4035
+ return parsePort(raw, "port");
4036
+ }
3961
4037
  }
4038
+ const fromEnv = process.env["MCP_HTTP_PORT"];
4039
+ if (fromEnv)
4040
+ return parsePort(fromEnv, "MCP_HTTP_PORT");
4041
+ return DEFAULT_MCP_HTTP_PORT;
4042
+ }
4043
+ function parsePort(raw, label) {
4044
+ const value = Number(raw);
4045
+ if (!Number.isInteger(value) || value < 1 || value > 65535) {
4046
+ throw new Error(`Invalid ${label}: ${raw}`);
4047
+ }
4048
+ return value;
4049
+ }
4050
+ async function handleMcpHttpRequest(req) {
4051
+ const url = new URL(req.url);
4052
+ if (url.pathname === "/health" && req.method === "GET") {
4053
+ return Response.json({ status: "ok", name: MCP_NAME });
4054
+ }
4055
+ if (url.pathname === "/mcp") {
4056
+ const transport = new WebStandardStreamableHTTPServerTransport({
4057
+ sessionIdGenerator: undefined
4058
+ });
4059
+ const server = buildServer();
4060
+ await server.connect(transport);
4061
+ return transport.handleRequest(req);
4062
+ }
4063
+ return new Response("Not Found", { status: 404 });
4064
+ }
4065
+ function startHttpServer(options = {}) {
4066
+ const port = options.port ?? DEFAULT_MCP_HTTP_PORT;
4067
+ const hostname2 = options.hostname ?? "127.0.0.1";
4068
+ const log = options.log ?? console.error;
4069
+ const server = Bun.serve({
4070
+ port,
4071
+ hostname: hostname2,
4072
+ idleTimeout: MCP_HTTP_IDLE_TIMEOUT_SECONDS,
4073
+ fetch: handleMcpHttpRequest
4074
+ });
4075
+ const address = `http://${hostname2}:${server.port}`;
4076
+ log(`${MCP_NAME}-mcp HTTP listening on ${address}/mcp (health: ${address}/health)`);
4077
+ return server;
4078
+ }
4079
+
4080
+ // src/mcp/index.ts
4081
+ function printHelp() {
4082
+ console.log(`Usage: economy-mcp [options]
4083
+
4084
+ Runs the ${packageMetadata.name} MCP server (stdio by default).
4085
+
4086
+ Options:
4087
+ --http Serve MCP over Streamable HTTP on 127.0.0.1
4088
+ -p, --port <port> HTTP port (default: MCP_HTTP_PORT or 8860)
4089
+ -V, --version output the version number
4090
+ -h, --help display help for command
4091
+
4092
+ Environment:
4093
+ MCP_HTTP=1 Enable HTTP mode
4094
+ MCP_HTTP_PORT Override default HTTP port (8860)`);
4095
+ }
4096
+ var args = process.argv.slice(2);
4097
+ if (args.includes("--help") || args.includes("-h")) {
4098
+ printHelp();
4099
+ process.exit(0);
4100
+ }
4101
+ if (args.includes("--version") || args.includes("-V")) {
4102
+ console.log(packageMetadata.version);
4103
+ process.exit(0);
4104
+ }
4105
+ async function main() {
4106
+ if (isStdioMode(args) || !isHttpMode(args)) {
4107
+ const server = buildServer();
4108
+ const transport = new StdioServerTransport;
4109
+ await server.connect(transport);
4110
+ return;
4111
+ }
4112
+ startHttpServer({ port: resolveHttpPort(args) });
4113
+ await new Promise(() => {});
4114
+ }
4115
+ main().catch((error) => {
4116
+ console.error("MCP server error:", error);
4117
+ process.exit(1);
3962
4118
  });
3963
- var transport = new StdioServerTransport;
3964
- registerCloudTools(server, "economy", {
3965
- dbPath: getDbPath(),
3966
- migrations: PG_MIGRATIONS
3967
- });
3968
- await server.connect(transport);