@hasna/economy 0.3.26 → 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.
@@ -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 getDataDir() {
629
+ function getHomeDir() {
629
630
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
630
- const newDir = join(home, ".hasna", "economy");
631
- const oldDir = join(home, ".economy");
632
- if (existsSync(oldDir) && !existsSync(newDir)) {
633
- mkdirSync(newDir, { recursive: true });
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(newDir, file));
665
+ copyFileSync(oldPath, join(effective, file));
638
666
  }
639
667
  }
640
668
  }
641
- mkdirSync(newDir, { recursive: true });
642
- return newDir;
669
+ mkdirSync(effective, { recursive: true });
670
+ return effective;
643
671
  }
644
672
  function getDbPath() {
645
673
  if (process.env["HASNA_ECONOMY_DB_PATH"])
@@ -1385,18 +1413,118 @@ async function ingestOtelRows(db, rows) {
1385
1413
  rollupSession(db, sessionId);
1386
1414
  return { requests, sessions: sessions.size };
1387
1415
  }
1416
+ // package.json
1417
+ var package_default = {
1418
+ name: "@hasna/economy",
1419
+ version: "0.3.28",
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",
1421
+ repository: {
1422
+ type: "git",
1423
+ url: "git+https://github.com/hasna/economy.git"
1424
+ },
1425
+ bugs: {
1426
+ url: "https://github.com/hasna/economy/issues"
1427
+ },
1428
+ homepage: "https://github.com/hasna/economy#readme",
1429
+ type: "module",
1430
+ main: "dist/index.js",
1431
+ types: "dist/index.d.ts",
1432
+ bin: {
1433
+ economy: "dist/cli/index.js",
1434
+ "economy-mcp": "dist/mcp/index.js",
1435
+ "economy-serve": "dist/server/index.js",
1436
+ "economy-otel": "dist/otel/index.js"
1437
+ },
1438
+ exports: {
1439
+ ".": {
1440
+ types: "./dist/index.d.ts",
1441
+ import: "./dist/index.js"
1442
+ },
1443
+ "./sdk": {
1444
+ types: "./dist/index.d.ts",
1445
+ import: "./dist/index.js"
1446
+ }
1447
+ },
1448
+ files: [
1449
+ "dist",
1450
+ "docs",
1451
+ "dashboard/dist",
1452
+ "postinstall.js",
1453
+ "LICENSE",
1454
+ "README.md",
1455
+ "CHANGELOG.md",
1456
+ "SECURITY.md",
1457
+ "CONTRIBUTING.md",
1458
+ "CODE_OF_CONDUCT.md"
1459
+ ],
1460
+ scripts: {
1461
+ 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",
1462
+ "build:cli": "bun build src/cli/index.ts --outdir dist/cli --target bun --packages external",
1463
+ "build:mcp": "bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external",
1464
+ "build:server": "bun build src/server/index.ts --outdir dist/server --target bun --packages external",
1465
+ "build:lib": "bun build src/index.ts --outdir dist --target bun --packages external",
1466
+ "build:dashboard": "cd dashboard && bun run build",
1467
+ "artifact-scan": "bun scripts/contracts-cli.mjs artifact-scan dist",
1468
+ prepack: "bun run build && bun run artifact-scan",
1469
+ typecheck: "tsc --noEmit",
1470
+ test: "bun test",
1471
+ "dev:cli": "bun run src/cli/index.ts",
1472
+ "dev:mcp": "bun run src/mcp/index.ts",
1473
+ "dev:serve": "bun run src/server/index.ts",
1474
+ "dev:otel": "bun run src/otel/index.ts",
1475
+ postinstall: "node postinstall.js"
1476
+ },
1477
+ keywords: [
1478
+ "economy",
1479
+ "cost",
1480
+ "ai",
1481
+ "claude",
1482
+ "codex",
1483
+ "gemini",
1484
+ "mcp",
1485
+ "cli",
1486
+ "budget",
1487
+ "tracking"
1488
+ ],
1489
+ author: "hasna",
1490
+ license: "Apache-2.0",
1491
+ publishConfig: {
1492
+ registry: "https://registry.npmjs.org",
1493
+ access: "public"
1494
+ },
1495
+ dependencies: {
1496
+ "@hasna/accounts": "0.2.23",
1497
+ "@hasna/agent-registry": "^0.1.0",
1498
+ "@hasna/contracts": "0.13.4",
1499
+ "@hasna/events": "^0.1.16",
1500
+ "@hasna/mcp-harness": "^0.1.0",
1501
+ "@hasna/paths": "0.1.0",
1502
+ "@hasna/projects": "1.0.0",
1503
+ "@modelcontextprotocol/sdk": "^1.12.1",
1504
+ chalk: "^5.4.1",
1505
+ commander: "^13.1.0",
1506
+ pg: "8.13.1",
1507
+ zod: "^3.24.2"
1508
+ },
1509
+ devDependencies: {
1510
+ "@types/bun": "latest",
1511
+ "@types/pg": "^8.20.0",
1512
+ "bun-types": "latest",
1513
+ typescript: "^5.7.2"
1514
+ },
1515
+ trustedDependencies: [
1516
+ "@hasna/projects"
1517
+ ]
1518
+ };
1388
1519
 
1389
1520
  // src/lib/package-metadata.ts
1390
- import { readFileSync } from "fs";
1391
1521
  var cachedMetadata = null;
1392
1522
  function getPackageMetadata() {
1393
1523
  if (cachedMetadata)
1394
1524
  return cachedMetadata;
1395
- const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf8");
1396
- const parsed = JSON.parse(raw);
1397
1525
  cachedMetadata = {
1398
- name: parsed.name ?? "@hasna/economy",
1399
- version: parsed.version ?? "0.0.0"
1526
+ name: package_default.name ?? "@hasna/economy",
1527
+ version: package_default.version ?? "0.0.0"
1400
1528
  };
1401
1529
  return cachedMetadata;
1402
1530
  }
@@ -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 getDataDir() {
630
+ function getHomeDir() {
630
631
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
631
- const newDir = join(home, ".hasna", "economy");
632
- const oldDir = join(home, ".economy");
633
- if (existsSync(oldDir) && !existsSync(newDir)) {
634
- mkdirSync(newDir, { recursive: true });
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(newDir, file));
666
+ copyFileSync(oldPath, join(effective, file));
639
667
  }
640
668
  }
641
669
  }
