@hasna/economy 0.3.28 → 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 (216) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +18 -22
  4. package/SECURITY.md +1 -1
  5. package/dist/chunks/agent-registry-store-tpnhx3wj.js +39 -0
  6. package/dist/chunks/billing-bkpxy4kv.js +13 -0
  7. package/dist/chunks/config-j9mv3ehk.js +15 -0
  8. package/dist/chunks/index-2hwzmfgv.js +3407 -0
  9. package/dist/chunks/index-axh32d30.js +1313 -0
  10. package/dist/chunks/index-emvs5ph2.js +2085 -0
  11. package/dist/chunks/index-ez9thsr7.js +3440 -0
  12. package/dist/chunks/index-hesxq90g.js +62 -0
  13. package/dist/chunks/index-k540hfww.js +258 -0
  14. package/dist/chunks/index-kgy9b6bf.js +451 -0
  15. package/dist/chunks/index-kn5572kd.js +124 -0
  16. package/dist/chunks/index-p35cyhc4.js +4 -0
  17. package/dist/chunks/index-w3bvv0ns.js +26 -0
  18. package/dist/chunks/index-zpvea08j.js +3372 -0
  19. package/dist/chunks/menubar-6xtkt17h.js +296 -0
  20. package/dist/chunks/open-projects-6z4pq6mx.js +39 -0
  21. package/dist/chunks/pricing-f80p81gr.js +29 -0
  22. package/dist/chunks/pricing-vf2pycmh.js +30 -0
  23. package/dist/chunks/pricing-y21mcjsa.js +30 -0
  24. package/dist/chunks/serve-0k69e4h9.js +4655 -0
  25. package/dist/chunks/sqlite-store-501f32ts.js +258 -0
  26. package/dist/chunks/sqlite-store-fq4natnf.js +256 -0
  27. package/dist/chunks/sqlite-store-st99garx.js +258 -0
  28. package/dist/chunks/third-party-sqlite-5e85fxa2.js +11 -0
  29. package/dist/chunks/tui-7f8f951w.js +22 -0
  30. package/dist/chunks/watch-mm1j39vy.js +196 -0
  31. package/dist/chunks/webhooks-k4fh2t9c.js +109 -0
  32. package/dist/cli/commands/brief.d.ts.map +1 -1
  33. package/dist/cli/commands/extras.d.ts.map +1 -1
  34. package/dist/cli/commands/journal.d.ts +3 -0
  35. package/dist/cli/commands/journal.d.ts.map +1 -0
  36. package/dist/cli/commands/menubar.d.ts +97 -4
  37. package/dist/cli/commands/menubar.d.ts.map +1 -1
  38. package/dist/cli/commands/tui.d.ts +1 -1
  39. package/dist/cli/commands/tui.d.ts.map +1 -1
  40. package/dist/cli/commands/watch.d.ts.map +1 -1
  41. package/dist/cli/index.js +1524 -9998
  42. package/dist/db/cloud.d.ts +18 -12
  43. package/dist/db/cloud.d.ts.map +1 -1
  44. package/dist/db/database.d.ts +33 -12
  45. package/dist/db/database.d.ts.map +1 -1
  46. package/dist/db/ingest-concurrency-worker.d.ts +2 -0
  47. package/dist/db/ingest-concurrency-worker.d.ts.map +1 -0
  48. package/dist/db/ingest-errors.d.ts +9 -0
  49. package/dist/db/ingest-errors.d.ts.map +1 -0
  50. package/dist/db/ingest-schema.d.ts +3 -0
  51. package/dist/db/ingest-schema.d.ts.map +1 -0
  52. package/dist/db/ingest-specs.d.ts +8 -0
  53. package/dist/db/ingest-specs.d.ts.map +1 -0
  54. package/dist/db/ingest-test-contract.d.ts +3 -0
  55. package/dist/db/ingest-test-contract.d.ts.map +1 -0
  56. package/dist/db/ingest-validation.d.ts +12 -0
  57. package/dist/db/ingest-validation.d.ts.map +1 -0
  58. package/dist/db/ingest.d.ts +27 -0
  59. package/dist/db/ingest.d.ts.map +1 -0
  60. package/dist/db/legacy-schema.d.ts +7 -0
  61. package/dist/db/legacy-schema.d.ts.map +1 -0
  62. package/dist/db/observation-contract.d.ts +3 -0
  63. package/dist/db/observation-contract.d.ts.map +1 -0
  64. package/dist/db/observation-provenance.d.ts +23 -0
  65. package/dist/db/observation-provenance.d.ts.map +1 -0
  66. package/dist/db/observation-schema.d.ts +24 -0
  67. package/dist/db/observation-schema.d.ts.map +1 -0
  68. package/dist/db/pg-connection.d.ts +10 -0
  69. package/dist/db/pg-connection.d.ts.map +1 -0
  70. package/dist/db/pg-migrate.d.ts +4 -9
  71. package/dist/db/pg-migrate.d.ts.map +1 -1
  72. package/dist/db/pg-migrations.d.ts.map +1 -1
  73. package/dist/db/pg-protocol.d.ts +28 -0
  74. package/dist/db/pg-protocol.d.ts.map +1 -0
  75. package/dist/db/sqlite-store.d.ts +4 -0
  76. package/dist/db/sqlite-store.d.ts.map +1 -0
  77. package/dist/db/sync-pg.d.ts +17 -21
  78. package/dist/db/sync-pg.d.ts.map +1 -1
  79. package/dist/db/third-party-sqlite.d.ts +10 -0
  80. package/dist/db/third-party-sqlite.d.ts.map +1 -0
  81. package/dist/index.d.ts +3 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1281 -2405
  84. package/dist/ingest/claude-quota.d.ts.map +1 -1
  85. package/dist/ingest/claude.d.ts.map +1 -1
  86. package/dist/ingest/codex-quota.d.ts.map +1 -1
  87. package/dist/ingest/codex.d.ts +25 -0
  88. package/dist/ingest/codex.d.ts.map +1 -1
  89. package/dist/ingest/collection-outcome.d.ts +17 -0
  90. package/dist/ingest/collection-outcome.d.ts.map +1 -0
  91. package/dist/ingest/cursor.d.ts.map +1 -1
  92. package/dist/ingest/gemini.d.ts.map +1 -1
  93. package/dist/ingest/hermes.d.ts +1 -1
  94. package/dist/ingest/hermes.d.ts.map +1 -1
  95. package/dist/ingest/loops.d.ts +9 -0
  96. package/dist/ingest/loops.d.ts.map +1 -1
  97. package/dist/ingest/opencode.d.ts +1 -1
  98. package/dist/ingest/opencode.d.ts.map +1 -1
  99. package/dist/ingest/pi.d.ts +1 -1
  100. package/dist/ingest/pi.d.ts.map +1 -1
  101. package/dist/ingest/quota-observation.d.ts +16 -0
  102. package/dist/ingest/quota-observation.d.ts.map +1 -0
  103. package/dist/ingest/source-time.d.ts +4 -0
  104. package/dist/ingest/source-time.d.ts.map +1 -0
  105. package/dist/lib/accounts-store.d.ts +109 -0
  106. package/dist/lib/accounts-store.d.ts.map +1 -0
  107. package/dist/lib/accounts.d.ts +2 -1
  108. package/dist/lib/accounts.d.ts.map +1 -1
  109. package/dist/lib/analytics.d.ts.map +1 -1
  110. package/dist/lib/api-display-url.d.ts +19 -0
  111. package/dist/lib/api-display-url.d.ts.map +1 -0
  112. package/dist/lib/autosync-gate.d.ts +22 -0
  113. package/dist/lib/autosync-gate.d.ts.map +1 -0
  114. package/dist/lib/billing-diff.d.ts +18 -40
  115. package/dist/lib/billing-diff.d.ts.map +1 -1
  116. package/dist/lib/brief.d.ts +2 -0
  117. package/dist/lib/brief.d.ts.map +1 -1
  118. package/dist/lib/budget-decimal.d.ts +14 -0
  119. package/dist/lib/budget-decimal.d.ts.map +1 -0
  120. package/dist/lib/cloud-ingest.d.ts +30 -1
  121. package/dist/lib/cloud-ingest.d.ts.map +1 -1
  122. package/dist/lib/cloud-storage.d.ts +166 -10
  123. package/dist/lib/cloud-storage.d.ts.map +1 -1
  124. package/dist/lib/gatherer.d.ts.map +1 -1
  125. package/dist/lib/ingest-batches.d.ts +21 -0
  126. package/dist/lib/ingest-batches.d.ts.map +1 -0
  127. package/dist/lib/ingest-journal-client.d.ts +37 -0
  128. package/dist/lib/ingest-journal-client.d.ts.map +1 -0
  129. package/dist/lib/ingest-journal-lock.d.ts +5 -0
  130. package/dist/lib/ingest-journal-lock.d.ts.map +1 -0
  131. package/dist/lib/ingest-journal-maintenance.d.ts +92 -0
  132. package/dist/lib/ingest-journal-maintenance.d.ts.map +1 -0
  133. package/dist/lib/ingest-journal-operation.d.ts +55 -0
  134. package/dist/lib/ingest-journal-operation.d.ts.map +1 -0
  135. package/dist/lib/ingest-journal.d.ts +73 -0
  136. package/dist/lib/ingest-journal.d.ts.map +1 -0
  137. package/dist/lib/money-display.d.ts +13 -0
  138. package/dist/lib/money-display.d.ts.map +1 -0
  139. package/dist/lib/periods.d.ts +12 -0
  140. package/dist/lib/periods.d.ts.map +1 -1
  141. package/dist/lib/pricing.d.ts +14 -0
  142. package/dist/lib/pricing.d.ts.map +1 -1
  143. package/dist/lib/savings.d.ts +64 -7
  144. package/dist/lib/savings.d.ts.map +1 -1
  145. package/dist/lib/serve-auth.d.ts.map +1 -1
  146. package/dist/lib/store/index.d.ts +29 -22
  147. package/dist/lib/store/index.d.ts.map +1 -1
  148. package/dist/lib/summary-contract.d.ts +32 -0
  149. package/dist/lib/summary-contract.d.ts.map +1 -0
  150. package/dist/lib/summary-test-contract.d.ts +3 -0
  151. package/dist/lib/summary-test-contract.d.ts.map +1 -0
  152. package/dist/lib/sync-all.d.ts +4 -0
  153. package/dist/lib/sync-all.d.ts.map +1 -1
  154. package/dist/lib/sync-maintenance.d.ts.map +1 -1
  155. package/dist/lib/sync-report.d.ts +4 -0
  156. package/dist/lib/sync-report.d.ts.map +1 -0
  157. package/dist/lib/test-keychain-fixture.d.ts +9 -0
  158. package/dist/lib/test-keychain-fixture.d.ts.map +1 -0
  159. package/dist/mcp/agent-registry-store.d.ts +30 -0
  160. package/dist/mcp/agent-registry-store.d.ts.map +1 -0
  161. package/dist/mcp/agent-registry.d.ts +78 -0
  162. package/dist/mcp/agent-registry.d.ts.map +1 -0
  163. package/dist/mcp/harness.d.ts +26 -0
  164. package/dist/mcp/harness.d.ts.map +1 -0
  165. package/dist/mcp/index.js +3219 -3357
  166. package/dist/mcp/ingest-journal-tool.d.ts +2 -0
  167. package/dist/mcp/ingest-journal-tool.d.ts.map +1 -0
  168. package/dist/mcp/server.d.ts.map +1 -1
  169. package/dist/openapi.d.ts.map +1 -1
  170. package/dist/otel/index.js +2197 -121
  171. package/dist/server/human-auth.d.ts +20 -0
  172. package/dist/server/human-auth.d.ts.map +1 -0
  173. package/dist/server/index.js +10477 -6378
  174. package/dist/server/legacy-inventory.d.ts +52 -0
  175. package/dist/server/legacy-inventory.d.ts.map +1 -0
  176. package/dist/server/legacy-operations.d.ts +64 -0
  177. package/dist/server/legacy-operations.d.ts.map +1 -0
  178. package/dist/server/legacy-plan.d.ts +55 -0
  179. package/dist/server/legacy-plan.d.ts.map +1 -0
  180. package/dist/server/legacy-preservation.d.ts +58 -0
  181. package/dist/server/legacy-preservation.d.ts.map +1 -0
  182. package/dist/server/pg-sync-worker.js +195 -21
  183. package/dist/server/serve.d.ts +20 -18
  184. package/dist/server/serve.d.ts.map +1 -1
  185. package/dist/server/service-admission.d.ts +10 -0
  186. package/dist/server/service-admission.d.ts.map +1 -0
  187. package/dist/server/service-dispatcher.d.ts +45 -0
  188. package/dist/server/service-dispatcher.d.ts.map +1 -0
  189. package/dist/server/service-operations.d.ts +5 -0
  190. package/dist/server/service-operations.d.ts.map +1 -0
  191. package/dist/server/service-protocol.d.ts +50 -0
  192. package/dist/server/service-protocol.d.ts.map +1 -0
  193. package/dist/server/service-worker.d.ts +2 -0
  194. package/dist/server/service-worker.d.ts.map +1 -0
  195. package/dist/server/service-worker.js +8029 -0
  196. package/dist/types/index.d.ts +66 -0
  197. package/dist/types/index.d.ts.map +1 -1
  198. package/docs/README.md +1 -1
  199. package/docs/cli.md +2 -3
  200. package/docs/configuration.md +25 -12
  201. package/docs/cost-reporting.md +49 -0
  202. package/docs/historical-data-reconciliation.md +203 -0
  203. package/docs/ingest-journal.md +108 -0
  204. package/docs/ingestion.md +61 -5
  205. package/docs/mcp.md +5 -3
  206. package/docs/native-budget-save.md +84 -0
  207. package/docs/native-human-auth.md +102 -0
  208. package/docs/otel.md +8 -2
  209. package/docs/rest-api.md +4 -4
  210. package/package.json +17 -20
  211. package/postinstall.js +52 -1
  212. package/dashboard/README.md +0 -26
  213. package/dist/cli/brains.d.ts +0 -3
  214. package/dist/cli/brains.d.ts.map +0 -1
  215. package/dist/lib/test-hermetic-accounts.d.ts +0 -6
  216. package/dist/lib/test-hermetic-accounts.d.ts.map +0 -1
