@hasna/economy 0.2.9 → 0.2.11

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 (46) hide show
  1. package/README.md +37 -45
  2. package/dist/cli/brains.d.ts +3 -0
  3. package/dist/cli/brains.d.ts.map +1 -0
  4. package/dist/cli/commands/menubar.d.ts +7 -0
  5. package/dist/cli/commands/menubar.d.ts.map +1 -0
  6. package/dist/cli/commands/watch.d.ts +9 -0
  7. package/dist/cli/commands/watch.d.ts.map +1 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +127 -82
  11. package/dist/db/database.d.ts +68 -0
  12. package/dist/db/database.d.ts.map +1 -0
  13. package/dist/db/pg-migrations.d.ts +7 -0
  14. package/dist/db/pg-migrations.d.ts.map +1 -0
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +2 -2
  18. package/dist/ingest/claude.d.ts +7 -0
  19. package/dist/ingest/claude.d.ts.map +1 -0
  20. package/dist/ingest/codex.d.ts +7 -0
  21. package/dist/ingest/codex.d.ts.map +1 -0
  22. package/dist/ingest/gemini.d.ts +5 -0
  23. package/dist/ingest/gemini.d.ts.map +1 -0
  24. package/dist/lib/config.d.ts +13 -0
  25. package/dist/lib/config.d.ts.map +1 -0
  26. package/dist/lib/gatherer.d.ts +21 -0
  27. package/dist/lib/gatherer.d.ts.map +1 -0
  28. package/dist/lib/model-config.d.ts +8 -0
  29. package/dist/lib/model-config.d.ts.map +1 -0
  30. package/dist/lib/package-metadata.d.ts +8 -0
  31. package/dist/lib/package-metadata.d.ts.map +1 -0
  32. package/dist/lib/pricing.d.ts +10 -0
  33. package/dist/lib/pricing.d.ts.map +1 -0
  34. package/dist/lib/webhooks.d.ts +3 -0
  35. package/dist/lib/webhooks.d.ts.map +1 -0
  36. package/dist/mcp/index.d.ts +3 -0
  37. package/dist/mcp/index.d.ts.map +1 -0
  38. package/dist/mcp/index.js +305 -326
  39. package/dist/server/index.d.ts +3 -0
  40. package/dist/server/index.d.ts.map +1 -0
  41. package/dist/server/index.js +168 -10
  42. package/dist/server/serve.d.ts +4 -0
  43. package/dist/server/serve.d.ts.map +1 -0
  44. package/dist/types/index.d.ts +101 -0
  45. package/dist/types/index.d.ts.map +1 -0
  46. package/package.json +6 -4
package/README.md CHANGED
@@ -1,69 +1,61 @@
1
1
  # @hasna/economy
2
2
 
3
- AI coding cost tracker for Claude Code, Codex, and Gemini.
3
+ AI coding cost tracker — CLI + MCP server + REST API + web dashboard for Claude Code, Codex, and Gemini
4
4
 
