@hasna/economy 0.5.2 → 0.5.4

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +4 -2
  3. package/dist/chunks/{billing-bkpxy4kv.js → billing-22az60hy.js} +2 -2
  4. package/dist/chunks/{config-j9mv3ehk.js → config-hsp7ndhj.js} +2 -2
  5. package/dist/chunks/{index-npsjjv96.js → index-9arv0b1j.js} +162 -36
  6. package/dist/chunks/{index-hesxq90g.js → index-9wa1f2kv.js} +1 -1
  7. package/dist/chunks/{index-zpvea08j.js → index-c7h2rxx1.js} +38 -12
  8. package/dist/chunks/{index-9d2aky06.js → index-ct3zdefz.js} +3 -3
  9. package/dist/chunks/{index-k540hfww.js → index-dk5rq1rq.js} +1 -1
  10. package/dist/chunks/{index-ez9thsr7.js → index-f5jv6q5q.js} +38 -12
  11. package/dist/chunks/{index-dj3tctqh.js → index-n1wwqxfe.js} +14 -13
  12. package/dist/chunks/{index-kzwqw3ve.js → index-nheq2nrz.js} +2 -2
  13. package/dist/chunks/{index-2hwzmfgv.js → index-vajzfzgv.js} +38 -12
  14. package/dist/chunks/{open-projects-6z4pq6mx.js → open-projects-8qm46m3d.js} +1 -1
  15. package/dist/chunks/{pricing-vf2pycmh.js → pricing-462f8xkm.js} +1 -1
  16. package/dist/chunks/{pricing-f80p81gr.js → pricing-k9wsjwbz.js} +1 -1
  17. package/dist/chunks/{pricing-y21mcjsa.js → pricing-vxejj1qa.js} +1 -1
  18. package/dist/chunks/{serve-p2yarhm8.js → serve-ra46ms69.js} +18 -10
  19. package/dist/chunks/{sqlite-store-st99garx.js → sqlite-store-13nhr1z0.js} +2 -2
  20. package/dist/chunks/{sqlite-store-501f32ts.js → sqlite-store-gamn65xp.js} +2 -2
  21. package/dist/chunks/{sqlite-store-fq4natnf.js → sqlite-store-ht6frztj.js} +2 -2
  22. package/dist/chunks/{tui-x64hjfrh.js → tui-a1tvcw2j.js} +4 -4
  23. package/dist/chunks/{watch-bxstjpbx.js → watch-rvd3dn6r.js} +5 -5
  24. package/dist/chunks/{webhooks-k4fh2t9c.js → webhooks-nw4daw9y.js} +20 -9
  25. package/dist/cli/index.js +27 -27
  26. package/dist/db/database.d.ts +2 -2
  27. package/dist/db/database.d.ts.map +1 -1
  28. package/dist/db/sync-pg.d.ts.map +1 -1
  29. package/dist/index.js +15 -14
  30. package/dist/lib/accounts-store.d.ts +25 -1
  31. package/dist/lib/accounts-store.d.ts.map +1 -1
  32. package/dist/lib/accounts.d.ts.map +1 -1
  33. package/dist/lib/analytics.d.ts +1 -1
  34. package/dist/lib/analytics.d.ts.map +1 -1
  35. package/dist/lib/periods.d.ts +20 -1
  36. package/dist/lib/periods.d.ts.map +1 -1
  37. package/dist/lib/spikes.d.ts +3 -3
  38. package/dist/lib/spikes.d.ts.map +1 -1
  39. package/dist/lib/subscriptions-registry.d.ts +26 -0
  40. package/dist/lib/subscriptions-registry.d.ts.map +1 -0
  41. package/dist/lib/test-keychain-fixture.d.ts +3 -2
  42. package/dist/lib/test-keychain-fixture.d.ts.map +1 -1
  43. package/dist/mcp/index.js +245 -118
  44. package/dist/otel/index.js +56 -27
  45. package/dist/server/index.js +246 -77
  46. package/dist/server/service-operations.d.ts.map +1 -1
  47. package/dist/server/service-worker.js +237 -68
  48. package/package.json +1 -1
package/dist/mcp/index.js CHANGED
@@ -56,6 +56,7 @@ import {
56
56
  queryZeroCostTokenizedModels,
57
57
  requireCurrentBillingSummary,
58
58
  requireCurrentCostSummary,
59
+ resolveReportingInterval,
59
60
  rollupSession,
60
61
  scanReceiptId,
61
62
  sessionOverlapWhere,
@@ -74,7 +75,7 @@ import {
74
75
  upsertUsageSnapshot,
75
76
  usageSnapshotFilterForPeriod,
76
77
  validateIngest
77
- } from "../chunks/index-2hwzmfgv.js";
78
+ } from "../chunks/index-vajzfzgv.js";
78
79
  import {
79
80
  __require
80
81
  } from "../chunks/index-p35cyhc4.js";
@@ -84,7 +85,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
84
85
  // package.json
