@hasna/economy 0.3.27 → 0.3.28
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.
- package/CHANGELOG.md +6 -0
- package/README.md +3 -3
- package/dist/cli/index.js +61 -23
- package/dist/db/database.d.ts +39 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +41 -11
- package/dist/ingest/loops.d.ts +10 -0
- package/dist/ingest/loops.d.ts.map +1 -1
- package/dist/lib/model-config.d.ts +1 -1
- package/dist/lib/model-config.d.ts.map +1 -1
- package/dist/mcp/index.js +53 -15
- package/dist/otel/index.js +41 -11
- package/dist/server/index.js +56 -18
- package/package.json +4 -2
- package/postinstall.js +31 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.28
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Switch @hasna/economy local path reads/writes through the @hasna/paths resolver (XDG/macOS home layout). The legacy `~/.hasna/economy` data root (with the `HASNA_ECONOMY_HOME` / `ECONOMY_HOME` exact-app overrides layered on top of the existing `HASNA_ECONOMY_DB_PATH` / `ECONOMY_DB` store override) stays the effective data root until the store has actually been migrated to the XDG data home or the operator sets the data-kind override `HASNA_DATA_HOME` — an existing local store never becomes invisible on upgrade. The install-time postinstall now creates the same effective data root (and its `training` subdir) the runtime resolves. The OpenLoops ingest read (`economy sync --loops`) resolves the loops store through the resolver with a legacy-read fallback, so it keeps working whichever side of the XDG migration `@hasna/loops` is on. Dependency pinned exactly to `@hasna/paths@0.1.0` — the wave-wide pin for the hasna/apps resolver-switch lanes (XDG home migration, hotfixes plan 0f49f56a, task P3.3).
|
|
8
|
+
|
|
3
9
|
## 0.3.27
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ economy sync --hermes
|
|
|
124
124
|
economy sync --loops
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
`economy sync --loops` reads `~/.hasna/loops/loops.db` in read-only mode and imports OpenLoops orchestration/judge `goal_runs.tokens_used` into `loop:*` cost centers. It intentionally does not ingest dispatched coding-agent work from loops; heavy agent spend remains captured by the existing per-agent ingesters and can be analyzed alongside loop cost centers through account/profile attribution.
|
|
127
|
+
`economy sync --loops` reads the OpenLoops store (`~/.hasna/loops/loops.db` by default, resolved through the `@hasna/paths` XDG data home once the loops store has migrated there) in read-only mode and imports OpenLoops orchestration/judge `goal_runs.tokens_used` into `loop:*` cost centers. It intentionally does not ingest dispatched coding-agent work from loops; heavy agent spend remains captured by the existing per-agent ingesters and can be analyzed alongside loop cost centers through account/profile attribution.
|
|
128
128
|
|
|
129
129
|
Useful repair options:
|
|
130
130
|
|
|
@@ -272,9 +272,9 @@ economy menubar uninstall
|
|
|
272
272
|
|
|
273
273
|
## Data Directory
|
|
274
274
|
|
|
275
|
-
Data is stored
|
|
275
|
+
Data is stored under a single data root resolved through the `@hasna/paths` resolver (XDG/macOS home layout). The legacy `~/.hasna/economy/` stays the effective root until the store is actually migrated to the XDG data home or the operator sets the data-kind override `HASNA_DATA_HOME`; the exact-app overrides `HASNA_ECONOMY_HOME` / `ECONOMY_HOME` win unconditionally.
|
|
276
276
|
|
|
277
|
-
The main SQLite database lives at `~/.hasna/economy/economy.db
|
|
277
|
+
The main SQLite database lives at `<data-root>/economy.db` (`~/.hasna/economy/economy.db` by default). Older `~/.economy/` data is copied on first open when the new directory does not exist. Override the database path with `HASNA_ECONOMY_DB_PATH` or `ECONOMY_DB`.
|
|
278
278
|
|
|
279
279
|
For shared deployments, CLI and MCP can use a remote `/v1` API instead of local SQLite by setting `HASNA_ECONOMY_API_URL` and `HASNA_ECONOMY_API_KEY`. See [configuration](docs/configuration.md) for client resolution, server auth, Postgres mode, and all environment variables.
|
|
280
280
|
|
package/dist/cli/index.js
CHANGED
|
@@ -595,7 +595,8 @@ import { execFileSync } from "child_process";
|
|
|
595
595
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
596
596
|
import { hostname, platform } from "os";
|
|
597
597
|
import { homedir } from "os";
|
|
598
|
-
import { join } from "path";
|
|
598
|
+
import { join, resolve } from "path";
|
|
599
|
+
import { dataDir } from "@hasna/paths";
|
|
599
600
|
function normalizeMachineId(value) {
|
|
600
601
|
const id = value?.trim().toLowerCase().split(".")[0];
|
|
601
602
|
return id && id.length > 0 ? id : null;
|
|
@@ -626,21 +627,48 @@ function getMachineId() {
|
|
|
626
627
|
return macHostMachineId() ?? hostMachine;
|
|
627
628
|
return hostMachine;
|
|
628
629
|
}
|
|
629
|
-
function
|
|
630
|
+
function getHomeDir() {
|
|
630
631
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
632
|
+
if (!home)
|
|
633
|
+
throw new Error("Could not resolve the user home directory");
|
|
634
|
+
return home;
|
|
635
|
+
}
|
|
636
|
+
function getResolverDataRoot() {
|
|
637
|
+
return dataDir({
|
|
638
|
+
app: "economy",
|
|
639
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
function getLegacyDataRoot() {
|
|
643
|
+
return join(getHomeDir(), ".hasna", "economy");
|
|
644
|
+
}
|
|
645
|
+
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
646
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
647
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
648
|
+
return true;
|
|
649
|
+
return existsSync(join(resolved, "economy.db"));
|
|
650
|
+
}
|
|
651
|
+
function getExactDataRoot() {
|
|
652
|
+
const dir = process.env["HASNA_ECONOMY_HOME"] ?? process.env["ECONOMY_HOME"];
|
|
653
|
+
if (dir && dir.trim())
|
|
654
|
+
return dir.trim();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
function getDataDir() {
|
|
658
|
+
const exact = getExactDataRoot();
|
|
659
|
+
const effective = exact ? resolve(exact) : adoptResolverDataRoot(getResolverDataRoot()) ? resolve(getResolverDataRoot()) : getLegacyDataRoot();
|
|
660
|
+
const oldDir = join(getHomeDir(), ".economy");
|
|
661
|
+
if (existsSync(oldDir) && !existsSync(effective)) {
|
|
662
|
+
mkdirSync(effective, { recursive: true });
|
|
635
663
|
for (const file of readdirSync(oldDir)) {
|
|
636
664
|
const oldPath = join(oldDir, file);
|
|
637
665
|
if (statSync(oldPath).isFile()) {
|
|
638
|
-
copyFileSync(oldPath, join(
|
|
666
|
+
copyFileSync(oldPath, join(effective, file));
|
|
639
667
|
}
|
|
640
668
|
}
|
|
641
669
|
}
|
|
642
|
-
mkdirSync(
|
|
643
|
-
return
|
|
670
|
+
mkdirSync(effective, { recursive: true });
|
|
671
|
+
return effective;
|
|
644
672
|
}
|
|
645
673
|
function getDbPath() {
|
|
646
674
|
if (process.env["HASNA_ECONOMY_DB_PATH"])
|
|
@@ -2379,7 +2407,7 @@ var package_default;
|
|
|
2379
2407
|
var init_package = __esm(() => {
|
|
2380
2408
|
package_default = {
|
|
2381
2409
|
name: "@hasna/economy",
|
|
2382
|
-
version: "0.3.
|
|
2410
|
+
version: "0.3.28",
|
|
2383
2411
|
description: "AI coding cost tracker \u2014 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",
|
|
2384
2412
|
repository: {
|
|
2385
2413
|
type: "git",
|
|
@@ -2412,6 +2440,7 @@ var init_package = __esm(() => {
|
|
|
2412
2440
|
"dist",
|
|
2413
2441
|
"docs",
|
|
2414
2442
|
"dashboard/dist",
|
|
2443
|
+
"postinstall.js",
|
|
2415
2444
|
"LICENSE",
|
|
2416
2445
|
"README.md",
|
|
2417
2446
|
"CHANGELOG.md",
|
|
@@ -2434,7 +2463,7 @@ var init_package = __esm(() => {
|
|
|
2434
2463
|
"dev:mcp": "bun run src/mcp/index.ts",
|
|
2435
2464
|
"dev:serve": "bun run src/server/index.ts",
|
|
2436
2465
|
"dev:otel": "bun run src/otel/index.ts",
|
|
2437
|
-
postinstall: "
|
|
2466
|
+
postinstall: "node postinstall.js"
|
|
2438
2467
|
},
|
|
2439
2468
|
keywords: [
|
|
2440
2469
|
"economy",
|
|
@@ -2460,6 +2489,7 @@ var init_package = __esm(() => {
|
|
|
2460
2489
|
"@hasna/contracts": "0.13.4",
|
|
2461
2490
|
"@hasna/events": "^0.1.16",
|
|
2462
2491
|
"@hasna/mcp-harness": "^0.1.0",
|
|
2492
|
+
"@hasna/paths": "0.1.0",
|
|
2463
2493
|
"@hasna/projects": "1.0.0",
|
|
2464
2494
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
2465
2495
|
chalk: "^5.4.1",
|
|
@@ -4093,11 +4123,11 @@ var init_agents = __esm(() => {
|
|
|
4093
4123
|
// src/lib/paths.ts
|
|
4094
4124
|
import { homedir as homedir2 } from "os";
|
|
4095
4125
|
import { join as join4 } from "path";
|
|
4096
|
-
function
|
|
4126
|
+
function getHomeDir2() {
|
|
4097
4127
|
return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir2();
|
|
4098
4128
|
}
|
|
4099
4129
|
function agentPaths() {
|
|
4100
|
-
const home =
|
|
4130
|
+
const home = getHomeDir2();
|
|
4101
4131
|
return {
|
|
4102
4132
|
claudeProjects: join4(home, ".claude", "projects"),
|
|
4103
4133
|
claudeCredentials: join4(home, ".claude", ".credentials.json"),
|
|
@@ -5270,8 +5300,17 @@ import { existsSync as existsSync10 } from "fs";
|
|
|
5270
5300
|
import { homedir as homedir9 } from "os";
|
|
5271
5301
|
import { join as join12 } from "path";
|
|
5272
5302
|
import { Database as BunDatabase2 } from "bun:sqlite";
|
|
5303
|
+
import { dataDir as dataDir2 } from "@hasna/paths";
|
|
5304
|
+
function defaultLoopsDbPath() {
|
|
5305
|
+
const resolved = dataDir2({
|
|
5306
|
+
app: "loops",
|
|
5307
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
5308
|
+
});
|
|
5309
|
+
const legacy = join12(process.env["HOME"] || process.env["USERPROFILE"] || homedir9(), ".hasna", "loops", "loops.db");
|
|
5310
|
+
return existsSync10(join12(resolved, "loops.db")) ? join12(resolved, "loops.db") : legacy;
|
|
5311
|
+
}
|
|
5273
5312
|
function loopsDbPath() {
|
|
5274
|
-
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ??
|
|
5313
|
+
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
|
|
5275
5314
|
}
|
|
5276
5315
|
function loopsModel() {
|
|
5277
5316
|
return process.env["HASNA_ECONOMY_LOOPS_MODEL"] ?? process.env["ECONOMY_LOOPS_MODEL"] ?? "gpt-5.3-codex";
|
|
@@ -5447,11 +5486,10 @@ async function ingestLoops(db, verbose = false) {
|
|
|
5447
5486
|
rollupSession(db, sessionId);
|
|
5448
5487
|
return { sessions: sessions.size, requests };
|
|
5449
5488
|
}
|
|
5450
|
-
var
|
|
5489
|
+
var LOOPS_INGEST_VERSION = "goal-runs-orchestration-v1";
|
|
5451
5490
|
var init_loops = __esm(() => {
|
|
5452
5491
|
init_database();
|
|
5453
5492
|
init_pricing();
|
|
5454
|
-
DEFAULT_LOOPS_DB_PATH = join12(homedir9(), ".hasna", "loops", "loops.db");
|
|
5455
5493
|
});
|
|
5456
5494
|
|
|
5457
5495
|
// src/ingest/claude-quota.ts
|
|
@@ -8332,7 +8370,7 @@ __export(exports_serve, {
|
|
|
8332
8370
|
});
|
|
8333
8371
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8334
8372
|
import { existsSync as existsSync16 } from "fs";
|
|
8335
|
-
import { resolve, sep } from "path";
|
|
8373
|
+
import { resolve as resolve2, sep } from "path";
|
|
8336
8374
|
import { verifyApiKey, ApiKeyStore } from "@hasna/contracts/auth";
|
|
8337
8375
|
function openApiDocument() {
|
|
8338
8376
|
const spec = openApiSpec;
|
|
@@ -8401,8 +8439,8 @@ function dashboardPath(root, pathname) {
|
|
|
8401
8439
|
return null;
|
|
8402
8440
|
}
|
|
8403
8441
|
const relativePath = decoded === "/" ? "index.html" : decoded.replace(/^\/+/, "");
|
|
8404
|
-
const rootPath =
|
|
8405
|
-
const filePath =
|
|
8442
|
+
const rootPath = resolve2(root);
|
|
8443
|
+
const filePath = resolve2(rootPath, relativePath);
|
|
8406
8444
|
return filePath === rootPath || filePath.startsWith(rootPath + sep) ? filePath : null;
|
|
8407
8445
|
}
|
|
8408
8446
|
function createServerFetch(apiHandler, dashboardDir = DEFAULT_DASHBOARD_DIR) {
|
|
@@ -9351,7 +9389,7 @@ function clearActiveModel() {
|
|
|
9351
9389
|
init_database();
|
|
9352
9390
|
function registerBrainsCommand(program) {
|
|
9353
9391
|
const brainsCmd = program.command("brains").description("Fine-tune an AI model on your economy cost data");
|
|
9354
|
-
brainsCmd.command("gather").description("Gather training data from economy cost data and write to
|
|
9392
|
+
brainsCmd.command("gather").description("Gather training data from economy cost data and write to the economy data root (training/)").option("--limit <n>", "Maximum number of training examples", "500").option("--output <path>", "Output file path (default: <data-root>/training/training-<timestamp>.jsonl)").action(async (opts) => {
|
|
9355
9393
|
const limit = opts.limit ? parseInt(opts.limit, 10) : 500;
|
|
9356
9394
|
console.log(chalk.cyan(`Gathering up to ${limit} training examples from economy data...`));
|
|
9357
9395
|
try {
|
|
@@ -9377,7 +9415,7 @@ ${chalk.dim("Next step:")} economy brains train --base-model gpt-4o-mini`);
|
|
|
9377
9415
|
process.exit(1);
|
|
9378
9416
|
}
|
|
9379
9417
|
});
|
|
9380
|
-
brainsCmd.command("train").description("Start a fine-tuning job using gathered training data").option("--base-model <model>", "Base model to fine-tune", "gpt-4o-mini").option("--name <name>", "Name for the fine-tuned model", "economy-assistant").option("--dataset <path>", "Path to JSONL training file (default: latest in
|
|
9418
|
+
brainsCmd.command("train").description("Start a fine-tuning job using gathered training data").option("--base-model <model>", "Base model to fine-tune", "gpt-4o-mini").option("--name <name>", "Name for the fine-tuned model", "economy-assistant").option("--dataset <path>", "Path to JSONL training file (default: latest in <data-root>/training/)").action(async (opts) => {
|
|
9381
9419
|
const baseModel = opts.baseModel ?? "gpt-4o-mini";
|
|
9382
9420
|
const name = opts.name ?? "economy-assistant";
|
|
9383
9421
|
console.log(chalk.cyan("Starting fine-tuning job..."));
|
|
@@ -11840,8 +11878,8 @@ program.command("dashboard").description("Open the web dashboard (auto-starts se
|
|
|
11840
11878
|
if (!serverRunning) {
|
|
11841
11879
|
console.log(chalk7.cyan(`\u2192 Starting economy server on port ${port}...`));
|
|
11842
11880
|
const { spawn } = await import("child_process");
|
|
11843
|
-
const { resolve:
|
|
11844
|
-
const serveScript =
|
|
11881
|
+
const { resolve: resolve3, dirname: dirname4 } = await import("path");
|
|
11882
|
+
const serveScript = resolve3(dirname4(process.argv[1]), "..", "server", "index.js");
|
|
11845
11883
|
const child = spawn(process.execPath, [serveScript], {
|
|
11846
11884
|
detached: true,
|
|
11847
11885
|
stdio: "ignore",
|
package/dist/db/database.d.ts
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
import { SqliteAdapter as Database } from './sqlite-adapter.js';
|
|
2
2
|
import type { EconomyRequest, EconomySession, EconomyProject, CostCenter, CostCenterBreakdown, CostCenterKind, Budget, BudgetStatus, CostSummary, ZeroCostModelBreakdown, ModelBreakdown, ProjectBreakdown, AgentBreakdown, AccountBreakdown, Period, Agent, SessionFilter } from '../types/index.js';
|
|
3
3
|
export declare function getMachineId(): string;
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the user's home directory: $HOME, then $USERPROFILE (Windows), then
|
|
6
|
+
* the OS user database. A home that cannot be resolved is a hard error — never
|
|
7
|
+
* a literal "~" path (relative to cwd) and never an "undefined"-prefixed path.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getHomeDir(): string;
|
|
10
|
+
/**
|
|
11
|
+
* The @hasna/paths-resolved (XDG / macOS home layout) data root for economy.
|
|
12
|
+
* This is the forward-looking home the XDG migration (hotfixes plan
|
|
13
|
+
* `0f49f56a`, task P3.3) moves the store toward: `~/.local/share/hasna/economy`
|
|
14
|
+
* on Linux, `~/Library/Application Support/Hasna/economy` on macOS. The home
|
|
15
|
+
* override mirrors the pre-existing $HOME-first resolution so the resolver
|
|
16
|
+
* follows the same home the legacy path does.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getResolverDataRoot(): string;
|
|
19
|
+
/** The legacy (pre-XDG) data root: ~/.hasna/economy */
|
|
20
|
+
export declare function getLegacyDataRoot(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the resolver (XDG) data root should be adopted as the effective data
|
|
23
|
+
* root. The resolver root is adopted only when the operator has set
|
|
24
|
+
* `HASNA_DATA_HOME` (the data-kind override — a deliberate opt-in to the XDG
|
|
25
|
+
* layout) or the store has already been physically migrated there
|
|
26
|
+
* (`economy.db` exists). A machine that only redirects another kind (e.g.
|
|
27
|
+
* cache to tmpfs) must NOT have its data home moved, and a live store at the
|
|
28
|
+
* legacy home must never become invisible on upgrade.
|
|
29
|
+
*/
|
|
30
|
+
export declare function adoptResolverDataRoot(resolved: string, env?: Record<string, string | undefined>): boolean;
|
|
31
|
+
/** The exact-app override root, when set: `HASNA_ECONOMY_HOME`, then `ECONOMY_HOME`. */
|
|
32
|
+
export declare function getExactDataRoot(): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The effective data root for economy: an exact-app override
|
|
35
|
+
* (`HASNA_ECONOMY_HOME`, then `ECONOMY_HOME`) wins unconditionally; otherwise
|
|
36
|
+
* the resolver (XDG) data root once adopted; otherwise the legacy
|
|
37
|
+
* `~/.hasna/economy` default — an existing store never becomes invisible on
|
|
38
|
+
* upgrade. The store path (`HASNA_ECONOMY_DB_PATH` / `ECONOMY_DB`) is layered
|
|
39
|
+
* on top of this by `getDbPath`, so an explicit store path always wins
|
|
40
|
+
* regardless. The legacy `~/.economy` files are auto-migrated into the
|
|
41
|
+
* effective root, preserving the historical behavior.
|
|
42
|
+
*/
|
|
4
43
|
export declare function getDataDir(): string;
|
|
5
44
|
export declare function getDbPath(): string;
|
|
6
45
|
export declare function openDatabase(dbPath?: string, skipSeed?: boolean): Database;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -593,7 +593,8 @@ import { execFileSync } from "child_process";
|
|
|
593
593
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
594
594
|
import { hostname, platform } from "os";
|
|
595
595
|
import { homedir } from "os";
|
|
596
|
-
import { join } from "path";
|
|
596
|
+
import { join, resolve } from "path";
|
|
597
|
+
import { dataDir } from "@hasna/paths";
|
|
597
598
|
function normalizeMachineId(value) {
|
|
598
599
|
const id = value?.trim().toLowerCase().split(".")[0];
|
|
599
600
|
return id && id.length > 0 ? id : null;
|
|
@@ -624,21 +625,48 @@ function getMachineId() {
|
|
|
624
625
|
return macHostMachineId() ?? hostMachine;
|
|
625
626
|
return hostMachine;
|
|
626
627
|
}
|
|
627
|
-
function
|
|
628
|
+
function getHomeDir() {
|
|
628
629
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
630
|
+
if (!home)
|
|
631
|
+
throw new Error("Could not resolve the user home directory");
|
|
632
|
+
return home;
|
|
633
|
+
}
|
|
634
|
+
function getResolverDataRoot() {
|
|
635
|
+
return dataDir({
|
|
636
|
+
app: "economy",
|
|
637
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
function getLegacyDataRoot() {
|
|
641
|
+
return join(getHomeDir(), ".hasna", "economy");
|
|
642
|
+
}
|
|
643
|
+
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
644
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
645
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
646
|
+
return true;
|
|
647
|
+
return existsSync(join(resolved, "economy.db"));
|
|
648
|
+
}
|
|
649
|
+
function getExactDataRoot() {
|
|
650
|
+
const dir = process.env["HASNA_ECONOMY_HOME"] ?? process.env["ECONOMY_HOME"];
|
|
651
|
+
if (dir && dir.trim())
|
|
652
|
+
return dir.trim();
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
function getDataDir() {
|
|
656
|
+
const exact = getExactDataRoot();
|
|
657
|
+
const effective = exact ? resolve(exact) : adoptResolverDataRoot(getResolverDataRoot()) ? resolve(getResolverDataRoot()) : getLegacyDataRoot();
|
|
658
|
+
const oldDir = join(getHomeDir(), ".economy");
|
|
659
|
+
if (existsSync(oldDir) && !existsSync(effective)) {
|
|
660
|
+
mkdirSync(effective, { recursive: true });
|
|
633
661
|
for (const file of readdirSync(oldDir)) {
|
|
634
662
|
const oldPath = join(oldDir, file);
|
|
635
663
|
if (statSync(oldPath).isFile()) {
|
|
636
|
-
copyFileSync(oldPath, join(
|
|
664
|
+
copyFileSync(oldPath, join(effective, file));
|
|
637
665
|
}
|
|
638
666
|
}
|
|
639
667
|
}
|
|
640
|
-
mkdirSync(
|
|
641
|
-
return
|
|
668
|
+
mkdirSync(effective, { recursive: true });
|
|
669
|
+
return effective;
|
|
642
670
|
}
|
|
643
671
|
function getDbPath() {
|
|
644
672
|
if (process.env["HASNA_ECONOMY_DB_PATH"])
|
|
@@ -1993,7 +2021,7 @@ import { randomUUID } from "crypto";
|
|
|
1993
2021
|
// package.json
|
|
1994
2022
|
var package_default = {
|
|
1995
2023
|
name: "@hasna/economy",
|
|
1996
|
-
version: "0.3.
|
|
2024
|
+
version: "0.3.28",
|
|
1997
2025
|
description: "AI coding cost tracker \u2014 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",
|
|
1998
2026
|
repository: {
|
|
1999
2027
|
type: "git",
|
|
@@ -2026,6 +2054,7 @@ var package_default = {
|
|
|
2026
2054
|
"dist",
|
|
2027
2055
|
"docs",
|
|
2028
2056
|
"dashboard/dist",
|
|
2057
|
+
"postinstall.js",
|
|
2029
2058
|
"LICENSE",
|
|
2030
2059
|
"README.md",
|
|
2031
2060
|
"CHANGELOG.md",
|
|
@@ -2048,7 +2077,7 @@ var package_default = {
|
|
|
2048
2077
|
"dev:mcp": "bun run src/mcp/index.ts",
|
|
2049
2078
|
"dev:serve": "bun run src/server/index.ts",
|
|
2050
2079
|
"dev:otel": "bun run src/otel/index.ts",
|
|
2051
|
-
postinstall: "
|
|
2080
|
+
postinstall: "node postinstall.js"
|
|
2052
2081
|
},
|
|
2053
2082
|
keywords: [
|
|
2054
2083
|
"economy",
|
|
@@ -2074,6 +2103,7 @@ var package_default = {
|
|
|
2074
2103
|
"@hasna/contracts": "0.13.4",
|
|
2075
2104
|
"@hasna/events": "^0.1.16",
|
|
2076
2105
|
"@hasna/mcp-harness": "^0.1.0",
|
|
2106
|
+
"@hasna/paths": "0.1.0",
|
|
2077
2107
|
"@hasna/projects": "1.0.0",
|
|
2078
2108
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
2079
2109
|
chalk: "^5.4.1",
|
package/dist/ingest/loops.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { SqliteAdapter as Database } from '../db/sqlite-adapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* The default OpenLoops store path, resolved through the @hasna/paths resolver
|
|
4
|
+
* (XDG / macOS home layout): the resolver data root for `loops` once its store
|
|
5
|
+
* has been migrated there (`loops.db` present), otherwise the legacy
|
|
6
|
+
* `~/.hasna/loops/loops.db` — a legacy-read fallback, so economy keeps reading
|
|
7
|
+
* loops' store whichever side of the XDG migration loops is on. The exact-app
|
|
8
|
+
* override `HASNA_ECONOMY_LOOPS_DB_PATH` still wins unconditionally.
|
|
9
|
+
*/
|
|
10
|
+
export declare function defaultLoopsDbPath(): string;
|
|
11
|
+
export declare function loopsDbPath(): string;
|
|
2
12
|
export declare function ingestLoops(db: Database, verbose?: boolean): Promise<{
|
|
3
13
|
sessions: number;
|
|
4
14
|
requests: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loops.d.ts","sourceRoot":"","sources":["../../src/ingest/loops.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loops.d.ts","sourceRoot":"","sources":["../../src/ingest/loops.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAiCxE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAc3C;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAwGD,wBAAsB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA+FhH"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const DEFAULT_MODEL = "gpt-4o-mini";
|
|
2
2
|
/** Returns the active fine-tuned model ID, or DEFAULT_MODEL if none set. */
|
|
3
3
|
export declare function getActiveModel(): string;
|
|
4
|
-
/** Persists the active fine-tuned model ID to
|
|
4
|
+
/** Persists the active fine-tuned model ID to the economy data root (config.json). */
|
|
5
5
|
export declare function setActiveModel(id: string): void;
|
|
6
6
|
/** Clears the active model, falling back to DEFAULT_MODEL. */
|
|
7
7
|
export declare function clearActiveModel(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-config.d.ts","sourceRoot":"","sources":["../../src/lib/model-config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"model-config.d.ts","sourceRoot":"","sources":["../../src/lib/model-config.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,aAAa,gBAAgB,CAAA;AA8B1C,4EAA4E;AAC5E,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAI/C;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -595,7 +595,8 @@ import { execFileSync } from "child_process";
|
|
|
595
595
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
596
596
|
import { hostname, platform } from "os";
|
|
597
597
|
import { homedir } from "os";
|
|
598
|
-
import { join } from "path";
|
|
598
|
+
import { join, resolve } from "path";
|
|
599
|
+
import { dataDir } from "@hasna/paths";
|
|
599
600
|
function normalizeMachineId(value) {
|
|
600
601
|
const id = value?.trim().toLowerCase().split(".")[0];
|
|
601
602
|
return id && id.length > 0 ? id : null;
|
|
@@ -626,21 +627,48 @@ function getMachineId() {
|
|
|
626
627
|
return macHostMachineId() ?? hostMachine;
|
|
627
628
|
return hostMachine;
|
|
628
629
|
}
|
|
629
|
-
function
|
|
630
|
+
function getHomeDir() {
|
|
630
631
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
632
|
+
if (!home)
|
|
633
|
+
throw new Error("Could not resolve the user home directory");
|
|
634
|
+
return home;
|
|
635
|
+
}
|
|
636
|
+
function getResolverDataRoot() {
|
|
637
|
+
return dataDir({
|
|
638
|
+
app: "economy",
|
|
639
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
function getLegacyDataRoot() {
|
|
643
|
+
return join(getHomeDir(), ".hasna", "economy");
|
|
644
|
+
}
|
|
645
|
+
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
646
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
647
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
648
|
+
return true;
|
|
649
|
+
return existsSync(join(resolved, "economy.db"));
|
|
650
|
+
}
|
|
651
|
+
function getExactDataRoot() {
|
|
652
|
+
const dir = process.env["HASNA_ECONOMY_HOME"] ?? process.env["ECONOMY_HOME"];
|
|
653
|
+
if (dir && dir.trim())
|
|
654
|
+
return dir.trim();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
function getDataDir() {
|
|
658
|
+
const exact = getExactDataRoot();
|
|
659
|
+
const effective = exact ? resolve(exact) : adoptResolverDataRoot(getResolverDataRoot()) ? resolve(getResolverDataRoot()) : getLegacyDataRoot();
|
|
660
|
+
const oldDir = join(getHomeDir(), ".economy");
|
|
661
|
+
if (existsSync(oldDir) && !existsSync(effective)) {
|
|
662
|
+
mkdirSync(effective, { recursive: true });
|
|
635
663
|
for (const file of readdirSync(oldDir)) {
|
|
636
664
|
const oldPath = join(oldDir, file);
|
|
637
665
|
if (statSync(oldPath).isFile()) {
|
|
638
|
-
copyFileSync(oldPath, join(
|
|
666
|
+
copyFileSync(oldPath, join(effective, file));
|
|
639
667
|
}
|
|
640
668
|
}
|
|
641
669
|
}
|
|
642
|
-
mkdirSync(
|
|
643
|
-
return
|
|
670
|
+
mkdirSync(effective, { recursive: true });
|
|
671
|
+
return effective;
|
|
644
672
|
}
|
|
645
673
|
function getDbPath() {
|
|
646
674
|
if (process.env["HASNA_ECONOMY_DB_PATH"])
|
|
@@ -2085,7 +2113,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
2085
2113
|
// package.json
|
|
2086
2114
|
var package_default = {
|
|
2087
2115
|
name: "@hasna/economy",
|
|
2088
|
-
version: "0.3.
|
|
2116
|
+
version: "0.3.28",
|
|
2089
2117
|
description: "AI coding cost tracker \u2014 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",
|
|
2090
2118
|
repository: {
|
|
2091
2119
|
type: "git",
|
|
@@ -2118,6 +2146,7 @@ var package_default = {
|
|
|
2118
2146
|
"dist",
|
|
2119
2147
|
"docs",
|
|
2120
2148
|
"dashboard/dist",
|
|
2149
|
+
"postinstall.js",
|
|
2121
2150
|
"LICENSE",
|
|
2122
2151
|
"README.md",
|
|
2123
2152
|
"CHANGELOG.md",
|
|
@@ -2140,7 +2169,7 @@ var package_default = {
|
|
|
2140
2169
|
"dev:mcp": "bun run src/mcp/index.ts",
|
|
2141
2170
|
"dev:serve": "bun run src/server/index.ts",
|
|
2142
2171
|
"dev:otel": "bun run src/otel/index.ts",
|
|
2143
|
-
postinstall: "
|
|
2172
|
+
postinstall: "node postinstall.js"
|
|
2144
2173
|
},
|
|
2145
2174
|
keywords: [
|
|
2146
2175
|
"economy",
|
|
@@ -2166,6 +2195,7 @@ var package_default = {
|
|
|
2166
2195
|
"@hasna/contracts": "0.13.4",
|
|
2167
2196
|
"@hasna/events": "^0.1.16",
|
|
2168
2197
|
"@hasna/mcp-harness": "^0.1.0",
|
|
2198
|
+
"@hasna/paths": "0.1.0",
|
|
2169
2199
|
"@hasna/projects": "1.0.0",
|
|
2170
2200
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
2171
2201
|
chalk: "^5.4.1",
|
|
@@ -3486,10 +3516,18 @@ import { existsSync as existsSync8 } from "fs";
|
|
|
3486
3516
|
import { homedir as homedir8 } from "os";
|
|
3487
3517
|
import { join as join8 } from "path";
|
|
3488
3518
|
import { Database as BunDatabase2 } from "bun:sqlite";
|
|
3489
|
-
|
|
3519
|
+
import { dataDir as dataDir2 } from "@hasna/paths";
|
|
3490
3520
|
var LOOPS_INGEST_VERSION = "goal-runs-orchestration-v1";
|
|
3521
|
+
function defaultLoopsDbPath() {
|
|
3522
|
+
const resolved = dataDir2({
|
|
3523
|
+
app: "loops",
|
|
3524
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
3525
|
+
});
|
|
3526
|
+
const legacy = join8(process.env["HOME"] || process.env["USERPROFILE"] || homedir8(), ".hasna", "loops", "loops.db");
|
|
3527
|
+
return existsSync8(join8(resolved, "loops.db")) ? join8(resolved, "loops.db") : legacy;
|
|
3528
|
+
}
|
|
3491
3529
|
function loopsDbPath() {
|
|
3492
|
-
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ??
|
|
3530
|
+
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
|
|
3493
3531
|
}
|
|
3494
3532
|
function loopsModel() {
|
|
3495
3533
|
return process.env["HASNA_ECONOMY_LOOPS_MODEL"] ?? process.env["ECONOMY_LOOPS_MODEL"] ?? "gpt-5.3-codex";
|
|
@@ -3673,11 +3711,11 @@ import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
|
3673
3711
|
// src/lib/paths.ts
|
|
3674
3712
|
import { homedir as homedir9 } from "os";
|
|
3675
3713
|
import { join as join9 } from "path";
|
|
3676
|
-
function
|
|
3714
|
+
function getHomeDir2() {
|
|
3677
3715
|
return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir9();
|
|
3678
3716
|
}
|
|
3679
3717
|
function agentPaths() {
|
|
3680
|
-
const home =
|
|
3718
|
+
const home = getHomeDir2();
|
|
3681
3719
|
return {
|
|
3682
3720
|
claudeProjects: join9(home, ".claude", "projects"),
|
|
3683
3721
|
claudeCredentials: join9(home, ".claude", ".credentials.json"),
|
package/dist/otel/index.js
CHANGED
|
@@ -594,7 +594,8 @@ import { execFileSync } from "child_process";
|
|
|
594
594
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
595
595
|
import { hostname, platform } from "os";
|
|
596
596
|
import { homedir } from "os";
|
|
597
|
-
import { join } from "path";
|
|
597
|
+
import { join, resolve } from "path";
|
|
598
|
+
import { dataDir } from "@hasna/paths";
|
|
598
599
|
function normalizeMachineId(value) {
|
|
599
600
|
const id = value?.trim().toLowerCase().split(".")[0];
|
|
600
601
|
return id && id.length > 0 ? id : null;
|
|
@@ -625,21 +626,48 @@ function getMachineId() {
|
|
|
625
626
|
return macHostMachineId() ?? hostMachine;
|
|
626
627
|
return hostMachine;
|
|
627
628
|
}
|
|
628
|
-
function
|
|
629
|
+
function getHomeDir() {
|
|
629
630
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
631
|
+
if (!home)
|
|
632
|
+
throw new Error("Could not resolve the user home directory");
|
|
633
|
+
return home;
|
|
634
|
+
}
|
|
635
|
+
function getResolverDataRoot() {
|
|
636
|
+
return dataDir({
|
|
637
|
+
app: "economy",
|
|
638
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
function getLegacyDataRoot() {
|
|
642
|
+
return join(getHomeDir(), ".hasna", "economy");
|
|
643
|
+
}
|
|
644
|
+
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
645
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
646
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
647
|
+
return true;
|
|
648
|
+
return existsSync(join(resolved, "economy.db"));
|
|
649
|
+
}
|
|
650
|
+
function getExactDataRoot() {
|
|
651
|
+
const dir = process.env["HASNA_ECONOMY_HOME"] ?? process.env["ECONOMY_HOME"];
|
|
652
|
+
if (dir && dir.trim())
|
|
653
|
+
return dir.trim();
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
function getDataDir() {
|
|
657
|
+
const exact = getExactDataRoot();
|
|
658
|
+
const effective = exact ? resolve(exact) : adoptResolverDataRoot(getResolverDataRoot()) ? resolve(getResolverDataRoot()) : getLegacyDataRoot();
|
|
659
|
+
const oldDir = join(getHomeDir(), ".economy");
|
|
660
|
+
if (existsSync(oldDir) && !existsSync(effective)) {
|
|
661
|
+
mkdirSync(effective, { recursive: true });
|
|
634
662
|
for (const file of readdirSync(oldDir)) {
|
|
635
663
|
const oldPath = join(oldDir, file);
|
|
636
664
|
if (statSync(oldPath).isFile()) {
|
|
637
|
-
copyFileSync(oldPath, join(
|
|
665
|
+
copyFileSync(oldPath, join(effective, file));
|
|
638
666
|
}
|
|
639
667
|
}
|
|
640
668
|
}
|
|
641
|
-
mkdirSync(
|
|
642
|
-
return
|
|
669
|
+
mkdirSync(effective, { recursive: true });
|
|
670
|
+
return effective;
|
|
643
671
|
}
|
|
644
672
|
function getDbPath() {
|
|
645
673
|
if (process.env["HASNA_ECONOMY_DB_PATH"])
|
|
@@ -1388,7 +1416,7 @@ async function ingestOtelRows(db, rows) {
|
|
|
1388
1416
|
// package.json
|
|
1389
1417
|
var package_default = {
|
|
1390
1418
|
name: "@hasna/economy",
|
|
1391
|
-
version: "0.3.
|
|
1419
|
+
version: "0.3.28",
|
|
1392
1420
|
description: "AI coding cost tracker \u2014 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",
|
|
1393
1421
|
repository: {
|
|
1394
1422
|
type: "git",
|
|
@@ -1421,6 +1449,7 @@ var package_default = {
|
|
|
1421
1449
|
"dist",
|
|
1422
1450
|
"docs",
|
|
1423
1451
|
"dashboard/dist",
|
|
1452
|
+
"postinstall.js",
|
|
1424
1453
|
"LICENSE",
|
|
1425
1454
|
"README.md",
|
|
1426
1455
|
"CHANGELOG.md",
|
|
@@ -1443,7 +1472,7 @@ var package_default = {
|
|
|
1443
1472
|
"dev:mcp": "bun run src/mcp/index.ts",
|
|
1444
1473
|
"dev:serve": "bun run src/server/index.ts",
|
|
1445
1474
|
"dev:otel": "bun run src/otel/index.ts",
|
|
1446
|
-
postinstall: "
|
|
1475
|
+
postinstall: "node postinstall.js"
|
|
1447
1476
|
},
|
|
1448
1477
|
keywords: [
|
|
1449
1478
|
"economy",
|
|
@@ -1469,6 +1498,7 @@ var package_default = {
|
|
|
1469
1498
|
"@hasna/contracts": "0.13.4",
|
|
1470
1499
|
"@hasna/events": "^0.1.16",
|
|
1471
1500
|
"@hasna/mcp-harness": "^0.1.0",
|
|
1501
|
+
"@hasna/paths": "0.1.0",
|
|
1472
1502
|
"@hasna/projects": "1.0.0",
|
|
1473
1503
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
1474
1504
|
chalk: "^5.4.1",
|
package/dist/server/index.js
CHANGED
|
@@ -595,7 +595,8 @@ import { execFileSync } from "child_process";
|
|
|
595
595
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
596
596
|
import { hostname, platform } from "os";
|
|
597
597
|
import { homedir } from "os";
|
|
598
|
-
import { join } from "path";
|
|
598
|
+
import { join, resolve } from "path";
|
|
599
|
+
import { dataDir } from "@hasna/paths";
|
|
599
600
|
function normalizeMachineId(value) {
|
|
600
601
|
const id = value?.trim().toLowerCase().split(".")[0];
|
|
601
602
|
return id && id.length > 0 ? id : null;
|
|
@@ -626,21 +627,48 @@ function getMachineId() {
|
|
|
626
627
|
return macHostMachineId() ?? hostMachine;
|
|
627
628
|
return hostMachine;
|
|
628
629
|
}
|
|
629
|
-
function
|
|
630
|
+
function getHomeDir() {
|
|
630
631
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
632
|
+
if (!home)
|
|
633
|
+
throw new Error("Could not resolve the user home directory");
|
|
634
|
+
return home;
|
|
635
|
+
}
|
|
636
|
+
function getResolverDataRoot() {
|
|
637
|
+
return dataDir({
|
|
638
|
+
app: "economy",
|
|
639
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
function getLegacyDataRoot() {
|
|
643
|
+
return join(getHomeDir(), ".hasna", "economy");
|
|
644
|
+
}
|
|
645
|
+
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
646
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
647
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
648
|
+
return true;
|
|
649
|
+
return existsSync(join(resolved, "economy.db"));
|
|
650
|
+
}
|
|
651
|
+
function getExactDataRoot() {
|
|
652
|
+
const dir = process.env["HASNA_ECONOMY_HOME"] ?? process.env["ECONOMY_HOME"];
|
|
653
|
+
if (dir && dir.trim())
|
|
654
|
+
return dir.trim();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
function getDataDir() {
|
|
658
|
+
const exact = getExactDataRoot();
|
|
659
|
+
const effective = exact ? resolve(exact) : adoptResolverDataRoot(getResolverDataRoot()) ? resolve(getResolverDataRoot()) : getLegacyDataRoot();
|
|
660
|
+
const oldDir = join(getHomeDir(), ".economy");
|
|
661
|
+
if (existsSync(oldDir) && !existsSync(effective)) {
|
|
662
|
+
mkdirSync(effective, { recursive: true });
|
|
635
663
|
for (const file of readdirSync(oldDir)) {
|
|
636
664
|
const oldPath = join(oldDir, file);
|
|
637
665
|
if (statSync(oldPath).isFile()) {
|
|
638
|
-
copyFileSync(oldPath, join(
|
|
666
|
+
copyFileSync(oldPath, join(effective, file));
|
|
639
667
|
}
|
|
640
668
|
}
|
|
641
669
|
}
|
|
642
|
-
mkdirSync(
|
|
643
|
-
return
|
|
670
|
+
mkdirSync(effective, { recursive: true });
|
|
671
|
+
return effective;
|
|
644
672
|
}
|
|
645
673
|
function getDbPath() {
|
|
646
674
|
if (process.env["HASNA_ECONOMY_DB_PATH"])
|
|
@@ -4599,10 +4627,18 @@ import { existsSync as existsSync8 } from "fs";
|
|
|
4599
4627
|
import { homedir as homedir8 } from "os";
|
|
4600
4628
|
import { join as join8 } from "path";
|
|
4601
4629
|
import { Database as BunDatabase2 } from "bun:sqlite";
|
|
4602
|
-
|
|
4630
|
+
import { dataDir as dataDir2 } from "@hasna/paths";
|
|
4603
4631
|
var LOOPS_INGEST_VERSION = "goal-runs-orchestration-v1";
|
|
4632
|
+
function defaultLoopsDbPath() {
|
|
4633
|
+
const resolved = dataDir2({
|
|
4634
|
+
app: "loops",
|
|
4635
|
+
home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
|
|
4636
|
+
});
|
|
4637
|
+
const legacy = join8(process.env["HOME"] || process.env["USERPROFILE"] || homedir8(), ".hasna", "loops", "loops.db");
|
|
4638
|
+
return existsSync8(join8(resolved, "loops.db")) ? join8(resolved, "loops.db") : legacy;
|
|
4639
|
+
}
|
|
4604
4640
|
function loopsDbPath() {
|
|
4605
|
-
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ??
|
|
4641
|
+
return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
|
|
4606
4642
|
}
|
|
4607
4643
|
function loopsModel() {
|
|
4608
4644
|
return process.env["HASNA_ECONOMY_LOOPS_MODEL"] ?? process.env["ECONOMY_LOOPS_MODEL"] ?? "gpt-5.3-codex";
|
|
@@ -4786,11 +4822,11 @@ import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
|
4786
4822
|
// src/lib/paths.ts
|
|
4787
4823
|
import { homedir as homedir9 } from "os";
|
|
4788
4824
|
import { join as join9 } from "path";
|
|
4789
|
-
function
|
|
4825
|
+
function getHomeDir2() {
|
|
4790
4826
|
return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir9();
|
|
4791
4827
|
}
|
|
4792
4828
|
function agentPaths() {
|
|
4793
|
-
const home =
|
|
4829
|
+
const home = getHomeDir2();
|
|
4794
4830
|
return {
|
|
4795
4831
|
claudeProjects: join9(home, ".claude", "projects"),
|
|
4796
4832
|
claudeCredentials: join9(home, ".claude", ".credentials.json"),
|
|
@@ -5975,11 +6011,11 @@ function isAuthorizedRequest(req, path) {
|
|
|
5975
6011
|
// src/server/serve.ts
|
|
5976
6012
|
import { randomUUID } from "crypto";
|
|
5977
6013
|
import { existsSync as existsSync13 } from "fs";
|
|
5978
|
-
import { resolve, sep } from "path";
|
|
6014
|
+
import { resolve as resolve2, sep } from "path";
|
|
5979
6015
|
// package.json
|
|
5980
6016
|
var package_default = {
|
|
5981
6017
|
name: "@hasna/economy",
|
|
5982
|
-
version: "0.3.
|
|
6018
|
+
version: "0.3.28",
|
|
5983
6019
|
description: "AI coding cost tracker \u2014 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",
|
|
5984
6020
|
repository: {
|
|
5985
6021
|
type: "git",
|
|
@@ -6012,6 +6048,7 @@ var package_default = {
|
|
|
6012
6048
|
"dist",
|
|
6013
6049
|
"docs",
|
|
6014
6050
|
"dashboard/dist",
|
|
6051
|
+
"postinstall.js",
|
|
6015
6052
|
"LICENSE",
|
|
6016
6053
|
"README.md",
|
|
6017
6054
|
"CHANGELOG.md",
|
|
@@ -6034,7 +6071,7 @@ var package_default = {
|
|
|
6034
6071
|
"dev:mcp": "bun run src/mcp/index.ts",
|
|
6035
6072
|
"dev:serve": "bun run src/server/index.ts",
|
|
6036
6073
|
"dev:otel": "bun run src/otel/index.ts",
|
|
6037
|
-
postinstall: "
|
|
6074
|
+
postinstall: "node postinstall.js"
|
|
6038
6075
|
},
|
|
6039
6076
|
keywords: [
|
|
6040
6077
|
"economy",
|
|
@@ -6060,6 +6097,7 @@ var package_default = {
|
|
|
6060
6097
|
"@hasna/contracts": "0.13.4",
|
|
6061
6098
|
"@hasna/events": "^0.1.16",
|
|
6062
6099
|
"@hasna/mcp-harness": "^0.1.0",
|
|
6100
|
+
"@hasna/paths": "0.1.0",
|
|
6063
6101
|
"@hasna/projects": "1.0.0",
|
|
6064
6102
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
6065
6103
|
chalk: "^5.4.1",
|
|
@@ -7807,8 +7845,8 @@ function dashboardPath(root, pathname) {
|
|
|
7807
7845
|
return null;
|
|
7808
7846
|
}
|
|
7809
7847
|
const relativePath = decoded === "/" ? "index.html" : decoded.replace(/^\/+/, "");
|
|
7810
|
-
const rootPath =
|
|
7811
|
-
const filePath =
|
|
7848
|
+
const rootPath = resolve2(root);
|
|
7849
|
+
const filePath = resolve2(rootPath, relativePath);
|
|
7812
7850
|
return filePath === rootPath || filePath.startsWith(rootPath + sep) ? filePath : null;
|
|
7813
7851
|
}
|
|
7814
7852
|
var FOUNDATION_PATHS = new Set(["/health", "/healthz", "/ready", "/readyz", "/version", "/openapi.json"]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/economy",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.28",
|
|
4
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",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"dist",
|
|
34
34
|
"docs",
|
|
35
35
|
"dashboard/dist",
|
|
36
|
+
"postinstall.js",
|
|
36
37
|
"LICENSE",
|
|
37
38
|
"README.md",
|
|
38
39
|
"CHANGELOG.md",
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
"dev:mcp": "bun run src/mcp/index.ts",
|
|
56
57
|
"dev:serve": "bun run src/server/index.ts",
|
|
57
58
|
"dev:otel": "bun run src/otel/index.ts",
|
|
58
|
-
"postinstall": "
|
|
59
|
+
"postinstall": "node postinstall.js"
|
|
59
60
|
},
|
|
60
61
|
"keywords": [
|
|
61
62
|
"economy",
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
"@hasna/contracts": "0.13.4",
|
|
82
83
|
"@hasna/events": "^0.1.16",
|
|
83
84
|
"@hasna/mcp-harness": "^0.1.0",
|
|
85
|
+
"@hasna/paths": "0.1.0",
|
|
84
86
|
"@hasna/projects": "1.0.0",
|
|
85
87
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
86
88
|
"chalk": "^5.4.1",
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Best-effort install-time creation of the economy data home, resolving the
|
|
2
|
+
// SAME effective data root the runtime uses (src/db/database.ts getDataDir):
|
|
3
|
+
// an exact-app override (HASNA_ECONOMY_HOME / ECONOMY_HOME) wins; otherwise
|
|
4
|
+
// the @hasna/paths XDG data home once adopted (HASNA_DATA_HOME set, or
|
|
5
|
+
// economy.db already migrated there); otherwise the legacy
|
|
6
|
+
// ~/.hasna/economy default. Failures are non-fatal: the runtime getDataDir()
|
|
7
|
+
// creates the same directories on first use.
|
|
8
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
const EXACT_OVERRIDE = (process.env["HASNA_ECONOMY_HOME"] || process.env["ECONOMY_HOME"] || "").trim();
|
|
13
|
+
const DATA_HOME_OVERRIDE = (process.env["HASNA_DATA_HOME"] || "").trim();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const { dataDir } = await import("@hasna/paths");
|
|
17
|
+
const resolved = dataDir({ app: "economy" });
|
|
18
|
+
let root;
|
|
19
|
+
if (EXACT_OVERRIDE) {
|
|
20
|
+
root = EXACT_OVERRIDE;
|
|
21
|
+
} else if (DATA_HOME_OVERRIDE || existsSync(join(resolved, "economy.db"))) {
|
|
22
|
+
root = resolved;
|
|
23
|
+
} else {
|
|
24
|
+
root = join(homedir(), ".hasna", "economy");
|
|
25
|
+
}
|
|
26
|
+
for (const dir of [root, join(root, "training")]) {
|
|
27
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// never fail an install over pre-created directories
|
|
31
|
+
}
|