@hasna/economy 0.5.1 → 0.5.3

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 (52) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/chunks/{billing-bkpxy4kv.js → billing-22az60hy.js} +2 -2
  3. package/dist/chunks/{config-j9mv3ehk.js → config-hsp7ndhj.js} +2 -2
  4. package/dist/chunks/{index-d480p2wh.js → index-1vz1arz5.js} +14 -13
  5. package/dist/chunks/{index-hesxq90g.js → index-9wa1f2kv.js} +1 -1
  6. package/dist/chunks/{index-49f12css.js → index-bn04v378.js} +2 -2
  7. package/dist/chunks/{index-zpvea08j.js → index-c7h2rxx1.js} +38 -12
  8. package/dist/chunks/{index-k540hfww.js → index-dk5rq1rq.js} +1 -1
  9. package/dist/chunks/{index-ez9thsr7.js → index-f5jv6q5q.js} +38 -12
  10. package/dist/chunks/{index-r1jv3kpy.js → index-mhrkwvtk.js} +3 -3
  11. package/dist/chunks/{index-xsah93s2.js → index-s9mvven6.js} +2 -2
  12. package/dist/chunks/{index-2hwzmfgv.js → index-vajzfzgv.js} +38 -12
  13. package/dist/chunks/{open-projects-6z4pq6mx.js → open-projects-8qm46m3d.js} +1 -1
  14. package/dist/chunks/{pricing-vf2pycmh.js → pricing-462f8xkm.js} +1 -1
  15. package/dist/chunks/{pricing-f80p81gr.js → pricing-k9wsjwbz.js} +1 -1
  16. package/dist/chunks/{pricing-y21mcjsa.js → pricing-vxejj1qa.js} +1 -1
  17. package/dist/chunks/{serve-2knem98x.js → serve-8yqe7nhs.js} +44 -11
  18. package/dist/chunks/{sqlite-store-501f32ts.js → sqlite-store-13nhr1z0.js} +2 -2
  19. package/dist/chunks/{sqlite-store-st99garx.js → sqlite-store-gamn65xp.js} +2 -2
  20. package/dist/chunks/{sqlite-store-fq4natnf.js → sqlite-store-ht6frztj.js} +2 -2
  21. package/dist/chunks/{tui-khmnkat2.js → tui-fe94bvp9.js} +4 -4
  22. package/dist/chunks/{watch-4y064yys.js → watch-ct8dvrpr.js} +5 -5
  23. package/dist/chunks/{webhooks-k4fh2t9c.js → webhooks-nw4daw9y.js} +20 -9
  24. package/dist/cli/index.js +27 -27
  25. package/dist/db/database.d.ts +2 -2
  26. package/dist/db/database.d.ts.map +1 -1
  27. package/dist/db/legacy-large-schema.d.ts +6 -0
  28. package/dist/db/legacy-large-schema.d.ts.map +1 -0
  29. package/dist/db/pg-migrations.d.ts.map +1 -1
  30. package/dist/db/sync-pg.d.ts.map +1 -1
  31. package/dist/index.js +15 -14
  32. package/dist/lib/analytics.d.ts +1 -1
  33. package/dist/lib/analytics.d.ts.map +1 -1
  34. package/dist/lib/periods.d.ts +20 -1
  35. package/dist/lib/periods.d.ts.map +1 -1
  36. package/dist/lib/spikes.d.ts +3 -3
  37. package/dist/lib/spikes.d.ts.map +1 -1
  38. package/dist/mcp/index.js +18 -17
  39. package/dist/otel/index.js +1 -1
  40. package/dist/server/index.js +213 -74
  41. package/dist/server/legacy-limits.d.ts +51 -0
  42. package/dist/server/legacy-limits.d.ts.map +1 -0
  43. package/dist/server/legacy-operations.d.ts +2 -1
  44. package/dist/server/legacy-operations.d.ts.map +1 -1
  45. package/dist/server/legacy-plan.d.ts +2 -1
  46. package/dist/server/legacy-plan.d.ts.map +1 -1
  47. package/dist/server/legacy-preservation.d.ts +2 -1
  48. package/dist/server/legacy-preservation.d.ts.map +1 -1
  49. package/dist/server/service-operations.d.ts.map +1 -1
  50. package/dist/server/service-worker.js +78 -33
  51. package/docs/historical-data-reconciliation.md +38 -0
  52. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ import {
45
45
  queryUsageSnapshots,
46
46
  requireCurrentBillingSummary,
47
47
  requireCurrentCostSummary,
48
+ resolveReportingInterval,
48
49
  sessionOverlapWhere,
49
50
  stableJson,
50
51
  subscriptionId,
@@ -55,13 +56,13 @@ import {
55
56
  upsertSubscription,
56
57
  usageSnapshotFilterForPeriod,
57
58
  validateIngest
58
- } from "./chunks/index-zpvea08j.js";
59
+ } from "./chunks/index-c7h2rxx1.js";
59
60
  // src/lib/store/index.ts