package/docs/ingestion.md CHANGED
@@ -2,7 +2,39 @@
2
2
 
3
3
  `economy sync` imports local coding-agent usage into Economy's SQLite database. A sync with no source flag runs every source; one or more source flags limit the run. Reads such as `economy today` also auto-sync all local sources before querying.
4
4
 
5
- In cloud-client mode, CLI and MCP reads/writes go directly to the shared HTTP API. Local `sync` and `billing sync` deliberately do nothing in that mode; ingest on a machine running in local mode, or use the authenticated server ingest endpoints.
5
+ In cloud-client mode, CLI and MCP reads/writes go directly to the shared HTTP API (on-box auto-sync is skipped). The explicit `economy sync` / `economy billing sync` verbs still run the same on-box provider ingest against a scratch store and push the rows to `/v1/ingest`.
6
+
7
+ ## Hosted batch integrity
8
+
9
+ `POST /v1/ingest` accepts the supported table arrays, optional `schema_version: 1`
10
+ and an `idempotency_key` of 1–200 letters, digits, dots, underscores, colons or
11
+ hyphens. A command is limited to 10000 rows total and 4 MiB of JSON. The client
12
+ splits large scans into deterministic commands of at most 500 rows; each command
13
+ commits separately. All rows, session totals, predecessor snapshots and the
14
+ receipt for one command share a transaction.
15
+
16
+ Supply source `updated_at` values as ISO UTC timestamps ending in `Z`. An older
17
+ version is skipped, while different content at the same version is a conflict.
18
+ An unversioned row may be inserted or replayed, but cannot silently replace
19
+ different existing content. The server does not treat arrival time as a source
20
+ version. Session totals are derived from canonical request rows when present.
21
+ Collectors use recorded source revisions or event times, never a fresh scan time
22
+ as revision authority. A changed event without a newer source version conflicts;
23
+ changing a file's mtime does not grant permission to replace accepted usage.
24
+
25
+ The response's `data` contains `ingested`, `total`, `stale`, `unchanged`,
26
+ `operation_key` and `replayed`. Reuse the **identical body and key** after an
27
+ uncertain response: a committed command returns its original receipt. Reusing
28
+ that key for different content returns 409. Keys are scoped to the authenticated
29
+ principal. Legacy commands without a key derive one from their exact content.
30
+
31
+ Invalid fields return 400, source/key conflicts return 409, and oversized
32
+ commands return 413, with `code`, `field` and `expected` details. These are
33
+ definitive refusals. A timeout or lost response has an uncertain outcome; the
34
+ client never automatically repeats it. The authority-bound durable journal
35
+ retains the exact intent and its committed receipt or proven terminal refusal.
36
+ Use [journal status and reconciliation](ingest-journal.md) after an interruption;
37
+ a newly reconstructed scan is not exact reconciliation of the previous command.
6
38
 