642
- mkdirSync(newDir, { recursive: true });
643
- return newDir;
670
+ mkdirSync(effective, { recursive: true });
671
+ return effective;
644
672
  }
645
673
  function getDbPath() {
646
674
  if (process.env["HASNA_ECONOMY_DB_PATH"])
@@ -2874,7 +2902,7 @@ __export(exports_billing, {
2874
2902
  syncGeminiBilling: () => syncGeminiBilling,
2875
2903
  syncAnthropicBilling: () => syncAnthropicBilling
2876
2904
  });
2877
- import { readFileSync as readFileSync9 } from "fs";
2905
+ import { readFileSync as readFileSync8 } from "fs";
2878
2906
  function getAnthropicAdminKey() {
2879
2907
  return process.env["HASNAXYZ_ANTHROPIC_LIVE_ADMIN_API_KEY"] ?? process.env["ANTHROPIC_ADMIN_API_KEY"] ?? null;
2880
2908
  }
@@ -3101,7 +3129,7 @@ async function syncGeminiBilling(db, opts = {}) {
3101
3129
  const start = opts.fromDate ? new Date(opts.fromDate) : new Date(end.getTime() - days * 24 * 3600000);
3102
3130
  const fromDateStr = toISODate(start);
3103
3131
  const toDateStr = toISODate(end);
3104
- const rows = parseBillingRows(readFileSync9(exportPath, "utf-8"));
3132
+ const rows = parseBillingRows(readFileSync8(exportPath, "utf-8"));
3105
3133
  clearBillingRange(db, "gemini", fromDateStr, toDateStr);
3106
3134
  const updatedAt = new Date().toISOString();
3107
3135
  let totalUsd = 0;
@@ -3165,7 +3193,7 @@ var init_open_projects = __esm(() => {
3165
3193
  });
3166
3194
 
3167
3195
  // src/lib/config.ts
3168
- import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
3196
+ import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
3169
3197
  import { dirname as dirname2, join as join11 } from "path";
3170
3198
  function getConfigPath() {
3171
3199
  return process.env["HASNA_ECONOMY_CONFIG_PATH"] ?? join11(getDataDir(), "config.json");
@@ -3174,7 +3202,7 @@ function loadConfig() {
3174
3202
  try {
3175
3203
  const configPath = getConfigPath();
3176
3204
  if (existsSync12(configPath)) {
3177
- const raw = readFileSync10(configPath, "utf-8");
3205
+ const raw = readFileSync9(configPath, "utf-8");
3178
3206
  return { ...DEFAULTS, ...JSON.parse(raw) };
3179
3207
  }
3180
3208
  } catch {}
@@ -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
- var DEFAULT_LOOPS_DB_PATH = join8(homedir8(), ".hasna", "loops", "loops.db");
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"] ?? DEFAULT_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 getHomeDir() {
4825
+ function getHomeDir2() {
4790
4826
  return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir9();
4791
4827
  }
4792
4828
  function agentPaths() {
4793
- const home = getHomeDir();
4829
+ const home = getHomeDir2();
4794
4830
  return {
4795
4831
  claudeProjects: join9(home, ".claude", "projects"),
4796
4832
  claudeCredentials: join9(home, ".claude", ".credentials.json"),
@@ -5975,19 +6011,119 @@ 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";
6015
+ // package.json
6016
+ var package_default = {
6017
+ name: "@hasna/economy",
6018
+ version: "0.3.28",
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",
6020
+ repository: {
6021
+ type: "git",
6022
+ url: "git+https://github.com/hasna/economy.git"
6023
+ },
6024
+ bugs: {
6025
+ url: "https://github.com/hasna/economy/issues"
6026
+ },
6027
+ homepage: "https://github.com/hasna/economy#readme",
6028
+ type: "module",
6029
+ main: "dist/index.js",
6030
+ types: "dist/index.d.ts",
6031
+ bin: {
6032
+ economy: "dist/cli/index.js",
6033
+ "economy-mcp": "dist/mcp/index.js",
6034
+ "economy-serve": "dist/server/index.js",
6035
+ "economy-otel": "dist/otel/index.js"
6036
+ },
6037
+ exports: {
6038
+ ".": {
6039
+ types: "./dist/index.d.ts",
6040
+ import: "./dist/index.js"
6041
+ },
6042
+ "./sdk": {
6043
+ types: "./dist/index.d.ts",
6044
+ import: "./dist/index.js"
6045
+ }
6046
+ },
6047
+ files: [
6048
+ "dist",
6049
+ "docs",
6050
+ "dashboard/dist",
6051
+ "postinstall.js",
6052
+ "LICENSE",
6053
+ "README.md",
6054
+ "CHANGELOG.md",
6055
+ "SECURITY.md",
6056
+ "CONTRIBUTING.md",
6057
+ "CODE_OF_CONDUCT.md"
6058
+ ],
6059
+ scripts: {
6060
+ 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",
6061
+ "build:cli": "bun build src/cli/index.ts --outdir dist/cli --target bun --packages external",
6062
+ "build:mcp": "bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external",
6063
+ "build:server": "bun build src/server/index.ts --outdir dist/server --target bun --packages external",
6064
+ "build:lib": "bun build src/index.ts --outdir dist --target bun --packages external",
6065
+ "build:dashboard": "cd dashboard && bun run build",
6066
+ "artifact-scan": "bun scripts/contracts-cli.mjs artifact-scan dist",
6067
+ prepack: "bun run build && bun run artifact-scan",
6068
+ typecheck: "tsc --noEmit",
6069
+ test: "bun test",
6070
+ "dev:cli": "bun run src/cli/index.ts",
6071
+ "dev:mcp": "bun run src/mcp/index.ts",
6072
+ "dev:serve": "bun run src/server/index.ts",
6073
+ "dev:otel": "bun run src/otel/index.ts",
6074
+ postinstall: "node postinstall.js"
6075
+ },
6076
+ keywords: [
6077
+ "economy",
6078
+ "cost",
6079
+ "ai",
6080
+ "claude",
6081
+ "codex",
6082
+ "gemini",
6083
+ "mcp",
6084
+ "cli",
6085
+ "budget",
6086
+ "tracking"
6087
+ ],
6088
+ author: "hasna",
6089
+ license: "Apache-2.0",
6090
+ publishConfig: {
6091
+ registry: "https://registry.npmjs.org",
6092
+ access: "public"
6093
+ },
6094
+ dependencies: {
6095
+ "@hasna/accounts": "0.2.23",
6096
+ "@hasna/agent-registry": "^0.1.0",
6097
+ "@hasna/contracts": "0.13.4",
6098
+ "@hasna/events": "^0.1.16",
6099
+ "@hasna/mcp-harness": "^0.1.0",
6100
+ "@hasna/paths": "0.1.0",
6101
+ "@hasna/projects": "1.0.0",
6102
+ "@modelcontextprotocol/sdk": "^1.12.1",
6103
+ chalk: "^5.4.1",
6104
+ commander: "^13.1.0",
6105
+ pg: "8.13.1",
6106
+ zod: "^3.24.2"
6107
+ },
6108
+ devDependencies: {
6109
+ "@types/bun": "latest",
6110
+ "@types/pg": "^8.20.0",
6111
+ "bun-types": "latest",
6112
+ typescript: "^5.7.2"
6113
+ },
6114
+ trustedDependencies: [
6115
+ "@hasna/projects"
6116
+ ]
6117
+ };
5979
6118
 
5980
6119
  // src/lib/package-metadata.ts
5981
- import { readFileSync as readFileSync8 } from "fs";
5982
6120
  var cachedMetadata = null;
5983
6121
  function getPackageMetadata() {
5984
6122
  if (cachedMetadata)
5985
6123
  return cachedMetadata;
5986
- const raw = readFileSync8(new URL("../../package.json", import.meta.url), "utf8");
5987
- const parsed = JSON.parse(raw);
5988
6124
  cachedMetadata = {
5989
- name: parsed.name ?? "@hasna/economy",
5990
- version: parsed.version ?? "0.0.0"
6125
+ name: package_default.name ?? "@hasna/economy",
6126
+ version: package_default.version ?? "0.0.0"
5991
6127
  };
5992
6128
  return cachedMetadata;
5993
6129
  }
@@ -7709,8 +7845,8 @@ function dashboardPath(root, pathname) {
7709
7845
  return null;
7710
7846
  }
7711
7847
  const relativePath = decoded === "/" ? "index.html" : decoded.replace(/^\/+/, "");
7712
- const rootPath = resolve(root);
7713
- const filePath = resolve(rootPath, relativePath);
7848
+ const rootPath = resolve2(root);
7849
+ const filePath = resolve2(rootPath, relativePath);
7714
7850
  return filePath === rootPath || filePath.startsWith(rootPath + sep) ? filePath : null;
7715
7851
  }
7716
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.26",
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",
@@ -23,12 +23,17 @@
23
23
  ".": {
24
24
  "types": "./dist/index.d.ts",
25
25
  "import": "./dist/index.js"
26
+ },
27
+ "./sdk": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
26
30
  }
27
31
  },
28
32
  "files": [
29
33
  "dist",
30
34
  "docs",
31
35
  "dashboard/dist",
36
+ "postinstall.js",
32
37
  "LICENSE",
33
38
  "README.md",
34
39
  "CHANGELOG.md",
@@ -51,7 +56,7 @@
51
56
  "dev:mcp": "bun run src/mcp/index.ts",
52
57
  "dev:serve": "bun run src/server/index.ts",
53
58
  "dev:otel": "bun run src/otel/index.ts",
54
- "postinstall": "mkdir -p $HOME/.hasna/economy/training 2>/dev/null || true"
59
+ "postinstall": "node postinstall.js"
55
60
  },
56
61
  "keywords": [
57
62
  "economy",
@@ -77,6 +82,7 @@
77
82
  "@hasna/contracts": "0.13.4",
78
83
  "@hasna/events": "^0.1.16",
79
84
  "@hasna/mcp-harness": "^0.1.0",
85
+ "@hasna/paths": "0.1.0",
80
86
  "@hasna/projects": "1.0.0",
81
87
  "@modelcontextprotocol/sdk": "^1.12.1",
82
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
+ }