@hasna/economy 0.4.0 → 0.5.0

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 (202) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +12 -9
  3. package/dist/chunks/agent-registry-store-tpnhx3wj.js +39 -0
  4. package/dist/chunks/billing-bkpxy4kv.js +13 -0
  5. package/dist/chunks/config-j9mv3ehk.js +15 -0
  6. package/dist/chunks/index-2hwzmfgv.js +3407 -0
  7. package/dist/chunks/index-axh32d30.js +1313 -0
  8. package/dist/chunks/index-emvs5ph2.js +2085 -0
  9. package/dist/chunks/index-ez9thsr7.js +3440 -0
  10. package/dist/chunks/index-hesxq90g.js +62 -0
  11. package/dist/chunks/index-k540hfww.js +258 -0
  12. package/dist/chunks/index-kgy9b6bf.js +451 -0
  13. package/dist/chunks/index-kn5572kd.js +124 -0
  14. package/dist/chunks/index-p35cyhc4.js +4 -0
  15. package/dist/chunks/index-w3bvv0ns.js +26 -0
  16. package/dist/chunks/index-zpvea08j.js +3372 -0
  17. package/dist/chunks/menubar-6xtkt17h.js +296 -0
  18. package/dist/chunks/open-projects-6z4pq6mx.js +39 -0
  19. package/dist/chunks/pricing-f80p81gr.js +29 -0
  20. package/dist/chunks/pricing-vf2pycmh.js +30 -0
  21. package/dist/chunks/pricing-y21mcjsa.js +30 -0
  22. package/dist/chunks/serve-0k69e4h9.js +4655 -0
  23. package/dist/chunks/sqlite-store-501f32ts.js +258 -0
  24. package/dist/chunks/sqlite-store-fq4natnf.js +256 -0
  25. package/dist/chunks/sqlite-store-st99garx.js +258 -0
  26. package/dist/chunks/third-party-sqlite-5e85fxa2.js +11 -0
  27. package/dist/chunks/tui-7f8f951w.js +22 -0
  28. package/dist/chunks/watch-mm1j39vy.js +196 -0
  29. package/dist/chunks/webhooks-k4fh2t9c.js +109 -0
  30. package/dist/cli/commands/brief.d.ts.map +1 -1
  31. package/dist/cli/commands/extras.d.ts.map +1 -1
  32. package/dist/cli/commands/journal.d.ts +3 -0
  33. package/dist/cli/commands/journal.d.ts.map +1 -0
  34. package/dist/cli/commands/menubar.d.ts +97 -4
  35. package/dist/cli/commands/menubar.d.ts.map +1 -1
  36. package/dist/cli/commands/tui.d.ts +1 -1
  37. package/dist/cli/commands/tui.d.ts.map +1 -1
  38. package/dist/cli/commands/watch.d.ts.map +1 -1
  39. package/dist/cli/index.js +1358 -10114
  40. package/dist/db/cloud.d.ts +7 -9
  41. package/dist/db/cloud.d.ts.map +1 -1
  42. package/dist/db/database.d.ts +14 -12
  43. package/dist/db/database.d.ts.map +1 -1
  44. package/dist/db/ingest-concurrency-worker.d.ts +2 -0
  45. package/dist/db/ingest-concurrency-worker.d.ts.map +1 -0
  46. package/dist/db/ingest-errors.d.ts +9 -0
  47. package/dist/db/ingest-errors.d.ts.map +1 -0
  48. package/dist/db/ingest-schema.d.ts +3 -0
  49. package/dist/db/ingest-schema.d.ts.map +1 -0
  50. package/dist/db/ingest-specs.d.ts +8 -0
  51. package/dist/db/ingest-specs.d.ts.map +1 -0
  52. package/dist/db/ingest-test-contract.d.ts +3 -0
  53. package/dist/db/ingest-test-contract.d.ts.map +1 -0
  54. package/dist/db/ingest-validation.d.ts +12 -0
  55. package/dist/db/ingest-validation.d.ts.map +1 -0
  56. package/dist/db/ingest.d.ts +27 -0
  57. package/dist/db/ingest.d.ts.map +1 -0
  58. package/dist/db/legacy-schema.d.ts +7 -0
  59. package/dist/db/legacy-schema.d.ts.map +1 -0
  60. package/dist/db/observation-contract.d.ts +3 -0
  61. package/dist/db/observation-contract.d.ts.map +1 -0
  62. package/dist/db/observation-provenance.d.ts +23 -0
  63. package/dist/db/observation-provenance.d.ts.map +1 -0
  64. package/dist/db/observation-schema.d.ts +24 -0
  65. package/dist/db/observation-schema.d.ts.map +1 -0
  66. package/dist/db/pg-connection.d.ts +10 -0
  67. package/dist/db/pg-connection.d.ts.map +1 -0
  68. package/dist/db/pg-migrate.d.ts +4 -9
  69. package/dist/db/pg-migrate.d.ts.map +1 -1
  70. package/dist/db/pg-migrations.d.ts.map +1 -1
  71. package/dist/db/pg-protocol.d.ts +28 -0
  72. package/dist/db/pg-protocol.d.ts.map +1 -0
  73. package/dist/db/sqlite-store.d.ts +4 -0
  74. package/dist/db/sqlite-store.d.ts.map +1 -0
  75. package/dist/db/sync-pg.d.ts +17 -21
  76. package/dist/db/sync-pg.d.ts.map +1 -1
  77. package/dist/db/third-party-sqlite.d.ts +10 -0
  78. package/dist/db/third-party-sqlite.d.ts.map +1 -0
  79. package/dist/index.d.ts +3 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +1161 -2464
  82. package/dist/ingest/claude-quota.d.ts.map +1 -1
  83. package/dist/ingest/claude.d.ts.map +1 -1
  84. package/dist/ingest/codex-quota.d.ts.map +1 -1
  85. package/dist/ingest/codex.d.ts +25 -0
  86. package/dist/ingest/codex.d.ts.map +1 -1
  87. package/dist/ingest/collection-outcome.d.ts +17 -0
  88. package/dist/ingest/collection-outcome.d.ts.map +1 -0
  89. package/dist/ingest/cursor.d.ts.map +1 -1
  90. package/dist/ingest/gemini.d.ts.map +1 -1
  91. package/dist/ingest/hermes.d.ts +1 -1
  92. package/dist/ingest/hermes.d.ts.map +1 -1
  93. package/dist/ingest/loops.d.ts.map +1 -1
  94. package/dist/ingest/opencode.d.ts +1 -1
  95. package/dist/ingest/opencode.d.ts.map +1 -1
  96. package/dist/ingest/pi.d.ts +1 -1
  97. package/dist/ingest/pi.d.ts.map +1 -1
  98. package/dist/ingest/quota-observation.d.ts +16 -0
  99. package/dist/ingest/quota-observation.d.ts.map +1 -0
  100. package/dist/ingest/source-time.d.ts +4 -0
  101. package/dist/ingest/source-time.d.ts.map +1 -0
  102. package/dist/lib/accounts-store.d.ts +39 -13
  103. package/dist/lib/accounts-store.d.ts.map +1 -1
  104. package/dist/lib/analytics.d.ts.map +1 -1
  105. package/dist/lib/api-display-url.d.ts +1 -1
  106. package/dist/lib/autosync-gate.d.ts +12 -5
  107. package/dist/lib/autosync-gate.d.ts.map +1 -1
  108. package/dist/lib/billing-diff.d.ts +18 -40
  109. package/dist/lib/billing-diff.d.ts.map +1 -1
  110. package/dist/lib/brief.d.ts +2 -0
  111. package/dist/lib/brief.d.ts.map +1 -1
  112. package/dist/lib/budget-decimal.d.ts +14 -0
  113. package/dist/lib/budget-decimal.d.ts.map +1 -0
  114. package/dist/lib/cloud-ingest.d.ts +15 -0
  115. package/dist/lib/cloud-ingest.d.ts.map +1 -1
  116. package/dist/lib/cloud-storage.d.ts.map +1 -1
  117. package/dist/lib/ingest-batches.d.ts +21 -0
  118. package/dist/lib/ingest-batches.d.ts.map +1 -0
  119. package/dist/lib/ingest-journal-client.d.ts +37 -0
  120. package/dist/lib/ingest-journal-client.d.ts.map +1 -0
  121. package/dist/lib/ingest-journal-lock.d.ts +5 -0
  122. package/dist/lib/ingest-journal-lock.d.ts.map +1 -0
  123. package/dist/lib/ingest-journal-maintenance.d.ts +92 -0
  124. package/dist/lib/ingest-journal-maintenance.d.ts.map +1 -0
  125. package/dist/lib/ingest-journal-operation.d.ts +55 -0
  126. package/dist/lib/ingest-journal-operation.d.ts.map +1 -0
  127. package/dist/lib/ingest-journal.d.ts +73 -0
  128. package/dist/lib/ingest-journal.d.ts.map +1 -0
  129. package/dist/lib/money-display.d.ts +13 -0
  130. package/dist/lib/money-display.d.ts.map +1 -0
  131. package/dist/lib/periods.d.ts +12 -0
  132. package/dist/lib/periods.d.ts.map +1 -1
  133. package/dist/lib/pricing.d.ts +14 -0
  134. package/dist/lib/pricing.d.ts.map +1 -1
  135. package/dist/lib/savings.d.ts +64 -7
  136. package/dist/lib/savings.d.ts.map +1 -1
  137. package/dist/lib/store/index.d.ts +25 -21
  138. package/dist/lib/store/index.d.ts.map +1 -1
  139. package/dist/lib/summary-contract.d.ts +32 -0
  140. package/dist/lib/summary-contract.d.ts.map +1 -0
  141. package/dist/lib/summary-test-contract.d.ts +3 -0
  142. package/dist/lib/summary-test-contract.d.ts.map +1 -0
  143. package/dist/lib/sync-all.d.ts +4 -0
  144. package/dist/lib/sync-all.d.ts.map +1 -1
  145. package/dist/lib/sync-maintenance.d.ts.map +1 -1
  146. package/dist/lib/sync-report.d.ts +4 -0
  147. package/dist/lib/sync-report.d.ts.map +1 -0
  148. package/dist/lib/test-keychain-fixture.d.ts +9 -0
  149. package/dist/lib/test-keychain-fixture.d.ts.map +1 -0
  150. package/dist/mcp/agent-registry-store.d.ts +30 -0
  151. package/dist/mcp/agent-registry-store.d.ts.map +1 -0
  152. package/dist/mcp/agent-registry.d.ts +9 -16
  153. package/dist/mcp/agent-registry.d.ts.map +1 -1
  154. package/dist/mcp/index.js +2281 -3457
  155. package/dist/mcp/ingest-journal-tool.d.ts +2 -0
  156. package/dist/mcp/ingest-journal-tool.d.ts.map +1 -0
  157. package/dist/mcp/server.d.ts.map +1 -1
  158. package/dist/openapi.d.ts.map +1 -1
  159. package/dist/otel/index.js +1950 -288
  160. package/dist/server/human-auth.d.ts +20 -0
  161. package/dist/server/human-auth.d.ts.map +1 -0
  162. package/dist/server/index.js +10401 -6695
  163. package/dist/server/legacy-inventory.d.ts +52 -0
  164. package/dist/server/legacy-inventory.d.ts.map +1 -0
  165. package/dist/server/legacy-operations.d.ts +64 -0
  166. package/dist/server/legacy-operations.d.ts.map +1 -0
  167. package/dist/server/legacy-plan.d.ts +55 -0
  168. package/dist/server/legacy-plan.d.ts.map +1 -0
  169. package/dist/server/legacy-preservation.d.ts +58 -0
  170. package/dist/server/legacy-preservation.d.ts.map +1 -0
  171. package/dist/server/pg-sync-worker.js +195 -21
  172. package/dist/server/serve.d.ts +20 -16
  173. package/dist/server/serve.d.ts.map +1 -1
  174. package/dist/server/service-admission.d.ts +10 -0
  175. package/dist/server/service-admission.d.ts.map +1 -0
  176. package/dist/server/service-dispatcher.d.ts +45 -0
  177. package/dist/server/service-dispatcher.d.ts.map +1 -0
  178. package/dist/server/service-operations.d.ts +5 -0
  179. package/dist/server/service-operations.d.ts.map +1 -0
  180. package/dist/server/service-protocol.d.ts +50 -0
  181. package/dist/server/service-protocol.d.ts.map +1 -0
  182. package/dist/server/service-worker.d.ts +2 -0
  183. package/dist/server/service-worker.d.ts.map +1 -0
  184. package/dist/server/service-worker.js +8029 -0
  185. package/dist/types/index.d.ts +66 -0
  186. package/dist/types/index.d.ts.map +1 -1
  187. package/docs/README.md +1 -1
  188. package/docs/cli.md +1 -1
  189. package/docs/configuration.md +5 -5
  190. package/docs/cost-reporting.md +49 -0
  191. package/docs/historical-data-reconciliation.md +203 -0
  192. package/docs/ingest-journal.md +108 -0
  193. package/docs/ingestion.md +60 -4
  194. package/docs/mcp.md +2 -2
  195. package/docs/native-budget-save.md +84 -0
  196. package/docs/native-human-auth.md +102 -0
  197. package/docs/rest-api.md +2 -2
  198. package/package.json +15 -12
  199. package/dist/lib/test-hermetic-accounts.d.ts +0 -7
  200. package/dist/lib/test-hermetic-accounts.d.ts.map +0 -1
  201. package/dist/lib/test-hermetic-fleet-env.d.ts +0 -4
  202. package/dist/lib/test-hermetic-fleet-env.d.ts.map +0 -1