5
- Track every dollar spent across all your AI coding sessions — per request, per session, per project, per day.
6
-
7
- ## Features
8
-
9
- - **Claude Code** — exact costs from telemetry JSONL (`costUSD` per request)
10
- - **Codex** — estimated costs from token count × model pricing
11
- - **SQLite backend** — all data stored locally at `~/.economy/economy.db`
12
- - **DB-backed pricing** — model rates editable via CLI, seeded from defaults
13
- - **CLI** — `economy sync`, `economy today`, `economy sessions`, `economy watch`, etc.
14
- - **Live watch** — `economy watch` streams costs as they arrive
15
- - **Budgets** — set per-project or global budgets with alert thresholds
16
- - **MCP server** — agents can query their own costs
17
- - **REST API** — `economy serve` on port 3456
18
- - **Web dashboard** — charts, sessions table, model/project breakdown
19
- - **macOS menubar** — live cost display in your menu bar
20
- - **SDK** — `@hasna/economy-sdk` for programmatic access
5
+ [![npm](https://img.shields.io/npm/v/@hasna/economy)](https://www.npmjs.com/package/@hasna/economy)
6
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
21
7
 
22
8
  ## Install
23
9
 
24
10
  ```bash
25
- bun add -g @hasna/economy
26
- economy sync
27
- economy today
11
+ bun install -g @hasna/economy
28
12
  ```
29
13
 
30
- ## Usage
14
+ ## CLI Usage
31
15
 
32
16
  ```bash
33
- economy sync # ingest Claude Code + Codex data
34
- economy today # today's cost summary
35
- economy week # this week
36
- economy month # this month
37
- economy sessions # list sessions with costs
38
- economy top # most expensive sessions
39
- economy watch # live cost stream
40
- economy breakdown # by model/agent/project
41
- economy budget set --period monthly --limit 100
42
- economy budget list
43
- economy project add /path/to/project --name "My Project"
44
- economy pricing list
45
- economy pricing set gpt-4o --input 2.50 --output 10.00
46
- economy serve # start REST API on port 3456
47
- economy dashboard # open web dashboard
48
- economy mcp --all # show MCP install commands
17
+ economy --help
49
18
  ```
50
19
 
51
20
  ## MCP Server
52
21
 
53
22
  ```bash
54
- claude mcp add --transport stdio --scope user economy -- economy-mcp
23
+ economy-mcp --help
24
+ ```
25
+
26
+ ## REST API
27
+
28
+ ```bash
29
+ economy-serve --help
30
+ ```
31
+
32
+ ## Native macOS Menubar
33
+
34
+ The `menubar/` app is a native SwiftUI menu bar app built with `MenuBarExtra`, not Electron. It targets macOS 26 and talks to the REST API exposed by `economy-serve`. The server URL is configurable inside the app and defaults to `http://127.0.0.1:3456`.
35
+
36
+ Build it on macOS with Xcode / Swift 6.2:
37
+
38
+ ```bash
39
+ cd menubar
40
+ swift build -c release
55
41
  ```
56
42
 
57
- ## SDK
43
+ ## Cloud Sync
58
44
 
59
- ```ts
60
- import { EconomyClient } from '@hasna/economy-sdk'
45
+ This package supports cloud sync via `@hasna/cloud`:
61
46
 
62
- const client = new EconomyClient()
63
- const today = await client.getSummary('today')
64
- console.log(`Today's cost: $${today.total_usd.toFixed(4)}`)
47
+ ```bash
48
+ cloud setup
49
+ cloud sync push --service economy
50
+ cloud sync pull --service economy
65
51
  ```
66
52
 
53
+ ## Data Directory
54
+
55
+ Data is stored in `~/.hasna/economy/`.
56
+
57
+ The main SQLite database lives at `~/.hasna/economy/economy.db`. Older `~/.economy/` data is auto-migrated on first open.
58
+
67
59
  ## License
68
60
 
69
- Apache-2.0
61
+ Apache-2.0 -- see [LICENSE](LICENSE)
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerBrainsCommand(program: Command): void;
3
+ //# sourceMappingURL=brains.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brains.d.ts","sourceRoot":"","sources":["../../src/cli/brains.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAQnC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoL5D"}
@@ -0,0 +1,7 @@
1
+ export declare function menubarInstall(opts: {
2
+ force?: boolean;
3
+ }): Promise<void>;
4
+ export declare function menubarUninstall(): void;
5
+ export declare function menubarStart(): void;
6
+ export declare function menubarStop(): void;
7
+ //# sourceMappingURL=menubar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menubar.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/menubar.ts"],"names":[],"mappings":"AA0BA,wBAAsB,cAAc,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsE7E;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAcvC;AAED,wBAAgB,YAAY,IAAI,IAAI,CAOnC;AAED,wBAAgB,WAAW,IAAI,IAAI,CAWlC"}
@@ -0,0 +1,9 @@
1
+ import type { Agent } from '../../types/index.js';
2
+ interface WatchOptions {
3
+ interval: number;
4
+ agent?: Agent;
5
+ notify?: number;
6
+ }
7
+ export declare function watchCosts(opts: WatchOptions): Promise<void>;
8
+ export {};
9
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/watch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAEjD,UAAU,YAAY;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAuBD,wBAAsB,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAkFlE"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
package/dist/cli/index.js CHANGED
@@ -689,7 +689,7 @@ var init_claude = __esm(() => {
689
689
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
690
690
  import { homedir as homedir3 } from "os";
691
691
  import { join as join5, basename as basename2 } from "path";
692
- import { Database as Database2 } from "bun:sqlite";
692
+ import { Database as BunDatabase } from "bun:sqlite";
693
693
  async function ingestCodex(db, verbose = false) {
694
694
  if (!existsSync4(CODEX_DB_PATH)) {
695
695
  if (verbose)
@@ -699,7 +699,7 @@ async function ingestCodex(db, verbose = false) {
699
699
  let codexDb = null;
700
700
  let ingested = 0;
701
701
  try {
702
- codexDb = new Database2(CODEX_DB_PATH, { readonly: true });
702
+ codexDb = new BunDatabase(CODEX_DB_PATH, { readonly: true });
703
703
  const threads = codexDb.prepare(`SELECT id, cwd, created_at, updated_at, tokens_used, title FROM threads WHERE tokens_used > 0`).all();
704
704
  for (const thread of threads) {
705
705
  const stateKey = thread.id;
@@ -739,6 +739,86 @@ var init_codex = __esm(() => {
739
739
  CODEX_CONFIG_PATH = join5(homedir3(), ".codex", "config.toml");
740
740
  });
741
741
 
742
+ // src/ingest/gemini.ts
743
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync3 } from "fs";
744
+ import { homedir as homedir4 } from "os";
745
+ import { join as join6 } from "path";
746
+ async function ingestGemini(db, verbose) {
747
+ if (!existsSync5(GEMINI_TMP_DIR)) {
748
+ if (verbose)
749
+ console.log("Gemini tmp dir not found:", GEMINI_TMP_DIR);
750
+ return { sessions: 0 };
751
+ }
752
+ let totalSessions = 0;
753
+ const touchedSessions = new Set;
754
+ let projectHashDirs = [];
755
+ try {
756
+ projectHashDirs = readdirSync3(GEMINI_TMP_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && /^[0-9a-f]{64}$/.test(d.name)).map((d) => join6(GEMINI_TMP_DIR, d.name));
757
+ } catch {
758
+ return { sessions: 0 };
759
+ }
760
+ for (const projectDir of projectHashDirs) {
761
+ const chatsDir = join6(projectDir, "chats");
762
+ if (!existsSync5(chatsDir))
763
+ continue;
764
+ let chatFiles = [];
765
+ try {
766
+ chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join6(chatsDir, f));
767
+ } catch {
768
+ continue;
769
+ }
770
+ for (const filePath of chatFiles) {
771
+ const stateKey = filePath.replace(homedir4(), "~");
772
+ let fileMtime = "0";
773
+ try {
774
+ fileMtime = statSync3(filePath).mtimeMs.toString();
775
+ } catch {
776
+ continue;
777
+ }
778
+ const processed = getIngestState(db, "gemini", stateKey);
779
+ if (processed === fileMtime)
780
+ continue;
781
+ let chatData;
782
+ try {
783
+ chatData = JSON.parse(readFileSync4(filePath, "utf-8"));
784
+ } catch {
785
+ continue;
786
+ }
787
+ const sessionId = chatData.sessionId;
788
+ if (!sessionId)
789
+ continue;
790
+ const startTime = chatData.startTime ?? new Date().toISOString();
791
+ const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
792
+ if (!existing) {
793
+ const session = {
794
+ id: sessionId,
795
+ agent: "gemini",
796
+ project_path: "",
797
+ project_name: "",
798
+ started_at: startTime,
799
+ ended_at: chatData.lastUpdated ?? null,
800
+ total_cost_usd: 0,
801
+ total_tokens: 0,
802
+ request_count: 0
803
+ };
804
+ upsertSession(db, session);
805
+ touchedSessions.add(sessionId);
806
+ totalSessions++;
807
+ }
808
+ setIngestState(db, "gemini", stateKey, fileMtime);
809
+ }
810
+ }
811
+ for (const sessionId of touchedSessions) {
812
+ rollupSession(db, sessionId);
813
+ }
814
+ return { sessions: totalSessions };
815
+ }
816
+ var GEMINI_TMP_DIR;
817
+ var init_gemini = __esm(() => {
818
+ init_database();
819
+ GEMINI_TMP_DIR = join6(homedir4(), ".gemini", "tmp");
820
+ });
821
+
742
822
  // src/lib/config.ts
743
823
  var exports_config = {};
744
824
  __export(exports_config, {
@@ -747,12 +827,12 @@ __export(exports_config, {
747
827
  loadConfig: () => loadConfig2,
748
828
  getConfigValue: () => getConfigValue
749
829
  });
750
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
830
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
751
831
  import { join as join7 } from "path";
752
832
  function loadConfig2() {
753
833
  try {
754
834
  if (existsSync6(CONFIG_PATH2)) {
755
- const raw = readFileSync5(CONFIG_PATH2, "utf-8");
835
+ const raw = readFileSync6(CONFIG_PATH2, "utf-8");
756
836
  return { ...DEFAULTS, ...JSON.parse(raw) };
757
837
  }
758
838
  } catch {}
@@ -962,6 +1042,20 @@ function ok(data, meta) {
962
1042
  function err(message, status = 400) {
963
1043
  return json({ error: message }, status);
964
1044
  }
1045
+ function normalizeBudgetPeriod(value) {
1046
+ switch (value) {
1047
+ case "day":
1048
+ case "daily":
1049
+ return "daily";
1050
+ case "week":
1051
+ case "weekly":
1052
+ return "weekly";
1053
+ case "month":
1054
+ case "monthly":
1055
+ default:
1056
+ return "monthly";
1057
+ }
1058
+ }
965
1059
  function applyFields(obj, fields) {
966
1060
  if (!fields || fields.length === 0)
967
1061
  return obj;
@@ -987,12 +1081,20 @@ function createHandler(db) {
987
1081
  if (path === "/api/sessions" && method === "GET") {
988
1082
  const agent = url.searchParams.get("agent");
989
1083
  const project = url.searchParams.get("project") ?? undefined;
1084
+ const search = url.searchParams.get("search") ?? undefined;
990
1085
  const limit = Number(url.searchParams.get("limit") ?? 50);
991
1086
  const offset = Number(url.searchParams.get("offset") ?? 0);
992
1087
  const since = url.searchParams.get("since") ?? undefined;
993
1088
  const fieldsParam = url.searchParams.get("fields");
994
1089
  const fields = fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
995
- const sessions = querySessions(db, { agent: agent ?? undefined, project, limit, offset, since });
1090
+ const sessions = querySessions(db, {
1091
+ agent: agent ?? undefined,
1092
+ project,
1093
+ search,
1094
+ limit,
1095
+ offset,
1096
+ since
1097
+ });
996
1098
  return ok(fields ? sessions.map((s) => applyFields(s, fields)) : sessions, { limit, offset });
997
1099
  }
998
1100
  if (path === "/api/top" && method === "GET") {
@@ -1020,7 +1122,7 @@ function createHandler(db) {
1020
1122
  id: randomUUID(),
1021
1123
  project_path: body["project_path"] ?? null,
1022
1124
  agent: body["agent"] ?? null,
1023
- period: body["period"] ?? "monthly",
1125
+ period: normalizeBudgetPeriod(body["period"]),
1024
1126
  limit_usd: Number(body["limit_usd"]),
1025
1127
  alert_at_percent: Number(body["alert_at_percent"] ?? 80),
1026
1128
  created_at: now,
@@ -1083,6 +1185,8 @@ function createHandler(db) {
1083
1185
  results["claude"] = await ingestClaude(db);
1084
1186
  if (sources === "all" || sources === "codex")
1085
1187
  results["codex"] = await ingestCodex(db);
1188
+ if (sources === "all" || sources === "gemini")
1189
+ results["gemini"] = await ingestGemini(db);
1086
1190
  return ok(results);
1087
1191
  }
1088
1192
  const sessionRequestsMatch = path.match(/^\/api\/sessions\/([^/]+)\/requests$/);
@@ -1155,6 +1259,7 @@ var init_serve = __esm(() => {
1155
1259
  init_database();
1156
1260
  init_claude();
1157
1261
  init_codex();
1262
+ init_gemini();
1158
1263
  init_pricing();
1159
1264
  CORS = {
1160
1265
  "Access-Control-Allow-Origin": "*",
@@ -1650,90 +1755,30 @@ ${chalk.dim("Set it active:")} economy brains model set ${String(status["fine_tu
1650
1755
  init_database();
1651
1756
  init_claude();
1652
1757
  init_codex();
1758
+ init_gemini();
1653
1759
 
1654
- // src/ingest/gemini.ts
1655
- init_database();
1656
- import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync3 } from "fs";
1657
- import { homedir as homedir4 } from "os";
1658
- import { join as join6 } from "path";
1659
- var GEMINI_TMP_DIR = join6(homedir4(), ".gemini", "tmp");
1660
- async function ingestGemini(db, verbose) {
1661
- if (!existsSync5(GEMINI_TMP_DIR)) {
1662
- if (verbose)
1663
- console.log("Gemini tmp dir not found:", GEMINI_TMP_DIR);
1664
- return { sessions: 0 };
1665
- }
1666
- let totalSessions = 0;
1667
- const touchedSessions = new Set;
1668
- let projectHashDirs = [];
1669
- try {
1670
- projectHashDirs = readdirSync3(GEMINI_TMP_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && /^[0-9a-f]{64}$/.test(d.name)).map((d) => join6(GEMINI_TMP_DIR, d.name));
1671
- } catch {
1672
- return { sessions: 0 };
1673
- }
1674
- for (const projectDir of projectHashDirs) {
1675
- const chatsDir = join6(projectDir, "chats");
1676
- if (!existsSync5(chatsDir))
1677
- continue;
1678
- let chatFiles = [];
1679
- try {
1680
- chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join6(chatsDir, f));
1681
- } catch {
1682
- continue;
1683
- }
1684
- for (const filePath of chatFiles) {
1685
- const stateKey = filePath.replace(homedir4(), "~");
1686
- let fileMtime = "0";
1687
- try {
1688
- fileMtime = statSync3(filePath).mtimeMs.toString();
1689
- } catch {
1690
- continue;
1691
- }
1692
- const processed = getIngestState(db, "gemini", stateKey);
1693
- if (processed === fileMtime)
1694
- continue;
1695
- let chatData;
1696
- try {
1697
- chatData = JSON.parse(readFileSync4(filePath, "utf-8"));
1698
- } catch {
1699
- continue;
1700
- }
1701
- const sessionId = chatData.sessionId;
1702
- if (!sessionId)
1703
- continue;
1704
- const startTime = chatData.startTime ?? new Date().toISOString();
1705
- const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
1706
- if (!existing) {
1707
- const session = {
1708
- id: sessionId,
1709
- agent: "gemini",
1710
- project_path: "",
1711
- project_name: "",
1712
- started_at: startTime,
1713
- ended_at: chatData.lastUpdated ?? null,
1714
- total_cost_usd: 0,
1715
- total_tokens: 0,
1716
- request_count: 0
1717
- };
1718
- upsertSession(db, session);
1719
- touchedSessions.add(sessionId);
1720
- totalSessions++;
1721
- }
1722
- setIngestState(db, "gemini", stateKey, fileMtime);
1723
- }
1724
- }
1725
- for (const sessionId of touchedSessions) {
1726
- rollupSession(db, sessionId);
1727
- }
1728
- return { sessions: totalSessions };
1760
+ // src/lib/package-metadata.ts
1761
+ import { readFileSync as readFileSync5 } from "fs";
1762
+ var cachedMetadata = null;
1763
+ function getPackageMetadata() {
1764
+ if (cachedMetadata)
1765
+ return cachedMetadata;
1766
+ const raw = readFileSync5(new URL("../../package.json", import.meta.url), "utf8");
1767
+ const parsed = JSON.parse(raw);
1768
+ cachedMetadata = {
1769
+ name: parsed.name ?? "@hasna/economy",
1770
+ version: parsed.version ?? "0.0.0"
1771
+ };
1772
+ return cachedMetadata;
1729
1773
  }
1774
+ var packageMetadata = getPackageMetadata();
1730
1775
 
1731
1776
  // src/cli/index.ts
1732
1777
  init_pricing();
1733
1778
  import { randomUUID as randomUUID2 } from "crypto";
1734
1779
  import { execSync as execSync2 } from "child_process";
1735
1780
  var program = new Command;
1736
- program.name("economy").description("AI coding cost tracker \u2014 Claude Code, Codex, and Gemini").version("0.2.2");
1781
+ program.name("economy").description("AI coding cost tracker \u2014 Claude Code, Codex, and Gemini").version(packageMetadata.version);
1737
1782
  async function autoSync() {
1738
1783
  const db = openDatabase();
1739
1784
  ensurePricingSeeded(db);
@@ -0,0 +1,68 @@
1
+ import { SqliteAdapter as Database } from '@hasna/cloud';
2
+ import type { EconomyRequest, EconomySession, EconomyProject, Budget, BudgetStatus, CostSummary, ModelBreakdown, ProjectBreakdown, Period, SessionFilter } from '../types/index.js';
3
+ export declare function getDataDir(): string;
4
+ export declare function getDbPath(): string;
5
+ export declare function openDatabase(dbPath?: string, skipSeed?: boolean): Database;
6
+ export declare function upsertRequest(db: Database, req: EconomyRequest): void;
7
+ export declare function upsertSession(db: Database, session: EconomySession): void;
8
+ export declare function rollupSession(db: Database, sessionId: string): void;
9
+ export declare function querySessions(db: Database, filter?: SessionFilter): EconomySession[];
10
+ export declare function queryTopSessions(db: Database, n?: number, agent?: string): EconomySession[];
11
+ export declare function querySummary(db: Database, period: Period): CostSummary;
12
+ export declare function queryModelBreakdown(db: Database): ModelBreakdown[];
13
+ export declare function queryProjectBreakdown(db: Database): ProjectBreakdown[];
14
+ export declare function queryDailyBreakdown(db: Database, days?: number): Array<{
15
+ date: string;
16
+ cost_usd: number;
17
+ agent: string;
18
+ }>;
19
+ export declare function upsertProject(db: Database, project: EconomyProject): void;
20
+ export declare function getProject(db: Database, path: string): EconomyProject | null;
21
+ export declare function listProjects(db: Database): EconomyProject[];
22
+ export declare function deleteProject(db: Database, path: string): void;
23
+ export declare function upsertBudget(db: Database, budget: Budget): void;
24
+ export declare function listBudgets(db: Database): Budget[];
25
+ export declare function deleteBudget(db: Database, id: string): void;
26
+ export declare function getBudgetStatuses(db: Database): BudgetStatus[];
27
+ export interface Goal {
28
+ id: string;
29
+ period: 'day' | 'week' | 'month' | 'year';
30
+ project_path: string | null;
31
+ agent: string | null;
32
+ limit_usd: number;
33
+ created_at: string;
34
+ updated_at: string;
35
+ }
36
+ export interface GoalStatus extends Goal {
37
+ current_spend_usd: number;
38
+ percent_used: number;
39
+ is_on_track: boolean;
40
+ is_at_risk: boolean;
41
+ is_over: boolean;
42
+ }
43
+ export declare function upsertGoal(db: Database, goal: Goal): void;
44
+ export declare function deleteGoal(db: Database, id: string): void;
45
+ export declare function listGoals(db: Database): Goal[];
46
+ export declare function getGoalStatuses(db: Database): GoalStatus[];
47
+ export declare function getIngestState(db: Database, source: string, key: string): string | null;
48
+ export declare function setIngestState(db: Database, source: string, key: string, value: string): void;
49
+ export declare function queryRequestsSince(db: Database, since: string): EconomyRequest[];
50
+ export interface DbModelPricing {
51
+ model: string;
52
+ input_per_1m: number;
53
+ output_per_1m: number;
54
+ cache_read_per_1m: number;
55
+ cache_write_per_1m: number;
56
+ updated_at: string;
57
+ }
58
+ export declare function upsertModelPricing(db: Database, p: DbModelPricing): void;
59
+ export declare function getModelPricing(db: Database, model: string): DbModelPricing | null;
60
+ export declare function listModelPricing(db: Database): DbModelPricing[];
61
+ export declare function deleteModelPricing(db: Database, model: string): void;
62
+ export declare function seedModelPricing(db: Database, defaults: Record<string, {
63
+ inputPer1M: number;
64
+ outputPer1M: number;
65
+ cacheReadPer1M: number;
66
+ cacheWritePer1M: number;
67
+ }>): void;
68
+ //# sourceMappingURL=database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAIxD,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,cAAc,EACd,MAAM,EACN,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,MAAM,EACN,aAAa,EACd,MAAM,mBAAmB,CAAA;AAE1B,wBAAgB,UAAU,IAAI,MAAM,CAkBnC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAIlC;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,QAAQ,CAexE;AAwHD,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAarE;AAID,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAWzE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAYnE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAE,aAAkB,GAAG,cAAc,EAAE,CAiBxF;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,SAAK,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAKvF;AAID,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CA+BtE;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,cAAc,EAAE,CAUlE;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,GAAG,gBAAgB,EAAE,CAiBtE;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,SAAK,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAQrH;AAID,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAKzE;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAI5E;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,cAAc,EAAE,CAG3D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9D;AAID,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAU/D;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,EAAE,CAElD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,GAAG,YAAY,EAAE,CA2B9D;AAID,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;IACzC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,UAAW,SAAQ,IAAI;IACtC,iBAAiB,EAAE,MAAM,CAAA;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,OAAO,CAAA;IACpB,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CASzD;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,EAAE,CAE9C;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,GAAG,UAAU,EAAE,CA6B1D;AAID,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGvF;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAE7F;AAID,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,EAAE,CAEhF;AAID,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,cAAc,GAAG,IAAI,CAMxE;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAElF;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,cAAc,EAAE,CAE/D;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEpE;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAc3K"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * PostgreSQL migrations for open-economy cloud sync.
3
+ *
4
+ * Equivalent to the SQLite schema in database.ts, translated for PostgreSQL.
5
+ */
6
+ export declare const PG_MIGRATIONS: string[];
7
+ //# sourceMappingURL=pg-migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-migrations.d.ts","sourceRoot":"","sources":["../../src/db/pg-migrations.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,aAAa,EAAE,MAAM,EAmGjC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export * from './types/index.js';
2
+ export * from './db/database.js';
3
+ export * from './lib/pricing.js';
4
+ export * from './lib/gatherer.js';
5
+ export * from './lib/model-config.js';
6
+ export * from './ingest/claude.js';
7
+ export * from './ingest/codex.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -903,7 +903,7 @@ init_database();
903
903
  import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
904
904
  import { homedir as homedir3 } from "os";
905
905
  import { join as join4, basename as basename2 } from "path";
906
- import { Database as Database2 } from "bun:sqlite";
906
+ import { Database as BunDatabase } from "bun:sqlite";
907
907
  var CODEX_DB_PATH = join4(homedir3(), ".codex", "state_5.sqlite");
908
908
  var CODEX_CONFIG_PATH = join4(homedir3(), ".codex", "config.toml");
909
909
  function readCodexModel() {
@@ -926,7 +926,7 @@ async function ingestCodex(db, verbose = false) {
926
926
  let codexDb = null;
927
927
  let ingested = 0;
928
928
  try {
929
- codexDb = new Database2(CODEX_DB_PATH, { readonly: true });
929
+ codexDb = new BunDatabase(CODEX_DB_PATH, { readonly: true });
930
930
  const threads = codexDb.prepare(`SELECT id, cwd, created_at, updated_at, tokens_used, title FROM threads WHERE tokens_used > 0`).all();
931
931
  for (const thread of threads) {
932
932
  const stateKey = thread.id;
@@ -0,0 +1,7 @@
1
+ import type { SqliteAdapter as Database } from '@hasna/cloud';
2
+ export declare function ingestClaude(db: Database, verbose?: boolean, _telemetryDir?: string): Promise<{
3
+ files: number;
4
+ requests: number;
5
+ sessions: number;
6
+ }>;
7
+ //# sourceMappingURL=claude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/ingest/claude.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA2D7D,wBAAsB,YAAY,CAChC,EAAE,EAAE,QAAQ,EACZ,OAAO,UAAQ,EACf,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA2HhE"}
@@ -0,0 +1,7 @@
1
+ import type { SqliteAdapter as Database } from '@hasna/cloud';
2
+ declare function readCodexModel(): string;
3
+ export declare function ingestCodex(db: Database, verbose?: boolean): Promise<{
4
+ sessions: number;
5
+ }>;
6
+ export { readCodexModel };
7
+ //# sourceMappingURL=codex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/ingest/codex.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAkB7D,iBAAS,cAAc,IAAI,MAAM,CAShC;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAyD9F;AAED,OAAO,EAAE,cAAc,EAAE,CAAA"}
@@ -0,0 +1,5 @@
1
+ import type { SqliteAdapter as Database } from '@hasna/cloud';
2
+ export declare function ingestGemini(db: Database, verbose?: boolean): Promise<{
3
+ sessions: number;
4
+ }>;
5
+ //# sourceMappingURL=gemini.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/ingest/gemini.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AA0B7D,wBAAsB,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA2EjG"}
@@ -0,0 +1,13 @@
1
+ export interface EconomyConfig {
2
+ port: number;
3
+ 'default-period': string;
4
+ 'auto-sync': boolean;
5
+ 'sync-interval': number;
6
+ 'alert-thresholds': number[];
7
+ 'webhook-url': string | null;
8
+ }
9
+ export declare function loadConfig(): EconomyConfig;
10
+ export declare function saveConfig(config: EconomyConfig): void;
11
+ export declare function getConfigValue(key: string): unknown;
12
+ export declare function setConfigValue(key: string, value: string): void;
13
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,gBAAgB,EAAE,MAAM,CAAA;IACxB,WAAW,EAAE,OAAO,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB,kBAAkB,EAAE,MAAM,EAAE,CAAA;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAWD,wBAAgB,UAAU,IAAI,aAAa,CAQ1C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAItD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAW/D"}
@@ -0,0 +1,21 @@
1
+ interface TrainingMessage {
2
+ role: 'system' | 'user' | 'assistant';
3
+ content: string;
4
+ }
5
+ interface TrainingExample {
6
+ messages: TrainingMessage[];
7
+ }
8
+ interface GatherResult {
9
+ source: string;
10
+ examples: TrainingExample[];
11
+ count: number;
12
+ }
13
+ interface GathererOptions {
14
+ limit?: number;
15
+ since?: Date;
16
+ outputDir?: string;
17
+ }
18
+ type GatherTrainingDataFn = (options?: GathererOptions) => Promise<GatherResult>;
19
+ export declare const gatherTrainingData: GatherTrainingDataFn;
20
+ export {};
21
+ //# sourceMappingURL=gatherer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gatherer.d.ts","sourceRoot":"","sources":["../../src/lib/gatherer.ts"],"names":[],"mappings":"AAQA,UAAU,eAAe;IACvB,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAA;IACrC,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAA;CAC5B;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,KAAK,oBAAoB,GAAG,CAAC,OAAO,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;AAKhF,eAAO,MAAM,kBAAkB,EAAE,oBAiNhC,CAAA"}
@@ -0,0 +1,8 @@
1
+ export declare const DEFAULT_MODEL = "gpt-4o-mini";
2
+ /** Returns the active fine-tuned model ID, or DEFAULT_MODEL if none set. */
3
+ export declare function getActiveModel(): string;
4
+ /** Persists the active fine-tuned model ID to ~/.hasna/economy/config.json. */
5
+ export declare function setActiveModel(id: string): void;
6
+ /** Clears the active model, falling back to DEFAULT_MODEL. */
7
+ export declare function clearActiveModel(): void;
8
+ //# sourceMappingURL=model-config.d.ts.map