@hasna/economy 0.3.7 → 0.3.8

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 (77) hide show
  1. package/CHANGELOG.md +0 -4
  2. package/README.md +28 -61
  3. package/dashboard/README.md +17 -64
  4. package/dist/cli/index.js +221 -114
  5. package/dist/db/cloud.d.ts +3 -4
  6. package/dist/db/cloud.d.ts.map +1 -1
  7. package/dist/db/database.d.ts +2 -2
  8. package/dist/db/database.d.ts.map +1 -1
  9. package/dist/db/dialect.d.ts +13 -0
  10. package/dist/db/dialect.d.ts.map +1 -0
  11. package/dist/db/pg-migrate.d.ts +30 -0
  12. package/dist/db/pg-migrate.d.ts.map +1 -0
  13. package/dist/db/sqlite-adapter.d.ts +36 -0
  14. package/dist/db/sqlite-adapter.d.ts.map +1 -0
  15. package/dist/db/sync-pg.d.ts +1 -1
  16. package/dist/db/sync-pg.d.ts.map +1 -1
  17. package/dist/index.js +129 -56
  18. package/dist/ingest/billing.d.ts +1 -1
  19. package/dist/ingest/billing.d.ts.map +1 -1
  20. package/dist/ingest/claude-quota.d.ts +1 -1
  21. package/dist/ingest/claude-quota.d.ts.map +1 -1
  22. package/dist/ingest/claude.d.ts +1 -1
  23. package/dist/ingest/claude.d.ts.map +1 -1
  24. package/dist/ingest/codex-quota.d.ts +1 -1
  25. package/dist/ingest/codex-quota.d.ts.map +1 -1
  26. package/dist/ingest/codex.d.ts +1 -1
  27. package/dist/ingest/codex.d.ts.map +1 -1
  28. package/dist/ingest/cursor.d.ts +1 -1
  29. package/dist/ingest/cursor.d.ts.map +1 -1
  30. package/dist/ingest/gemini.d.ts +1 -1
  31. package/dist/ingest/gemini.d.ts.map +1 -1
  32. package/dist/ingest/hermes.d.ts +1 -1
  33. package/dist/ingest/hermes.d.ts.map +1 -1
  34. package/dist/ingest/loops.d.ts +1 -1
  35. package/dist/ingest/loops.d.ts.map +1 -1
  36. package/dist/ingest/opencode.d.ts +1 -1
  37. package/dist/ingest/opencode.d.ts.map +1 -1
  38. package/dist/ingest/otel.d.ts +1 -1
  39. package/dist/ingest/otel.d.ts.map +1 -1
  40. package/dist/ingest/pi.d.ts +1 -1
  41. package/dist/ingest/pi.d.ts.map +1 -1
  42. package/dist/ingest/plugin.d.ts +1 -1
  43. package/dist/ingest/plugin.d.ts.map +1 -1
  44. package/dist/lib/accounts.d.ts.map +1 -1
  45. package/dist/lib/analytics.d.ts +1 -1
  46. package/dist/lib/analytics.d.ts.map +1 -1
  47. package/dist/lib/billing-diff.d.ts +1 -1
  48. package/dist/lib/billing-diff.d.ts.map +1 -1
  49. package/dist/lib/brief.d.ts +1 -1
  50. package/dist/lib/brief.d.ts.map +1 -1
  51. package/dist/lib/open-projects.d.ts +10 -2
  52. package/dist/lib/open-projects.d.ts.map +1 -1
  53. package/dist/lib/pricing.d.ts +1 -1
  54. package/dist/lib/pricing.d.ts.map +1 -1
  55. package/dist/lib/savings.d.ts +1 -1
  56. package/dist/lib/savings.d.ts.map +1 -1
  57. package/dist/lib/spikes.d.ts +1 -1
  58. package/dist/lib/spikes.d.ts.map +1 -1
  59. package/dist/lib/sync-all.d.ts +1 -1
  60. package/dist/lib/sync-all.d.ts.map +1 -1
  61. package/dist/lib/webhooks.d.ts +1 -1
  62. package/dist/lib/webhooks.d.ts.map +1 -1
  63. package/dist/mcp/index.js +144 -94
  64. package/dist/mcp/server.d.ts.map +1 -1
  65. package/dist/openapi.d.ts.map +1 -1
  66. package/dist/otel/index.js +69 -4
  67. package/dist/server/index.js +248 -88
  68. package/dist/server/serve.d.ts +1 -1
  69. package/dist/server/serve.d.ts.map +1 -1
  70. package/docs/README.md +12 -0
  71. package/docs/cli.md +88 -0
  72. package/docs/configuration.md +86 -0
  73. package/docs/ingestion.md +47 -0
  74. package/docs/mcp.md +65 -0
  75. package/docs/otel.md +53 -0
  76. package/docs/rest-api.md +89 -0
  77. package/package.json +4 -5