7
39
  ## Sources
8
40
 
@@ -16,23 +48,47 @@ In cloud-client mode, CLI and MCP reads/writes go directly to the shared HTTP AP
16
48
  | OpenCode | `~/.local/share/opencode/storage/message/**/*.json` | Imports assistant-message usage and uses the recorded cost when present. |
17
49
  | Cursor | Cursor `/api/usage` and `/api/usage-summary` | Requires `CURSOR_SESSION_TOKEN` (or `CURSOR_API_TOKEN`). Creates daily usage snapshots and a subscription rollup when spend is present. |
18
50
  | Pi | `~/.pi/agent/sessions/**/*.json` | Override with `PI_CODING_AGENT_SESSION_DIR`. Uses recorded turn cost; a missing cost remains zero until pricing is repaired or the source records one. |
19
- | Hermes | `~/.hermes/state.db` | Imports session-level token and cost rollups. |
20
- | OpenLoops | `~/.hasna/loops/loops.db` | Override with `HASNA_ECONOMY_LOOPS_DB_PATH`; price with `HASNA_ECONOMY_LOOPS_MODEL` or `ECONOMY_LOOPS_MODEL`. Imports orchestration/judge `goal_runs.tokens_used` only, into `loop:*` cost centers. |
51
+ | Hermes | `~/.hermes/state.db` | Retains session lifetime totals and their actual start/end span. It cannot establish individual request times. |
52
+ | OpenLoops | `~/.hasna/loops/loops.db` | **Local lane only** — a hosted client refuses this cross-app on-box read (`loops ingest skipped`, one stderr line); pending a hosted loops read. Override with `HASNA_ECONOMY_LOOPS_DB_PATH`; price with `HASNA_ECONOMY_LOOPS_MODEL` or `ECONOMY_LOOPS_MODEL`. Imports orchestration/judge `goal_runs.tokens_used` only, into `loop:*` cost centers. |
21
53
 
22
54
  Full, unfiltered sync also attempts to import active metadata from `@hasna/projects`. Missing files, optional registries, and unavailable quota credentials are skipped; use `--verbose` to see source-level diagnostics.
23
55
 