60
61
  import { randomUUID } from "crypto";
61
62
  // package.json
62
63
  var package_default = {
63
64
  name: "@hasna/economy",
64
- version: "0.5.1",
65
+ version: "0.5.3",
65
66
  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",
66
67
  repository: {
67
68
  type: "git",
@@ -1070,7 +1071,8 @@ function buildBrief(db, opts = {}) {
1070
1071
  }
1071
1072
 
1072
1073
  // src/lib/analytics.ts
1073
- function queryProjectDetail(db, nameOrPath) {
1074
+ function queryProjectDetail(db, nameOrPath, now = new Date) {
1075
+ const dailyBounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: 14 }, now);
1074
1076
  const like = `%${nameOrPath}%`;
1075
1077
  const sessions = db.prepare(`SELECT id, project_name, project_path, total_cost_usd, total_tokens, started_at
1076
1078
  FROM economy_effective_sessions
@@ -1084,8 +1086,8 @@ function queryProjectDetail(db, nameOrPath) {
1084
1086
  const daily = db.prepare(`SELECT DATE(r.timestamp) as date, COALESCE(SUM(r.cost_usd), 0) as cost_usd
1085
1087
  FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
1086
1088
  WHERE (s.project_name LIKE ? OR s.project_path LIKE ?)
1087
- AND r.timestamp >= DATE('now', '-14 days')
1088
- GROUP BY date ORDER BY date ASC`).all(like, like);
1089
+ AND r.timestamp >= ? AND r.timestamp < ?
1090
+ GROUP BY date ORDER BY date ASC`).all(like, like, dailyBounds.start, dailyBounds.end_exclusive);
1089
1091
  const models = db.prepare(`SELECT r.model as model, COUNT(*) as requests, COALESCE(SUM(r.cost_usd), 0) as cost_usd
1090
1092
  FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
1091
1093
  WHERE s.project_name LIKE ? OR s.project_path LIKE ?
@@ -1124,18 +1126,17 @@ function queryRangeStats(db, from, to) {
1124
1126
  return { cost: r.cost, requests: r.requests, tokens: r.tokens, sessions: s.sessions };
1125
1127
  }
1126
1128
  function queryForecast(db, now = new Date) {
1127
- const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
1128
- const dayOfMonth = now.getDate();
1129
- const monthStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-01`;
1130
- const monthSoFar = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM economy_effective_requests WHERE DATE(timestamp) >= ?`).get(monthStart);
1129
+ const month = resolveReportingInterval({ kind: "calendar", period: "month" }, now);
1130
+ const recent = resolveReportingInterval({ kind: "rolling", unit: "day", count: 7 }, now);
1131
+ const daysInMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0)).getUTCDate();
1132
+ const dayOfMonth = now.getUTCDate();
1133
+ 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);
1131
1134
  const dailyAvg = dayOfMonth > 0 ? monthSoFar.cost / dayOfMonth : 0;
1132
1135
  const projected = dailyAvg * daysInMonth;
1133
- const sevenDaysAgo = new Date(now);
1134
- sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
1135
- 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));
1136
+ 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);
1136
1137
  const last7DailyAvg = last7.cost / 7;
1137
1138
  const dailyCosts = db.prepare(`SELECT DATE(timestamp) as date, COALESCE(SUM(cost_usd),0) as cost_usd
1138
- FROM economy_effective_requests WHERE DATE(timestamp) >= ? GROUP BY date ORDER BY cost_usd ASC`).all(monthStart);
1139
+ FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ? GROUP BY date ORDER BY cost_usd ASC`).all(month.start, recent.end_exclusive);
1139
1140
  return {
1140
1141
  day_of_month: dayOfMonth,
1141
1142
  days_in_month: daysInMonth,
@@ -1337,7 +1338,7 @@ class LocalStore {
1337
1338
  _db;
1338
1339
  async db() {
1339
1340
  if (!this._db) {
1340
- const { openDatabase } = await import("./chunks/sqlite-store-fq4natnf.js");
1341
+ const { openDatabase } = await import("./chunks/sqlite-store-ht6frztj.js");
1341
1342
  this._db = openDatabase();
1342
1343
  ensurePricingSeeded(this._db);
1343
1344
  }
@@ -23,7 +23,7 @@ export interface ProjectDetail {
23
23
  top_sessions: ProjectDetailSession[];
24
24
  }
25
25
  /** Detailed breakdown for a single project matched by name or path substring. */
26
- export declare function queryProjectDetail(db: Database, nameOrPath: string): ProjectDetail | null;
26
+ export declare function queryProjectDetail(db: Database, nameOrPath: string, now?: Date): ProjectDetail | null;
27
27
  export type ExportType = 'sessions' | 'requests';
28
28
  /** Raw rows for a CSV export. CSV formatting stays in the caller. */
29
29
  export declare function queryExportRows(db: Database, type: ExportType, period: string): Array<Record<string, unknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/lib/analytics.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAIxE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAChD,MAAM,EAAE,kBAAkB,EAAE,CAAA;IAC5B,YAAY,EAAE,oBAAoB,EAAE,CAAA;CACrC;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAmDzF;AAID,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,CAAA;AAShD,qEAAqE;AACrE,wBAAgB,eAAe,CAC7B,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,GACb,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAOhC;AAID,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,CAUlF;AAID,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACvD,kBAAkB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC9D;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,GAAE,IAAiB,GAAG,YAAY,CAkChF;AAID,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,GAAG,eAAe,EAAE,CAWpE"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/lib/analytics.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAIxE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAChD,MAAM,EAAE,kBAAkB,EAAE,CAAA;IAC5B,YAAY,EAAE,oBAAoB,EAAE,CAAA;CACrC;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,aAAa,GAAG,IAAI,CAoD3G;AAID,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,CAAA;AAShD,qEAAqE;AACrE,wBAAgB,eAAe,CAC7B,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,GACb,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAOhC;AAID,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,CAUlF;AAID,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACvD,kBAAkB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC9D;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,GAAE,IAAiB,GAAG,YAAY,CAiChF;AAID,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,GAAG,eAAe,EAAE,CAWpE"}
@@ -4,7 +4,26 @@ export interface CalendarPeriodBounds {
4
4
  start_date: string | null;
5
5
  end_date_exclusive: string | null;
6
6
  }
7
- /** Existing daily records use UTC. A week starts on Sunday; ranges are half-open. */
7
+ export type ReportingInterval = {
8
+ kind: 'calendar';
9
+ period: Period;
10
+ } | {
11
+ kind: 'rolling';
12
+ unit: 'day' | 'hour';
13
+ count: number;
14
+ };
15
+ export interface ReportingBounds {
16
+ time_zone: 'UTC';
17
+ start: string | null;
18
+ end_exclusive: string | null;
19
+ }
20
+ /**
21
+ * All ranges are half-open and use one supplied clock. Calendar weeks start on
22
+ * Sunday. N rolling days means N UTC dates including today (which is partial),
23
+ * while N rolling hours means the elapsed N hours ending at the supplied clock.
24
+ */
25
+ export declare function resolveReportingInterval(interval: ReportingInterval, now?: Date): ReportingBounds;
26
+ /** Retain the calendar report's existing public field names. */
8
27
  export declare function calendarPeriodBounds(period: Period, now?: Date): CalendarPeriodBounds;
9
28
  /** Column names are code-owned identifiers; only generated ISO dates enter SQL. */
10
29
  export declare function calendarPeriodWhere(period: Period, column: string, now?: Date): string;
@@ -1 +1 @@
1
- {"version":3,"file":"periods.d.ts","sourceRoot":"","sources":["../../src/lib/periods.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAM/C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,KAAK,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,oBAAoB,CAgC3F;AAED,mFAAmF;AACnF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,MAAM,CAO5F;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAK/G;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,MAAM,EAAC,MAAM,EAAC,KAAK,EAAC,MAAM,EAAC,GAAG,OAAW,GAAE,MAAM,CAIpF"}
1
+ {"version":3,"file":"periods.d.ts","sourceRoot":"","sources":["../../src/lib/periods.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAM/C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,KAAK,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5D,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,KAAK,CAAA;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,OAAa,GAAG,eAAe,CA+CvG;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,oBAAoB,CAG3F;AAED,mFAAmF;AACnF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,MAAM,CAO5F;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAK/G;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,MAAM,EAAC,MAAM,EAAC,KAAK,EAAC,MAAM,EAAC,GAAG,OAAW,GAAE,MAAM,CAIpF"}
@@ -5,7 +5,7 @@ export interface SpikeInfo {
5
5
  average_usd: number;
6
6
  ratio: number;
7
7
  }
8
- /** Detect daily cost spikes: today > 2× rolling 7-day mean (min 7 days history). */
8
+ /** Detect spikes against consecutive observed UTC days; a missing day is unknown. */
9
9
  export declare function detectCostSpikes(dailyTotals: Array<{
10
10
  date: string;
11
11
  cost_usd: number;
@@ -13,6 +13,6 @@ export declare function detectCostSpikes(dailyTotals: Array<{
13
13
  windowDays?: number;
14
14
  multiplier?: number;
15
15
  }): SpikeInfo[];
16
- export declare function queryRecentSpikes(db: Database, days?: number): SpikeInfo[];
17
- export declare function getTodaySpike(db: Database): SpikeInfo | null;
16
+ export declare function queryRecentSpikes(db: Database, days?: number, now?: Date): SpikeInfo[];
17
+ export declare function getTodaySpike(db: Database, now?: Date): SpikeInfo | null;
18
18
  //# sourceMappingURL=spikes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spikes.d.ts","sourceRoot":"","sources":["../../src/lib/spikes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAExE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EACtD,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAClD,SAAS,EAAE,CAqBb;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,SAAK,GAAG,SAAS,EAAE,CAUtE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,SAAS,GAAG,IAAI,CAI5D"}
1
+ {"version":3,"file":"spikes.d.ts","sourceRoot":"","sources":["../../src/lib/spikes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAGxE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EACtD,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAClD,SAAS,EAAE,CA8Bb;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,SAAK,EAAE,GAAG,OAAa,GAAG,SAAS,EAAE,CAWxF;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,OAAa,GAAG,SAAS,GAAG,IAAI,CAI9E"}
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.1",
88
+ version: "0.5.3",
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",
@@ -3579,7 +3580,7 @@ function backfillMachineId(db) {
3579
3580
  return { requests: Number(reqCount.changes), sessions: Number(sessCount.changes) };
3580
3581
  }
3581
3582
  async function recalculateZeroCostRequests(db) {
3582
- const { assessCostFromDb: assessCostFromDb2 } = await import("../chunks/pricing-vf2pycmh.js");
3583
+ const { assessCostFromDb: assessCostFromDb2 } = await import("../chunks/pricing-vxejj1qa.js");
3583
3584
  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
3585
  FROM requests
3585
3586
  WHERE cost_usd = 0
@@ -3788,7 +3789,7 @@ async function postIngest(cloud, body) {
3788
3789
  }
3789
3790
  async function syncAllToCloud(cloud, opts = {}) {
3790
3791
  await runIngestJournalOperation(cloud, { action: "reconcile" });
3791
- const { openDatabase } = await import("../chunks/sqlite-store-st99garx.js");
3792
+ const { openDatabase } = await import("../chunks/sqlite-store-gamn65xp.js");
3792
3793
  const scratch = openDatabase(":memory:", true);
3793
3794
  ensurePricingSeeded(scratch);
3794
3795
  const cachePath = opts.cachePath ?? getIngestCachePath();
@@ -4714,7 +4715,8 @@ function buildBrief(db, opts = {}) {
4714
4715
  }
4715
4716
 
4716
4717
  // src/lib/analytics.ts
4717
- function queryProjectDetail(db, nameOrPath) {
4718
+ function queryProjectDetail(db, nameOrPath, now2 = new Date) {
4719
+ const dailyBounds = resolveReportingInterval({ kind: "rolling", unit: "day", count: 14 }, now2);
4718
4720
  const like = `%${nameOrPath}%`;
4719
4721
  const sessions = db.prepare(`SELECT id, project_name, project_path, total_cost_usd, total_tokens, started_at
4720
4722
  FROM economy_effective_sessions
@@ -4728,8 +4730,8 @@ function queryProjectDetail(db, nameOrPath) {
4728
4730
  const daily = db.prepare(`SELECT DATE(r.timestamp) as date, COALESCE(SUM(r.cost_usd), 0) as cost_usd
4729
4731
  FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
4730
4732
  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);
4733
+ AND r.timestamp >= ? AND r.timestamp < ?
4734
+ GROUP BY date ORDER BY date ASC`).all(like, like, dailyBounds.start, dailyBounds.end_exclusive);
4733
4735
  const models = db.prepare(`SELECT r.model as model, COUNT(*) as requests, COALESCE(SUM(r.cost_usd), 0) as cost_usd
4734
4736
  FROM economy_effective_requests r JOIN economy_effective_sessions s ON r.session_id = s.id
4735
4737
  WHERE s.project_name LIKE ? OR s.project_path LIKE ?
@@ -4768,18 +4770,17 @@ function queryRangeStats(db, from, to) {
4768
4770
  return { cost: r.cost, requests: r.requests, tokens: r.tokens, sessions: s.sessions };
4769
4771
  }
4770
4772
  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);
4773
+ const month = resolveReportingInterval({ kind: "calendar", period: "month" }, now2);
4774
+ const recent = resolveReportingInterval({ kind: "rolling", unit: "day", count: 7 }, now2);
4775
+ const daysInMonth = new Date(Date.UTC(now2.getUTCFullYear(), now2.getUTCMonth() + 1, 0)).getUTCDate();
4776
+ const dayOfMonth = now2.getUTCDate();
4777
+ 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
4778
  const dailyAvg = dayOfMonth > 0 ? monthSoFar.cost / dayOfMonth : 0;
4776
4779
  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));
4780
+ 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
4781
  const last7DailyAvg = last7.cost / 7;
4781
4782
  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);
4783
+ FROM economy_effective_requests WHERE timestamp >= ? AND timestamp < ? GROUP BY date ORDER BY cost_usd ASC`).all(month.start, recent.end_exclusive);
4783
4784
  return {
4784
4785
  day_of_month: dayOfMonth,
4785
4786
  days_in_month: daysInMonth,
@@ -4830,7 +4831,7 @@ class LocalStore2 {
4830
4831
  _db;
4831
4832
  async db() {
4832
4833
  if (!this._db) {
4833
- const { openDatabase } = await import("../chunks/sqlite-store-st99garx.js");
4834
+ const { openDatabase } = await import("../chunks/sqlite-store-gamn65xp.js");
4834
4835
  this._db = openDatabase();
4835
4836
  ensurePricingSeeded(this._db);
4836
4837
  }
@@ -5193,7 +5194,7 @@ function buildServer() {
5193
5194
  let _db;
5194
5195
  const localDb = async () => {
5195
5196
  if (!_db) {
5196
- const { openDatabase } = await import("../chunks/sqlite-store-st99garx.js");
5197
+ const { openDatabase } = await import("../chunks/sqlite-store-gamn65xp.js");
5197
5198
  _db = openDatabase();
5198
5199
  ensurePricingSeeded(_db);
5199
5200
  }
@@ -3458,7 +3458,7 @@ async function pushIngestRows(cloud, scratch) {
3458
3458
  // package.json
3459
3459
  var package_default = {
3460
3460
  name: "@hasna/economy",
3461
- version: "0.5.1",
3461
+ version: "0.5.3",
3462
3462
  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",
3463
3463
  repository: {
3464
3464
  type: "git",