@@ -0,0 +1,258 @@
1
+ // @bun
2
+ import {
3
+ adoptResolverDataRoot,
4
+ bulkIngest,
5
+ cacheDir,
6
+ clearBillingRange,
7
+ countBillingRecords,
8
+ dataDir,
9
+ dedupeRequests,
10
+ deleteBudget,
11
+ deleteGoal,
12
+ deleteModelPricing,
13
+ deleteProject,
14
+ deleteSubscription,
15
+ getBudgetStatuses,
16
+ getCostCenter,
17
+ getDataDir,
18
+ getDbPath,
19
+ getExactDataRoot,
20
+ getGoalStatuses,
21
+ getHomeDir,
22
+ getIngestState,
23
+ getLegacyDataRoot,
24
+ getMachineId,
25
+ getModelPricing,
26
+ getProject,
27
+ getResolverDataRoot,
28
+ getSessionDetail,
29
+ initSchema,
30
+ insertFeedback,
31
+ listBudgets,
32
+ listGoals,
33
+ listMachineRegistry,
34
+ listMachines,
35
+ listModelPricing,
36
+ listProjects,
37
+ listSubscriptions,
38
+ loadIngestState,
39
+ queryAccountBreakdown,
40
+ queryAccountBreakdownSince,
41
+ queryAgentBreakdown,
42
+ queryAgentBreakdownSince,
43
+ queryBillingSummary,
44
+ queryCostCenterBreakdown,
45
+ queryDailyBreakdown,
46
+ queryHourlyBreakdown,
47
+ queryModelBreakdown,
48
+ queryModelBreakdownSince,
49
+ queryProjectBreakdown,
50
+ queryProjectBreakdownSince,
51
+ queryRequestsSince,
52
+ queryScanReceipts,
53
+ querySessions,
54
+ querySummary,
55
+ queryTopSessions,
56
+ queryUsageSnapshots,
57
+ queryZeroCostTokenizedModels,
58
+ rollupSession,
59
+ seedModelPricing,
60
+ setIngestState,
61
+ upsertBillingDaily,
62
+ upsertBudget,
63
+ upsertCostCenter,
64
+ upsertGoal,
65
+ upsertModelPricing,
66
+ upsertProject,
67
+ upsertRequest,
68
+ upsertScanReceipt,
69
+ upsertSession,
70
+ upsertSubscription,
71
+ upsertUsageSnapshot
72
+ } from "./index-2hwzmfgv.js";
73
+ import {
74
+ __require
75
+ } from "./index-p35cyhc4.js";
76
+
77
+ // src/db/sqlite-adapter.ts
78
+ import { Database } from "bun:sqlite";
79
+
80
+ class SqliteAdapter {
81
+ db;
82
+ constructor(path) {
83
+ this.db = new Database(path, { create: true });
84
+ this.db.exec("PRAGMA journal_mode=WAL");
85
+ this.db.exec("PRAGMA foreign_keys=ON");
86
+ }
87
+ run(sql, ...params) {
88
+ const result = this.db.prepare(sql).run(...params);
89
+ return { changes: result.changes, lastInsertRowid: result.lastInsertRowid };
90
+ }
91
+ get(sql, ...params) {
92
+ return this.db.prepare(sql).get(...params);
93
+ }
94
+ all(sql, ...params) {
95
+ return this.db.prepare(sql).all(...params);
96
+ }
97
+ exec(sql) {
98
+ this.db.exec(sql);
99
+ }
100
+ query(sql) {
101
+ return this.db.query(sql);
102
+ }
103
+ prepare(sql) {
104
+ const stmt = this.db.prepare(sql);
105
+ return {
106
+ run(...params) {
107
+ const r = stmt.run(...params);
108
+ return { changes: r.changes, lastInsertRowid: r.lastInsertRowid };
109
+ },
110
+ get(...params) {
111
+ return stmt.get(...params);
112
+ },
113
+ all(...params) {
114
+ return stmt.all(...params);
115
+ },
116
+ finalize() {
117
+ stmt.finalize();
118
+ }
119
+ };
120
+ }
121
+ close() {
122
+ this.db.close();
123
+ }
124
+ transaction(fn) {
125
+ return this.db.transaction(fn)();
126
+ }
127
+ get raw() {
128
+ return this.db;
129
+ }
130
+ }
131
+
132
+ // src/db/sqlite-store.ts
133
+ import { existsSync, mkdirSync } from "fs";
134
+ function isSqliteBusyError(error) {
135
+ const candidate = error;
136
+ const code = typeof candidate.code === "string" ? candidate.code : "";
137
+ const message = typeof candidate.message === "string" ? candidate.message : String(error);
138
+ return code === "SQLITE_BUSY" || code === "SQLITE_BUSY_RECOVERY" || /database is locked|SQLITE_BUSY/i.test(message);
139
+ }
140
+ function retryDelayMs(attempt) {
141
+ return Math.min(1000, 50 * 2 ** attempt);
142
+ }
143
+ function withSqliteBusyRetry(operation, context) {
144
+ const maxAttempts = 8;
145
+ let lastError;
146
+ for (let attempt = 0;attempt < maxAttempts; attempt++) {
147
+ try {
148
+ return operation();
149
+ } catch (error) {
150
+ if (!isSqliteBusyError(error))
151
+ throw error;
152
+ lastError = error;
153
+ if (attempt === maxAttempts - 1)
154
+ break;
155
+ Bun.sleepSync(retryDelayMs(attempt));
156
+ }
157
+ }
158
+ throw new Error(`SQLite database is locked after ${maxAttempts} attempts while ${context}. Another economy sync/merge may be recovering the database; retry shortly.`, { cause: lastError });
159
+ }
160
+ function openDatabase(dbPath, skipSeed = false) {
161
+ const path = dbPath ?? getDbPath();
162
+ if (path !== ":memory:") {
163
+ const dir = path.substring(0, path.lastIndexOf("/"));
164
+ if (dir && !existsSync(dir))
165
+ mkdirSync(dir, { recursive: true });
166
+ }
167
+ const db = withSqliteBusyRetry(() => {
168
+ const opened = new SqliteAdapter(path);
169
+ try {
170
+ opened.exec("PRAGMA busy_timeout = 10000");
171
+ opened.exec("PRAGMA journal_mode = WAL");
172
+ opened.exec("PRAGMA foreign_keys = ON");
173
+ initSchema(opened);
174
+ return opened;
175
+ } catch (error) {
176
+ try {
177
+ opened.close();
178
+ } catch {}
179
+ throw error;
180
+ }
181
+ }, `opening ${path}`);
182
+ if (!skipSeed) {
183
+ import("./pricing-vf2pycmh.js").then(({ ensurePricingSeeded }) => ensurePricingSeeded(db)).catch(() => {});
184
+ }
185
+ return db;
186
+ }
187
+ export {
188
+ upsertUsageSnapshot,
189
+ upsertSubscription,
190
+ upsertSession,
191
+ upsertScanReceipt,
192
+ upsertRequest,
193
+ upsertProject,
194
+ upsertModelPricing,
195
+ upsertGoal,
196
+ upsertCostCenter,
197
+ upsertBudget,
198
+ upsertBillingDaily,
199
+ setIngestState,
200
+ seedModelPricing,
201
+ rollupSession,
202
+ queryZeroCostTokenizedModels,
203
+ queryUsageSnapshots,
204
+ queryTopSessions,
205
+ querySummary,
206
+ querySessions,
207
+ queryScanReceipts,
208
+ queryRequestsSince,
209
+ queryProjectBreakdownSince,
210
+ queryProjectBreakdown,
211
+ queryModelBreakdownSince,
212
+ queryModelBreakdown,
213
+ queryHourlyBreakdown,
214
+ queryDailyBreakdown,
215
+ queryCostCenterBreakdown,
216
+ queryBillingSummary,
217
+ queryAgentBreakdownSince,
218
+ queryAgentBreakdown,
219
+ queryAccountBreakdownSince,
220
+ queryAccountBreakdown,
221
+ openDatabase,
222
+ loadIngestState,
223
+ listSubscriptions,
224
+ listProjects,
225
+ listModelPricing,
226
+ listMachines,
227
+ listMachineRegistry,
228
+ listGoals,
229
+ listBudgets,
230
+ insertFeedback,
231
+ initSchema,
232
+ getSessionDetail,
233
+ getResolverDataRoot,
234
+ getProject,
235
+ getModelPricing,
236
+ getMachineId,
237
+ getLegacyDataRoot,
238
+ getIngestState,
239
+ getHomeDir,
240
+ getGoalStatuses,
241
+ getExactDataRoot,
242
+ getDbPath,
243
+ getDataDir,
244
+ getCostCenter,
245
+ getBudgetStatuses,
246
+ deleteSubscription,
247
+ deleteProject,
248
+ deleteModelPricing,
249
+ deleteGoal,
250
+ deleteBudget,
251
+ dedupeRequests,
252
+ dataDir,
253
+ countBillingRecords,
254
+ clearBillingRange,
255
+ cacheDir,
256
+ bulkIngest,
257
+ adoptResolverDataRoot
258
+ };
@@ -0,0 +1,11 @@
1
+ // @bun
2
+ import"./index-p35cyhc4.js";
3
+
4
+ // src/db/third-party-sqlite.ts
5
+ import { Database } from "bun:sqlite";
6
+ function openThirdPartySqlite(path, options = {}) {
7
+ return options.readonly === false ? new Database(path) : new Database(path, { readonly: true });
8
+ }
9
+ export {
10
+ openThirdPartySqlite
11
+ };
@@ -0,0 +1,22 @@
1
+ // @bun
2
+ import {
3
+ buildStatusLine,
4
+ buildWaybarJson,
5
+ gatherStatusData,
6
+ printStatusLine,
7
+ printWaybarJson,
8
+ runTui
9
+ } from "./index-kn5572kd.js";
10
+ import"./index-w3bvv0ns.js";
11
+ import"./index-kgy9b6bf.js";
12
+ import"./index-axh32d30.js";
13
+ import"./index-ez9thsr7.js";
14
+ import"./index-p35cyhc4.js";
15
+ export {
16
+ runTui,
17
+ printWaybarJson,
18
+ printStatusLine,
19
+ gatherStatusData,
20
+ buildWaybarJson,
21
+ buildStatusLine
22
+ };
@@ -0,0 +1,196 @@
1
+ // @bun
2
+ import {
3
+ agentPaths,
4
+ syncAll
5
+ } from "./index-emvs5ph2.js";
6
+ import {
7
+ getStore,
8
+ isCloudStore
9
+ } from "./index-kgy9b6bf.js";
10
+ import"./index-axh32d30.js";
11
+ import {
12
+ formatSummaryValue
13
+ } from "./index-ez9thsr7.js";
14
+ import {
15
+ __require
16
+ } from "./index-p35cyhc4.js";
17
+
18
+ // src/cli/commands/watch.ts
19
+ import chalk from "chalk";
20
+ import { watch } from "fs";
21
+
22
+ // src/lib/watch-paths.ts
23
+ import { existsSync } from "fs";
24
+ function getWatchPaths() {
25
+ const p = agentPaths();
26
+ const candidates = [
27
+ p.claudeProjects,
28
+ p.takumiProjects,
29
+ p.codexDir,
30
+ p.geminiTmp,
31
+ p.geminiHistory,
32
+ p.opencodeMessages,
33
+ p.piSessions,
34
+ p.hermesDir
35
+ ];
36
+ return candidates.filter((path) => existsSync(path));
37
+ }
38
+
39
+ // src/cli/commands/notification.ts
40
+ import { execFileSync } from "child_process";
41
+ function appleScriptString(value) {
42
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
43
+ }
44
+ function notificationScript(title, body) {
45
+ return `display notification ${appleScriptString(body)} with title ${appleScriptString(title)}`;
46
+ }
47
+ function sendNotification(title, body, execFile = execFileSync) {
48
+ try {
49
+ execFile("osascript", ["-e", notificationScript(title, body)], { stdio: "ignore" });
50
+ } catch {}
51
+ }
52
+
53
+ // src/cli/commands/watch.ts
54
+ function fmt(usd) {
55
+ return chalk.green(`$${usd.toFixed(4)}`);
56
+ }
57
+ function renderHeader(today, week, mode) {
58
+ process.stdout.write("\x1B[H\x1B[2J");
59
+ console.log(chalk.bold.cyan(" economy watch") + chalk.dim(` \u2014 stored usage stream (${mode})`));
60
+ console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
61
+ console.log(` Today: ${formatSummaryValue(today)} Week: ${formatSummaryValue(week)}`);
62
+ console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
63
+ console.log(" UTC; selected stored records only; source coverage unverified");
64
+ console.log(chalk.dim(" [agent] cost model tokens project"));
65
+ console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
66
+ }
67
+ function agentLabel(agent) {
68
+ if (agent === "claude")
69
+ return chalk.blue("[claude]");
70
+ if (agent === "codex")
71
+ return chalk.yellow("[codex] ");
72
+ if (agent === "gemini")
73
+ return chalk.green("[gemini]");
74
+ if (agent === "takumi")
75
+ return chalk.magenta("[takumi]");
76
+ if (agent === "opencode")
77
+ return chalk.cyan("[opncde]");
78
+ if (agent === "cursor")
79
+ return chalk.white("[cursor]");
80
+ if (agent === "pi")
81
+ return chalk.white("[pi ]");
82
+ if (agent === "hermes")
83
+ return chalk.white("[hermes]");
84
+ return chalk.gray(`[${agent.slice(0, 6).padEnd(6)}]`);
85
+ }
86
+ async function watchCosts(opts) {
87
+ const store = getStore();
88
+ const cloud = isCloudStore();
89
+ const db = cloud ? null : (await import("./sqlite-store-501f32ts.js")).openDatabase();
90
+ let lastCheck = new Date(Date.now() - opts.interval * 1000).toISOString();
91
+ const lines = [];
92
+ const MAX_LINES = 20;
93
+ let sessionCumulativeCost = 0;
94
+ let notifyThresholdFired = 0;
95
+ let ingestPending = false;
96
+ let ingestTimer = null;
97
+ const daemon = opts.daemon && !cloud;
98
+ const mode = cloud ? "cloud" : daemon ? "daemon" : "poll";
99
+ const initialSummaryToday = await store.summary("today");
100
+ const initialSummaryWeek = await store.summary("week");
101
+ renderHeader(initialSummaryToday, initialSummaryWeek, mode);
102
+ if (daemon) {
103
+ const paths = getWatchPaths();
104
+ console.log(chalk.dim(`
105
+ Watching ${paths.length} paths \u2014 sync on change, refresh every ${opts.interval}s
106
+ `));
107
+ for (const p of paths) {
108
+ try {
109
+ watch(p, { recursive: true }, () => scheduleIngest());
110
+ } catch {
111
+ try {
112
+ watch(p, () => scheduleIngest());
113
+ } catch {}
114
+ }
115
+ }
116
+ } else if (cloud) {
117
+ console.log(chalk.dim(`
118
+ Streaming from cloud API every ${opts.interval}s \u2014 Ctrl+C to exit
119
+ `));
120
+ } else {
121
+ console.log(chalk.dim(`
122
+ Polling every ${opts.interval}s \u2014 Ctrl+C to exit
123
+ `));
124
+ }
125
+ function scheduleIngest() {
126
+ ingestPending = true;
127
+ if (ingestTimer)
128
+ return;
129
+ ingestTimer = setTimeout(() => {
130
+ ingestTimer = null;
131
+ poll();
132
+ }, 500);
133
+ }
134
+ async function poll() {
135
+ const now = new Date().toISOString();
136
+ const shouldSync = !daemon || ingestPending;
137
+ ingestPending = false;
138
+ if (shouldSync && db)
139
+ await syncAll(db);
140
+ const newRequests = await store.recentRequests(lastCheck);
141
+ lastCheck = now;
142
+ for (const req of newRequests) {
143
+ if (opts.agent && req.agent !== opts.agent)
144
+ continue;
145
+ const tokens = req.input_tokens + req.output_tokens;
146
+ const tokStr = tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}k` : String(tokens);
147
+ const line = ` ${agentLabel(req.agent)} ${fmt(req.cost_usd).padEnd(14)}${req.model.substring(0, 24).padEnd(26)}${tokStr.padEnd(10)}${req.session_id.substring(0, 12)}`;
148
+ lines.push(line);
149
+ if (lines.length > MAX_LINES)
150
+ lines.shift();
151
+ if (req.cost_usd > 1) {
152
+ sendNotification("economy: high cost", `$${req.cost_usd.toFixed(2)} on ${req.model}`);
153
+ }
154
+ if (opts.notify && opts.notify > 0) {
155
+ sessionCumulativeCost += req.cost_usd;
156
+ const crossedThresholds = Math.floor(sessionCumulativeCost / opts.notify);
157
+ if (crossedThresholds > notifyThresholdFired) {
158
+ notifyThresholdFired = crossedThresholds;
159
+ sendNotification("Cost Alert", `Economy: $${sessionCumulativeCost.toFixed(2)} spent this session`);
160
+ }
161
+ }
162
+ }
163
+ const today = await store.summary("today");
164
+ const week = await store.summary("week");
165
+ renderHeader(today, week, mode);
166
+ for (const line of lines)
167
+ console.log(line);
168
+ if (lines.length === 0)
169
+ console.log(chalk.dim(" Waiting for new requests..."));
170
+ const suffix = daemon ? `file watch + ${opts.interval}s refresh` : cloud ? `cloud stream every ${opts.interval}s` : `polling every ${opts.interval}s`;
171
+ console.log(chalk.dim(`
172
+ Last updated: ${new Date().toLocaleTimeString()} \u2014 ${suffix} \u2014 Ctrl+C to exit`));
173
+ }
174
+ if (daemon)
175
+ ingestPending = true;
176
+ await poll();
177
+ const timer = setInterval(() => {
178
+ poll().catch((e) => {
179
+ console.log(chalk.dim(`
180
+ Poll error: ${e instanceof Error ? e.message : String(e)} \u2014 retrying next tick`));
181
+ });
182
+ }, opts.interval * 1000);
183
+ process.on("SIGINT", () => {
184
+ clearInterval(timer);
185
+ if (ingestTimer)
186
+ clearTimeout(ingestTimer);
187
+ console.log(chalk.dim(`
188
+
189
+ Stopped watching.`));
190
+ process.exit(0);
191
+ });
192
+ await new Promise(() => {});
193
+ }
194
+ export {
195
+ watchCosts
196
+ };
@@ -0,0 +1,109 @@
1
+ // @bun
2
+ import {
3
+ loadConfig
4
+ } from "./index-hesxq90g.js";
5
+ import {
6
+ getBudgetStatuses,
7
+ getIngestState,
8
+ setIngestState
9
+ } from "./index-ez9thsr7.js";
10
+ import"./index-p35cyhc4.js";
11
+
12
+ // src/lib/spikes.ts
13
+ function detectCostSpikes(dailyTotals, opts) {
14
+ const windowDays = opts?.windowDays ?? 7;
15
+ const multiplier = opts?.multiplier ?? 2;
16
+ const sorted = [...dailyTotals].sort((a, b) => a.date.localeCompare(b.date));
17
+ const spikes = [];
18
+ for (let i = windowDays;i < sorted.length; i++) {
19
+ const window = sorted.slice(i - windowDays, i);
20
+ const avg = window.reduce((s, d) => s + d.cost_usd, 0) / window.length;
21
+ const current = sorted[i];
22
+ if (avg > 0 && current.cost_usd > avg * multiplier) {
23
+ spikes.push({
24
+ date: current.date,
25
+ cost_usd: current.cost_usd,
26
+ average_usd: avg,
27
+ ratio: current.cost_usd / avg
28
+ });
29
+ }
30
+ }
31
+ return spikes;
32
+ }
33
+ function queryRecentSpikes(db, days = 14) {
34
+ const rows = db.prepare(`
35
+ SELECT DATE(timestamp) as date, COALESCE(SUM(cost_usd), 0) as cost_usd
36
+ FROM economy_effective_requests
37
+ WHERE timestamp >= DATE('now', ? || ' days')
38
+ GROUP BY DATE(timestamp)
39
+ ORDER BY date ASC
40
+ `).all(-days);
41
+ return detectCostSpikes(rows);
42
+ }
43
+ function getTodaySpike(db) {
44
+ const today = new Date().toISOString().substring(0, 10);
45
+ const spikes = queryRecentSpikes(db, 14);
46
+ return spikes.find((s) => s.date === today) ?? null;
47
+ }
48
+
49
+ // src/lib/webhooks.ts
50
+ async function checkAndFireWebhooks(db) {
51
+ const config = loadConfig();
52
+ const url = config["webhook-url"];
53
+ if (!url)
54
+ return;
55
+ const statuses = getBudgetStatuses(db);
56
+ for (const b of statuses) {
57
+ if (!b.is_over_alert)
58
+ continue;
59
+ const key = `webhook-budget-${b.id}-${b.period}`;
60
+ const lastFired = getIngestState(db, "webhook", key);
61
+ const pctBucket = Math.floor(b.percent_used / 10) * 10;
62
+ if (lastFired === String(pctBucket))
63
+ continue;
64
+ const delivered = await fireWebhook(url, {
65
+ event: "budget_alert",
66
+ budget_id: b.id,
67
+ project: b.project_path ?? "global",
68
+ cost_center_id: b.cost_center_id ?? null,
69
+ scope: b.cost_center_id ?? b.project_path ?? "global",
70
+ period: b.period,
71
+ spend: b.current_spend_usd,
72
+ limit: b.limit_usd,
73
+ percent: Math.round(b.percent_used * 10) / 10
74
+ });
75
+ if (delivered)
76
+ setIngestState(db, "webhook", key, String(pctBucket));
77
+ }
78
+ const spike = getTodaySpike(db);
79
+ if (spike) {
80
+ const key = `webhook-spike-${spike.date}`;
81
+ if (getIngestState(db, "webhook", key) !== "1") {
82
+ const delivered = await fireWebhook(url, {
83
+ event: "cost_spike",
84
+ date: spike.date,
85
+ cost_usd: spike.cost_usd,
86
+ average_usd: spike.average_usd,
87
+ ratio: Math.round(spike.ratio * 100) / 100
88
+ });
89
+ if (delivered)
90
+ setIngestState(db, "webhook", key, "1");
91
+ }
92
+ }
93
+ }
94
+ async function fireWebhook(url, payload) {
95
+ try {
96
+ const response = await fetch(url, {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json" },
99
+ body: JSON.stringify(payload),
100
+ signal: AbortSignal.timeout(5000)
101
+ });
102
+ return response.ok;
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+ export {
108
+ checkAndFireWebhooks
109
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/brief.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGnC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,KAAK,EAGV,YAAY,EACb,MAAM,oBAAoB,CAAA;AAE3B,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,UAAU,gBAAgB;IACxB,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxC;AA4CD,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAyE3D;AAGD,OAAO,EAAE,SAAS,EAAE,CAAA;AAEpB,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,gBAAqB,GAAG,IAAI,CAgCxF"}
1
+ {"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/brief.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGnC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,KAAK,EAGV,YAAY,EACb,MAAM,oBAAoB,CAAA;AAE3B,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,UAAU,gBAAgB;IACxB,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxC;AA4CD,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CA2E3D;AAGD,OAAO,EAAE,SAAS,EAAE,CAAA;AAEpB,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,gBAAqB,GAAG,IAAI,CAgCxF"}
@@ -1 +1 @@
1
- {"version":3,"file":"extras.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/extras.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAmDnC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+V/D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyC5D"}
1
+ {"version":3,"file":"extras.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/extras.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA+CnC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmW/D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0C5D"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerJournalCommands(program: Command): void;
3
+ //# sourceMappingURL=journal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/journal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAInC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAa9D"}