56
+ Claude/Takumi, Gemini, Pi and OpenCode usage without a valid recorded event timestamp remains uncollected and
57
+ reports `SOURCE_INVALID`, including on repeat scans. The original source stays
58
+ intact. A file's modification time or the containing session's update time cannot
59
+ date an otherwise undated request. Hermes lifetime-only usage reports `USAGE_EVENTS_UNAVAILABLE`; it creates
60
+ no artificial end-date request. Summaries identify overlapping session-only
61
+ lifetime totals separately and leave the complete period total unavailable.
62
+ These values are not allocated to days or months. Existing Hermes rollup requests
63
+ require a separately preserved historical migration and are not rewritten by sync.
64
+
65
+ Ingestion treats subscriptions as `quota_observation` by default, preserving the
66
+ existing assignment, active status, plan label and billing-cycle settings. Explicit
67
+ subscription edits use `subscription_mode: "configuration"` with a subscriptions
68
+ array; the normal subscription command supplies this mode. Both modes remain
69
+ subject to the same source-version, current write-authority and receipt checks.
70
+ Authenticated mutating API requests require `economy:write`; a read-only key
71
+ cannot change subscription configuration or other stored settings.
72
+ An explicit configuration replaces monetary values and their provenance, including
73
+ unknown amounts. Omitting a fee on a plan change does not retain the previous plan's
74
+ fee; the comparison stays unavailable until the new fee is known.
75
+ Quota refreshes with unknown monetary amounts preserve a subscription's existing
76
+ configured or observed fee and included value. A deliberate known zero must carry
77
+ `configured` or `observed` provenance. A zero-priced request with no affirmative
78
+ price evidence keeps both summaries and subscription comparisons unavailable.
79
+
24
80
  ## Incremental and repair behavior
25
81
 
26
82
  File/database state is cached in the `ingest_state` table, and request IDs are upserted and deduplicated. Consequently, normal repeated syncs are incremental.
27
83
 
28
84
  - `--force` clears the ingest-state entries for the supported sources and reprocesses them.
29
- - `--backfill-machine` fills empty `machine_id` values with `ECONOMY_MACHINE_ID` or the normalized hostname.
85
+ - `--backfill-machine` fills empty `machine_id` values with `HASNA_ECONOMY_MACHINE_ID` or the normalized hostname.
30
86
  - `--recalculate` prices token-bearing requests whose `cost_usd` is zero, then rerolls affected sessions. It reports buckets still missing usable pricing.
31
87
  - Budget webhooks are checked after a CLI or REST sync. Failed deliveries remain eligible for a later retry.
32
88
 
33
89
  ## Account and cost-center attribution
34
90
 
