@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,451 @@
1
+ // @bun
2
+ import {
3
+ buildBrief,
4
+ cloudListItems,
5
+ cloudObject,
6
+ economyCloudStorage,
7
+ packageMetadata,
8
+ queryBillingDiff,
9
+ queryExportRows,
10
+ queryForecast,
11
+ queryModelEfficiency,
12
+ queryProjectDetail,
13
+ queryRangeStats,
14
+ querySavingsSummary,
15
+ requireCurrentBillingDiffSummary,
16
+ requireCurrentSavingsReport
17
+ } from "./index-axh32d30.js";
18
+ import {
19
+ deleteBudget,
20
+ deleteGoal,
21
+ deleteModelPricing,
22
+ deleteProject,
23
+ deleteSubscription,
24
+ ensurePricingSeeded,
25
+ estimateCostFromRows,
26
+ getBudgetStatuses,
27
+ getGoalStatuses,
28
+ getMachineId,
29
+ getProject,
30
+ getSessionDetail,
31
+ insertFeedback,
32
+ listMachineRegistry,
33
+ listMachines,
34
+ listModelPricing,
35
+ listSubscriptions,
36
+ queryAccountBreakdown,
37
+ queryAccountBreakdownSince,
38
+ queryAgentBreakdown,
39
+ queryAgentBreakdownSince,
40
+ queryBillingSummary,
41
+ queryCostCenterBreakdown,
42
+ queryDailyBreakdown,
43
+ queryModelBreakdown,
44
+ queryModelBreakdownSince,
45
+ queryProjectBreakdown,
46
+ queryProjectBreakdownSince,
47
+ queryRequestsSince,
48
+ querySessions,
49
+ querySummary,
50
+ queryTopSessions,
51
+ queryUsageSnapshots,
52
+ requireCurrentBillingSummary,
53
+ requireCurrentCostSummary,
54
+ subscriptionId,
55
+ upsertBudget,
56
+ upsertGoal,
57
+ upsertModelPricing,
58
+ upsertProject,
59
+ upsertSubscription,
60
+ usageSnapshotFilterForPeriod,
61
+ validateIngest
62
+ } from "./index-ez9thsr7.js";
63
+ import {
64
+ __require
65
+ } from "./index-p35cyhc4.js";
66
+
67
+ // src/lib/store/index.ts
68
+ import { randomUUID } from "crypto";
69
+ function configuredSubscription(input) {
70
+ if (typeof input.account_key !== "string" || !input.account_key.trim()) {
71
+ throw new TypeError("account_key must identify the provider account; accepted: nonempty account key");
72
+ }
73
+ const now = new Date().toISOString();
74
+ const row = {
75
+ ...input,
76
+ id: input.id ?? subscriptionId(input),
77
+ monthly_fee_usd: input.monthly_fee_usd === undefined ? 0 : input.monthly_fee_usd,
78
+ included_usage_usd: input.included_usage_usd === undefined ? 0 : input.included_usage_usd,
79
+ fee_basis: input.monthly_fee_usd === undefined ? "unknown" : "configured",
80
+ included_basis: input.included_usage_usd === undefined ? "unknown" : "configured",
81
+ observed_station: input.observed_station ?? null,
82
+ observed_at: now,
83
+ created_at: now,
84
+ updated_at: now
85
+ };
86
+ validateIngest({ subscriptions: [row] });
87
+ return row;
88
+ }
89
+
90
+ class LocalStore {
91
+ transport = "local";
92
+ _db;
93
+ async db() {
94
+ if (!this._db) {
95
+ const { openDatabase } = await import("./sqlite-store-501f32ts.js");
96
+ this._db = openDatabase();
97
+ ensurePricingSeeded(this._db);
98
+ }
99
+ return this._db;
100
+ }
101
+ async summary(period, machine) {
102
+ return querySummary(await this.db(), period, machine);
103
+ }
104
+ async sessions(filter) {
105
+ return querySessions(await this.db(), filter);
106
+ }
107
+ async topSessions(n, agent, since) {
108
+ return queryTopSessions(await this.db(), n, agent, since);
109
+ }
110
+ async sessionDetail(id) {
111
+ return getSessionDetail(await this.db(), id);
112
+ }
113
+ async modelBreakdown(query = {}) {
114
+ return query.since ? queryModelBreakdownSince(await this.db(), query.since) : queryModelBreakdown(await this.db());
115
+ }
116
+ async agentBreakdown(query = {}) {
117
+ return query.since ? queryAgentBreakdownSince(await this.db(), query.since) : queryAgentBreakdown(await this.db(), query.period ?? "all", query.machine);
118
+ }
119
+ async projectBreakdown(query = {}) {
120
+ return query.since ? queryProjectBreakdownSince(await this.db(), query.since, query.machine) : queryProjectBreakdown(await this.db(), query.period ?? "all", query.machine);
121
+ }
122
+ async accountBreakdown(query = {}) {
123
+ return query.since ? queryAccountBreakdownSince(await this.db(), query.since) : queryAccountBreakdown(await this.db(), query.period ?? "all", query.machine);
124
+ }
125
+ async costCenterBreakdown(query = {}) {
126
+ return queryCostCenterBreakdown(await this.db(), query.period ?? "all", {
127
+ kind: query.kind,
128
+ machine: query.machine,
129
+ since: query.since
130
+ });
131
+ }
132
+ async accounts(period) {
133
+ return queryAccountBreakdown(await this.db(), period);
134
+ }
135
+ async daily(days, machine) {
136
+ return queryDailyBreakdown(await this.db(), days, machine);
137
+ }
138
+ async machines() {
139
+ return listMachines(await this.db());
140
+ }
141
+ async fleet(period) {
142
+ return {
143
+ summary: querySummary(await this.db(), period, undefined, true),
144
+ machines: listMachines(await this.db(), period),
145
+ registry: listMachineRegistry(await this.db())
146
+ };
147
+ }
148
+ async billingSummary(period) {
149
+ return queryBillingSummary(await this.db(), period);
150
+ }
151
+ async billingDiff(period) {
152
+ return queryBillingDiff(await this.db(), period);
153
+ }
154
+ async usage(period, agent) {
155
+ const snapshots = queryUsageSnapshots(await this.db(), { agent, ...usageSnapshotFilterForPeriod(period) });
156
+ const summary = querySummary(await this.db(), period, undefined, true, agent);
157
+ return { snapshots, summary };
158
+ }
159
+ async savings(period, agent) {
160
+ return querySavingsSummary(await this.db(), period, agent);
161
+ }
162
+ async listBudgets() {
163
+ return getBudgetStatuses(await this.db());
164
+ }
165
+ async listGoals() {
166
+ return getGoalStatuses(await this.db());
167
+ }
168
+ async listPricing() {
169
+ return listModelPricing(await this.db());
170
+ }
171
+ async listSubscriptions() {
172
+ return listSubscriptions(await this.db());
173
+ }
174
+ async listProjects() {
175
+ return queryProjectBreakdown(await this.db());
176
+ }
177
+ async brief(query = {}) {
178
+ return buildBrief(await this.db(), {
179
+ since: query.since,
180
+ machine: query.machine,
181
+ currentMachineId: query.currentMachineId,
182
+ localSyncAt: query.localSyncAt
183
+ });
184
+ }
185
+ async projectDetail(nameOrPath) {
186
+ return queryProjectDetail(await this.db(), nameOrPath);
187
+ }
188
+ async exportRows(type, period) {
189
+ return queryExportRows(await this.db(), type, period);
190
+ }
191
+ async rangeStats(from, to) {
192
+ return queryRangeStats(await this.db(), from, to);
193
+ }
194
+ async forecast() {
195
+ return queryForecast(await this.db());
196
+ }
197
+ async efficiency() {
198
+ return queryModelEfficiency(await this.db());
199
+ }
200
+ async recentRequests(since) {
201
+ return queryRequestsSince(await this.db(), since);
202
+ }
203
+ async estimate(input) {
204
+ return estimateCostFromRows(listModelPricing(await this.db()), input.model, input.inputTokens, input.outputTokens, input.cacheReadTokens ?? 0, input.cacheWriteTokens ?? 0, input.cacheWrite1hTokens ?? 0, input.cacheStorageTokenHours ?? 0);
205
+ }
206
+ async setBudget(input) {
207
+ const now = new Date().toISOString();
208
+ const budget = { id: randomUUID(), ...input, created_at: now, updated_at: now };
209
+ upsertBudget(await this.db(), budget);
210
+ return budget.id;
211
+ }
212
+ async removeBudget(id) {
213
+ deleteBudget(await this.db(), id);
214
+ }
215
+ async setGoal(input) {
216
+ const now = new Date().toISOString();
217
+ upsertGoal(await this.db(), { id: randomUUID(), ...input, created_at: now, updated_at: now });
218
+ }
219
+ async removeGoal(id) {
220
+ deleteGoal(await this.db(), id);
221
+ }
222
+ async setPricing(input) {
223
+ upsertModelPricing(await this.db(), { ...input, updated_at: new Date().toISOString() });
224
+ }
225
+ async removePricing(model) {
226
+ deleteModelPricing(await this.db(), model);
227
+ }
228
+ async setSubscription(input) {
229
+ const row = configuredSubscription(input);
230
+ const db = await this.db();
231
+ upsertSubscription(db, row);
232
+ return db.get("SELECT * FROM subscriptions WHERE id = ?", row.id);
233
+ }
234
+ async removeSubscription(id) {
235
+ deleteSubscription(await this.db(), id);
236
+ }
237
+ async addProject(path, name) {
238
+ upsertProject(await this.db(), {
239
+ id: randomUUID(),
240
+ path,
241
+ name,
242
+ description: null,
243
+ tags: [],
244
+ created_at: new Date().toISOString()
245
+ });
246
+ }
247
+ async renameProject(path, name) {
248
+ const existing = getProject(await this.db(), path);
249
+ if (!existing)
250
+ throw new Error("Project not found");
251
+ upsertProject(await this.db(), { ...existing, name });
252
+ }
253
+ async removeProject(path) {
254
+ deleteProject(await this.db(), path);
255
+ }
256
+ async sendFeedback(input) {
257
+ insertFeedback(await this.db(), {
258
+ message: input.message,
259
+ email: input.email ?? null,
260
+ category: input.category ?? "general",
261
+ version: packageMetadata.version,
262
+ machine_id: getMachineId()
263
+ });
264
+ }
265
+ }
266
+ function q(params) {
267
+ const out = {};
268
+ for (const [k, v] of Object.entries(params))
269
+ if (v !== undefined && v !== null && v !== "")
270
+ out[k] = v;
271
+ return out;
272
+ }
273
+
274
+ class ApiStore {
275
+ cloud;
276
+ transport = "cloud-http";
277
+ constructor(cloud) {
278
+ this.cloud = cloud;
279
+ }
280
+ async summary(period, machine) {
281
+ return requireCurrentCostSummary(await cloudObject(this.cloud, "/summary", q({ period, machine })));
282
+ }
283
+ async sessions(filter) {
284
+ return cloudListItems(this.cloud, "sessions", q({ ...filter }));
285
+ }
286
+ async topSessions(n, agent, since) {
287
+ return cloudListItems(this.cloud, "top", q({ n, agent, since }));
288
+ }
289
+ async sessionDetail(id) {
290
+ const matches = await cloudListItems(this.cloud, "sessions", q({ search: id, limit: 1 }));
291
+ const session = matches[0];
292
+ if (!session)
293
+ return null;
294
+ const requests = await cloudObject(this.cloud, `/sessions/${encodeURIComponent(String(session["id"]))}/requests`);
295
+ return { session, requests: requests ?? [] };
296
+ }
297
+ async modelBreakdown(query = {}) {
298
+ return cloudListItems(this.cloud, "breakdown", q({ by: "model", period: query.period, since: query.since }));
299
+ }
300
+ async agentBreakdown(query = {}) {
301
+ return cloudListItems(this.cloud, "breakdown", q({ by: "agent", period: query.period, since: query.since }));
302
+ }
303
+ async projectBreakdown(query = {}) {
304
+ return cloudListItems(this.cloud, "breakdown", q({ by: "project", period: query.period, since: query.since, machine: query.machine }));
305
+ }
306
+ async accountBreakdown(query = {}) {
307
+ return cloudListItems(this.cloud, "breakdown", q({ by: "account", period: query.period, since: query.since }));
308
+ }
309
+ async costCenterBreakdown(query = {}) {
310
+ const rows = await cloudListItems(this.cloud, "breakdown", q({
311
+ by: query.kind ?? "cost-center",
312
+ period: query.period,
313
+ since: query.since
314
+ }));
315
+ return rows.filter((row) => Boolean(row?.cost_center_id));
316
+ }
317
+ async accounts(period) {
318
+ return cloudListItems(this.cloud, "accounts", q({ period }));
319
+ }
320
+ async daily(days, machine) {
321
+ return cloudListItems(this.cloud, "daily", q({ days, machine }));
322
+ }
323
+ async machines() {
324
+ return cloudListItems(this.cloud, "machines");
325
+ }
326
+ async fleet(period) {
327
+ const data = await cloudObject(this.cloud, "/fleet", q({ period }));
328
+ return {
329
+ summary: requireCurrentCostSummary(data.summary),
330
+ machines: data.machines ?? [],
331
+ registry: data.registry ?? []
332
+ };
333
+ }
334
+ async billingSummary(period) {
335
+ return requireCurrentBillingSummary(await cloudObject(this.cloud, "/billing", q({ period })));
336
+ }
337
+ async billingDiff(period) {
338
+ return requireCurrentBillingDiffSummary(await cloudObject(this.cloud, "/billing/diff", q({ period })));
339
+ }
340
+ async usage(period, agent) {
341
+ const data = await cloudObject(this.cloud, "/usage", q({ period, agent }));
342
+ return { ...data, summary: requireCurrentCostSummary(data.summary) };
343
+ }
344
+ async savings(period, agent) {
345
+ return requireCurrentSavingsReport(await cloudObject(this.cloud, "/savings", q({ period, agent })));
346
+ }
347
+ async listBudgets() {
348
+ return cloudListItems(this.cloud, "budgets");
349
+ }
350
+ async listGoals() {
351
+ return cloudListItems(this.cloud, "goals");
352
+ }
353
+ async listPricing() {
354
+ return cloudListItems(this.cloud, "pricing");
355
+ }
356
+ async listSubscriptions() {
357
+ return cloudListItems(this.cloud, "subscriptions");
358
+ }
359
+ async listProjects() {
360
+ return cloudListItems(this.cloud, "projects");
361
+ }
362
+ async brief(query = {}) {
363
+ return cloudObject(this.cloud, "/brief", q({ since: query.since, machine: query.machine }));
364
+ }
365
+ async projectDetail(nameOrPath) {
366
+ return cloudObject(this.cloud, "/projects/detail", q({ q: nameOrPath }));
367
+ }
368
+ async exportRows(type, period) {
369
+ return cloudListItems(this.cloud, "export", q({ type, period }));
370
+ }
371
+ async rangeStats(from, to) {
372
+ return cloudObject(this.cloud, "/compare", q({ from, to }));
373
+ }
374
+ async forecast() {
375
+ return cloudObject(this.cloud, "/forecast");
376
+ }
377
+ async efficiency() {
378
+ return cloudListItems(this.cloud, "efficiency");
379
+ }
380
+ async recentRequests(since) {
381
+ return cloudListItems(this.cloud, "requests", q({ since }));
382
+ }
383
+ async estimate(input) {
384
+ const rows = await this.listPricing();
385
+ return estimateCostFromRows(rows, input.model, input.inputTokens, input.outputTokens, input.cacheReadTokens ?? 0, input.cacheWriteTokens ?? 0, input.cacheWrite1hTokens ?? 0, input.cacheStorageTokenHours ?? 0);
386
+ }
387
+ async setBudget(input) {
388
+ const created = await this.cloud.client.create("budgets", {
389
+ project_path: input.project_path,
390
+ ...input.agent ? { agent: input.agent } : {},
391
+ ...input.cost_center_id ? { cost_center_id: input.cost_center_id } : {},
392
+ period: input.period,
393
+ limit_usd: input.limit_usd,
394
+ alert_at_percent: input.alert_at_percent
395
+ });
396
+ return created?.id ?? "";
397
+ }
398
+ async removeBudget(id) {
399
+ await this.cloud.client.delete("budgets", id);
400
+ }
401
+ async setGoal(input) {
402
+ await this.cloud.client.create("goals", {
403
+ period: input.period,
404
+ project_path: input.project_path,
405
+ ...input.agent ? { agent: input.agent } : {},
406
+ limit_usd: input.limit_usd
407
+ });
408
+ }
409
+ async removeGoal(id) {
410
+ await this.cloud.client.delete("goals", id);
411
+ }
412
+ async setPricing(input) {
413
+ await this.cloud.client.create("pricing", { ...input, updated_at: new Date().toISOString() });
414
+ }
415
+ async removePricing(model) {
416
+ await this.cloud.client.delete("pricing", model);
417
+ }
418
+ async setSubscription(input) {
419
+ const row = configuredSubscription(input);
420
+ return this.cloud.client.create("subscriptions", row);
421
+ }
422
+ async removeSubscription(id) {
423
+ await this.cloud.client.delete("subscriptions", id);
424
+ }
425
+ async addProject(path, name) {
426
+ await this.cloud.client.create("project-registry", { path, name, description: null, tags: [] });
427
+ }
428
+ async renameProject(path, name) {
429
+ await this.cloud.client.delete("project-registry", path);
430
+ await this.cloud.client.create("project-registry", { path, name, description: null, tags: [] });
431
+ }
432
+ async removeProject(path) {
433
+ await this.cloud.client.delete("project-registry", path);
434
+ }
435
+ async sendFeedback(input) {
436
+ await this.cloud.client.create("feedback", {
437
+ message: input.message,
438
+ ...input.email ? { email: input.email } : {},
439
+ category: input.category ?? "general"
440
+ });
441
+ }
442
+ }
443
+ function getStore(env = process.env) {
444
+ const cloud = economyCloudStorage(env);
445
+ return cloud.active ? new ApiStore(cloud) : new LocalStore;
446
+ }
447
+ function isCloudStore(env = process.env) {
448
+ return economyCloudStorage(env).active;
449
+ }
450
+
451
+ export { getStore, isCloudStore };
@@ -0,0 +1,124 @@
1
+ // @bun
2
+ import {
3
+ getServeApiToken
4
+ } from "./index-w3bvv0ns.js";
5
+ import {
6
+ getStore
7
+ } from "./index-kgy9b6bf.js";
8
+ import {
9
+ formatReportedUsd,
10
+ formatSummaryCoverage,
11
+ formatSummaryValue
12
+ } from "./index-ez9thsr7.js";
13
+
14
+ // src/cli/commands/tui.ts
15
+ import chalk from "chalk";
16
+ function topAgentFrom(rows) {
17
+ let top;
18
+ for (const row of rows) {
19
+ if (!top || row.cost_usd > top.cost_usd)
20
+ top = row;
21
+ }
22
+ return top?.agent ?? "\u2014";
23
+ }
24
+ function quotaFrom(snapshots) {
25
+ const claude = snapshots.find((s) => s.agent === "claude" && s.metric === "five_hour_utilization");
26
+ const codex = snapshots.find((s) => s.agent === "codex" && s.metric === "five_hour_utilization");
27
+ const parts = [];
28
+ if (claude)
29
+ parts.push(`claude ${claude.value.toFixed(0)}%`);
30
+ if (codex)
31
+ parts.push(`codex ${codex.value.toFixed(0)}%`);
32
+ return parts.length ? parts.join(" \xB7 ") : null;
33
+ }
34
+ async function gatherStatusData(store = getStore()) {
35
+ const [today, week, month, savings, machines, agents, usage] = await Promise.all([
36
+ store.summary("today"),
37
+ store.summary("week"),
38
+ store.summary("month"),
39
+ store.savings("month"),
40
+ store.machines(),
41
+ store.agentBreakdown({ period: "month" }),
42
+ store.usage("today")
43
+ ]);
44
+ return {
45
+ today,
46
+ week,
47
+ month,
48
+ savings,
49
+ machineCount: machines.length,
50
+ topAgent: topAgentFrom(agents),
51
+ quota: quotaFrom(usage?.snapshots ?? []),
52
+ transport: store.transport
53
+ };
54
+ }
55
+ function modeLabel(transport) {
56
+ return transport === "cloud-http" ? "hosted" : "local";
57
+ }
58
+ function buildStatusLine(data) {
59
+ const parts = [
60
+ `today ${formatSummaryValue(data.today)}`,
61
+ `week ${formatSummaryValue(data.week)}`,
62
+ `top ${data.topAgent}`,
63
+ `${data.machineCount} machines`,
64
+ modeLabel(data.transport),
65
+ "UTC; stored records only; source coverage unverified"
66
+ ];
67
+ if (data.quota)
68
+ parts.push(data.quota);
69
+ return parts.join(" \xB7 ");
70
+ }
71
+ function buildWaybarJson(data) {
72
+ return {
73
+ text: formatSummaryValue(data.today),
74
+ tooltip: buildStatusLine(data),
75
+ class: data.quota?.includes("%") && Number(data.quota.match(/(\d+)%/)?.[1] ?? 0) >= 80 ? "warning" : "default",
76
+ percentage: null,
77
+ summary: data.today,
78
+ summary_coverage: formatSummaryCoverage(data.today),
79
+ savings_usd: data.savings.saved_usd,
80
+ comparison_status: data.savings.comparison_status
81
+ };
82
+ }
83
+ async function printStatusLine() {
84
+ console.log(buildStatusLine(await gatherStatusData()));
85
+ }
86
+ async function printWaybarJson() {
87
+ console.log(JSON.stringify(buildWaybarJson(await gatherStatusData())));
88
+ }
89
+ async function runTui(opts) {
90
+ const interval = opts.interval ?? 30;
91
+ const render = async () => {
92
+ const data = await gatherStatusData();
93
+ process.stdout.write("\x1B[H\x1B[2J");
94
+ console.log(chalk.bold.cyan(" economy"));
95
+ 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"));
96
+ console.log(` Today ${chalk.green(formatSummaryValue(data.today))} ${data.today.sessions} sessions`);
97
+ console.log(` Week ${formatSummaryValue(data.week)} ${data.week.sessions} sessions`);
98
+ console.log(` Month ${formatSummaryValue(data.month)} estimated plan benefit ${formatReportedUsd(data.savings.saved_usd)}`);
99
+ 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"));
100
+ console.log(` ${formatSummaryCoverage(data.today)}`);
101
+ console.log(` Top agent: ${data.topAgent}`);
102
+ if (data.quota)
103
+ console.log(` Quota: ${data.quota}`);
104
+ console.log(` Fleet: ${data.machineCount} machines`);
105
+ console.log(` Store: ${data.transport === "cloud-http" ? "hosted API" : "local"}`);
106
+ if (getServeApiToken())
107
+ console.log(chalk.dim(" API auth: HASNA_ECONOMY_API_TOKEN set"));
108
+ console.log(chalk.dim(`
109
+ ${opts.watch ? `Refreshing every ${interval}s \u2014 Ctrl+C to exit` : "Run with --watch for live refresh"}`));
110
+ };
111
+ await render();
112
+ if (!opts.watch)
113
+ return;
114
+ const timer = setInterval(() => {
115
+ render();
116
+ }, interval * 1000);
117
+ process.on("SIGINT", () => {
118
+ clearInterval(timer);
119
+ process.exit(0);
120
+ });
121
+ await new Promise(() => {});
122
+ }
123
+
124
+ export { gatherStatusData, buildStatusLine, buildWaybarJson, printStatusLine, printWaybarJson, runTui };
@@ -0,0 +1,4 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ export { __require };
@@ -0,0 +1,26 @@
1
+ // @bun
2
+ // src/lib/serve-auth.ts
3
+ function getServeApiToken() {
4
+ return process.env["HASNA_ECONOMY_API_TOKEN"];
5
+ }
6
+ function getServeBindHost() {
7
+ const explicit = process.env["ECONOMY_BIND"] ?? process.env["ECONOMY_HOST"];
8
+ if (explicit)
9
+ return explicit;
10
+ return getServeApiToken() ? "127.0.0.1" : "0.0.0.0";
11
+ }
12
+ function isAuthorizedRequest(req, path) {
13
+ const token = getServeApiToken();
14
+ if (!token)
15
+ return true;
16
+ if (path === "/health")
17
+ return true;
18
+ const auth = req.headers.get("Authorization");
19
+ if (auth === `Bearer ${token}`)
20
+ return true;
21
+ if (req.headers.get("X-Economy-Token") === token)
22
+ return true;
23
+ return false;
24
+ }
25
+
26
+ export { getServeApiToken, getServeBindHost, isAuthorizedRequest };