85
86
  var package_default = {
86
87
  name: "@hasna/economy",
87
- version: "0.5.2",
88
+ version: "0.5.4",
88
89
  description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
89
90
  repository: {
90
91
  type: "git",
@@ -1506,7 +1507,7 @@ function registerIngestJournalTool(server) {
1506
1507
  }
1507
1508
 
1508
1509
  // src/mcp/server.ts
1509
- import { z as z6 } from "zod";
1510
+ import { z as z7 } from "zod";
1510
1511
 
1511
1512
  // src/ingest/collection-outcome.ts
1512
1513
  import { AsyncLocalStorage } from "async_hooks";
@@ -1562,34 +1563,141 @@ import { join as join6, basename } from "path";
1562
1563
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
1563
1564
  import { homedir } from "os";
1564
1565
  import { join as join5 } from "path";
1565
- import { z as z5 } from "zod";
1566
+ import { z as z6 } from "zod";
1566
1567
  import { resolveClientTransport, resolveCredential } from "@hasna/contracts/client";
1567
- var ACCOUNTS_APP = "accounts";
1568
- var profileNameSchema = z5.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase alphanumeric/hyphen and start with a letter or digit");
1568
+
1569
+ // src/lib/subscriptions-registry.ts
1570
+ import { execFile } from "child_process";
1571
+ import { promisify } from "util";
1572
+ import { isAbsolute } from "path";
1573
+ import { z as z5 } from "zod";
1574
+ var execute = promisify(execFile);
1575
+ var readSubscriptionsMetadata = async (args, env) => {
1576
+ let stdout;
1577
+ try {
1578
+ const result = await execute("subscriptions", args, {
1579
+ env,
1580
+ encoding: "utf8",
1581
+ timeout: 30000,
1582
+ maxBuffer: 16 * 1024 * 1024,
1583
+ windowsHide: true
1584
+ });
1585
+ stdout = result.stdout;
1586
+ } catch (error) {
1587
+ const code = error?.code;
1588
+ if (code === "ENOENT")
1589
+ throw new Error("subscriptions: CLI not found on PATH; install the Subscriptions CLI to resolve station account metadata");
1590
+ const status = typeof code === "number" && Number.isInteger(code) ? `exit ${code}` : "execution limit or transport failure";
1591
+ throw new Error(`subscriptions: read-only metadata command failed (${status}); verify the installed CLI and its hosted credentials`);
1592
+ }
1593
+ try {
1594
+ return JSON.parse(stdout);
1595
+ } catch {
1596
+ throw new Error("subscriptions: metadata command returned invalid JSON");
1597
+ }
1598
+ };
1599
+ var nonempty = z5.string().min(1);
1569
1600
  var profileSchema = z5.object({
1570
- name: z5.string(),
1571
- tool: z5.string(),
1601
+ name: nonempty,
1602
+ tool: nonempty,
1572
1603
  email: z5.string().optional(),
1573
1604
  displayName: z5.string().optional(),
1574
- dir: z5.string(),
1605
+ dir: nonempty.refine(isAbsolute).refine((value) => !/[\0\r\n]/.test(value)),
1606
+ createdAt: nonempty,
1607
+ lastUsedAt: z5.string().optional(),
1575
1608
  description: z5.string().optional(),
1576
- createdAt: z5.string(),
1577
- lastUsedAt: z5.string().optional()
1609
+ active: z5.boolean(),
1610
+ applied: z5.boolean(),
1611
+ switchedAway: z5.object({ profile: nonempty, email: z5.string().optional() }).optional()
1612
+ });
1613
+ var toolsSchema = z5.array(z5.object({
1614
+ id: nonempty,
1615
+ label: nonempty,
1616
+ envVar: nonempty,
1617
+ defaultDir: nonempty,
1618
+ bin: nonempty
1619
+ }));
1620
+ function unique(rows, identity) {
1621
+ if (new Set(rows.map(identity)).size !== rows.length) {
1622
+ throw new Error("subscriptions: duplicate identity in metadata response");
1623
+ }
1624
+ return rows;
1625
+ }
1626
+
1627
+ class SubscriptionsRegistry {
1628
+ read;
1629
+ transport = "api";
1630
+ registry = "subscriptions";
1631
+ env;
1632
+ profiles;
1633
+ tools;
1634
+ constructor(baseUrl, env, read = readSubscriptionsMetadata) {
1635
+ this.read = read;
1636
+ this.env = { ...env, HASNA_SUBSCRIPTIONS_API_URL: baseUrl.replace(/\/v1\/?$/, "") };
1637
+ }
1638
+ allProfiles() {
1639
+ return this.profiles ??= this.read(["list", "--json"], this.env).then((raw) => {
1640
+ const parsed = z5.array(profileSchema).safeParse(raw);
1641
+ if (!parsed.success)
1642
+ throw new Error("subscriptions: invalid profile metadata response");
1643
+ return unique(parsed.data, (row) => JSON.stringify([row.tool, row.name]));
1644
+ });
1645
+ }
1646
+ async listProfiles(tool) {
1647
+ const rows = await this.allProfiles();
1648
+ return rows.filter((row) => !tool || row.tool === tool);
1649
+ }
1650
+ async findProfile(name, tool) {
1651
+ const matches = (await this.listProfiles(tool)).filter((row) => row.name === name);
1652
+ return matches.length === 1 ? matches[0] : undefined;
1653
+ }
1654
+ async appliedProfile(tool) {
1655
+ const matches = (await this.allProfiles()).filter((row) => row.tool === tool && row.applied);
1656
+ if (matches.length > 1)
1657
+ throw new Error("subscriptions: ambiguous station applied-profile binding");
1658
+ return matches[0];
1659
+ }
1660
+ async currentProfile(_tool) {
1661
+ return;
1662
+ }
1663
+ async listTools() {
1664
+ return this.tools ??= this.read(["tools", "list", "--json"], this.env).then((raw) => {
1665
+ const parsed = toolsSchema.safeParse(raw);
1666
+ if (!parsed.success)
1667
+ throw new Error("subscriptions: invalid tool metadata response");
1668
+ return unique(parsed.data, (row) => row.id);
1669
+ });
1670
+ }
1671
+ }
1672
+
1673
+ // src/lib/accounts-store.ts
1674
+ var ACCOUNTS_APP = "accounts";
1675
+ var profileNameSchema = z6.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase alphanumeric/hyphen and start with a letter or digit");
1676
+ var profileSchema2 = z6.object({
1677
+ name: z6.string(),
1678
+ tool: z6.string(),
1679
+ email: z6.string().optional(),
1680
+ displayName: z6.string().optional(),
1681
+ dir: z6.string(),
1682
+ description: z6.string().optional(),
1683
+ createdAt: z6.string(),
1684
+ lastUsedAt: z6.string().optional(),
1685
+ switchedAway: z6.object({ profile: z6.string().min(1), email: z6.string().optional() }).optional()
1578
1686
  });
1579
- var toolDefSchema = z5.object({
1580
- id: z5.string(),
1581
- label: z5.string(),
1582
- envVar: z5.string(),
1583
- defaultDir: z5.string(),
1584
- bin: z5.string()
1687
+ var toolDefSchema = z6.object({
1688
+ id: z6.string(),
1689
+ label: z6.string(),
1690
+ envVar: z6.string(),
1691
+ defaultDir: z6.string(),
1692
+ bin: z6.string()
1585
1693
  });
1586
- var storeSchema = z5.object({
1587
- version: z5.literal(1),
1588
- current: z5.record(z5.string(), z5.string()).default({}),
1589
- applied: z5.record(z5.string(), z5.string()).default({}),
1590
- toolLocks: z5.record(z5.string(), z5.string()).default({}),
1591
- profiles: z5.array(profileSchema).default([]),
1592
- tools: z5.array(toolDefSchema).default([])
1694
+ var storeSchema = z6.object({
1695
+ version: z6.literal(1),
1696
+ current: z6.record(z6.string(), z6.string()).default({}),
1697
+ applied: z6.record(z6.string(), z6.string()).default({}),
1698
+ toolLocks: z6.record(z6.string(), z6.string()).default({}),
1699
+ profiles: z6.array(profileSchema2).default([]),
1700
+ tools: z6.array(toolDefSchema).default([])
1593
1701
  });
1594
1702
  function localRegistryAllowed(env = process.env) {
1595
1703
  return LOCAL_STORAGE_OPT_IN_KEYS.some((key) => {
@@ -1871,25 +1979,34 @@ class ApiStore {
1871
1979
  }
1872
1980
  }
1873
1981
  function resolveStore(env = process.env, options = {}) {
1982
+ const legacy = resolveRegistryTransport(ACCOUNTS_APP, env, options.credentials);
1983
+ if (legacy)
1984
+ return new ApiStore(legacy.baseUrl, legacy.apiKey);
1985
+ const current = resolveRegistryTransport("subscriptions", env, options.subscriptionsCredentials);
1986
+ if (current)
1987
+ return new SubscriptionsRegistry(current.baseUrl, env, options.readSubscriptionsMetadata);
1988
+ return unconfiguredStore(env);
1989
+ }
1990
+ function resolveRegistryTransport(app, env, credentials) {
1874
1991
  const clientEnv = env;
1875
- const credential = resolveCredential(ACCOUNTS_APP, clientEnv, options.credentials);
1992
+ const credential = resolveCredential(app, clientEnv, credentials);
1876
1993
  if (credential === null) {
1877
1994
  try {
1878
- resolveClientTransport(ACCOUNTS_APP, clientEnv, options.credentials ? { credentials: options.credentials } : {});
1995
+ resolveClientTransport(app, clientEnv, credentials ? { credentials } : {});
1879
1996
  } catch (err) {
1880
1997
  if (isNoAccountsCredentialConfigurationError(err))
1881
- return unconfiguredStore(env);
1998
+ return null;
1882
1999
  throw err;
1883
2000
  }
1884
- return unconfiguredStore(env);
2001
+ return null;
1885
2002
  }
1886
2003
  if (credential.tier === "pointer") {
1887
- throw new Error(`accounts: ${credential.source} names a secrets-vault item, and the local accounts API store resolves ` + "credentials synchronously, so it cannot complete the pointer per request. It is a deliberate " + "selection and is not resolved around. Use a literal tier instead \u2014 the Keychain item " + "hasna.credentials.accounts.api-key, ~/.hasna/accounts/config/credentials, or HASNA_ACCOUNTS_API_KEY.");
2004
+ throw new Error(`${app}: ${credential.source} names a secrets-vault item, and the registry adapter resolves ` + "credentials synchronously, so it cannot complete the pointer per request. It is a deliberate " + "selection and is not resolved around. Use a literal tier instead \u2014 the Keychain item " + `hasna.credentials.${app}.api-key, ~/.hasna/${app}/config/credentials, or HASNA_${app.toUpperCase()}_API_KEY.`);
1888
2005
  }
1889
- const resolution = resolveClientTransport(ACCOUNTS_APP, clientEnv, {
1890
- credentials: { ...options.credentials, apiKey: credential.apiKey }
2006
+ const resolution = resolveClientTransport(app, clientEnv, {
2007
+ credentials: { ...credentials, apiKey: credential.apiKey }
1891
2008
  });
1892
- return new ApiStore(resolution.baseUrl, credential.apiKey);
2009
+ return { baseUrl: resolution.baseUrl, apiKey: credential.apiKey };
1893
2010
  }
1894
2011
  function isNoAccountsCredentialConfigurationError(error) {
1895
2012
  if (typeof error !== "object" || error === null)
@@ -1906,10 +2023,10 @@ function unconfiguredStore(env) {
1906
2023
  return new NoRegistryStore;
1907
2024
  }
1908
2025
  function accountsLocalModeNotice() {
1909
- return `accounts: LOCAL mode (${LOCAL_STORAGE_OPT_IN_KEYS[0]}=1) \u2014 no Hasna Accounts credential resolved, so ` + "attribution reads the on-box JSON registry (~/.hasna/accounts/accounts.json) instead of accounts-serve. " + "To go hosted, put the fleet key in the Keychain item hasna.credentials.accounts.api-key, write " + "~/.hasna/accounts/config/credentials, or set HASNA_ACCOUNTS_API_KEY.";
2026
+ return `accounts: LOCAL mode (${LOCAL_STORAGE_OPT_IN_KEYS[0]}=1) \u2014 neither Accounts nor Subscriptions credentials resolved, so ` + "attribution reads the on-box JSON registry (~/.hasna/accounts/accounts.json) instead of accounts-serve. " + "For hosted attribution, configure the Subscriptions CLI through its normal sign-in or credential resolver.";
1910
2027
  }
1911
2028
  function accountsRefusalNotice() {
1912
- return "accounts: no Hasna Accounts credential resolved (Keychain item hasna.credentials.accounts.api-key, " + "~/.hasna/accounts/config/credentials, HASNA_ACCOUNTS_API_KEY), so spend attribution is omitted. The " + `on-box registry (~/.hasna/accounts/accounts.json) is NOT read without ${LOCAL_STORAGE_OPT_IN_KEYS[0]}=1 \u2014 ` + "economy fails closed instead of attributing hosted spend from an on-box file.";
2029
+ return "accounts: neither Accounts nor Subscriptions credentials resolved (legacy hasna.credentials.accounts.api-key, " + "~/.hasna/accounts/config/credentials, HASNA_ACCOUNTS_API_KEY; current hasna.credentials.subscriptions.api-key, " + "~/.hasna/subscriptions/config/credentials, HASNA_SUBSCRIPTIONS_API_KEY), so spend attribution is omitted. The " + `on-box registry (~/.hasna/accounts/accounts.json) is NOT read without ${LOCAL_STORAGE_OPT_IN_KEYS[0]}=1 \u2014 ` + "economy fails closed instead of attributing hosted spend from an on-box file.";
1913
2030
  }
1914
2031
  var accountsLocalNoticePrinted = false;
1915
2032
  function announceAccountsLocalMode() {
@@ -1997,7 +2114,10 @@ async function profileForEnvDir(store, tool, env) {
1997
2114
  if (!configuredDir)
1998
2115
  return null;
1999
2116
  const normalized = normalizeDir(configuredDir);
2000
- return (await store.listProfiles(tool.id)).find((profile) => normalizeDir(profile.dir) === normalized) ?? null;
2117
+ const matches = (await store.listProfiles(tool.id)).filter((profile) => normalizeDir(profile.dir) === normalized);
2118
+ if (matches.length > 1)
2119
+ throw new Error("account attribution: multiple profiles match the configured directory");
2120
+ return matches[0] ?? null;
2001
2121
  }
2002
2122
  async function resolveAccountForAgent(agent, env = process.env, options = {}) {
2003
2123
  const override = envOverride(agent, env);
@@ -2011,8 +2131,15 @@ async function resolveAccountForAgent(agent, env = process.env, options = {}) {
2011
2131
  continue;
2012
2132
  const envProfile = await profileForEnvDir(store, tool, env);
2013
2133
  if (envProfile)
2014
- return fromProfile(envProfile, "env");
2015
- const appliedName = appliedProfileName(toolId);
2134
+ return envProfile.switchedAway ? null : fromProfile(envProfile, "env");
2135
+ if (store.registry === "subscriptions" && env[tool.envVar])
2136
+ return null;
2137
+ if (store.appliedProfile) {
2138
+ const applied = await store.appliedProfile(toolId);
2139
+ if (applied)
2140
+ return applied.switchedAway ? null : fromProfile(applied, "applied");
2141
+ }
2142
+ const appliedName = store.registry === "subscriptions" ? undefined : appliedProfileName(toolId);
2016
2143
  if (appliedName) {
2017
2144
  const applied = await store.findProfile(appliedName, toolId);
2018
2145
  if (applied)
@@ -3579,7 +3706,7 @@ function backfillMachineId(db) {
3579
3706
  return { requests: Number(reqCount.changes), sessions: Number(sessCount.changes) };
3580
3707
  }
3581
3708
  async function recalculateZeroCostRequests(db) {
3582
- const { assessCostFromDb: assessCostFromDb2 } = await import("../chunks/pricing-vf2pycmh.js");
3709
+ const { assessCostFromDb: assessCostFromDb2 } = await import("../chunks/pricing-vxejj1qa.js");
3583
3710
  const zeroRows = db.prepare(`SELECT id, model, input_tokens, output_tokens, cache_read_tokens, cache_create_tokens, cache_create_5m_tokens, cache_create_1h_tokens
3584
3711
  FROM requests
3585
3712
  WHERE cost_usd = 0
@@ -3788,7 +3915,7 @@ async function postIngest(cloud, body) {
3788
3915
  }
3789
3916
  async function syncAllToCloud(cloud, opts = {}) {
3790
3917
  await runIngestJournalOperation(cloud, { action: "reconcile" });
3791
- const { openDatabase } = await import("../chunks/sqlite-store-st99garx.js");
3918
+ const { openDatabase } = await import("../chunks/sqlite-store-gamn65xp.js");
3792
3919
  const scratch = openDatabase(":memory:", true);
3793
3920
  ensurePricingSeeded(scratch);
3794
3921
  const cachePath = opts.cachePath ?? getIngestCachePath();
@@ -4714,7 +4841,8 @@ function buildBrief(db, opts = {}) {
4714
4841
  }
4715
4842
 
4716
4843
  // src/lib/analytics.ts
4717
- function queryProjectDetail(db, nameOrPath) {
4844
+ function queryProjectDetail(db, nameOrPath, now2 = new Date) {
4845
+ const dailyBounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: 14 }, now2);
4718
4846
  const like = `%${nameOrPath}%`;
4719
4847
  const sessions = db.prepare(`SELECT id, project_name, project_path, total_cost_usd, total_tokens, started_at
4720
4848
  FROM economy_effective_sessions
@@ -4728,8 +4856,8 @@ function queryProjectDetail(db, nameOrPath) {
4728
4856
  const daily = db.prepare(`SELECT DATE(r.timestamp) as date, COALESCE(SUM(r.cost_usd), 0) as cost_usd
4729
4857
  FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
4730
4858
  WHERE (s.project_name LIKE ? OR s.project_path LIKE ?)
4731
- AND r.timestamp >= DATE('now', '-14 days')
4732
- GROUP BY date ORDER BY date ASC`).all(like, like);
4859
+ AND r.timestamp >= ? AND r.timestamp < ?
4860
+ GROUP BY date ORDER BY date ASC`).all(like, like, dailyBounds.start, dailyBounds.end_exclusive);
4733
4861
  const models = db.prepare(`SELECT r.model as model, COUNT(*) as requests, COALESCE(SUM(r.cost_usd), 0) as cost_usd
4734
4862
  FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
4735
4863
  WHERE s.project_name LIKE ? OR s.project_path LIKE ?
@@ -4768,18 +4896,17 @@ function queryRangeStats(db, from, to) {
4768
4896
  return { cost: r.cost, requests: r.requests, tokens: r.tokens, sessions: s.sessions };
4769
4897
  }
4770
4898
  function queryForecast(db, now2 = new Date) {
4771
- const daysInMonth = new Date(now2.getFullYear(), now2.getMonth() + 1, 0).getDate();
4772
- const dayOfMonth = now2.getDate();
4773
- const monthStart = `${now2.getFullYear()}-${String(now2.getMonth() + 1).padStart(2, "0")}-01`;
4774
- const monthSoFar = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE DATE(timestamp) >= ?`).get(monthStart);
4899
+ const month = resolveReportingInterval({ kind: "calendar", period: "month" }, now2);
4900
+ const recent = resolveReportingInterval({ kind: "rolling", unit: "day", count: 7 }, now2);
4901
+ const daysInMonth = new Date(Date.UTC(now2.getUTCFullYear(), now2.getUTCMonth() + 1, 0)).getUTCDate();
4902
+ const dayOfMonth = now2.getUTCDate();
4903
+ const monthSoFar = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ?`).get(month.start, recent.end_exclusive);
4775
4904
  const dailyAvg = dayOfMonth > 0 ? monthSoFar.cost / dayOfMonth : 0;
4776
4905
  const projected = dailyAvg * daysInMonth;
4777
- const sevenDaysAgo = new Date(now2);
4778
- sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
4779
- const last7 = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE DATE(timestamp) >= ?`).get(sevenDaysAgo.toISOString().substring(0, 10));
4906
+ const last7 = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ?`).get(recent.start, recent.end_exclusive);
4780
4907
  const last7DailyAvg = last7.cost / 7;
4781
4908
  const dailyCosts = db.prepare(`SELECT DATE(timestamp) as date, COALESCE(SUM(cost_usd),0) as cost_usd
4782
- FROM economy_effective_requests WHERE DATE(timestamp) >= ? GROUP BY date ORDER BY cost_usd ASC`).all(monthStart);
4909
+ FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ? GROUP BY date ORDER BY cost_usd ASC`).all(month.start, recent.end_exclusive);
4783
4910
  return {
4784
4911
  day_of_month: dayOfMonth,
4785
4912
  days_in_month: daysInMonth,
@@ -4830,7 +4957,7 @@ class LocalStore2 {
4830
4957
  _db;
4831
4958
  async db() {
4832
4959
  if (!this._db) {
4833
- const { openDatabase } = await import("../chunks/sqlite-store-st99garx.js");
4960
+ const { openDatabase } = await import("../chunks/sqlite-store-gamn65xp.js");
4834
4961
  this._db = openDatabase();
4835
4962
  ensurePricingSeeded(this._db);
4836
4963
  }
@@ -5193,7 +5320,7 @@ function buildServer() {
5193
5320
  let _db;
5194
5321
  const localDb = async () => {
5195
5322
  if (!_db) {
5196
- const { openDatabase } = await import("../chunks/sqlite-store-st99garx.js");
5323
+ const { openDatabase } = await import("../chunks/sqlite-store-gamn65xp.js");
5197
5324
  _db = openDatabase();
5198
5325
  ensurePricingSeeded(_db);
5199
5326
  }
@@ -5331,17 +5458,17 @@ function buildServer() {
5331
5458
  function textError(message) {
5332
5459
  return { content: [{ type: "text", text: message }], isError: true };
5333
5460
  }
5334
- server.tool("search_tools", "List tool names matching query. Use first to find relevant tools.", { query: z6.string().optional() }, async ({ query }) => {
5461
+ server.tool("search_tools", "List tool names matching query. Use first to find relevant tools.", { query: z7.string().optional() }, async ({ query }) => {
5335
5462
  const q2 = query?.toLowerCase();
5336
5463
  const matches = q2 ? TOOL_NAMES.filter((name) => name.includes(q2)) : [...TOOL_NAMES];
5337
5464
  return text(matches.join(", "));
5338
5465
  });
5339
- server.tool("describe_tools", "Get param hints for specific tools by name.", { names: z6.array(z6.string()) }, async ({ names }) => {
5466
+ server.tool("describe_tools", "Get param hints for specific tools by name.", { names: z7.array(z7.string()) }, async ({ names }) => {
5340
5467
  const result = names.map((name) => `${name}: ${TOOL_DESCRIPTIONS[name] ?? "see tool schema"}`).join(`
5341
5468
  `);
5342
5469
  return text(result);
5343
5470
  });
5344
- server.tool("get_cost_summary", "Stored usage-value summary with pricing coverage; never a paid-spend or complete-source claim. period: today|week|month|year|all. machine: filter by hostname.", { period: z6.enum(["today", "week", "month", "year", "all"]).optional(), machine: z6.string().optional() }, async ({ period, machine }) => {
5471
+ server.tool("get_cost_summary", "Stored usage-value summary with pricing coverage; never a paid-spend or complete-source claim. period: today|week|month|year|all. machine: filter by hostname.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional(), machine: z7.string().optional() }, async ({ period, machine }) => {
5345
5472
  const resolved = period ?? "today";
5346
5473
  const s = await store.summary(resolved, machine);
5347
5474
  const machineLabel = machine ? ` on ${machine}` : "";
@@ -5357,11 +5484,11 @@ function buildServer() {
5357
5484
  `));
5358
5485
  });
5359
5486
  server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, account, machine, limit(20)", {
5360
- agent: z6.enum(AGENTS).optional(),
5361
- project: z6.string().optional(),
5362
- account: z6.string().optional(),
5363
- machine: z6.string().optional(),
5364
- limit: z6.number().int().positive().max(100).optional()
5487
+ agent: z7.enum(AGENTS).optional(),
5488
+ project: z7.string().optional(),
5489
+ account: z7.string().optional(),
5490
+ machine: z7.string().optional(),
5491
+ limit: z7.number().int().positive().max(100).optional()
5365
5492
  }, async ({ agent, project, account, machine, limit }) => {
5366
5493
  const sessions = await store.sessions({ agent, project, account, machine, limit: limit ?? 20 });
5367
5494
  const lines = ["id agent cost tokens project"];
@@ -5371,8 +5498,8 @@ function buildServer() {
5371
5498
  `));
5372
5499
  });
5373
5500
  server.tool("get_top_sessions", "Top sessions by cost. Params: n(10), agent", {
5374
- n: z6.number().int().positive().max(100).optional(),
5375
- agent: z6.enum(AGENTS).optional()
5501
+ n: z7.number().int().positive().max(100).optional(),
5502
+ agent: z7.enum(AGENTS).optional()
5376
5503
  }, async ({ n, agent }) => {
5377
5504
  const sessions = await store.topSessions(n ?? 10, agent);
5378
5505
  const lines = ["rank id agent cost tokens project"];
@@ -5380,7 +5507,7 @@ function buildServer() {
5380
5507
  return text(lines.join(`
5381
5508
  `));
5382
5509
  });
5383
- server.tool("get_model_breakdown", "Cost per model. Params: limit(20), verbose, json.", { limit: z6.number().int().positive().max(100).optional(), verbose: z6.boolean().optional(), json: z6.boolean().optional() }, async ({ limit, verbose, json }) => {
5510
+ server.tool("get_model_breakdown", "Cost per model. Params: limit(20), verbose, json.", { limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ limit, verbose, json }) => {
5384
5511
  const rows = await store.modelBreakdown();
5385
5512
  if (json)
5386
5513
  return text(JSON.stringify(rows, null, 2));
@@ -5395,7 +5522,7 @@ function buildServer() {
5395
5522
  return text(lines.join(`
5396
5523
  `));
5397
5524
  });
5398
- server.tool("get_project_breakdown", "Cost per project. Params: period(today|week|month|year|all), limit(20), verbose, json.", { period: z6.enum(["today", "week", "month", "year", "all"]).optional(), limit: z6.number().int().positive().max(100).optional(), verbose: z6.boolean().optional(), json: z6.boolean().optional() }, async ({ period, limit, verbose, json }) => {
5525
+ server.tool("get_project_breakdown", "Cost per project. Params: period(today|week|month|year|all), limit(20), verbose, json.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional(), limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ period, limit, verbose, json }) => {
5399
5526
  const rows = await store.projectBreakdown({ period: period ?? "all" });
5400
5527
  if (json)
5401
5528
  return text(JSON.stringify(rows, null, 2));
@@ -5411,7 +5538,7 @@ function buildServer() {
5411
5538
  return text(lines.join(`
5412
5539
  `));
5413
5540
  });
5414
- 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: z6.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
5541
+ 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: z7.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
5415
5542
  const rows = await store.agentBreakdown({ period: period ?? "all" });
5416
5543
  if (rows.length === 0)
5417
5544
  return text("No agent usage yet.");
@@ -5422,7 +5549,7 @@ function buildServer() {
5422
5549
  return text(lines.join(`
5423
5550
  `));
5424
5551
  });
5425
- server.tool("get_account_breakdown", "Cost per account/profile. Params: period(today|week|month|year|all), limit(20), verbose, json. Shows API-equivalent, billable API, and subscription-included usage.", { period: z6.enum(["today", "week", "month", "year", "all"]).optional(), limit: z6.number().int().positive().max(100).optional(), verbose: z6.boolean().optional(), json: z6.boolean().optional() }, async ({ period, limit, verbose, json }) => {
5552
+ server.tool("get_account_breakdown", "Cost per account/profile. Params: period(today|week|month|year|all), limit(20), verbose, json. Shows API-equivalent, billable API, and subscription-included usage.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional(), limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ period, limit, verbose, json }) => {
5426
5553
  const rows = await store.accountBreakdown({ period: period ?? "all" });
5427
5554
  if (json)
5428
5555
  return text(JSON.stringify(rows, null, 2));
@@ -5441,12 +5568,12 @@ function buildServer() {
5441
5568
  `));
5442
5569
  });
5443
5570
  server.tool("get_cost_center_breakdown", "Cost per cost center. Params: period, kind, cursor(0), limit(20), verbose, json. json=true preserves the complete legacy array.", {
5444
- period: z6.enum(["today", "week", "month", "year", "all"]).optional(),
5445
- kind: z6.enum(["loop", "app", "repo", "service", "team"]).optional(),
5446
- cursor: z6.number().int().nonnegative().optional(),
5447
- limit: z6.number().int().positive().max(100).optional(),
5448
- verbose: z6.boolean().optional(),
5449
- json: z6.boolean().optional()
5571
+ period: z7.enum(["today", "week", "month", "year", "all"]).optional(),
5572
+ kind: z7.enum(["loop", "app", "repo", "service", "team"]).optional(),
5573
+ cursor: z7.number().int().nonnegative().optional(),
5574
+ limit: z7.number().int().positive().max(100).optional(),
5575
+ verbose: z7.boolean().optional(),
5576
+ json: z7.boolean().optional()
5450
5577
  }, async ({ period, kind, cursor, limit, verbose, json }) => {
5451
5578
  const rows = await store.costCenterBreakdown({ period: period ?? "all", kind });
5452
5579
  if (json)
@@ -5469,7 +5596,7 @@ function buildServer() {
5469
5596
  return text(lines.join(`
5470
5597
  `));
5471
5598
  });
5472
- server.tool("get_budget_status", "Budget limits vs spend, percent used, alert flags. Params: limit(20), verbose, json.", { limit: z6.number().int().positive().max(100).optional(), verbose: z6.boolean().optional(), json: z6.boolean().optional() }, async ({ limit, verbose, json }) => {
5599
+ server.tool("get_budget_status", "Budget limits vs spend, percent used, alert flags. Params: limit(20), verbose, json.", { limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ limit, verbose, json }) => {
5473
5600
  const budgets = await store.listBudgets();
5474
5601
  if (json)
5475
5602
  return text(JSON.stringify(budgets, null, 2));
@@ -5490,12 +5617,12 @@ function buildServer() {
5490
5617
  `));
5491
5618
  });
5492
5619
  server.tool("set_budget", "Create a spending budget. period: daily|weekly|monthly. limit_usd must be positive. alert_at_percent defaults to 80.", {
5493
- period: z6.enum(["daily", "weekly", "monthly"]),
5494
- limit_usd: z6.number().positive(),
5495
- project_path: z6.string().optional(),
5496
- agent: z6.enum(AGENTS).optional(),
5497
- cost_center_id: z6.string().optional(),
5498
- alert_at_percent: z6.number().positive().max(100).optional()
5620
+ period: z7.enum(["daily", "weekly", "monthly"]),
5621
+ limit_usd: z7.number().positive(),
5622
+ project_path: z7.string().optional(),
5623
+ agent: z7.enum(AGENTS).optional(),
5624
+ cost_center_id: z7.string().optional(),
5625
+ alert_at_percent: z7.number().positive().max(100).optional()
5499
5626
  }, async ({ period, limit_usd, project_path, agent, cost_center_id, alert_at_percent }) => {
5500
5627
  const id = await store.setBudget({
5501
5628
  project_path: project_path ?? null,
@@ -5507,11 +5634,11 @@ function buildServer() {
5507
5634
  });
5508
5635
  return text(`Budget set: ${id}`);
5509
5636
  });
5510
- server.tool("remove_budget", "Delete a budget by id.", { id: z6.string() }, async ({ id }) => {
5637
+ server.tool("remove_budget", "Delete a budget by id.", { id: z7.string() }, async ({ id }) => {
5511
5638
  await store.removeBudget(id);
5512
5639
  return text("Budget removed.");
5513
5640
  });
5514
- server.tool("get_pricing", "Editable model pricing rows. Includes input/output/cache rates and context-cache storage. Params: limit(20), verbose, json.", { limit: z6.number().int().positive().max(100).optional(), verbose: z6.boolean().optional(), json: z6.boolean().optional() }, async ({ limit, verbose, json }) => {
5641
+ server.tool("get_pricing", "Editable model pricing rows. Includes input/output/cache rates and context-cache storage. Params: limit(20), verbose, json.", { limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ limit, verbose, json }) => {
5515
5642
  const rows = await store.listPricing();
5516
5643
  if (json)
5517
5644
  return text(JSON.stringify(rows, null, 2));
@@ -5527,13 +5654,13 @@ function buildServer() {
5527
5654
  `));
5528
5655
  });
5529
5656
  server.tool("set_pricing", "Create or update a model pricing row. Values are USD per 1M tokens except cache_storage_per_1m_hour.", {
5530
- model: z6.string().min(1),
5531
- input_per_1m: z6.number().nonnegative(),
5532
- output_per_1m: z6.number().nonnegative(),
5533
- cache_read_per_1m: z6.number().nonnegative().optional(),
5534
- cache_write_per_1m: z6.number().nonnegative().optional(),
5535
- cache_write_1h_per_1m: z6.number().nonnegative().optional(),
5536
- cache_storage_per_1m_hour: z6.number().nonnegative().optional()
5657
+ model: z7.string().min(1),
5658
+ input_per_1m: z7.number().nonnegative(),
5659
+ output_per_1m: z7.number().nonnegative(),
5660
+ cache_read_per_1m: z7.number().nonnegative().optional(),
5661
+ cache_write_per_1m: z7.number().nonnegative().optional(),
5662
+ cache_write_1h_per_1m: z7.number().nonnegative().optional(),
5663
+ cache_storage_per_1m_hour: z7.number().nonnegative().optional()
5537
5664
  }, async (input) => {
5538
5665
  const model = input.model.trim();
5539
5666
  if (!model)
@@ -5549,11 +5676,11 @@ function buildServer() {
5549
5676
  });
5550
5677
  return text(`Pricing set: ${model}`);
5551
5678
  });
5552
- server.tool("remove_pricing", "Delete a model pricing row by model id.", { model: z6.string() }, async ({ model }) => {
5679
+ server.tool("remove_pricing", "Delete a model pricing row by model id.", { model: z7.string() }, async ({ model }) => {
5553
5680
  await store.removePricing(model);
5554
5681
  return text("Pricing removed.");
5555
5682
  });
5556
- server.tool("get_daily", "Daily cost table by agent. Params: days(30)", { days: z6.number().int().positive().max(365).optional() }, async ({ days }) => {
5683
+ server.tool("get_daily", "Daily cost table by agent. Params: days(30)", { days: z7.number().int().positive().max(365).optional() }, async ({ days }) => {
5557
5684
  const rows = await store.daily(days ?? 30);
5558
5685
  const byDate = new Map;
5559
5686
  for (const row of rows) {
@@ -5577,7 +5704,7 @@ function buildServer() {
5577
5704
  return text(lines.join(`
5578
5705
  `));
5579
5706
  });
5580
- server.tool("get_billing_summary", "Recorded provider billing-row subtotals; complete account/source coverage is unverified. Params: period(today|yesterday|week|month|year|all)", { period: z6.enum(["today", "yesterday", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
5707
+ server.tool("get_billing_summary", "Recorded provider billing-row subtotals; complete account/source coverage is unverified. Params: period(today|yesterday|week|month|year|all)", { period: z7.enum(["today", "yesterday", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
5581
5708
  const summary = await store.billingSummary(period ?? "month");
5582
5709
  const lines = ["provider recorded subtotal"];
5583
5710
  for (const [provider, cost] of Object.entries(summary.by_provider)) {
@@ -5588,7 +5715,7 @@ function buildServer() {
5588
5715
  return text(lines.join(`
5589
5716
  `));
5590
5717
  });
5591
- server.tool("get_session_detail", "Per-request breakdown of a single session. Params: session_id (prefix ok), limit(20), verbose.", { session_id: z6.string(), limit: z6.number().int().positive().max(100).optional(), verbose: z6.boolean().optional() }, async ({ session_id, limit, verbose }) => {
5718
+ server.tool("get_session_detail", "Per-request breakdown of a single session. Params: session_id (prefix ok), limit(20), verbose.", { session_id: z7.string(), limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional() }, async ({ session_id, limit, verbose }) => {
5592
5719
  const detail = await store.sessionDetail(session_id);
5593
5720
  if (!detail)
5594
5721
  return textError(`Session not found: ${session_id}`);
@@ -5612,7 +5739,7 @@ function buildServer() {
5612
5739
  return text(lines.join(`
5613
5740
  `));
5614
5741
  });
5615
- server.tool("sync", `Ingest new cost data. sources: ${SYNC_SOURCES.join("|")} ('loops' reads another app's on-box store and is refused for a hosted client). Set json=true for the full result object.`, { sources: z6.enum([...SYNC_SOURCES]).optional(), json: z6.boolean().optional() }, async ({ sources, json }) => {
5742
+ server.tool("sync", `Ingest new cost data. sources: ${SYNC_SOURCES.join("|")} ('loops' reads another app's on-box store and is refused for a hosted client). Set json=true for the full result object.`, { sources: z7.enum([...SYNC_SOURCES]).optional(), json: z7.boolean().optional() }, async ({ sources, json }) => {
5616
5743
  const selected = sources ?? "all";
5617
5744
  const opts = selected === "all" ? {} : { [selected]: true };
5618
5745
  if (isCloudStore()) {
@@ -5669,7 +5796,7 @@ function buildServer() {
5669
5796
  return { ...text(lines.join(`
5670
5797
  `)), isError: failed };
5671
5798
  });
5672
- server.tool("get_usage", "Usage snapshots and fleet summary. period: today|week|month|year|all, agent, limit(20). Set json=true for full data.", { period: z6.enum(["today", "week", "month", "year", "all"]).optional(), agent: z6.enum(AGENTS).optional(), limit: z6.number().int().positive().max(100).optional(), json: z6.boolean().optional() }, async ({ period, agent, limit, json }) => {
5799
+ server.tool("get_usage", "Usage snapshots and fleet summary. period: today|week|month|year|all, agent, limit(20). Set json=true for full data.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional(), agent: z7.enum(AGENTS).optional(), limit: z7.number().int().positive().max(100).optional(), json: z7.boolean().optional() }, async ({ period, agent, limit, json }) => {
5673
5800
  const p = period ?? "month";
5674
5801
  const { snapshots: snaps, summary } = await store.usage(p, agent);
5675
5802
  if (json)
@@ -5692,7 +5819,7 @@ function buildServer() {
5692
5819
  return text(lines.join(`
5693
5820
  `));
5694
5821
  });
5695
- server.tool("get_savings", "Estimated subscription-included usage benefit versus mapped current plan fees; unavailable when evidence is missing. Paid API cost is not savings. Params: period, agent, limit(20), json.", { period: z6.enum(["today", "week", "month", "year", "all"]).optional(), agent: z6.enum(AGENTS).optional(), limit: z6.number().int().positive().max(100).optional(), json: z6.boolean().optional() }, async ({ period, agent, limit, json }) => {
5822
+ server.tool("get_savings", "Estimated subscription-included usage benefit versus mapped current plan fees; unavailable when evidence is missing. Paid API cost is not savings. Params: period, agent, limit(20), json.", { period: z7.enum(["today", "week", "month", "year", "all"]).optional(), agent: z7.enum(AGENTS).optional(), limit: z7.number().int().positive().max(100).optional(), json: z7.boolean().optional() }, async ({ period, agent, limit, json }) => {
5696
5823
  const savings = await store.savings(period ?? "month", agent);
5697
5824
  if (json)
5698
5825
  return text(JSON.stringify(savings, null, 2));
@@ -5733,17 +5860,17 @@ function buildServer() {
5733
5860
  `));
5734
5861
  });
5735
5862
  server.tool("set_subscription", "Configure an account subscription plan. Explicit amounts are configured observations; omitted amounts remain unknown.", {
5736
- id: z6.string().optional(),
5737
- provider: z6.string().min(1),
5738
- plan: z6.string().min(1),
5739
- account_key: z6.string().min(1).max(256),
5740
- agent: z6.enum(AGENTS).optional(),
5741
- monthly_fee_usd: z6.number().nonnegative().optional(),
5742
- included_usage_usd: z6.number().nonnegative().optional(),
5743
- billing_cycle_start: z6.string().optional(),
5744
- reset_policy: z6.string().optional(),
5745
- active: z6.boolean().optional(),
5746
- json: z6.boolean().optional()
5863
+ id: z7.string().optional(),
5864
+ provider: z7.string().min(1),
5865
+ plan: z7.string().min(1),
5866
+ account_key: z7.string().min(1).max(256),
5867
+ agent: z7.enum(AGENTS).optional(),
5868
+ monthly_fee_usd: z7.number().nonnegative().optional(),
5869
+ included_usage_usd: z7.number().nonnegative().optional(),
5870
+ billing_cycle_start: z7.string().optional(),
5871
+ reset_policy: z7.string().optional(),
5872
+ active: z7.boolean().optional(),
5873
+ json: z7.boolean().optional()
5747
5874
  }, async (input) => {
5748
5875
  const row = await store.setSubscription({
5749
5876
  id: input.id,
@@ -5767,15 +5894,15 @@ agent: ${row.agent ?? "unallocated"}
5767
5894
  fee: ${money.fee}
5768
5895
  included: ${money.included}`);
5769
5896
  });
5770
- server.tool("remove_subscription", "Delete a subscription plan by id.", { id: z6.string() }, async ({ id }) => {
5897
+ server.tool("remove_subscription", "Delete a subscription plan by id.", { id: z7.string() }, async ({ id }) => {
5771
5898
  await store.removeSubscription(id);
5772
5899
  return text("Subscription removed.");
5773
5900
  });
5774
- server.tool("estimate_cost", "Pre-flight usage-value estimate for token counts; missing model pricing returns MODEL_PRICING_UNAVAILABLE, never a free estimate.", { model: z6.string().min(1), input_tokens: z6.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(), output_tokens: z6.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional() }, async ({ model, input_tokens, output_tokens }) => {
5901
+ server.tool("estimate_cost", "Pre-flight usage-value estimate for token counts; missing model pricing returns MODEL_PRICING_UNAVAILABLE, never a free estimate.", { model: z7.string().min(1), input_tokens: z7.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(), output_tokens: z7.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional() }, async ({ model, input_tokens, output_tokens }) => {
5775
5902
  const cost = await store.estimate({ model, inputTokens: input_tokens ?? 0, outputTokens: output_tokens ?? 0 });
5776
5903
  return text(`${model}: ${fmtUsd(cost)} (${input_tokens ?? 0} in / ${output_tokens ?? 0} out)`);
5777
5904
  });
5778
- server.tool("get_goals", "All spending goals with current progress. Params: limit(20), verbose, json.", { limit: z6.number().int().positive().max(100).optional(), verbose: z6.boolean().optional(), json: z6.boolean().optional() }, async ({ limit, verbose, json }) => {
5905
+ server.tool("get_goals", "All spending goals with current progress. Params: limit(20), verbose, json.", { limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ limit, verbose, json }) => {
5779
5906
  const goals = await store.listGoals();
5780
5907
  if (json)
5781
5908
  return text(JSON.stringify(goals, null, 2));
@@ -5796,10 +5923,10 @@ included: ${money.included}`);
5796
5923
  `));
5797
5924
  });
5798
5925
  server.tool("set_goal", "Create/update a spending goal. period(day|week|month|year), limit_usd, project_path?, agent?", {
5799
- period: z6.enum(["day", "week", "month", "year"]),
5800
- limit_usd: z6.number().positive(),
5801
- project_path: z6.string().optional(),
5802
- agent: z6.string().optional()
5926
+ period: z7.enum(["day", "week", "month", "year"]),
5927
+ limit_usd: z7.number().positive(),
5928
+ project_path: z7.string().optional(),
5929
+ agent: z7.string().optional()
5803
5930
  }, async ({ period, limit_usd, project_path, agent }) => {
5804
5931
  await store.setGoal({
5805
5932
  period,
@@ -5809,11 +5936,11 @@ included: ${money.included}`);
5809
5936
  });
5810
5937
  return text(`Goal set: ${period} $${limit_usd}`);
5811
5938
  });
5812
- server.tool("remove_goal", "Delete a goal by id.", { id: z6.string() }, async ({ id }) => {
5939
+ server.tool("remove_goal", "Delete a goal by id.", { id: z7.string() }, async ({ id }) => {
5813
5940
  await store.removeGoal(id);
5814
5941
  return text("Goal removed.");
5815
5942
  });
5816
- server.tool("list_machines", "List all machines that have synced data. Params: limit(20), verbose, json.", { limit: z6.number().int().positive().max(100).optional(), verbose: z6.boolean().optional(), json: z6.boolean().optional() }, async ({ limit, verbose, json }) => {
5943
+ server.tool("list_machines", "List all machines that have synced data. Params: limit(20), verbose, json.", { limit: z7.number().int().positive().max(100).optional(), verbose: z7.boolean().optional(), json: z7.boolean().optional() }, async ({ limit, verbose, json }) => {
5817
5944
  const machines = await store.machines();
5818
5945
  if (json)
5819
5946
  return text(JSON.stringify(machines, null, 2));
@@ -5835,9 +5962,9 @@ current machine: ${getMachineId()}`);
5835
5962
  registerAgentTools(server, { service: "economy" });
5836
5963
  registerIngestJournalTool(server);
5837
5964
  server.tool("send_feedback", "Send feedback about this service.", {
5838
- message: z6.string(),
5839
- email: z6.string().optional(),
5840
- category: z6.enum(["bug", "feature", "general"]).optional()
5965
+ message: z7.string(),
5966
+ email: z7.string().optional(),
5967
+ category: z7.enum(["bug", "feature", "general"]).optional()
5841
5968
  }, async ({ message, email, category }) => {
5842
5969
  try {
5843
5970
  await store.sendFeedback({ message, email, category });