35
- Economy first checks agent-specific overrides such as `ECONOMY_CODEX_ACCOUNT`, then generic `ECONOMY_ACCOUNT` overrides, then matching `@hasna/accounts` env-dir/applied/current profiles. An override may be `tool:name`, an email, or separate `*_ACCOUNT_TOOL`, `*_ACCOUNT_NAME`, and `*_ACCOUNT_EMAIL` fields. See [configuration](configuration.md#account-attribution).
91
+ Economy first checks agent-specific overrides such as `ECONOMY_CODEX_ACCOUNT`, then generic `ECONOMY_ACCOUNT` overrides, then matching accounts env-dir/applied/current profiles from the accounts API. The on-box accounts registry (`~/.hasna/accounts/accounts.json`) is consulted **only** under the explicit local opt-in `HASNA_ECONOMY_LOCAL=1`; without an accounts credential and without that flag, attribution is omitted and the refusal is printed once on stderr. An override may be `tool:name`, an email, or separate `*_ACCOUNT_TOOL`, `*_ACCOUNT_NAME`, and `*_ACCOUNT_EMAIL` fields. See [configuration](configuration.md#account-attribution).
36
92
 
37
93
  Apps and services can report explicit project, repository, account, attribution-tag, and cost-center fields through [`economy-otel`](otel.md). `ECONOMY_TAG` supplies a fallback attribution tag for records written through the local database helpers.
38
94
 
package/docs/mcp.md CHANGED
@@ -24,7 +24,7 @@ Gemini settings:
24
24
  }
25
25
  ```
26
26
 
27
- `economy mcp --all` prints these snippets. The MCP server uses the same local-versus-cloud Store selection as the CLI; configure a shared API with `HASNA_ECONOMY_API_URL` and `HASNA_ECONOMY_API_KEY` as described in [configuration](configuration.md#climcp-cloud-client).
27
+ `economy mcp --all` prints these snippets. The MCP server uses the same local-versus-cloud Store selection as the CLI; the credential resolves through the `@hasna/contracts` chain as described in [configuration](configuration.md#climcp-cloud-client).
28
28
 
29
29
  ## Streamable HTTP
30
30
 
@@ -56,10 +56,12 @@ Management and estimation:
56
56
  - `list_subscriptions`, `set_subscription`, `remove_subscription`
57
57
  - `sync`, `send_feedback`
58
58
 
59
- Shared agent-registry tools:
59
+ Shared agent lifecycle tools:
60
60
 
61
61
  - `register_agent`, `heartbeat`, `set_focus`, `list_agents`
62
62
 
63
+ The agent registry behind these tools is opened on first use, never at startup — through a dynamic import, so the published `dist/mcp` bundle contains no `bun:sqlite` reference at all. A hosted server (a resolved credential) keeps it in memory for the life of the process — no SQLite file is created under `~/.hasna/economy` in hosted mode; under the explicit local opt-in (`HASNA_ECONOMY_LOCAL=1`) it persists as `agent-registry.db` beside the local store and is shared by every MCP process on the box. `HASNA_AGENT_REGISTRY_DB_PATH` overrides that file only in the explicit local lane; hosted transport ignores it and remains in memory.
64
+
63
65
  High-cardinality tools return compact text by default. Where the schema offers them, use `limit`, `verbose=true`, or `json=true`. Limits are clamped to 100 for MCP calls.
64
66
 
65
- The `sync` tool accepts `all`, any supported coding agent, or `loops`. It ingests on-box files (in local mode into the local SQLite; in cloud-client mode it reads the on-box files on this machine and pushes the rows to the shared API).
67
+ The `sync` tool accepts `all`, any supported coding agent, or `loops`. It ingests on-box files (in local mode into the local SQLite; in cloud-client mode it reads the on-box files on this machine and pushes the rows to the shared API). `loops` is the exception: it reads ANOTHER app's on-box SQLite, so a hosted server refuses it and reports `loops ingest skipped`.
@@ -0,0 +1,84 @@
1
+ # Native metered API budget rules
2
+
3
+ The native Save action uses the existing authenticated `POST /v1/ingest` command
4
+ and `GET /v1/ingest/receipts` recovery path. It creates one rule; it does not edit
5
+ an existing rule, cap spending, or request notification delivery. Existing
6
+ `POST /v1/budgets`, CLI `budget set` and MCP `set_budget` retain legacy semantics.
7
+
8
+ Capture the rule ID, idempotency key and timestamps once when the user confirms.
9
+ Keep the complete command unchanged until its outcome is resolved. For example:
10
+
11
+ ```json
12
+ {
13
+ "schema_version": 1,
14
+ "idempotency_key": "budget-create-example-1",
15
+ "budget_mode": "create",
16
+ "budgets": [{
17
+ "id": "budget-example-1",
18
+ "metric": "metered_api_usd",
19
+ "period": "monthly",
20
+ "limit_usd": 50,
21
+ "alert_at_percent": 80,
22
+ "project_path": null,
23
+ "agent": null,
24
+ "created_at": "2026-09-30T12:00:00.000Z",
25
+ "updated_at": "2026-09-30T12:00:00.000Z"
26
+ }]
27
+ }
28
+ ```
29
+
30
+ Use fresh caller-owned UUIDs for real rule IDs and keys. The command requires
31
+ schema version 1, the explicit create mode, exactly one rule and no other tables.
32
+ Limits are positive finite USD values. Thresholds are integers from 1 through
33
+ 100. Periods are `daily`, `weekly`, or `monthly`. Omitted or null project, agent
34
+ and cost-center fields mean all values; provided text cannot be empty. Agents
35
+ must be members of Economy's published agent enum. Both timestamps are explicit
36
+ ISO UTC instants ending in `Z` and are part of the exact retry intent.
37
+
38
+ The transaction inserts the rule only when its ID is absent. Concurrent creation
39
+ or another actor's use of the same ID cannot overwrite it. A committed identical
40
+ command by the same authenticated actor returns the original receipt with
41
+ `replayed: true`. Reusing a key for different content returns 409. A new key is
42
+ not a way to edit an existing ID. Generic observation ingestion can recognize an
43
+ exact unchanged new rule but cannot edit it or convert a legacy rule's metric.
44
+
45
+ After a timeout or lost response, do not automatically create a new command. Read
46
+ `/v1/ingest/receipts?idempotency_key=…&body_hash=…` using the same authenticated
47
+ principal. `body_hash` is lowercase SHA-256 of `canonicalIngestBodyHash`'s stable
48
+ JSON encoding: recursively sort object keys, retain array order and JSON scalar
49
+ encoding. The hash includes the key, mode and all fields exactly as submitted.
50
+ Keep the serialized command and its hash together. A committed receipt must
51
+ match the principal and hash. `absent` is only a current observation; an earlier
52
+ request may still commit. Explicit retry, if chosen, uses that same full body/key.
53
+ Read-only or revoked principals cannot write or reconcile a write receipt.
54
+
55
+ After confirmation, fetch `GET /v1/budgets` and show the matching ID and its
56
+ authoritative threshold status. Do not label it saved merely because a request
57
+ was sent or a matching-looking row was listed before the transaction.
58
+
59
+ `metered_api_usd` sums stored requests whose `cost_basis` is exactly
60
+ `metered_api`. It excludes `subscription_included`, `estimated`, `unknown`,
61
+ subscription fees, quota values and separate provider billing rows. These are
62
+ recorded API usage values, not a guarantee of complete collection or a reconciled
63
+ provider invoice. Scope combines selected project, agent and cost center.
64
+ The new metric sums the stored numbers' round-trip decimal representations and
65
+ compares thresholds in decimal arithmetic. Fractional-cent API charges remain
66
+ intact; neither individual charges nor the comparison are rounded to cents.
67
+ The numeric display fields keep their existing JSON-number representation, while
68
+ the alert and limit flags come from the exact comparison. A total or percentage
69
+ outside that numeric display range fails visibly rather than reporting a false
70
+ zero or serializing infinity as null. Legacy rules retain their previous sum.
71
+ Thresholds are in-app status, not spending enforcement or guaranteed alerts.
72
+ Creating or replaying this rule never invokes optional webhook delivery.
73
+
74
+ Periods use the existing full UTC calendar windows. Days begin at 00:00 UTC,
75
+ weeks begin on Sunday, and months begin on the first. Each budget status exposes
76
+ `period_bounds` with `time_zone`, `start_date` and `end_date_exclusive`; display
77
+ those semantics rather than interpreting the date in the device's timezone.
78
+
79
+ The additive migration appends `budgets.metric` with default
80
+ `legacy_request_cost_usd`. Existing rows and their request-cost sum behavior are
81
+ preserved; historical migration bytes do not change. Apply the new migration
82
+ through the reviewed deployment path before enabling the native Save action.
83
+ This document and fixture results alone do not establish hosted activation,
84
+ native human authentication, push delivery or production data changes.
@@ -0,0 +1,102 @@
1
+ # Native human authentication
2
+
3
+ This additive protocol leaves signed agent keys under their existing verifier.
4
+ It does not activate human access by default. Deployment requires an operator to
5
+ verify which tenant and human principals own the entire existing Economy report
6
+ store. The store is not partitioned by the tenant in an incoming token.
7
+
8
+ ## Identity protocol
9
+
10
+ The native client uses the existing Identities authority at
11
+ `https://api.hasna.com/identities/v1`, with HTTPS and redirects disabled:
12
+
13
+ 1. `POST /auth/login` with `{ "email": "<human email>" }` requests an email code.
14
+ The response contains `challenge: true` and `expires_in`.
15
+ 2. `POST /auth/verify` with `{ "email": "<human email>", "code": "<code>" }`
16
+ returns `session`, `session_expires_in`,
17
+ `principal: { user_id, kind, email, display_name }` and
18
+ `tenants: [{ tenant_id, role }]`. Store the session in the platform credential
19
+ store. It is an Identities credential and must never be sent to Economy.
20
+ 3. Select an explicit tenant from the returned memberships. `POST /auth/token`
21
+ with that session in the Bearer header and body
22
+ `{ "app": "economy", "tenant_id": "<selected tenant>" }` returns
23
+ `access_token`, `token_type: "Bearer"`, `uid`, `tid`, `scope`, `expires_in`,
24
+ `aud: "economy"`, `pt: "user"`, `alg: "EdDSA"` and `kid`.
25
+ Omit `scopes` to receive current grants; never require write scope just to
26
+ sign in. Explicit scopes can only narrow access. Economy token TTL defaults
27
+ to 300 seconds, cannot exceed 300, and cannot outlive the session.
28
+ 4. Use the access token as Bearer at `https://api.hasna.com/economy/v1`.
29
+ `GET /auth/session` returns
30
+ `{ data: { subject, tenant_id, scopes, session_id, expires_at }, meta: {} }`.
31
+ `expires_at` is integer epoch seconds; `scopes` is the effective intersection
32
+ of current Identities grants and Economy's store binding. Hide write controls
33
+ unless it includes `economy:write`. Human report and session responses are
34
+ `Cache-Control: no-store`.
35
+ 5. Refresh by repeating step 3 while the session is valid. Stable account identity
36
+ is the verified `(tenant_id, subject)` pair, not token id or signing key.
37
+ Fence all pending requests when account, tenant or session changes. Do not
38
+ display a prior account's cached report after a change.
39
+ 6. Sign out locally first, cancelling requests and clearing session, access
40
+ token and account-scoped caches. Make one best-effort `POST /auth/logout` to
41
+ Identities using the previous session bearer. Success is
42
+ `{ "signed_out": true }`. This revokes only that session and its Economy
43
+ tokens. Report a failed remote sign-out accurately; other legacy app tokens
44
+ retain their existing semantics.
45
+
46
+ This is the existing email-code protocol, not OAuth/PKCE. No browser callback,
47
+ embedded station key, signup or new identity account is needed by the client.
48
+ Clients bound request/response sizes and deadlines and never follow redirects
49
+ while sending a credential. No token, code or raw auth response belongs in logs.
50
+
51
+ ## Economy store binding
52
+
53
+ `ECONOMY_HUMAN_AUTH_BINDING` is non-secret server configuration:
54
+
55
+ ```json
56
+ {
57
+ "tenantId": "<operator-verified data tenant>",
58
+ "principals": [
59
+ { "subject": "<verified human user id>", "scopes": ["economy:read"] }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ These values must come from independently verified ownership and current grants;
65
+ never populate them from the requesting token or an unverified native-client
66
+ choice. Only the named tenant and principals are admitted. Add write scope only
67
+ where the existing budget/configuration authority permits it. Agent permissions
68
+ remain unchanged. Missing configuration returns `503 human_auth_not_configured`
69
+ for human credentials while agent keys continue working. Malformed configuration
70
+ refuses server startup.
71
+
72
+ The server sends only the Economy access token to the fixed default endpoint
73
+ `https://api.hasna.com/identities/v1/auth/economy-authority`. An explicit HTTPS
74
+ `authorityUrl` permits a self-hosted authority with the same contract; it cannot
75
+ contain credentials, query parameters or fragments. No response can select a
76
+ different origin. Redirects are refused. Each authority call has a three-second
77
+ deadline and a 16 KiB response cap; tokens are capped at 8 KiB. Failure is closed
78
+ and redacted (`human_authority_unavailable`); no offline grant cache is used.
79
+
80
+ Identities verifies the EdDSA signature, issuer `identities`, audience `economy`,
81
+ 300-second lifetime and signed session id, then checks the current session,
82
+ human, tenant and membership together in one database statement. Only concrete
83
+ read/write grants survive the role and explicit membership-scope ceiling.
84
+ Economy intersects that response with its store binding. It checks at request
85
+ admission and again after body reading/queue waiting immediately before dispatch.
86
+ Actor identity uses the tenant and stable human subject across token refreshes.
87
+
88
+ This establishes current authority at dispatch. It does not introduce a
89
+ distributed transaction with Identities, undo already-started work on logout, or
90
+ claim that a later revocation is observed at database commit. Native session
91
+ generation fencing remains required for responses already in flight.
92
+
93
+ ## Verification and activation
94
+
95
+ Package tests cover config refusal, stable actor, effective scopes, agent-key
96
+ compatibility, changed authority before dispatch and bounded/redacted failures.
97
+ The owning Identities fixture uses fresh PostgreSQL, real HTTP login/verification
98
+ and signed tokens; the cross-app acceptance fixture also exercises actual TLS
99
+ requests, foreign tenant/principal refusal, queued revocation and redirect
100
+ refusal. These are source/fixture proofs. Live email delivery, existing human
101
+ grants/store ownership, service releases and production activation are separate
102
+ requirements. No production key or record is created by these source changes.
package/docs/otel.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # OTLP/HTTP sidecar
2
2
 
3
- `economy-otel` writes application/service metrics into the local Economy SQLite database:
3
+ `economy-otel` ingests application/service metrics into Economy — forwarded to the hosted API when a credential resolves, or written to the on-box SQLite store under the explicit local opt-in (see [Storage](#storage)):
4
4
 
5
5
  ```bash
6
6
  economy-otel --port 4318
@@ -50,4 +50,10 @@ The OTLP parser reads sum or gauge data points. Metric names containing cost or
50
50
 
51
51
  Resource and point attributes may use plain names (`model`, `session_id`, `request_id`, `project_path`, and the attribution fields above) or common dotted aliases such as `ai.model`, `session.id`, `event.id`, `service.name`, `economy.cost_center`, and `account.email`.
52
52
 
53
- The sidecar always opens the local SQLite database. It does not forward to a cloud API; use the authenticated REST bulk-ingest path for remote ingestion.
53
+ ## Storage
54
+
55
+ The sidecar follows the same storage seam as the CLI and the MCP server ([configuration](configuration.md#climcp-cloud-client)), decided once at startup, before the listener binds:
56
+
57
+ - **Hosted** — a credential resolves through the `@hasna/contracts` chain (Keychain, `~/.hasna/economy/config/credentials`, `HASNA_ECONOMY_API_KEY`): every accepted payload is ingested into a scratch in-memory store and its request/session rows are pushed to the shared API's `/v1/ingest` (idempotent upserts). The response adds `forwarded` (rows the server accepted). Nothing is written under `~/.hasna/economy`; the `cost_centers` table is not part of `/v1/ingest`, so pushed rows carry their `cost_center_id` but the server's cost-center registry is not updated by the sidecar. A failed push answers 502 and the payload is not retried.
58
+ - **Local** — `HASNA_ECONOMY_LOCAL=1`: rows are written to the on-box `economy.db`, and the sidecar prints `economy: local mode …` on stderr once.
59
+ - **Neither** — the sidecar fails closed: exit 1 with the resolver's diagnostic and no listener, no SQLite file.
package/docs/rest-api.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Start the service with `economy-serve` or `economy serve`. The default local origin is `http://127.0.0.1:3456`; the bind behavior is detailed in [configuration](configuration.md#rest-server).
4
4
 
5
- The canonical application prefix is `/v1`. Equivalent `/api` routes remain available for the bundled dashboard and older clients. Successful application responses use:
5
+ The canonical application prefix is `/v1`. Equivalent `/api` routes remain available for older clients. Successful application responses use:
6
6
 
7
7
  ```json
8
8
  { "data": {}, "meta": {} }
@@ -23,14 +23,14 @@ These routes are open. The checked-in OpenAPI source is [`openapi/economy.json`]
23
23
 
24
24
  ## Authentication
25
25
 
26
- In local mode, set `ECONOMY_API_TOKEN` or `HASNA_ECONOMY_API_TOKEN` and send either:
26
+ In local mode, set `HASNA_ECONOMY_API_TOKEN` (the unprefixed `ECONOMY_API_TOKEN` spelling is retired) and send either:
27
27
 
28
28
  ```text
29
29
  Authorization: Bearer <token>
30
30
  X-Economy-Token: <token>
31
31
  ```
32
32
 
33
- In self-hosted Postgres mode, send a valid Economy API key as `x-api-key` or a bearer token. All non-probe routes require a valid key when the server has an authenticator. Bulk ingest and feedback additionally request the `economy:write` scope.
33
+ With a Postgres backend (database URL configured), send a valid Economy API key as `x-api-key` or a bearer token. All non-probe routes require a valid key when the server has an authenticator. Bulk ingest and feedback additionally request the `economy:write` scope.
34
34
 
35
35
  ## Read routes
36
36
 
@@ -83,7 +83,7 @@ Common period values are `today`, `yesterday`, `week`, `month`, `year`, and `all
83
83
  | `DELETE` | `/v1/project-registry/{path}` | Delete a URL-encoded project path. |
84
84
  | `POST` | `/v1/sync` | `{ "sources": "all" }` or one of the eight agents/`loops`; triggers server-local ingestion. |
85
85
  | `POST` | `/v1/billing/sync` | Optional `days` (1–366) and `providers` from `anthropic`, `openai`, `gemini`. Provider failures are returned per provider. |
86
- | `POST` | `/v1/ingest` | Idempotently merge arrays named `requests`, `sessions`, `projects`, `budgets`, `goals`, `billing_daily`, `model_pricing`, `subscriptions`, and `usage_snapshots`. |
86
+ | `POST` | `/v1/ingest` | Atomically ingest versioned, bounded arrays named `requests`, `sessions`, `projects`, `budgets`, `goals`, `billing_daily`, `model_pricing`, `subscriptions`, and `usage_snapshots`. |
87
87
  | `POST` | `/v1/feedback` | Required `message`; optional `email` and `category=bug|feature|general`. |
88
88
 
89
89
  Agent fields accept `claude`, `takumi`, `codex`, `gemini`, `opencode`, `cursor`, `pi`, or `hermes`.
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@hasna/economy",
3
- "version": "0.3.28",
4
- "description": "AI coding cost tracker — CLI + MCP server + REST API + web dashboard for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
3
+ "version": "0.5.0",
4
+ "description": "AI coding cost tracker — CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "git+https://github.com/hasna/economy.git"
7
+ "url": "https://github.com/hasna/apps.git",
8
+ "directory": "apps/economy"
8
9
  },
9
10
  "bugs": {
10
- "url": "https://github.com/hasna/economy/issues"
11
+ "url": "https://github.com/hasna/apps/issues"
11
12
  },
12
- "homepage": "https://github.com/hasna/economy#readme",
13
+ "homepage": "https://github.com/hasna/apps/tree/main/apps/economy#readme",
13
14
  "type": "module",
14
15
  "main": "dist/index.js",
15
16
  "types": "dist/index.d.ts",
@@ -32,22 +33,21 @@
32
33
  "files": [
33
34
  "dist",
34
35
  "docs",
35
- "dashboard/dist",
36
36
  "postinstall.js",
37
37
  "LICENSE",
38
38
  "README.md",
39
39
  "CHANGELOG.md",
40
40
  "SECURITY.md",
41
41
  "CONTRIBUTING.md",
42
- "CODE_OF_CONDUCT.md"
42
+ "CODE_OF_CONDUCT.md",
43
+ "!dist/lib/test-hermetic-*"
43
44
  ],
44
45
  "scripts": {
45
- "build": "cd dashboard && bun install --frozen-lockfile && bun run build && cd .. && bun build src/cli/index.ts --outdir dist/cli --target bun --packages external && bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external && bun build src/server/index.ts --outdir dist/server --target bun --packages external && bun build src/db/pg-sync-worker.ts --outdir dist/server --target bun --packages external && bun build src/otel/index.ts --outdir dist/otel --target bun --packages external && bun build src/index.ts --outdir dist --target bun --packages external && tsc --emitDeclarationOnly --outDir dist",
46
- "build:cli": "bun build src/cli/index.ts --outdir dist/cli --target bun --packages external",
47
- "build:mcp": "bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external",
48
- "build:server": "bun build src/server/index.ts --outdir dist/server --target bun --packages external",
49
- "build:lib": "bun build src/index.ts --outdir dist --target bun --packages external",
50
- "build:dashboard": "cd dashboard && bun run build",
46
+ "build": "bun build src/cli/index.ts --outdir dist --root src --target bun --packages external --splitting --chunk-naming \"chunks/[name]-[hash].[ext]\" && bun build src/mcp/index.ts --outdir dist --root src --target bun --packages external --splitting --chunk-naming \"chunks/[name]-[hash].[ext]\" && bun build src/server/index.ts --outdir dist/server --target bun --packages external && bun build src/db/pg-sync-worker.ts --outdir dist/server --target bun --packages external && bun build src/server/service-worker.ts --outdir dist/server --target bun --packages external && bun build src/otel/index.ts --outdir dist/otel --target bun --packages external && bun build src/index.ts --outdir dist --root src --target bun --packages external --splitting --chunk-naming \"chunks/[name]-[hash].[ext]\" && tsc --emitDeclarationOnly --outDir dist",
47
+ "build:cli": "bun build src/cli/index.ts --outdir dist --root src --target bun --packages external --splitting --chunk-naming \"chunks/[name]-[hash].[ext]\"",
48
+ "build:mcp": "bun build src/mcp/index.ts --outdir dist --root src --target bun --packages external --splitting --chunk-naming \"chunks/[name]-[hash].[ext]\"",
49
+ "build:server": "bun build src/server/index.ts --outdir dist/server --target bun --packages external && bun build src/db/pg-sync-worker.ts --outdir dist/server --target bun --packages external && bun build src/server/service-worker.ts --outdir dist/server --target bun --packages external",
50
+ "build:lib": "bun build src/index.ts --outdir dist --root src --target bun --packages external --splitting --chunk-naming \"chunks/[name]-[hash].[ext]\"",
51
51
  "artifact-scan": "bun scripts/contracts-cli.mjs artifact-scan dist",
52
52
  "prepack": "bun run build && bun run artifact-scan",
53
53
  "typecheck": "tsc --noEmit",
@@ -77,21 +77,18 @@
77
77
  "access": "public"
78
78
  },
79
79
  "dependencies": {
80
- "@hasna/accounts": "0.2.23",
81
- "@hasna/agent-registry": "^0.1.0",
82
- "@hasna/contracts": "0.13.4",
80
+ "@hasna/contracts": "1.0.2",
83
81
  "@hasna/events": "^0.1.16",
84
- "@hasna/mcp-harness": "^0.1.0",
85
- "@hasna/paths": "0.1.0",
86
82
  "@hasna/projects": "1.0.0",
87
83
  "@modelcontextprotocol/sdk": "^1.12.1",
88
84
  "chalk": "^5.4.1",
89
85
  "commander": "^13.1.0",
90
86
  "pg": "8.13.1",
91
- "zod": "^3.24.2"
87
+ "zod": "^3.24.2",
88
+ "@hasna/trash": "0.2.3"
92
89
  },
93
90
  "devDependencies": {
94
- "@types/bun": "latest",
91
+ "@types/bun": "1.3.14",
95
92
  "@types/pg": "^8.20.0",
96
93
  "bun-types": "latest",
97
94
  "typescript": "^5.7.2"
package/postinstall.js CHANGED
@@ -6,6 +6,57 @@
6
6
  // ~/.hasna/economy default. Failures are non-fatal: the runtime getDataDir()
7
7
  // creates the same directories on first use.
8
8
  import { existsSync, mkdirSync } from "node:fs";
9
+ // --- Local path resolver -------------------------------------------------
10
+ // @hasna/paths was deleted (hasna/apps#1535, 2026-09-03); this in-package
11
+ // implementation preserves the resolver contract (XDG / macOS home layout
12
+ // honoring HASNA_{CONFIG,DATA,STATE,CACHE}_HOME, with the same env-override
13
+ // and home-override semantics the deleted package had).
14
+ import { homedir as pathsResolverHomedir } from "node:os";
15
+ import { join as pathsResolverJoin } from "node:path";
16
+
17
+ const PATHS_RESOLVER_KIND_ENV = {
18
+ config: "HASNA_CONFIG_HOME",
19
+ data: "HASNA_DATA_HOME",
20
+ state: "HASNA_STATE_HOME",
21
+ cache: "HASNA_CACHE_HOME",
22
+ };
23
+
24
+ function pathsResolverBaseDir(kind, options) {
25
+ const env = options.env ?? process.env;
26
+ const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
27
+ if (typeof override === "string" && override.length > 0) return override;
28
+ const home = options.home ?? pathsResolverHomedir();
29
+ const platform = options.platform ?? process.platform;
30
+ if (platform === "darwin") {
31
+ switch (kind) {
32
+ case "config":
33
+ case "data":
34
+ return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
35
+ case "cache":
36
+ return pathsResolverJoin(home, "Library", "Caches", "Hasna");
37
+ case "state":
38
+ return pathsResolverJoin(home, "Library", "Logs", "Hasna");
39
+ }
40
+ }
41
+ switch (kind) {
42
+ case "config":
43
+ return pathsResolverJoin(home, ".config", "hasna");
44
+ case "data":
45
+ return pathsResolverJoin(home, ".local", "share", "hasna");
46
+ case "state":
47
+ return pathsResolverJoin(home, ".local", "state", "hasna");
48
+ case "cache":
49
+ return pathsResolverJoin(home, ".cache", "hasna");
50
+ }
51
+ }
52
+
53
+ function pathsResolverResolve(kind, options) {
54
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
55
+ return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
56
+ }
57
+ function dataDir(options) {
58
+ return pathsResolverResolve("data", options);
59
+ }
9
60
  import { homedir } from "node:os";
10
61
  import { join } from "node:path";
11
62
 
@@ -13,7 +64,7 @@ const EXACT_OVERRIDE = (process.env["HASNA_ECONOMY_HOME"] || process.env["ECONOM
13
64
  const DATA_HOME_OVERRIDE = (process.env["HASNA_DATA_HOME"] || "").trim();
14
65
 
15
66
  try {
16
- const { dataDir } = await import("@hasna/paths");
67
+ // (local resolver — @hasna/paths deleted, hasna/apps#1535)
17
68
  const resolved = dataDir({ app: "economy" });
18
69
  let root;
19
70
  if (EXACT_OVERRIDE) {
@@ -1,26 +0,0 @@
1
- # Economy dashboard
2
-
3
- The dashboard is the React/Vite UI served by `economy-serve`. It covers overview, sessions, models, projects, budgets, goals, usage, accounts, savings, fleet, reconciliation, pricing, and provider billing.
4
-
5
- ## Development
6
-
7
- Run the API from the repository root, then start Vite:
8
-
9
- ```bash
10
- economy-serve --port 3456
11
- cd dashboard
12
- bun install
13
- bun run dev
14
- ```
15
-
16
- The development build uses `http://localhost:3456` by default. Set `VITE_API_URL` at build/dev time to use another origin. The dashboard currently calls the server's legacy-compatible `/api` routes; the public canonical API is `/v1`.
17
-
18
- ## Validation
19
-
20
- ```bash
21
- bun test
22
- bun run lint
23
- bun run build
24
- ```
25
-
26
- The root package build writes production assets to `dashboard/dist`; `economy-serve` serves those assets and provides SPA fallback routing.
@@ -1,3 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function registerBrainsCommand(program: Command): void;
3
- //# sourceMappingURL=brains.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1,6 +0,0 @@
1
- /**
2
- * Stash and delete the hosted-accounts env vars. Returns a restore function
3
- * for afterEach/afterAll. Safe to call when the variables are unset.
4
- */
5
- export declare function isolateHostedAccountsEnv(): () => void;
6
- //# sourceMappingURL=test-hermetic-accounts.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"test-hermetic-accounts.d.ts","sourceRoot":"","sources":["../../src/lib/test-hermetic-accounts.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,IAAI,CAUrD"}