@@ -1,4 +1,4 @@
1
- import type { SqliteAdapter as Database } from '@hasna/cloud';
1
+ import type { SqliteAdapter as Database } from '../db/sqlite-adapter.js';
2
2
  import type { Period } from '../types/index.js';
3
3
  export interface BillingDiffRow {
4
4
  agent: string;
@@ -1 +1 @@
1
- {"version":3,"file":"billing-diff.d.ts","sourceRoot":"","sources":["../../src/lib/billing-diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAE7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAS/C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AAED,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,MAAM,EACd,YAAY,SAAK,GAChB,kBAAkB,CAsCpB"}
1
+ {"version":3,"file":"billing-diff.d.ts","sourceRoot":"","sources":["../../src/lib/billing-diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAExE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAS/C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AAED,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,MAAM,EACd,YAAY,SAAK,GAChB,kBAAkB,CAsCpB"}
@@ -1,4 +1,4 @@
1
- import type { SqliteAdapter as Database } from '@hasna/cloud';
1
+ import type { SqliteAdapter as Database } from '../db/sqlite-adapter.js';
2
2
  type BriefPeriod = 'today' | 'week';
3
3
  export interface BriefTotals {
4
4
  sessions: number;
@@ -1 +1 @@
1
- {"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["../../src/lib/brief.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAU7D,KAAK,WAAW,GAAG,OAAO,GAAG,MAAM,CAAA;AAEnC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,GAAG,OAAO,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;IACD,SAAS,EAAE,kBAAkB,EAAE,CAAA;IAC/B,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,SAAS,EAAE;QACT,QAAQ,EAAE,qBAAqB,EAAE,CAAA;QACjC,gBAAgB,EAAE,MAAM,CAAA;QACxB,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AAcD,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,IAAI,CAAA;IACV,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,WAAW,CAAC,EAAE,IAAI,CAAA;CACnB;AAyBD,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,CAWjF;AAsmBD,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAE,YAAiB,GAAG,YAAY,CAmD9E"}
1
+ {"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["../../src/lib/brief.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAUxE,KAAK,WAAW,GAAG,OAAO,GAAG,MAAM,CAAA;AAEnC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,sBAAsB,EAAE,MAAM,CAAA;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,GAAG,OAAO,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;IACD,SAAS,EAAE,kBAAkB,EAAE,CAAA;IAC/B,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,SAAS,EAAE;QACT,QAAQ,EAAE,qBAAqB,EAAE,CAAA;QACjC,gBAAgB,EAAE,MAAM,CAAA;QACxB,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AAcD,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,IAAI,CAAA;IACV,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,WAAW,CAAC,EAAE,IAAI,CAAA;CACnB;AAyBD,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,CAWjF;AAsmBD,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAE,YAAiB,GAAG,YAAY,CAmD9E"}
@@ -1,9 +1,17 @@
1
- import type { SqliteAdapter as Database } from '@hasna/cloud';
1
+ import type { SqliteAdapter as Database } from '../db/sqlite-adapter.js';
2
+ /**
3
+ * The subset of a project-registry row economy needs. The registry renamed its
4
+ * filesystem-path field `path` -> `primary_path` when projects became
5
+ * workspaces, so both spellings are accepted and `resolveProjectPath` below
6
+ * picks whichever is present. Keeping both means the injectable
7
+ * `listActiveProjects` seam (and its tests) stays valid across that rename.
8
+ */
2
9
  interface OpenProject {
3
10
  id: string;
4
11
  name: string;
5
12
  description: string | null;
6
- path: string;
13
+ path?: string | null;
14
+ primary_path?: string | null;
7
15
  tags: string[];
8
16
  created_at: string;
9
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"open-projects.d.ts","sourceRoot":"","sources":["../../src/lib/open-projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAG7D,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,KAAK,gBAAgB,GAAG,CAAC,OAAO,EAAE;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,KAAK,WAAW,EAAE,CAAA;AAEvF,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,QAAQ,EACZ,kBAAkB,CAAC,EAAE,gBAAgB,GACpC,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA2BhD"}
1
+ {"version":3,"file":"open-projects.d.ts","sourceRoot":"","sources":["../../src/lib/open-projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAGxE;;;;;;GAMG;AACH,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,KAAK,gBAAgB,GAAG,CAAC,OAAO,EAAE;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,KAAK,WAAW,EAAE,CAAA;AAIvF,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,QAAQ,EACZ,kBAAkB,CAAC,EAAE,gBAAgB,GACpC,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA4BhD"}
@@ -1,4 +1,4 @@
1
- import type { SqliteAdapter as Database } from '@hasna/cloud';
1
+ import type { SqliteAdapter as Database } from '../db/sqlite-adapter.js';
2
2
  import type { ModelPricing } from '../types/index.js';
3
3
  import { type DbModelPricing } from '../db/database.js';
4
4
  export declare const DEFAULT_PRICING: Record<string, ModelPricing>;
@@ -1 +1 @@
1
- {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/lib/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAA6E,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAIlI,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA+FxD,CAAA;AAqND,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOtD;AA2CD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAMtD;AA+GD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAoBjF;AA4BD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAG7D;AAMD,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,EACpB,kBAAkB,SAAI,EACtB,sBAAsB,SAAI,GACzB,MAAM,CAIR;AAED,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,EACpB,kBAAkB,SAAI,EACtB,sBAAsB,SAAI,GACzB,MAAM,CAIR;AAMD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAW7F;AAMD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,cAAc,EAAE,EACtB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,EACpB,kBAAkB,SAAI,EACtB,sBAAsB,SAAI,GACzB,MAAM,CAIR"}
1
+ {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/lib/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAA6E,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAIlI,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA+FxD,CAAA;AAqND,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOtD;AA2CD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAMtD;AA+GD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAoBjF;AA4BD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAG7D;AAMD,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,EACpB,kBAAkB,SAAI,EACtB,sBAAsB,SAAI,GACzB,MAAM,CAIR;AAED,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,EACpB,kBAAkB,SAAI,EACtB,sBAAsB,SAAI,GACzB,MAAM,CAIR;AAMD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAW7F;AAMD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,cAAc,EAAE,EACtB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,EACpB,kBAAkB,SAAI,EACtB,sBAAsB,SAAI,GACzB,MAAM,CAIR"}
@@ -1,4 +1,4 @@
1
- import type { SqliteAdapter as Database } from '@hasna/cloud';
1
+ import type { SqliteAdapter as Database } from '../db/sqlite-adapter.js';
2
2
  import type { Agent, CostBasis } from './agents.js';
3
3
  import type { Period } from '../types/index.js';
4
4
  export interface SavingsSummary {
@@ -1 +1 @@
1
- {"version":3,"file":"savings.d.ts","sourceRoot":"","sources":["../../src/lib/savings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,kBAAkB,EAAE,MAAM,CAAA;IAC1B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,qBAAqB,EAAE,MAAM,CAAA;IAC7B,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAA;CAClD;AAoCD,6EAA6E;AAC7E,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,MAAM,CAER;AAED,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,KAAK,GACZ,cAAc,CAmHhB;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAIhE"}
1
+ {"version":3,"file":"savings.d.ts","sourceRoot":"","sources":["../../src/lib/savings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,kBAAkB,EAAE,MAAM,CAAA;IAC1B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,qBAAqB,EAAE,MAAM,CAAA;IAC7B,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAA;CAClD;AAoCD,6EAA6E;AAC7E,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,MAAM,CAER;AAED,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,KAAK,GACZ,cAAc,CAmHhB;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAIhE"}
@@ -1,4 +1,4 @@
1
- import type { SqliteAdapter as Database } from '@hasna/cloud';
1
+ import type { SqliteAdapter as Database } from '../db/sqlite-adapter.js';
2
2
  export interface SpikeInfo {
3
3
  date: string;
4
4
  cost_usd: number;
@@ -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,cAAc,CAAA;AAE7D,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;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,4 +1,4 @@
1
- import type { SqliteAdapter as Database } from '@hasna/cloud';
1
+ import type { SqliteAdapter as Database } from '../db/sqlite-adapter.js';
2
2
  import { ingestClaude, ingestTakumi } from '../ingest/claude.js';
3
3
  import { ingestCodex } from '../ingest/codex.js';
4
4
  import { ingestGemini } from '../ingest/gemini.js';
@@ -1 +1 @@
1
- {"version":3,"file":"sync-all.d.ts","sourceRoot":"","sources":["../../src/lib/sync-all.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;IAC/C,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAA;IACrD,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IACjD,EAAE,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAA;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;IAC/C,WAAW,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAA;IAC3D,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAA;IACzD,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wBAAsB,OAAO,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CA4B1F"}
1
+ {"version":3,"file":"sync-all.d.ts","sourceRoot":"","sources":["../../src/lib/sync-all.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;IAC/C,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,CAAA;IACrD,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IACjD,EAAE,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAA;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAA;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;IAC/C,WAAW,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAA;IAC3D,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAA;IACzD,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wBAAsB,OAAO,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CA4B1F"}
@@ -1,3 +1,3 @@
1
- import type { SqliteAdapter as Database } from '@hasna/cloud';
1
+ import type { SqliteAdapter as Database } from '../db/sqlite-adapter.js';
2
2
  export declare function checkAndFireWebhooks(db: Database): Promise<void>;
3
3
  //# sourceMappingURL=webhooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/lib/webhooks.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAE7D,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CtE"}
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/lib/webhooks.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAExE,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CtE"}
package/dist/mcp/index.js CHANGED
@@ -17,6 +17,62 @@ var __export = (target, all) => {
17
17
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
18
18
  var __require = import.meta.require;
19
19
 
20
+ // src/db/sqlite-adapter.ts
21
+ import { Database } from "bun:sqlite";
22
+
23
+ class SqliteAdapter {
24
+ db;
25
+ constructor(path) {
26
+ this.db = new Database(path, { create: true });
27
+ this.db.exec("PRAGMA journal_mode=WAL");
28
+ this.db.exec("PRAGMA foreign_keys=ON");
29
+ }
30
+ run(sql, ...params) {
31
+ const result = this.db.prepare(sql).run(...params);
32
+ return { changes: result.changes, lastInsertRowid: result.lastInsertRowid };
33
+ }
34
+ get(sql, ...params) {
35
+ return this.db.prepare(sql).get(...params);
36
+ }
37
+ all(sql, ...params) {
38
+ return this.db.prepare(sql).all(...params);
39
+ }
40
+ exec(sql) {
41
+ this.db.exec(sql);
42
+ }
43
+ query(sql) {
44
+ return this.db.query(sql);
45
+ }
46
+ prepare(sql) {
47
+ const stmt = this.db.prepare(sql);
48
+ return {
49
+ run(...params) {
50
+ const r = stmt.run(...params);
51
+ return { changes: r.changes, lastInsertRowid: r.lastInsertRowid };
52
+ },
53
+ get(...params) {
54
+ return stmt.get(...params);
55
+ },
56
+ all(...params) {
57
+ return stmt.all(...params);
58
+ },
59
+ finalize() {
60
+ stmt.finalize();
61
+ }
62
+ };
63
+ }
64
+ close() {
65
+ this.db.close();
66
+ }
67
+ transaction(fn) {
68
+ return this.db.transaction(fn)();
69
+ }
70
+ get raw() {
71
+ return this.db;
72
+ }
73
+ }
74
+ var init_sqlite_adapter = () => {};
75
+
20
76
  // src/lib/pricing.ts
21
77
  var exports_pricing = {};
22
78
  __export(exports_pricing, {
@@ -535,7 +591,6 @@ var init_pricing = __esm(() => {
535
591
  });
536
592
 
537
593
  // src/db/database.ts
538
- import { SqliteAdapter as Database } from "@hasna/cloud";
539
594
  import { execFileSync } from "child_process";
540
595
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
541
596
  import { hostname, platform } from "os";
@@ -628,7 +683,7 @@ function openDatabase(dbPath, skipSeed = false) {
628
683
  mkdirSync(dir, { recursive: true });
629
684
  }
630
685
  const db = withSqliteBusyRetry(() => {
631
- const opened = new Database(path);
686
+ const opened = new SqliteAdapter(path);
632
687
  try {
633
688
  opened.exec("PRAGMA busy_timeout = 10000");
634
689
  opened.exec("PRAGMA journal_mode = WAL");
@@ -1216,9 +1271,12 @@ function labelForPath(projectPath, projectName) {
1216
1271
  function groupKeyForPath(projectPath, projectName) {
1217
1272
  return labelForPath(projectPath, projectName).trim().toLowerCase();
1218
1273
  }
1219
- function queryProjectBreakdown(db, period = "all", machine) {
1220
- const requestWhere = requestPeriodWhere(period);
1221
- const sessionWhere = sessionPeriodWhere(period);
1274
+ function laterTimestamp(current, candidate) {
1275
+ if (!candidate)
1276
+ return current;
1277
+ return candidate > current ? candidate : current;
1278
+ }
1279
+ function queryProjectBreakdownScoped(db, requestWhere, sessionWhere, requestScopeParams = [], sessionScopeParams = [], machine) {
1222
1280
  const sessionMachineClause = machine ? " AND (machine_id = ? OR id IN (SELECT DISTINCT session_id FROM requests WHERE machine_id = ?))" : "";
1223
1281
  const requestMachineClause = machine ? " AND machine_id = ?" : "";
1224
1282
  const sessionMachineParams = machine ? [machine, machine] : [];
@@ -1226,67 +1284,89 @@ function queryProjectBreakdown(db, period = "all", machine) {
1226
1284
  const sessionOnlyMachineClause = machine ? " AND machine_id = ?" : "";
1227
1285
  const sessionOnlyMachineParams = machine ? [machine] : [];
1228
1286
  const sessions = db.prepare(`
1229
- SELECT id, project_path, project_name, total_cost_usd, started_at
1287
+ SELECT id, project_path, project_name
1230
1288
  FROM sessions
1231
1289
  WHERE (project_path != '' OR project_name != '')${sessionMachineClause}
1232
1290
  `).all(...sessionMachineParams);
1233
1291
  const groups = new Map;
1292
+ const groupKeyBySession = new Map;
1234
1293
  for (const s of sessions) {
1235
1294
  const label = labelForPath(s.project_path, s.project_name);
1236
1295
  if (!label)
1237
1296
  continue;
1238
1297
  const key = groupKeyForPath(s.project_path, s.project_name);
1239
- const g = groups.get(key) ?? { label, sessionIds: [], samplePath: s.project_path };
1240
- g.sessionIds.push(s.id);
1298
+ const g = groups.get(key) ?? { label, samplePath: s.project_path, sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: "" };
1241
1299
  if (!g.samplePath)
1242
1300
  g.samplePath = s.project_path;
1243
1301
  groups.set(key, g);
1302
+ groupKeyBySession.set(s.id, key);
1303
+ }
1304
+ const groupFor = (sessionId) => {
1305
+ const key = groupKeyBySession.get(sessionId);
1306
+ return key === undefined ? undefined : groups.get(key);
1307
+ };
1308
+ const requestRows = db.prepare(`
1309
+ SELECT session_id,
1310
+ COUNT(*) as requests,
1311
+ COALESCE(SUM(cost_usd), 0) as cost_usd,
1312
+ COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
1313
+ MAX(timestamp) as last_active
1314
+ FROM requests
1315
+ WHERE ${requestWhere}${requestMachineClause}
1316
+ GROUP BY session_id
1317
+ `).all(...requestScopeParams, ...requestMachineParams);
1318
+ for (const row of requestRows) {
1319
+ const g = groupFor(row.session_id);
1320
+ if (!g)
1321
+ continue;
1322
+ g.sessions += 1;
1323
+ g.requests += row.requests;
1324
+ g.cost_usd += row.cost_usd;
1325
+ g.total_tokens += row.total_tokens;
1326
+ g.last_active = laterTimestamp(g.last_active, row.last_active);
1327
+ }
1328
+ const sessionsWithRequests = new Set(db.prepare(`SELECT DISTINCT session_id FROM requests`).all().map((row) => row.session_id));
1329
+ const sessionOnlyRows = db.prepare(`
1330
+ SELECT id,
1331
+ COALESCE(request_count, 0) as request_count,
1332
+ COALESCE(total_tokens, 0) as total_tokens,
1333
+ COALESCE(total_cost_usd, 0) as total_cost_usd,
1334
+ started_at
1335
+ FROM sessions
1336
+ WHERE ${sessionWhere}${sessionOnlyMachineClause}
1337
+ `).all(...sessionScopeParams, ...sessionOnlyMachineParams);
1338
+ for (const row of sessionOnlyRows) {
1339
+ if (sessionsWithRequests.has(row.id))
1340
+ continue;
1341
+ const g = groupFor(row.id);
1342
+ if (!g)
1343
+ continue;
1344
+ g.sessions += 1;
1345
+ g.requests += row.request_count;
1346
+ g.total_tokens += row.total_tokens;
1347
+ g.cost_usd += row.total_cost_usd;
1348
+ g.last_active = laterTimestamp(g.last_active, row.started_at);
1244
1349
  }
1245
1350
  const result = [];
1246
1351
  for (const g of groups.values()) {
1247
- const placeholders = g.sessionIds.map(() => "?").join(",");
1248
- const reqStats = placeholders.length ? db.prepare(`
1249
- SELECT
1250
- COUNT(DISTINCT session_id) as sessions,
1251
- COUNT(*) as requests,
1252
- COALESCE(SUM(cost_usd), 0) as cost_usd,
1253
- COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
1254
- MAX(timestamp) as last_active
1255
- FROM requests
1256
- WHERE session_id IN (${placeholders})
1257
- AND ${requestWhere}
1258
- ${requestMachineClause}
1259
- `).get(...g.sessionIds, ...requestMachineParams) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
1260
- const sessionOnlyStats = placeholders.length ? db.prepare(`
1261
- SELECT
1262
- COUNT(*) as sessions,
1263
- COALESCE(SUM(request_count), 0) as requests,
1264
- COALESCE(SUM(total_tokens), 0) as total_tokens,
1265
- COALESCE(SUM(total_cost_usd), 0) as cost_usd,
1266
- MAX(started_at) as last_active
1267
- FROM sessions
1268
- WHERE id IN (${placeholders})
1269
- AND ${sessionWhere}
1270
- ${sessionOnlyMachineClause}
1271
- AND id NOT IN (SELECT DISTINCT session_id FROM requests)
1272
- `).get(...g.sessionIds, ...sessionOnlyMachineParams) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
1273
- const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
1274
- if (totalSessions === 0)
1352
+ if (g.sessions === 0)
1275
1353
  continue;
1276
- const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
1277
1354
  result.push({
1278
1355
  project_path: g.samplePath,
1279
1356
  project_name: g.label,
1280
- sessions: totalSessions,
1281
- requests: reqStats.requests + sessionOnlyStats.requests,
1282
- total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
1283
- cost_usd: reqStats.cost_usd + sessionOnlyStats.cost_usd,
1284
- last_active: lastActive
1357
+ sessions: g.sessions,
1358
+ requests: g.requests,
1359
+ total_tokens: g.total_tokens,
1360
+ cost_usd: g.cost_usd,
1361
+ last_active: g.last_active
1285
1362
  });
1286
1363
  }
1287
1364
  result.sort((a, b) => b.cost_usd - a.cost_usd);
1288
1365
  return result;
1289
1366
  }
1367
+ function queryProjectBreakdown(db, period = "all", machine) {
1368
+ return queryProjectBreakdownScoped(db, requestPeriodWhere(period), sessionPeriodWhere(period), [], [], machine);
1369
+ }
1290
1370
  function normalizeAccountEmail(email) {
1291
1371
  return (email ?? "").trim().toLowerCase();
1292
1372
  }
@@ -1560,17 +1640,8 @@ function queryCostCenterBreakdown(db, period = "all", filter = {}) {
1560
1640
  filtered.sort((a, b) => b.cost_usd - a.cost_usd);
1561
1641
  return filtered;
1562
1642
  }
1563
- function queryProjectBreakdownSince(db, since) {
1564
- return db.prepare(`
1565
- SELECT project_path, project_name,
1566
- COUNT(*) as sessions,
1567
- COALESCE(SUM(total_tokens), 0) as total_tokens,
1568
- COALESCE(SUM(request_count), 0) as requests,
1569
- COALESCE(SUM(total_cost_usd), 0) as cost_usd,
1570
- MAX(started_at) as last_active
1571
- FROM sessions WHERE started_at >= ?
1572
- GROUP BY project_path ORDER BY cost_usd DESC
1573
- `).all(since);
1643
+ function queryProjectBreakdownSince(db, since, machine) {
1644
+ return queryProjectBreakdownScoped(db, "timestamp >= ?", "started_at >= ?", [since], [since], machine);
1574
1645
  }
1575
1646
  function queryAgentBreakdownSince(db, since) {
1576
1647
  return db.prepare(`
@@ -1984,7 +2055,9 @@ function dedupeRequests(db) {
1984
2055
  }
1985
2056
  return removed;
1986
2057
  }
1987
- var init_database = () => {};
2058
+ var init_database = __esm(() => {
2059
+ init_sqlite_adapter();
2060
+ });
1988
2061
 
1989
2062
  // src/mcp/index.ts
1990
2063
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -2008,7 +2081,6 @@ var packageMetadata = getPackageMetadata();
2008
2081
  // src/mcp/server.ts
2009
2082
  init_database();
2010
2083
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2011
- import { registerCloudTools } from "@hasna/cloud";
2012
2084
  import { registerAgentTools } from "@hasna/agent-registry";
2013
2085
  import { z } from "zod";
2014
2086
 
@@ -2174,6 +2246,7 @@ function defaultCostBasisForAgent(agent) {
2174
2246
  }
2175
2247
 
2176
2248
  // src/lib/accounts.ts
2249
+ import { appliedProfileName, resolveStore } from "@hasna/accounts";
2177
2250
  var AGENT_ACCOUNT_TOOLS = {
2178
2251
  claude: ["claude"],
2179
2252
  takumi: ["takumi", "claude"],
@@ -2238,57 +2311,35 @@ function envOverride(agent, env) {
2238
2311
  account_source: "override"
2239
2312
  };
2240
2313
  }
2241
- function knownToolIds(api) {
2242
- try {
2243
- return new Set(api.listTools().map((tool) => tool.id));
2244
- } catch {
2245
- return new Set;
2246
- }
2247
- }
2248
- function profileForEnvDir(api, tool, env) {
2314
+ async function profileForEnvDir(store, tool, env) {
2249
2315
  const configuredDir = env[tool.envVar];
2250
2316
  if (!configuredDir)
2251
2317
  return null;
2252
2318
  const normalized = normalizeDir(configuredDir);
2253
- try {
2254
- return api.listProfiles(tool.id).find((profile) => normalizeDir(profile.dir) === normalized) ?? null;
2255
- } catch {
2256
- return null;
2257
- }
2319
+ return (await store.listProfiles(tool.id)).find((profile) => normalizeDir(profile.dir) === normalized) ?? null;
2258
2320
  }
2259
2321
  async function resolveAccountForAgent(agent, env = process.env) {
2260
2322
  const override = envOverride(agent, env);
2261
2323
  if (override)
2262
2324
  return override;
2263
- let api;
2264
- try {
2265
- api = await import("@hasna/accounts");
2266
- } catch {
2267
- return null;
2268
- }
2269
- const toolIds = knownToolIds(api);
2325
+ const store = resolveStore(env);
2326
+ const tools = new Map((await store.listTools()).map((tool) => [tool.id, tool]));
2270
2327
  for (const toolId of AGENT_ACCOUNT_TOOLS[agent]) {
2271
- if (!toolIds.has(toolId))
2328
+ const tool = tools.get(toolId);
2329
+ if (!tool)
2272
2330
  continue;
2273
- let tool;
2274
- try {
2275
- tool = api.getTool(toolId);
2276
- } catch {
2277
- continue;
2278
- }
2279
- const envProfile = profileForEnvDir(api, tool, env);
2331
+ const envProfile = await profileForEnvDir(store, tool, env);
2280
2332
  if (envProfile)
2281
2333
  return fromProfile(envProfile, "env");
2282
- try {
2283
- const applied = api.appliedProfile(toolId);
2334
+ const appliedName = appliedProfileName(toolId);
2335
+ if (appliedName) {
2336
+ const applied = await store.findProfile(appliedName, toolId);
2284
2337
  if (applied)
2285
2338
  return fromProfile(applied, "applied");
2286
- } catch {}
2287
- try {
2288
- const current = api.currentProfile(toolId);
2289
- if (current)
2290
- return fromProfile(current, "current");
2291
- } catch {}
2339
+ }
2340
+ const current = await store.currentProfile(toolId);
2341
+ if (current)
2342
+ return fromProfile(current, "current");
2292
2343
  }
2293
2344
  return null;
2294
2345
  }
@@ -5150,7 +5201,7 @@ class LocalStore {
5150
5201
  return query.since ? queryAgentBreakdownSince(this.db(), query.since) : queryAgentBreakdown(this.db(), query.period ?? "all", query.machine);
5151
5202
  }
5152
5203
  async projectBreakdown(query = {}) {
5153
- return query.since ? queryProjectBreakdownSince(this.db(), query.since) : queryProjectBreakdown(this.db(), query.period ?? "all", query.machine);
5204
+ return query.since ? queryProjectBreakdownSince(this.db(), query.since, query.machine) : queryProjectBreakdown(this.db(), query.period ?? "all", query.machine);
5154
5205
  }
5155
5206
  async accountBreakdown(query = {}) {
5156
5207
  return query.since ? queryAccountBreakdownSince(this.db(), query.since) : queryAccountBreakdown(this.db(), query.period ?? "all", query.machine);
@@ -5346,7 +5397,7 @@ class ApiStore {
5346
5397
  return cloudListItems(this.cloud, "breakdown", q({ by: "agent", period: query.period, since: query.since }));
5347
5398
  }
5348
5399
  async projectBreakdown(query = {}) {
5349
- return cloudListItems(this.cloud, "breakdown", q({ by: "project", period: query.period, since: query.since }));
5400
+ return cloudListItems(this.cloud, "breakdown", q({ by: "project", period: query.period, since: query.since, machine: query.machine }));
5350
5401
  }
5351
5402
  async accountBreakdown(query = {}) {
5352
5403
  return cloudListItems(this.cloud, "breakdown", q({ by: "account", period: query.period, since: query.since }));
@@ -6104,7 +6155,6 @@ current machine: ${getMachineId()}`);
6104
6155
  return textError(String(error));
6105
6156
  }
6106
6157
  });
6107
- registerCloudTools(server, MCP_NAME);
6108
6158
  return server;
6109
6159
  }
6110
6160
 
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,QAAQ,YAAY,CAAA;AACjC,eAAO,MAAM,qBAAqB,OAAO,CAAA;AAEzC,wBAAgB,WAAW,IAAI,GAAG,CAg0BjC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,QAAQ,YAAY,CAAA;AACjC,eAAO,MAAM,qBAAqB,OAAO,CAAA;AAEzC,wBAAgB,WAAW,IAAI,GAAG,CAm0BjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAwjD/C,CAAA;AACD,eAAe,WAAW,CAAA"}
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAikD/C,CAAA;AACD,eAAe,WAAW,CAAA"}
@@ -16,6 +16,62 @@ var __export = (target, all) => {
16
16
  };
17
17
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
18
18
 
19
+ // src/db/sqlite-adapter.ts
20
+ import { Database } from "bun:sqlite";
21
+
22
+ class SqliteAdapter {
23
+ db;
24
+ constructor(path) {
25
+ this.db = new Database(path, { create: true });
26
+ this.db.exec("PRAGMA journal_mode=WAL");
27
+ this.db.exec("PRAGMA foreign_keys=ON");
28
+ }
29
+ run(sql, ...params) {
30
+ const result = this.db.prepare(sql).run(...params);
31
+ return { changes: result.changes, lastInsertRowid: result.lastInsertRowid };
32
+ }
33
+ get(sql, ...params) {
34
+ return this.db.prepare(sql).get(...params);
35
+ }
36
+ all(sql, ...params) {
37
+ return this.db.prepare(sql).all(...params);
38
+ }
39
+ exec(sql) {
40
+ this.db.exec(sql);
41
+ }
42
+ query(sql) {
43
+ return this.db.query(sql);
44
+ }
45
+ prepare(sql) {
46
+ const stmt = this.db.prepare(sql);
47
+ return {
48
+ run(...params) {
49
+ const r = stmt.run(...params);
50
+ return { changes: r.changes, lastInsertRowid: r.lastInsertRowid };
51
+ },
52
+ get(...params) {
53
+ return stmt.get(...params);
54
+ },
55
+ all(...params) {
56
+ return stmt.all(...params);
57
+ },
58
+ finalize() {
59
+ stmt.finalize();
60
+ }
61
+ };
62
+ }
63
+ close() {
64
+ this.db.close();
65
+ }
66
+ transaction(fn) {
67
+ return this.db.transaction(fn)();
68
+ }
69
+ get raw() {
70
+ return this.db;
71
+ }
72
+ }
73
+ var init_sqlite_adapter = () => {};
74
+
19
75
  // src/lib/pricing.ts
20
76
  var exports_pricing = {};
21
77
  __export(exports_pricing, {
@@ -534,7 +590,6 @@ var init_pricing = __esm(() => {
534
590
  });
535
591
 
536
592
  // src/db/database.ts
537
- import { SqliteAdapter as Database } from "@hasna/cloud";
538
593
  import { execFileSync } from "child_process";
539
594
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
540
595
  import { hostname, platform } from "os";
@@ -627,7 +682,7 @@ function openDatabase(dbPath, skipSeed = false) {
627
682
  mkdirSync(dir, { recursive: true });
628
683
  }
629
684
  const db = withSqliteBusyRetry(() => {
630
- const opened = new Database(path);
685
+ const opened = new SqliteAdapter(path);
631
686
  try {
632
687
  opened.exec("PRAGMA busy_timeout = 10000");
633
688
  opened.exec("PRAGMA journal_mode = WAL");
@@ -984,7 +1039,9 @@ function seedModelPricing(db, defaults) {
984
1039
  });
985
1040
  }
986
1041
  }
987
- var init_database = () => {};
1042
+ var init_database = __esm(() => {
1043
+ init_sqlite_adapter();
1044
+ });
988
1045
 
989
1046
  // src/otel/index.ts
990
1047
  init_database();
@@ -1356,10 +1413,18 @@ function resolvePort(argv) {
1356
1413
  }
1357
1414
  var args = process.argv.slice(2);
1358
1415
  if (args.includes("--help") || args.includes("-h")) {
1359
- console.log(`Usage: economy-otel [--port 4318]
1416
+ console.log(`Usage: economy-otel [options]
1360
1417
 
1361
1418
  OTLP/HTTP metrics sidecar \u2014 ingests *.cost.* / *.token.* metrics into economy.db
1362
1419
 
1420
+ Options:
1421
+ -p, --port <port> Port to bind (default: ECONOMY_OTEL_PORT or 4318)
1422
+ -h, --help display help for command
1423
+
1424
+ Environment:
1425
+ ECONOMY_OTEL_PORT Override the default port (4318)
1426
+ ECONOMY_OTEL_BIND Bind address (default: 127.0.0.1)
1427
+
1363
1428
  Endpoints:
1364
1429
  POST /v1/metrics OTLP JSON metrics
1365
1430
  POST /ingest Simplified single-event JSON