@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.
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 getDataDir() {
628
+ function getHomeDir() {
628
629
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
629
- const newDir = join(home, ".hasna", "economy");
630
- const oldDir = join(home, ".economy");
631
- if (existsSync(oldDir) && !existsSync(newDir)) {
632
- mkdirSync(newDir, { recursive: true });
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(newDir, file));
664
+ copyFileSync(oldPath, join(effective, file));
637
665
  }
638
666
  }
639
667
  }
640
- mkdirSync(newDir, { recursive: true });
641
- return newDir;
668
+ mkdirSync(effective, { recursive: true });
669
+ return effective;
642
670
  }
643
671
  function getDbPath() {
644
672
  if (process.env["HASNA_ECONOMY_DB_PATH"])
@@ -1990,18 +2018,118 @@ function isAgent(value) {
1990
2018
  init_database();
1991
2019
  init_pricing();
1992
2020
  import { randomUUID } from "crypto";
2021
+ // package.json
2022
+ var package_default = {
2023
+ name: "@hasna/economy",
2024
+ version: "0.3.28",
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",
2026
+ repository: {
2027
+ type: "git",
2028
+ url: "git+https://github.com/hasna/economy.git"
2029
+ },
2030
+ bugs: {
2031
+ url: "https://github.com/hasna/economy/issues"
2032
+ },
2033
+ homepage: "https://github.com/hasna/economy#readme",
2034
+ type: "module",
2035
+ main: "dist/index.js",
2036
+ types: "dist/index.d.ts",
2037
+ bin: {
2038
+ economy: "dist/cli/index.js",
2039
+ "economy-mcp": "dist/mcp/index.js",
2040
+ "economy-serve": "dist/server/index.js",
2041
+ "economy-otel": "dist/otel/index.js"
2042
+ },
2043
+ exports: {
2044
+ ".": {
2045
+ types: "./dist/index.d.ts",
2046
+ import: "./dist/index.js"
2047
+ },
2048
+ "./sdk": {
2049
+ types: "./dist/index.d.ts",
2050
+ import: "./dist/index.js"
2051
+ }
2052
+ },
2053
+ files: [
2054
+ "dist",
2055
+ "docs",
2056
+ "dashboard/dist",
2057
+ "postinstall.js",
2058
+ "LICENSE",
2059
+ "README.md",
2060
+ "CHANGELOG.md",
2061
+ "SECURITY.md",
2062
+ "CONTRIBUTING.md",
2063
+ "CODE_OF_CONDUCT.md"
2064
+ ],
2065
+ scripts: {
2066
+ 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",
2067
+ "build:cli": "bun build src/cli/index.ts --outdir dist/cli --target bun --packages external",
2068
+ "build:mcp": "bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external",
2069
+ "build:server": "bun build src/server/index.ts --outdir dist/server --target bun --packages external",
2070
+ "build:lib": "bun build src/index.ts --outdir dist --target bun --packages external",
2071
+ "build:dashboard": "cd dashboard && bun run build",
2072
+ "artifact-scan": "bun scripts/contracts-cli.mjs artifact-scan dist",
2073
+ prepack: "bun run build && bun run artifact-scan",
2074
+ typecheck: "tsc --noEmit",
2075
+ test: "bun test",
2076
+ "dev:cli": "bun run src/cli/index.ts",
2077
+ "dev:mcp": "bun run src/mcp/index.ts",
2078
+ "dev:serve": "bun run src/server/index.ts",
2079
+ "dev:otel": "bun run src/otel/index.ts",
2080
+ postinstall: "node postinstall.js"
2081
+ },
2082
+ keywords: [
2083
+ "economy",
2084
+ "cost",
2085
+ "ai",
2086
+ "claude",
2087
+ "codex",
2088
+ "gemini",
2089
+ "mcp",
2090
+ "cli",
2091
+ "budget",
2092
+ "tracking"
2093
+ ],
2094
+ author: "hasna",
2095
+ license: "Apache-2.0",
2096
+ publishConfig: {
2097
+ registry: "https://registry.npmjs.org",
2098
+ access: "public"
2099
+ },
2100
+ dependencies: {
2101
+ "@hasna/accounts": "0.2.23",
2102
+ "@hasna/agent-registry": "^0.1.0",
2103
+ "@hasna/contracts": "0.13.4",
2104
+ "@hasna/events": "^0.1.16",
2105
+ "@hasna/mcp-harness": "^0.1.0",
2106
+ "@hasna/paths": "0.1.0",
2107
+ "@hasna/projects": "1.0.0",
2108
+ "@modelcontextprotocol/sdk": "^1.12.1",
2109
+ chalk: "^5.4.1",
2110
+ commander: "^13.1.0",
2111
+ pg: "8.13.1",
2112
+ zod: "^3.24.2"
2113
+ },
2114
+ devDependencies: {
2115
+ "@types/bun": "latest",
2116
+ "@types/pg": "^8.20.0",
2117
+ "bun-types": "latest",
2118
+ typescript: "^5.7.2"
2119
+ },
2120
+ trustedDependencies: [
2121
+ "@hasna/projects"
2122
+ ]
2123
+ };
1993
2124
 
1994
2125
  // src/lib/package-metadata.ts
1995
- import { readFileSync } from "fs";
1996
2126
  var cachedMetadata = null;
1997
2127
  function getPackageMetadata() {
1998
2128
  if (cachedMetadata)
1999
2129
  return cachedMetadata;
2000
- const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf8");
2001
- const parsed = JSON.parse(raw);
2002
2130
  cachedMetadata = {
2003
- name: parsed.name ?? "@hasna/economy",
2004
- version: parsed.version ?? "0.0.0"
2131
+ name: package_default.name ?? "@hasna/economy",
2132
+ version: package_default.version ?? "0.0.0"
2005
2133
  };
2006
2134
  return cachedMetadata;
2007
2135
  }
@@ -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":"AAIA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AA4IxE,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
+ {"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 ~/.hasna/economy/config.json. */
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":"AAOA,eAAO,MAAM,aAAa,gBAAgB,CAAA;AA8B1C,4EAA4E;AAC5E,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAI/C;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"package-metadata.d.ts","sourceRoot":"","sources":["../../src/lib/package-metadata.ts"],"names":[],"mappings":"AAEA,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAID,wBAAgB,kBAAkB,IAAI,eAAe,CAYpD;AAED,eAAO,MAAM,eAAe,iBAAuB,CAAA"}
1
+ {"version":3,"file":"package-metadata.d.ts","sourceRoot":"","sources":["../../src/lib/package-metadata.ts"],"names":[],"mappings":"AAEA,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAID,wBAAgB,kBAAkB,IAAI,eAAe,CASpD;AAED,eAAO,MAAM,eAAe,iBAAuB,CAAA"}
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 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"])
@@ -2082,18 +2110,118 @@ var init_database = __esm(() => {
2082
2110
 
2083
2111
  // src/mcp/index.ts
2084
2112
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2113
+ // package.json
2114
+ var package_default = {
2115
+ name: "@hasna/economy",
2116
+ version: "0.3.28",
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",
2118
+ repository: {
2119
+ type: "git",
2120
+ url: "git+https://github.com/hasna/economy.git"
2121
+ },
2122
+ bugs: {
2123
+ url: "https://github.com/hasna/economy/issues"
2124
+ },
2125
+ homepage: "https://github.com/hasna/economy#readme",
2126
+ type: "module",
2127
+ main: "dist/index.js",
2128
+ types: "dist/index.d.ts",
2129
+ bin: {
2130
+ economy: "dist/cli/index.js",
2131
+ "economy-mcp": "dist/mcp/index.js",
2132
+ "economy-serve": "dist/server/index.js",
2133
+ "economy-otel": "dist/otel/index.js"
2134
+ },
2135
+ exports: {
2136
+ ".": {
2137
+ types: "./dist/index.d.ts",
2138
+ import: "./dist/index.js"
2139
+ },
2140
+ "./sdk": {
2141
+ types: "./dist/index.d.ts",
2142
+ import: "./dist/index.js"
2143
+ }
2144
+ },
2145
+ files: [
2146
+ "dist",
2147
+ "docs",
2148
+ "dashboard/dist",
2149
+ "postinstall.js",
2150
+ "LICENSE",
2151
+ "README.md",
2152
+ "CHANGELOG.md",
2153
+ "SECURITY.md",
2154
+ "CONTRIBUTING.md",
2155
+ "CODE_OF_CONDUCT.md"
2156
+ ],
2157
+ scripts: {
2158
+ 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",
2159
+ "build:cli": "bun build src/cli/index.ts --outdir dist/cli --target bun --packages external",
2160
+ "build:mcp": "bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external",
2161
+ "build:server": "bun build src/server/index.ts --outdir dist/server --target bun --packages external",
2162
+ "build:lib": "bun build src/index.ts --outdir dist --target bun --packages external",
2163
+ "build:dashboard": "cd dashboard && bun run build",
2164
+ "artifact-scan": "bun scripts/contracts-cli.mjs artifact-scan dist",
2165
+ prepack: "bun run build && bun run artifact-scan",
2166
+ typecheck: "tsc --noEmit",
2167
+ test: "bun test",
2168
+ "dev:cli": "bun run src/cli/index.ts",
2169
+ "dev:mcp": "bun run src/mcp/index.ts",
2170
+ "dev:serve": "bun run src/server/index.ts",
2171
+ "dev:otel": "bun run src/otel/index.ts",
2172
+ postinstall: "node postinstall.js"
2173
+ },
2174
+ keywords: [
2175
+ "economy",
2176
+ "cost",
2177
+ "ai",
2178
+ "claude",
2179
+ "codex",
2180
+ "gemini",
2181
+ "mcp",
2182
+ "cli",
2183
+ "budget",
2184
+ "tracking"
2185
+ ],
2186
+ author: "hasna",
2187
+ license: "Apache-2.0",
2188
+ publishConfig: {
2189
+ registry: "https://registry.npmjs.org",
2190
+ access: "public"
2191
+ },
2192
+ dependencies: {
2193
+ "@hasna/accounts": "0.2.23",
2194
+ "@hasna/agent-registry": "^0.1.0",
2195
+ "@hasna/contracts": "0.13.4",
2196
+ "@hasna/events": "^0.1.16",
2197
+ "@hasna/mcp-harness": "^0.1.0",
2198
+ "@hasna/paths": "0.1.0",
2199
+ "@hasna/projects": "1.0.0",
2200
+ "@modelcontextprotocol/sdk": "^1.12.1",
2201
+ chalk: "^5.4.1",
2202
+ commander: "^13.1.0",
2203
+ pg: "8.13.1",
2204
+ zod: "^3.24.2"
2205
+ },
2206
+ devDependencies: {
2207
+ "@types/bun": "latest",
2208
+ "@types/pg": "^8.20.0",
2209
+ "bun-types": "latest",
2210
+ typescript: "^5.7.2"
2211
+ },
2212
+ trustedDependencies: [
2213
+ "@hasna/projects"
2214
+ ]
2215
+ };
2085
2216
 
2086
2217
  // src/lib/package-metadata.ts
2087
- import { readFileSync } from "fs";
2088
2218
  var cachedMetadata = null;
2089
2219
  function getPackageMetadata() {
2090
2220
  if (cachedMetadata)
2091
2221
  return cachedMetadata;
2092
- const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf8");
2093
- const parsed = JSON.parse(raw);
2094
2222
  cachedMetadata = {
2095
- name: parsed.name ?? "@hasna/economy",
2096
- version: parsed.version ?? "0.0.0"
2223
+ name: package_default.name ?? "@hasna/economy",
2224
+ version: package_default.version ?? "0.0.0"
2097
2225
  };
2098
2226
  return cachedMetadata;
2099
2227
  }
@@ -2108,7 +2236,7 @@ import { z } from "zod";
2108
2236
  // src/ingest/claude.ts
2109
2237
  init_database();
2110
2238
  init_pricing();
2111
- import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
2239
+ import { readdirSync as readdirSync2, readFileSync, existsSync as existsSync2, statSync as statSync2 } from "fs";
2112
2240
  import { homedir as homedir2 } from "os";
2113
2241
  import { join as join2, basename } from "path";
2114
2242
 
@@ -2437,7 +2565,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
2437
2565
  continue;
2438
2566
  let lines;
2439
2567
  try {
2440
- lines = readFileSync2(filePath, "utf-8").split(`
2568
+ lines = readFileSync(filePath, "utf-8").split(`
2441
2569
  `).filter((l) => l.trim());
2442
2570
  } catch {
2443
2571
  continue;
@@ -2557,7 +2685,7 @@ function supportsClaudeDataResidencyPricing(model) {
2557
2685
  // src/ingest/codex.ts
2558
2686
  init_database();
2559
2687
  init_pricing();
2560
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
2688
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2561
2689
  import { homedir as homedir3 } from "os";
2562
2690
  import { join as join3, basename as basename2 } from "path";
2563
2691
  import { Database as BunDatabase } from "bun:sqlite";
@@ -2609,7 +2737,7 @@ function readCodexModel(configPath = codexConfigPath()) {
2609
2737
  if (!existsSync3(configPath))
2610
2738
  return "gpt-5-codex";
2611
2739
  try {
2612
- const content = readFileSync3(configPath, "utf-8");
2740
+ const content = readFileSync2(configPath, "utf-8");
2613
2741
  const match = content.match(/^model\s*=\s*"([^"]+)"/m);
2614
2742
  return match?.[1] ?? "gpt-5-codex";
2615
2743
  } catch {
@@ -2652,7 +2780,7 @@ function readTokenEvents(rolloutPath) {
2652
2780
  const fallbackUsages = new Map;
2653
2781
  let fallbackTimestamp;
2654
2782
  let aggregate = null;
2655
- for (const line of readFileSync3(rolloutPath, "utf-8").split(`
2783
+ for (const line of readFileSync2(rolloutPath, "utf-8").split(`
2656
2784
  `)) {
2657
2785
  if (!line.trim())
2658
2786
  continue;
@@ -2809,7 +2937,7 @@ async function ingestCodex(db, verbose = false) {
2809
2937
  // src/ingest/gemini.ts
2810
2938
  init_database();
2811
2939
  init_pricing();
2812
- import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync4, statSync as statSync3 } from "fs";
2940
+ import { readdirSync as readdirSync3, readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync3 } from "fs";
2813
2941
  import { homedir as homedir4 } from "os";
2814
2942
  import { join as join4, basename as basename3 } from "path";
2815
2943
  var DEFAULT_GEMINI_TMP_DIR = join4(homedir4(), ".gemini", "tmp");
@@ -2849,7 +2977,7 @@ function projectRoot(projectDir, chatData) {
2849
2977
  const rootFile = join4(projectDir, ".project_root");
2850
2978
  try {
2851
2979
  if (existsSync4(rootFile))
2852
- return readFileSync4(rootFile, "utf-8").trim();
2980
+ return readFileSync3(rootFile, "utf-8").trim();
2853
2981
  } catch {}
2854
2982
  return "";
2855
2983
  }
@@ -2890,7 +3018,7 @@ async function ingestGemini(db, verbose) {
2890
3018
  continue;
2891
3019
  let chatData;
2892
3020
  try {
2893
- chatData = JSON.parse(readFileSync4(filePath, "utf-8"));
3021
+ chatData = JSON.parse(readFileSync3(filePath, "utf-8"));
2894
3022
  } catch {
2895
3023
  continue;
2896
3024
  }
@@ -2968,7 +3096,7 @@ async function ingestGemini(db, verbose) {
2968
3096
  // src/ingest/opencode.ts
2969
3097
  init_database();
2970
3098
  init_pricing();
2971
- import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
3099
+ import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
2972
3100
  import { homedir as homedir5 } from "os";
2973
3101
  import { join as join5 } from "path";
2974
3102
  var OPENCODE_STORAGE = join5(homedir5(), ".local", "share", "opencode", "storage");
@@ -3011,7 +3139,7 @@ async function ingestOpenCode(db, verbose = false) {
3011
3139
  continue;
3012
3140
  let parsed;
3013
3141
  try {
3014
- parsed = JSON.parse(readFileSync5(file, "utf-8"));
3142
+ parsed = JSON.parse(readFileSync4(file, "utf-8"));
3015
3143
  } catch {
3016
3144
  continue;
3017
3145
  }
@@ -3198,7 +3326,7 @@ async function ingestCursor(db, verbose = false) {
3198
3326
 
3199
3327
  // src/ingest/pi.ts
3200
3328
  init_database();
3201
- import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
3329
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
3202
3330
  import { homedir as homedir6 } from "os";
3203
3331
  import { join as join6 } from "path";
3204
3332
  var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join6(homedir6(), ".pi", "agent", "sessions");
@@ -3228,7 +3356,7 @@ async function ingestPi(db, verbose = false) {
3228
3356
  continue;
3229
3357
  let data;
3230
3358
  try {
3231
- data = JSON.parse(readFileSync6(file, "utf-8"));
3359
+ data = JSON.parse(readFileSync5(file, "utf-8"));
3232
3360
  } catch {
3233
3361
  continue;
3234
3362
  }
@@ -3388,10 +3516,18 @@ import { existsSync as existsSync8 } from "fs";
3388
3516
  import { homedir as homedir8 } from "os";
3389
3517
  import { join as join8 } from "path";
3390
3518
  import { Database as BunDatabase2 } from "bun:sqlite";
3391
- var DEFAULT_LOOPS_DB_PATH = join8(homedir8(), ".hasna", "loops", "loops.db");
3519
+ import { dataDir as dataDir2 } from "@hasna/paths";
3392
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
+ }
3393
3529
  function loopsDbPath() {
3394
- return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? DEFAULT_LOOPS_DB_PATH;
3530
+ return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
3395
3531
  }
3396
3532
  function loopsModel() {
3397
3533
  return process.env["HASNA_ECONOMY_LOOPS_MODEL"] ?? process.env["ECONOMY_LOOPS_MODEL"] ?? "gpt-5.3-codex";
@@ -3570,16 +3706,16 @@ async function ingestLoops(db, verbose = false) {
3570
3706
 
3571
3707
  // src/ingest/claude-quota.ts
3572
3708
  init_database();
3573
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
3709
+ import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
3574
3710
 
3575
3711
  // src/lib/paths.ts
3576
3712
  import { homedir as homedir9 } from "os";
3577
3713
  import { join as join9 } from "path";
3578
- function getHomeDir() {
3714
+ function getHomeDir2() {
3579
3715
  return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir9();
3580
3716
  }
3581
3717
  function agentPaths() {
3582
- const home = getHomeDir();
3718
+ const home = getHomeDir2();
3583
3719
  return {
3584
3720
  claudeProjects: join9(home, ".claude", "projects"),
3585
3721
  claudeCredentials: join9(home, ".claude", ".credentials.json"),
@@ -3608,7 +3744,7 @@ function readClaudeToken() {
3608
3744
  if (!existsSync9(CREDENTIALS_PATH))
3609
3745
  return null;
3610
3746
  try {
3611
- const creds = JSON.parse(readFileSync7(CREDENTIALS_PATH, "utf-8"));
3747
+ const creds = JSON.parse(readFileSync6(CREDENTIALS_PATH, "utf-8"));
3612
3748
  const oauth = creds.claudeAiOauth;
3613
3749
  if (!oauth?.accessToken)
3614
3750
  return null;
@@ -3744,7 +3880,7 @@ async function ingestClaudeQuota(db, verbose = false) {
3744
3880
 
3745
3881
  // src/ingest/codex-quota.ts
3746
3882
  init_database();
3747
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
3883
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
3748
3884
  var WHAM_USAGE_URL = process.env["CODEX_USAGE_URL"] ?? "https://chatgpt.com/backend-api/wham/usage";
3749
3885
  function readCodexAuth() {
3750
3886
  const fromEnv = process.env["CODEX_OAUTH_TOKEN"];
@@ -3754,7 +3890,7 @@ function readCodexAuth() {
3754
3890
  if (!existsSync10(authPath))
3755
3891
  return null;
3756
3892
  try {
3757
- const auth = JSON.parse(readFileSync8(authPath, "utf-8"));
3893
+ const auth = JSON.parse(readFileSync7(authPath, "utf-8"));
3758
3894
  const token = auth.tokens?.access_token;
3759
3895
  if (!token)
3760
3896
  return null;