@hasna/economy 0.3.28 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +6 -13
  4. package/SECURITY.md +1 -1
  5. package/dist/cli/commands/extras.d.ts.map +1 -1
  6. package/dist/cli/index.js +1135 -853
  7. package/dist/db/cloud.d.ts +16 -8
  8. package/dist/db/cloud.d.ts.map +1 -1
  9. package/dist/db/database.d.ts +19 -0
  10. package/dist/db/database.d.ts.map +1 -1
  11. package/dist/index.js +198 -19
  12. package/dist/ingest/claude.d.ts.map +1 -1
  13. package/dist/ingest/loops.d.ts +9 -0
  14. package/dist/ingest/loops.d.ts.map +1 -1
  15. package/dist/lib/accounts-store.d.ts +83 -0
  16. package/dist/lib/accounts-store.d.ts.map +1 -0
  17. package/dist/lib/accounts.d.ts +2 -1
  18. package/dist/lib/accounts.d.ts.map +1 -1
  19. package/dist/lib/api-display-url.d.ts +19 -0
  20. package/dist/lib/api-display-url.d.ts.map +1 -0
  21. package/dist/lib/autosync-gate.d.ts +15 -0
  22. package/dist/lib/autosync-gate.d.ts.map +1 -0
  23. package/dist/lib/cloud-ingest.d.ts +15 -1
  24. package/dist/lib/cloud-ingest.d.ts.map +1 -1
  25. package/dist/lib/cloud-storage.d.ts +166 -10
  26. package/dist/lib/cloud-storage.d.ts.map +1 -1
  27. package/dist/lib/gatherer.d.ts.map +1 -1
  28. package/dist/lib/serve-auth.d.ts.map +1 -1
  29. package/dist/lib/store/index.d.ts +6 -3
  30. package/dist/lib/store/index.d.ts.map +1 -1
  31. package/dist/lib/test-hermetic-accounts.d.ts +3 -2
  32. package/dist/lib/test-hermetic-accounts.d.ts.map +1 -1
  33. package/dist/lib/test-hermetic-fleet-env.d.ts +4 -0
  34. package/dist/lib/test-hermetic-fleet-env.d.ts.map +1 -0
  35. package/dist/mcp/agent-registry.d.ts +85 -0
  36. package/dist/mcp/agent-registry.d.ts.map +1 -0
  37. package/dist/mcp/harness.d.ts +26 -0
  38. package/dist/mcp/harness.d.ts.map +1 -0
  39. package/dist/mcp/index.js +1340 -302
  40. package/dist/mcp/server.d.ts.map +1 -1
  41. package/dist/otel/index.js +430 -16
  42. package/dist/server/index.js +548 -155
  43. package/dist/server/serve.d.ts +0 -2
  44. package/dist/server/serve.d.ts.map +1 -1
  45. package/docs/cli.md +2 -3
  46. package/docs/configuration.md +23 -10
  47. package/docs/ingestion.md +1 -1
  48. package/docs/mcp.md +4 -2
  49. package/docs/otel.md +8 -2
  50. package/docs/rest-api.md +2 -2
  51. package/package.json +5 -11
  52. package/postinstall.js +52 -1
  53. package/dashboard/README.md +0 -26
  54. package/dist/cli/brains.d.ts +0 -3
  55. package/dist/cli/brains.d.ts.map +0 -1
package/dist/mcp/index.js CHANGED
@@ -596,7 +596,62 @@ import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
596
596
  import { hostname, platform } from "os";
597
597
  import { homedir } from "os";
598
598
  import { join, resolve } from "path";
599
- import { dataDir } from "@hasna/paths";
599
+ import { homedir as pathsResolverHomedir } from "os";
600
+ import { join as pathsResolverJoin } from "path";
601
+ function pathsResolverAssertApp(app) {
602
+ if (typeof app !== "string" || app.length === 0) {
603
+ throw new TypeError("paths: app must be a non-empty string");
604
+ }
605
+ if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
606
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
607
+ }
608
+ }
609
+ function pathsResolverAssertKind(kind) {
610
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
611
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
612
+ }
613
+ }
614
+ function pathsResolverBaseDir(kind, options) {
615
+ pathsResolverAssertKind(kind);
616
+ const env = options.env ?? process.env;
617
+ const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
618
+ if (typeof override === "string" && override.length > 0)
619
+ return override;
620
+ const home = options.home ?? pathsResolverHomedir();
621
+ const platform2 = options.platform ?? process.platform;
622
+ if (platform2 === "darwin") {
623
+ switch (kind) {
624
+ case "config":
625
+ case "data":
626
+ return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
627
+ case "cache":
628
+ return pathsResolverJoin(home, "Library", "Caches", "Hasna");
629
+ case "state":
630
+ return pathsResolverJoin(home, "Library", "Logs", "Hasna");
631
+ }
632
+ }
633
+ switch (kind) {
634
+ case "config":
635
+ return pathsResolverJoin(home, ".config", "hasna");
636
+ case "data":
637
+ return pathsResolverJoin(home, ".local", "share", "hasna");
638
+ case "state":
639
+ return pathsResolverJoin(home, ".local", "state", "hasna");
640
+ case "cache":
641
+ return pathsResolverJoin(home, ".cache", "hasna");
642
+ }
643
+ }
644
+ function pathsResolverResolve(kind, options) {
645
+ pathsResolverAssertApp(options.app);
646
+ const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
647
+ return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
648
+ }
649
+ function dataDir(options) {
650
+ return pathsResolverResolve("data", options);
651
+ }
652
+ function cacheDir(options) {
653
+ return pathsResolverResolve("cache", options);
654
+ }
600
655
  function normalizeMachineId(value) {
601
656
  const id = value?.trim().toLowerCase().split(".")[0];
602
657
  return id && id.length > 0 ? id : null;
@@ -619,7 +674,7 @@ function macHostMachineId() {
619
674
  return null;
620
675
  }
621
676
  function getMachineId() {
622
- const envMachine = normalizeMachineId(process.env["ECONOMY_MACHINE_ID"]);
677
+ const envMachine = normalizeMachineId(process.env["HASNA_ECONOMY_MACHINE_ID"]);
623
678
  if (envMachine)
624
679
  return envMachine;
625
680
  const hostMachine = normalizeMachineId(hostname()) ?? "unknown";
@@ -1929,6 +1984,10 @@ function getIngestState(db, source, key) {
1929
1984
  const row = db.prepare(`SELECT value FROM ingest_state WHERE source = ? AND key = ?`).get(source, key);
1930
1985
  return row?.value ?? null;
1931
1986
  }
1987
+ function loadIngestState(db, source) {
1988
+ const rows = db.prepare(`SELECT key, value FROM ingest_state WHERE source = ?`).all(source);
1989
+ return new Map(rows.map((row) => [row.key, row.value]));
1990
+ }
1932
1991
  function setIngestState(db, source, key, value) {
1933
1992
  db.prepare(`INSERT OR REPLACE INTO ingest_state (source, key, value) VALUES (?, ?, ?)`).run(source, key, value);
1934
1993
  }
@@ -2104,8 +2163,16 @@ function dedupeRequests(db) {
2104
2163
  }
2105
2164
  return removed;
2106
2165
  }
2166
+ var PATHS_RESOLVER_KIND_ENV, PATHS_RESOLVER_APP_SLUG_RE;
2107
2167
  var init_database = __esm(() => {
2108
2168
  init_sqlite_adapter();
2169
+ PATHS_RESOLVER_KIND_ENV = {
2170
+ config: "HASNA_CONFIG_HOME",
2171
+ data: "HASNA_DATA_HOME",
2172
+ state: "HASNA_STATE_HOME",
2173
+ cache: "HASNA_CACHE_HOME"
2174
+ };
2175
+ PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2109
2176
  });
2110
2177
 
2111
2178
  // src/mcp/index.ts
@@ -2113,8 +2180,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
2113
2180
  // package.json
2114
2181
  var package_default = {
2115
2182
  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",
2183
+ version: "0.4.0",
2184
+ description: "AI coding cost tracker \u2014 CLI + MCP server + REST API for Claude Code, Codex, OpenCode, Cursor, Pi, and Hermes, with legacy Gemini CLI session and Gemini API billing ingestion",
2118
2185
  repository: {
2119
2186
  type: "git",
2120
2187
  url: "git+https://github.com/hasna/economy.git"
@@ -2145,7 +2212,6 @@ var package_default = {
2145
2212
  files: [
2146
2213
  "dist",
2147
2214
  "docs",
2148
- "dashboard/dist",
2149
2215
  "postinstall.js",
2150
2216
  "LICENSE",
2151
2217
  "README.md",
@@ -2155,12 +2221,11 @@ var package_default = {
2155
2221
  "CODE_OF_CONDUCT.md"
2156
2222
  ],
2157
2223
  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",
2224
+ build: "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
2225
  "build:cli": "bun build src/cli/index.ts --outdir dist/cli --target bun --packages external",
2160
2226
  "build:mcp": "bun build src/mcp/index.ts --outdir dist/mcp --target bun --packages external",
2161
2227
  "build:server": "bun build src/server/index.ts --outdir dist/server --target bun --packages external",
2162
2228
  "build:lib": "bun build src/index.ts --outdir dist --target bun --packages external",
2163
- "build:dashboard": "cd dashboard && bun run build",
2164
2229
  "artifact-scan": "bun scripts/contracts-cli.mjs artifact-scan dist",
2165
2230
  prepack: "bun run build && bun run artifact-scan",
2166
2231
  typecheck: "tsc --noEmit",
@@ -2190,12 +2255,8 @@ var package_default = {
2190
2255
  access: "public"
2191
2256
  },
2192
2257
  dependencies: {
2193
- "@hasna/accounts": "0.2.23",
2194
- "@hasna/agent-registry": "^0.1.0",
2195
- "@hasna/contracts": "0.13.4",
2258
+ "@hasna/contracts": "1.0.2",
2196
2259
  "@hasna/events": "^0.1.16",
2197
- "@hasna/mcp-harness": "^0.1.0",
2198
- "@hasna/paths": "0.1.0",
2199
2260
  "@hasna/projects": "1.0.0",
2200
2261
  "@modelcontextprotocol/sdk": "^1.12.1",
2201
2262
  chalk: "^5.4.1",
@@ -2204,7 +2265,7 @@ var package_default = {
2204
2265
  zod: "^3.24.2"
2205
2266
  },
2206
2267
  devDependencies: {
2207
- "@types/bun": "latest",
2268
+ "@types/bun": "1.3.14",
2208
2269
  "@types/pg": "^8.20.0",
2209
2270
  "bun-types": "latest",
2210
2271
  typescript: "^5.7.2"
@@ -2228,17 +2289,572 @@ function getPackageMetadata() {
2228
2289
  var packageMetadata = getPackageMetadata();
2229
2290
 
2230
2291
  // src/mcp/server.ts
2231
- init_database();
2232
2292
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2233
- import { registerAgentTools } from "@hasna/agent-registry";
2293
+
2294
+ // src/mcp/agent-registry.ts
2295
+ init_database();
2296
+ import { Database as Database2 } from "bun:sqlite";
2297
+ import { dirname, join as join2 } from "path";
2298
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
2234
2299
  import { z } from "zod";
2235
2300
 
2301
+ // src/lib/cloud-storage.ts
2302
+ import {
2303
+ appConfigDiskValue,
2304
+ ClientTransportConfigurationError,
2305
+ clientTransportEnvKeys,
2306
+ createClientTransport,
2307
+ credentialDiskSources,
2308
+ credentialOverrideEnvKey,
2309
+ credentialPointerEnvKey,
2310
+ CREDENTIAL_PROFILE_ENV_KEY,
2311
+ keychainConfigValue
2312
+ } from "@hasna/contracts/client";
2313
+ import { createHasnaStorageClient } from "@hasna/contracts/client/storage";
2314
+ var ECONOMY_APP = "economy";
2315
+ var RETIRED_STORAGE_MODE_KEYS = [
2316
+ "HASNA_ECONOMY_STORAGE_MODE",
2317
+ "HASNA_ECONOMY_MODE",
2318
+ "ECONOMY_STORAGE_MODE",
2319
+ "ECONOMY_MODE"
2320
+ ];
2321
+ function assertNoRetiredStorageMode(env) {
2322
+ const legacyKey = RETIRED_STORAGE_MODE_KEYS.find((key) => Object.hasOwn(env, key) && env[key] !== undefined);
2323
+ if (!legacyKey)
2324
+ return;
2325
+ throw new Error(`${legacyKey} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `The client routes through the HTTP API resolved by @hasna/contracts ` + `(HASNA_ECONOMY_API_URL + HASNA_ECONOMY_API_KEY, the Keychain, or ` + `~/.hasna/economy/config/credentials), or serves the local SQLite store only when ` + `HASNA_ECONOMY_LOCAL=1 is set.`);
2326
+ }
2327
+ var LOCAL_STORAGE_OPT_IN_KEYS = ["HASNA_ECONOMY_LOCAL", "ECONOMY_LOCAL"];
2328
+ function envFlagSet(env, key) {
2329
+ const raw = env[key];
2330
+ if (raw === undefined)
2331
+ return false;
2332
+ const value = raw.trim().toLowerCase();
2333
+ return value !== "" && value !== "0" && value !== "false" && value !== "no" && value !== "off";
2334
+ }
2335
+ function localStorageExplicitlyOptedIn(env) {
2336
+ return LOCAL_STORAGE_OPT_IN_KEYS.some((key) => envFlagSet(env, key));
2337
+ }
2338
+ function economyAuthorityEnvKeys() {
2339
+ const keys = clientTransportEnvKeys(ECONOMY_APP);
2340
+ return [
2341
+ ...keys.apiUrlKeys,
2342
+ ...keys.apiKeyKeys,
2343
+ credentialOverrideEnvKey(ECONOMY_APP),
2344
+ credentialPointerEnvKey(ECONOMY_APP),
2345
+ CREDENTIAL_PROFILE_ENV_KEY
2346
+ ];
2347
+ }
2348
+ function hasEconomyEnvAuthorityIntent(env) {
2349
+ return economyAuthorityEnvKeys().some((key) => (env[key] ?? "").trim().length > 0);
2350
+ }
2351
+ function hostedAuthorityConfigured(env, options = {}) {
2352
+ const keys = clientTransportEnvKeys(ECONOMY_APP);
2353
+ if (keys.apiUrlKeys.some((key) => (env[key] ?? "").trim().length > 0))
2354
+ return true;
2355
+ if (keychainConfigValue(ECONOMY_APP, env, options.credentials?.keychain) !== null)
2356
+ return true;
2357
+ return appConfigDiskValue(ECONOMY_APP, env, keys.apiUrlKeys) !== null;
2358
+ }
2359
+ function economyResolverEnv(env) {
2360
+ const blanks = economyAuthorityEnvKeys().filter((key) => (key in env) && (env[key] ?? "").trim() === "");
2361
+ if (blanks.length === 0)
2362
+ return env;
2363
+ const next = { ...env };
2364
+ for (const key of blanks)
2365
+ delete next[key];
2366
+ return next;
2367
+ }
2368
+ var CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
2369
+ function isAmbientEconomyEnv(env) {
2370
+ if (typeof process !== "undefined" && env === process.env)
2371
+ return true;
2372
+ return env[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
2373
+ }
2374
+ function economyResolverInputs(env, credentials = {}) {
2375
+ const normalised = economyResolverEnv(env);
2376
+ if (normalised === env)
2377
+ return { env: normalised, credentials };
2378
+ const keychain = { ...credentials.keychain };
2379
+ if (keychain.enabled === undefined && keychain.run === undefined) {
2380
+ keychain.enabled = isAmbientEconomyEnv(env);
2381
+ }
2382
+ return { env: normalised, credentials: { ...credentials, keychain } };
2383
+ }
2384
+ function resolveEconomyStorageClient(name, env, overrides = {}) {
2385
+ const { env: resolverEnv, credentials } = economyResolverInputs(env, overrides.credentials);
2386
+ const wired = createClientTransport(name, resolverEnv, {
2387
+ credentials,
2388
+ ...overrides.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {},
2389
+ ...overrides.headers ? { headers: overrides.headers } : {},
2390
+ ...overrides.timeoutMs ? { timeoutMs: overrides.timeoutMs } : {},
2391
+ ...overrides.retry !== undefined ? { retry: overrides.retry } : {},
2392
+ ...overrides.sleepImpl ? { sleepImpl: overrides.sleepImpl } : {}
2393
+ });
2394
+ return {
2395
+ transport: "http",
2396
+ client: createHasnaStorageClient(name, wired.client),
2397
+ resolution: wired.resolution
2398
+ };
2399
+ }
2400
+ function localEconomyNotice() {
2401
+ return `economy: local mode (${LOCAL_STORAGE_OPT_IN_KEYS[0]}=1) \u2014 reading and writing the on-box ` + `SQLite store, not the hosted fleet. Data in the shared economy API is NOT included.`;
2402
+ }
2403
+ function credentialRequiredError(env, cause) {
2404
+ const keys = clientTransportEnvKeys(ECONOMY_APP);
2405
+ const clientEnv = env;
2406
+ const detail = cause instanceof Error ? cause.message : `No API key could be resolved for '${ECONOMY_APP}'. Looked in the Keychain (macOS only), ` + `then for a credential file at ${credentialDiskSources(ECONOMY_APP, clientEnv).join(" or ") || "<no HOME set>"}, ` + `then for ${keys.apiKeyKeys[0]} in the environment.`;
2407
+ const wayOut = hostedAuthorityConfigured(clientEnv) ? `${keys.apiUrlKeys[0]} (or the Keychain hasna.credentials.${ECONOMY_APP}.api-url item, or ` + `~/.hasna/${ECONOMY_APP}/config/credentials) selects a hosted service, so ${LOCAL_STORAGE_OPT_IN_KEYS[0]} ` + `does not apply; unset the authority to use the local store.` : `Or set ${LOCAL_STORAGE_OPT_IN_KEYS[0]}=1 to explicitly opt into the local store.`;
2408
+ return new Error(`Economy fails closed instead of silently serving the local SQLite store: ${detail} ` + `Set ${keys.apiUrlKeys[0]} and ${keys.apiKeyKeys[0]} (or store the key in the Keychain item ` + `hasna.credentials.${ECONOMY_APP}.api-key, or in ~/.hasna/${ECONOMY_APP}/config/credentials) to use the ` + `hosted economy API at ${keys.apiUrlKeys[0]} or the default fleet gateway. ` + wayOut);
2409
+ }
2410
+ var cache;
2411
+ var localNoticePrinted = false;
2412
+ function resolveEconomyCloudStorage(env = process.env, overrides) {
2413
+ assertNoRetiredStorageMode(env);
2414
+ const clientEnv = env;
2415
+ if (localStorageExplicitlyOptedIn(env) && !hasEconomyEnvAuthorityIntent(clientEnv)) {
2416
+ return { active: false, client: null };
2417
+ }
2418
+ let resolved;
2419
+ try {
2420
+ resolved = resolveEconomyStorageClient(ECONOMY_APP, clientEnv, overrides ?? {});
2421
+ } catch (error) {
2422
+ if (error instanceof ClientTransportConfigurationError) {
2423
+ throw credentialRequiredError(env, error);
2424
+ }
2425
+ throw error;
2426
+ }
2427
+ return { active: true, client: resolved.client };
2428
+ }
2429
+ function economyCloudStorage(env = process.env) {
2430
+ if (cache === undefined) {
2431
+ cache = resolveEconomyCloudStorage(env);
2432
+ if (!cache.active && !localNoticePrinted) {
2433
+ localNoticePrinted = true;
2434
+ if (typeof process !== "undefined")
2435
+ process.stderr.write(`${localEconomyNotice()}
2436
+ `);
2437
+ }
2438
+ }
2439
+ return cache;
2440
+ }
2441
+ function cleanQuery(query) {
2442
+ if (!query)
2443
+ return;
2444
+ const out = {};
2445
+ for (const [k, v] of Object.entries(query)) {
2446
+ if (v !== undefined && v !== null && v !== "")
2447
+ out[k] = v;
2448
+ }
2449
+ return Object.keys(out).length > 0 ? out : undefined;
2450
+ }
2451
+ async function cloudListItems(storage, resource, query) {
2452
+ const cleaned = cleanQuery(query);
2453
+ const res = await storage.client.list(resource, cleaned ? { query: cleaned } : {});
2454
+ return res.items;
2455
+ }
2456
+ async function cloudObject(storage, path, query) {
2457
+ const cleaned = cleanQuery(query);
2458
+ const raw = await storage.client.transport.get(path, cleaned ? { query: cleaned } : {});
2459
+ if (raw && typeof raw === "object" && "data" in raw) {
2460
+ return raw.data;
2461
+ }
2462
+ return raw;
2463
+ }
2464
+
2465
+ // src/mcp/agent-registry.ts
2466
+ var SERVICE_NAME = "agent-registry";
2467
+ var DEFAULT_ACTIVE_WINDOW_MS = 30 * 60 * 1000;
2468
+ function envVar(suffix) {
2469
+ return process.env[`HASNA_AGENT_REGISTRY_${suffix}`] ?? process.env[`HASNA_AGENT_${suffix}`];
2470
+ }
2471
+ function getActiveWindowMs() {
2472
+ const env = envVar("TIMEOUT_MS");
2473
+ if (env) {
2474
+ const parsed = Number.parseInt(env, 10);
2475
+ if (Number.isFinite(parsed) && parsed > 0)
2476
+ return parsed;
2477
+ }
2478
+ return DEFAULT_ACTIVE_WINDOW_MS;
2479
+ }
2480
+ function envSessionId() {
2481
+ return envVar("SESSION_ID") ?? null;
2482
+ }
2483
+ var MEMORY_REGISTRY_PATH = ":memory:";
2484
+ function resolveRegistryDbPath() {
2485
+ const explicit = process.env.HASNA_AGENT_REGISTRY_DB_PATH;
2486
+ if (explicit && explicit.trim())
2487
+ return explicit;
2488
+ if (economyCloudStorage().active)
2489
+ return MEMORY_REGISTRY_PATH;
2490
+ return join2(getDataDir(), "agent-registry.db");
2491
+ }
2492
+ var defaultDb = null;
2493
+
2494
+ class RegistryDb {
2495
+ db;
2496
+ constructor(path) {
2497
+ if (path !== MEMORY_REGISTRY_PATH && dirname(path) && !existsSync2(dirname(path))) {
2498
+ mkdirSync2(dirname(path), { recursive: true, mode: 448 });
2499
+ }
2500
+ this.db = new Database2(path);
2501
+ this.db.exec("PRAGMA journal_mode = WAL");
2502
+ this.db.exec("PRAGMA busy_timeout = 10000");
2503
+ }
2504
+ get(sql, ...params) {
2505
+ return this.db.prepare(sql).get(...params);
2506
+ }
2507
+ all(sql, ...params) {
2508
+ return this.db.prepare(sql).all(...params);
2509
+ }
2510
+ run(sql, ...params) {
2511
+ return this.db.prepare(sql).run(...params);
2512
+ }
2513
+ exec(sql) {
2514
+ this.db.exec(sql);
2515
+ }
2516
+ close() {
2517
+ this.db.close();
2518
+ }
2519
+ }
2520
+ function getDefaultStore() {
2521
+ if (!defaultDb) {
2522
+ defaultDb = new RegistryDb(resolveRegistryDbPath());
2523
+ ensureAgentsTable(defaultDb);
2524
+ }
2525
+ return defaultDb;
2526
+ }
2527
+ var AGENTS_TABLE_SQL = `
2528
+ CREATE TABLE IF NOT EXISTS agents (
2529
+ id TEXT PRIMARY KEY,
2530
+ name TEXT NOT NULL,
2531
+ session_id TEXT,
2532
+ role TEXT NOT NULL DEFAULT 'agent',
2533
+ status TEXT NOT NULL DEFAULT 'active',
2534
+ active_project_id TEXT,
2535
+ working_dir TEXT,
2536
+ machine_id TEXT,
2537
+ capabilities TEXT NOT NULL DEFAULT '[]',
2538
+ metadata TEXT NOT NULL DEFAULT '{}',
2539
+ last_seen_at TEXT NOT NULL,
2540
+ created_at TEXT NOT NULL
2541
+ )`;
2542
+ var AGENTS_NAME_INDEX_SQL = `CREATE INDEX IF NOT EXISTS agents_name_idx ON agents(name)`;
2543
+ function ensureAgentsTable(db) {
2544
+ db.exec(AGENTS_TABLE_SQL);
2545
+ db.exec(AGENTS_NAME_INDEX_SQL);
2546
+ }
2547
+ function newAgentId() {
2548
+ return `ag_${crypto.randomUUID().slice(0, 8)}`;
2549
+ }
2550
+ function now() {
2551
+ return new Date().toISOString();
2552
+ }
2553
+ function normalizeName(name) {
2554
+ return name.trim().toLowerCase();
2555
+ }
2556
+ function rowToAgent(row) {
2557
+ return {
2558
+ id: String(row.id),
2559
+ name: String(row.name),
2560
+ session_id: row.session_id ?? null,
2561
+ role: row.role ?? "agent",
2562
+ status: row.status ?? "active",
2563
+ active_project_id: row.active_project_id ?? null,
2564
+ working_dir: row.working_dir ?? null,
2565
+ machine_id: row.machine_id ?? null,
2566
+ capabilities: safeParseArray(row.capabilities),
2567
+ metadata: safeParseObject(row.metadata),
2568
+ last_seen_at: String(row.last_seen_at),
2569
+ created_at: String(row.created_at)
2570
+ };
2571
+ }
2572
+ function safeParseArray(value) {
2573
+ if (!value)
2574
+ return [];
2575
+ try {
2576
+ const parsed = JSON.parse(String(value));
2577
+ return Array.isArray(parsed) ? parsed : [];
2578
+ } catch {
2579
+ return [];
2580
+ }
2581
+ }
2582
+ function safeParseObject(value) {
2583
+ if (!value)
2584
+ return {};
2585
+ try {
2586
+ const parsed = JSON.parse(String(value));
2587
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2588
+ } catch {
2589
+ return {};
2590
+ }
2591
+ }
2592
+ function isActiveAt(lastSeenAt, windowMs) {
2593
+ return Date.now() - new Date(lastSeenAt).getTime() < windowMs;
2594
+ }
2595
+ function isAgentConflict(result) {
2596
+ return Boolean(result) && result.conflict === true;
2597
+ }
2598
+ function getAgent(id, db) {
2599
+ const row = db.get("SELECT * FROM agents WHERE id = ?", id);
2600
+ return row ? rowToAgent(row) : null;
2601
+ }
2602
+ function getAgentByName(name, db) {
2603
+ const row = db.get("SELECT * FROM agents WHERE name = ? AND status = 'active' ORDER BY last_seen_at DESC LIMIT 1", normalizeName(name));
2604
+ return row ? rowToAgent(row) : null;
2605
+ }
2606
+ function resolveAgent(idOrName, db) {
2607
+ const key = idOrName ?? envSessionId();
2608
+ if (!key)
2609
+ return null;
2610
+ return getAgentByName(key, db) ?? getAgent(key, db);
2611
+ }
2612
+ function listAgents(opts = {}, db) {
2613
+ const includeArchived = opts.include_archived ?? false;
2614
+ const where = includeArchived ? "" : "WHERE status = 'active'";
2615
+ const rows = db.all(`SELECT * FROM agents ${where} ORDER BY name`);
2616
+ let agents = rows.map(rowToAgent);
2617
+ if (opts.online_only) {
2618
+ const windowMs = getActiveWindowMs();
2619
+ agents = agents.filter((a) => a.status === "active" && isActiveAt(a.last_seen_at, windowMs));
2620
+ }
2621
+ return agents;
2622
+ }
2623
+ function registerAgent(input, db) {
2624
+ const name = normalizeName(input.name);
2625
+ if (!name) {
2626
+ throw new Error("register_agent: name is required");
2627
+ }
2628
+ const sessionId = input.session_id ?? envSessionId() ?? null;
2629
+ const windowMs = getActiveWindowMs();
2630
+ const existing = getAgentByName(name, db);
2631
+ if (existing) {
2632
+ const active = isActiveAt(existing.last_seen_at, windowMs);
2633
+ const sameSession = Boolean(sessionId) && Boolean(existing.session_id) && sessionId === existing.session_id;
2634
+ const differentSession = Boolean(sessionId) && Boolean(existing.session_id) && sessionId !== existing.session_id;
2635
+ const callerHasNoSession = !sessionId;
2636
+ const existingHasActiveSession = Boolean(existing.session_id) && active;
2637
+ if (!input.force) {
2638
+ if (active && differentSession)
2639
+ return buildConflict(existing);
2640
+ if (callerHasNoSession && existingHasActiveSession)
2641
+ return buildConflict(existing);
2642
+ }
2643
+ const sets = ["last_seen_at = ?"];
2644
+ const params = [now()];
2645
+ if (sessionId && !sameSession) {
2646
+ sets.push("session_id = ?");
2647
+ params.push(sessionId);
2648
+ }
2649
+ if (input.role !== undefined) {
2650
+ sets.push("role = ?");
2651
+ params.push(input.role);
2652
+ }
2653
+ if (input.working_dir !== undefined) {
2654
+ sets.push("working_dir = ?");
2655
+ params.push(input.working_dir);
2656
+ }
2657
+ if (input.machine_id !== undefined) {
2658
+ sets.push("machine_id = ?");
2659
+ params.push(input.machine_id);
2660
+ }
2661
+ if (input.project_id !== undefined) {
2662
+ sets.push("active_project_id = ?");
2663
+ params.push(input.project_id);
2664
+ }
2665
+ if (input.capabilities !== undefined) {
2666
+ sets.push("capabilities = ?");
2667
+ params.push(JSON.stringify(input.capabilities));
2668
+ }
2669
+ if (input.metadata !== undefined) {
2670
+ sets.push("metadata = ?");
2671
+ params.push(JSON.stringify(input.metadata));
2672
+ }
2673
+ sets.push("status = 'active'");
2674
+ params.push(existing.id);
2675
+ db.run(`UPDATE agents SET ${sets.join(", ")} WHERE id = ?`, ...params);
2676
+ return getAgent(existing.id, db);
2677
+ }
2678
+ const id = newAgentId();
2679
+ const ts = now();
2680
+ db.run(`INSERT INTO agents
2681
+ (id, name, session_id, role, status, active_project_id, working_dir, machine_id, capabilities, metadata, last_seen_at, created_at)
2682
+ VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?)`, id, name, sessionId, input.role ?? "agent", input.project_id ?? null, input.working_dir ?? null, input.machine_id ?? null, JSON.stringify(input.capabilities ?? []), JSON.stringify(input.metadata ?? {}), ts, ts);
2683
+ return getAgent(id, db);
2684
+ }
2685
+ function buildConflict(existing) {
2686
+ const minutesAgo = Math.round((Date.now() - new Date(existing.last_seen_at).getTime()) / 60000);
2687
+ return {
2688
+ conflict: true,
2689
+ existing_id: existing.id,
2690
+ existing_name: existing.name,
2691
+ last_seen_at: existing.last_seen_at,
2692
+ session_hint: existing.session_id ? existing.session_id.slice(0, 8) : null,
2693
+ working_dir: existing.working_dir,
2694
+ message: `Agent "${existing.name}" is already active (last seen ${minutesAgo}m ago). ` + (existing.session_id ? `Pass session_id="${existing.session_id}" to reclaim it, ` : "") + "use force:true to take over, or choose a different name."
2695
+ };
2696
+ }
2697
+ function heartbeat(idOrName, db) {
2698
+ const agent = resolveAgent(idOrName, db);
2699
+ if (!agent)
2700
+ return null;
2701
+ db.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", now(), agent.id);
2702
+ return getAgent(agent.id, db);
2703
+ }
2704
+ function setFocus(idOrName, projectId, db) {
2705
+ const agent = resolveAgent(idOrName, db);
2706
+ if (!agent)
2707
+ return null;
2708
+ db.run("UPDATE agents SET active_project_id = ?, last_seen_at = ? WHERE id = ?", projectId, now(), agent.id);
2709
+ return getAgent(agent.id, db);
2710
+ }
2711
+ function agentData(agent) {
2712
+ return {
2713
+ agent_id: agent.id,
2714
+ session_id: agent.session_id,
2715
+ project_id: agent.active_project_id,
2716
+ machine_id: agent.machine_id,
2717
+ role: agent.role
2718
+ };
2719
+ }
2720
+ async function emitAgentEvent(client, type, agent, extra = {}) {
2721
+ if (!client)
2722
+ return;
2723
+ try {
2724
+ await client.emit({
2725
+ source: SERVICE_NAME,
2726
+ type,
2727
+ subject: agent.name,
2728
+ severity: type === "agent.heartbeat" ? "debug" : "info",
2729
+ data: { ...agentData(agent), ...extra }
2730
+ });
2731
+ } catch {}
2732
+ }
2733
+ async function emitConflictEvent(client, conflict) {
2734
+ if (!client)
2735
+ return;
2736
+ try {
2737
+ await client.emit({
2738
+ source: SERVICE_NAME,
2739
+ type: "agent.conflict",
2740
+ subject: conflict.existing_name,
2741
+ severity: "warning",
2742
+ data: {
2743
+ existing_id: conflict.existing_id,
2744
+ last_seen_at: conflict.last_seen_at,
2745
+ session_hint: conflict.session_hint
2746
+ },
2747
+ message: conflict.message
2748
+ });
2749
+ } catch {}
2750
+ }
2751
+ function jsonText(data) {
2752
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2753
+ }
2754
+ function errorText(message) {
2755
+ return { content: [{ type: "text", text: message }], isError: true };
2756
+ }
2757
+ function registerAgentTools(server, opts = {}) {
2758
+ let resolved;
2759
+ const db = () => resolved ??= opts.db ?? getDefaultStore();
2760
+ const events = opts.events;
2761
+ const focus = opts.agentFocus;
2762
+ const includeExtended = opts.includeExtendedTools ?? false;
2763
+ const allow = (name) => opts.toolFilter ? opts.toolFilter(name) : true;
2764
+ const tool = (name, description, schema, handler) => {
2765
+ if (!allow(name))
2766
+ return;
2767
+ server.tool(name, description, schema, handler);
2768
+ };
2769
+ tool("register_agent", "Register or heartbeat an agent by name. Returns the agent, or a structured conflict descriptor if the name is actively held by another session.", {
2770
+ name: z.string().describe("Agent name (normalized to lowercase; unique among active agents)"),
2771
+ session_id: z.string().optional().describe("Stable session id used to reclaim the name"),
2772
+ role: z.string().optional().describe("Agent role (default: agent)"),
2773
+ working_dir: z.string().optional().describe("Working directory of the agent"),
2774
+ project_id: z.string().optional().describe("Set active project focus on register"),
2775
+ capabilities: z.array(z.string()).optional().describe("Declared capabilities"),
2776
+ force: z.boolean().optional().describe("Take over the name even if actively held")
2777
+ }, async (args) => {
2778
+ const result = registerAgent({
2779
+ name: args.name,
2780
+ session_id: args.session_id,
2781
+ role: args.role,
2782
+ working_dir: args.working_dir,
2783
+ project_id: args.project_id,
2784
+ capabilities: args.capabilities,
2785
+ force: args.force
2786
+ }, db());
2787
+ if (isAgentConflict(result)) {
2788
+ await emitConflictEvent(events, result);
2789
+ return { ...jsonText(result), isError: true };
2790
+ }
2791
+ await emitAgentEvent(events, "agent.registered", result);
2792
+ return jsonText(result);
2793
+ });
2794
+ tool("heartbeat", "Update an agent's last_seen_at to signal it is still active.", {
2795
+ agent_id: z.string().optional().describe("Agent id"),
2796
+ name: z.string().optional().describe("Agent name (alternative to agent_id)")
2797
+ }, async (args) => {
2798
+ const agent = heartbeat(args.agent_id ?? args.name, db());
2799
+ if (!agent)
2800
+ return errorText(`Agent not found: ${args.agent_id ?? args.name ?? "(none)"}`);
2801
+ await emitAgentEvent(events, "agent.heartbeat", agent);
2802
+ return jsonText(agent);
2803
+ });
2804
+ tool("set_focus", "Set (or clear) the active project context for an agent. Omit project_id to clear.", {
2805
+ agent_id: z.string().optional().describe("Agent id"),
2806
+ name: z.string().optional().describe("Agent name (alternative to agent_id)"),
2807
+ project_id: z.string().optional().describe("Project id to focus on; omit to clear")
2808
+ }, async (args) => {
2809
+ const projectId = args.project_id ?? null;
2810
+ const agent = setFocus(args.agent_id ?? args.name, projectId, db());
2811
+ if (!agent)
2812
+ return errorText(`Agent not found: ${args.agent_id ?? args.name ?? "(none)"}`);
2813
+ focus?.set(agent.id, { project_id: projectId });
2814
+ await emitAgentEvent(events, "agent.focus_changed", agent, { project_id: projectId });
2815
+ return jsonText(agent);
2816
+ });
2817
+ tool("list_agents", "List registered agents.", {
2818
+ online_only: z.boolean().optional().describe("Only agents seen within the active window"),
2819
+ include_archived: z.boolean().optional().describe("Include archived agents")
2820
+ }, async (args) => {
2821
+ const agents = listAgents({ online_only: args.online_only, include_archived: args.include_archived }, db());
2822
+ return jsonText(agents);
2823
+ });
2824
+ if (includeExtended) {
2825
+ tool("get_focus", "Get the active project context for an agent.", {
2826
+ agent_id: z.string().optional(),
2827
+ name: z.string().optional()
2828
+ }, async (args) => {
2829
+ const agent = resolveAgent(args.agent_id ?? args.name, db());
2830
+ if (!agent)
2831
+ return errorText(`Agent not found: ${args.agent_id ?? args.name ?? "(none)"}`);
2832
+ return jsonText({ agent_id: agent.id, project_id: agent.active_project_id });
2833
+ });
2834
+ tool("unfocus", "Clear the active project context for an agent.", {
2835
+ agent_id: z.string().optional(),
2836
+ name: z.string().optional()
2837
+ }, async (args) => {
2838
+ const agent = setFocus(args.agent_id ?? args.name, null, db());
2839
+ if (!agent)
2840
+ return errorText(`Agent not found: ${args.agent_id ?? args.name ?? "(none)"}`);
2841
+ focus?.set(agent.id, { project_id: null });
2842
+ await emitAgentEvent(events, "agent.focus_changed", agent, { project_id: null });
2843
+ return jsonText(agent);
2844
+ });
2845
+ }
2846
+ }
2847
+
2848
+ // src/mcp/server.ts
2849
+ init_database();
2850
+ import { z as z3 } from "zod";
2851
+
2236
2852
  // src/ingest/claude.ts
2237
2853
  init_database();
2238
2854
  init_pricing();
2239
- import { readdirSync as readdirSync2, readFileSync, existsSync as existsSync2, statSync as statSync2 } from "fs";
2240
- import { homedir as homedir2 } from "os";
2241
- import { join as join2, basename } from "path";
2855
+ import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync4, statSync as statSync2 } from "fs";
2856
+ import { homedir as homedir3 } from "os";
2857
+ import { join as join4, basename } from "path";
2242
2858
 
2243
2859
  // src/lib/savings.ts
2244
2860
  function periodWhere2(period, column) {
@@ -2258,8 +2874,8 @@ function periodWhere2(period, column) {
2258
2874
  }
2259
2875
  }
2260
2876
  function prorateMonthlyFee(monthlyFee, period) {
2261
- const now = new Date;
2262
- const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
2877
+ const now2 = new Date;
2878
+ const daysInMonth = new Date(now2.getFullYear(), now2.getMonth() + 1, 0).getDate();
2263
2879
  switch (period) {
2264
2880
  case "today":
2265
2881
  case "yesterday":
@@ -2394,8 +3010,319 @@ function defaultCostBasisForAgent(agent) {
2394
3010
  return "estimated";
2395
3011
  }
2396
3012
 
3013
+ // src/lib/accounts-store.ts
3014
+ import { existsSync as existsSync3, readFileSync } from "fs";
3015
+ import { homedir as homedir2 } from "os";
3016
+ import { join as join3 } from "path";
3017
+ import { z as z2 } from "zod";
3018
+ import { resolveClientTransport, resolveCredential } from "@hasna/contracts/client";
3019
+ var ACCOUNTS_APP = "accounts";
3020
+ var profileNameSchema = z2.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase alphanumeric/hyphen and start with a letter or digit");
3021
+ var profileSchema = z2.object({
3022
+ name: z2.string(),
3023
+ tool: z2.string(),
3024
+ email: z2.string().optional(),
3025
+ displayName: z2.string().optional(),
3026
+ dir: z2.string(),
3027
+ description: z2.string().optional(),
3028
+ createdAt: z2.string(),
3029
+ lastUsedAt: z2.string().optional()
3030
+ });
3031
+ var toolDefSchema = z2.object({
3032
+ id: z2.string(),
3033
+ label: z2.string(),
3034
+ envVar: z2.string(),
3035
+ defaultDir: z2.string(),
3036
+ bin: z2.string()
3037
+ });
3038
+ var storeSchema = z2.object({
3039
+ version: z2.literal(1),
3040
+ current: z2.record(z2.string(), z2.string()).default({}),
3041
+ applied: z2.record(z2.string(), z2.string()).default({}),
3042
+ toolLocks: z2.record(z2.string(), z2.string()).default({}),
3043
+ profiles: z2.array(profileSchema).default([]),
3044
+ tools: z2.array(toolDefSchema).default([])
3045
+ });
3046
+ function validateEnvPath(value, label) {
3047
+ const trimmed = value.trim();
3048
+ if (!trimmed || trimmed.includes("\x00") || /[\r\n]/.test(trimmed))
3049
+ throw new Error(`invalid ${label}`);
3050
+ return trimmed;
3051
+ }
3052
+ function accountsHome() {
3053
+ const override = process.env.ACCOUNTS_HOME;
3054
+ if (override && override.trim())
3055
+ return validateEnvPath(override, "ACCOUNTS_HOME");
3056
+ return join3(homedir2(), ".hasna", "accounts");
3057
+ }
3058
+ function storePath() {
3059
+ const override = process.env.ACCOUNTS_STORE_PATH;
3060
+ if (override && override.trim())
3061
+ return validateEnvPath(override, "ACCOUNTS_STORE_PATH");
3062
+ return join3(accountsHome(), "accounts.json");
3063
+ }
3064
+ var EMPTY_STORE = {
3065
+ version: 1,
3066
+ current: {},
3067
+ applied: {},
3068
+ toolLocks: {},
3069
+ profiles: [],
3070
+ tools: []
3071
+ };
3072
+ function parseStoreFile() {
3073
+ const path = storePath();
3074
+ if (!existsSync3(path))
3075
+ return structuredClone(EMPTY_STORE);
3076
+ let raw;
3077
+ try {
3078
+ raw = JSON.parse(readFileSync(path, "utf8"));
3079
+ } catch (err) {
3080
+ throw new Error(`could not parse store at ${path}: ${err instanceof Error ? err.message : String(err)}`);
3081
+ }
3082
+ const parsed = storeSchema.safeParse(raw);
3083
+ if (!parsed.success) {
3084
+ throw new Error(`invalid store at ${path}: ${parsed.error.issues.map((issue) => issue.message).join("; ")}`);
3085
+ }
3086
+ return parsed.data;
3087
+ }
3088
+ function loadAppliedMap() {
3089
+ const applied = {};
3090
+ for (const [toolId, name] of Object.entries(parseStoreFile().applied)) {
3091
+ if (name && profileNameSchema.safeParse(name).success)
3092
+ applied[toolId] = name;
3093
+ }
3094
+ return applied;
3095
+ }
3096
+ function appliedProfileName(toolId) {
3097
+ return loadAppliedMap()[toolId];
3098
+ }
3099
+ var BUILTIN_TOOLS = [
3100
+ { id: "claude", label: "Claude Code", envVar: "CLAUDE_CONFIG_DIR", defaultDir: join3(homedir2(), ".claude"), bin: "claude" },
3101
+ { id: "codex-app", label: "Codex App", envVar: "CODEX_HOME", defaultDir: join3(homedir2(), ".codex"), bin: "/Applications/Codex.app/Contents/MacOS/Codex" },
3102
+ { id: "codex", label: "Codex CLI", envVar: "CODEX_HOME", defaultDir: join3(homedir2(), ".codex"), bin: "codex" },
3103
+ { id: "codewith", label: "Codewith", envVar: "CODEWITH_HOME", defaultDir: join3(homedir2(), ".codewith"), bin: "codewith" },
3104
+ { id: "takumi", label: "Takumi", envVar: "TAKUMI_CONFIG_DIR", defaultDir: join3(homedir2(), ".takumi"), bin: "takumi" },
3105
+ { id: "gemini", label: "Gemini CLI", envVar: "GEMINI_CONFIG_DIR", defaultDir: join3(homedir2(), ".gemini"), bin: "gemini" },
3106
+ { id: "opencode", label: "opencode", envVar: "OPENCODE_CONFIG_DIR", defaultDir: join3(homedir2(), ".config", "opencode"), bin: "opencode" },
3107
+ { id: "cursor", label: "Cursor Agent", envVar: "CURSOR_CONFIG_DIR", defaultDir: join3(homedir2(), ".cursor"), bin: "cursor-agent" },
3108
+ { id: "pi", label: "Pi Coding Agent", envVar: "PI_CODING_AGENT_HOME", defaultDir: join3(homedir2(), ".pi"), bin: "pi" },
3109
+ { id: "hermes", label: "Hermes", envVar: "HERMES_HOME", defaultDir: join3(homedir2(), ".hermes"), bin: "hermes" },
3110
+ { id: "kimi", label: "Kimi Code", envVar: "KIMI_CODE_HOME", defaultDir: join3(homedir2(), ".kimi-code"), bin: "kimi" },
3111
+ { id: "grok", label: "Grok Build", envVar: "HOME", defaultDir: join3(homedir2(), ".grok"), bin: "grok" }
3112
+ ];
3113
+
3114
+ class LocalStore {
3115
+ transport = "local";
3116
+ async listProfiles(tool) {
3117
+ const profiles = parseStoreFile().profiles;
3118
+ const filtered = tool ? profiles.filter((profile) => profile.tool === tool) : profiles;
3119
+ return filtered.slice().sort((a, b) => a.tool.localeCompare(b.tool) || a.name.localeCompare(b.name));
3120
+ }
3121
+ async findProfile(name, tool) {
3122
+ const matches = parseStoreFile().profiles.filter((profile) => profile.name === name && (!tool || profile.tool === tool));
3123
+ return matches.length === 1 ? matches[0] : undefined;
3124
+ }
3125
+ async currentProfile(tool) {
3126
+ const store = parseStoreFile();
3127
+ const name = store.current[tool];
3128
+ if (!name)
3129
+ return;
3130
+ return store.profiles.find((profile) => profile.name === name && profile.tool === tool);
3131
+ }
3132
+ async listTools() {
3133
+ const custom = parseStoreFile().tools;
3134
+ const byId = new Map;
3135
+ for (const tool of BUILTIN_TOOLS)
3136
+ byId.set(tool.id, tool);
3137
+ for (const tool of custom)
3138
+ byId.set(tool.id, tool);
3139
+ return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
3140
+ }
3141
+ }
3142
+ var RETIRED_STORAGE_MODE_KEYS2 = [
3143
+ "HASNA_ACCOUNTS_STORAGE_MODE",
3144
+ "ACCOUNTS_STORAGE_MODE",
3145
+ "HASNA_ACCOUNTS_MODE",
3146
+ "ACCOUNTS_MODE"
3147
+ ];
3148
+ function assertNoRetiredStorageMode2(env) {
3149
+ const legacyKey = RETIRED_STORAGE_MODE_KEYS2.find((key) => Object.hasOwn(env, key) && env[key] !== undefined);
3150
+ if (!legacyKey)
3151
+ return;
3152
+ throw new Error(`${legacyKey} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `The accounts registry uses the local JSON store, or the HTTP API selected by ` + `HASNA_ACCOUNTS_API_URL + HASNA_ACCOUNTS_API_KEY.`);
3153
+ }
3154
+ var RETRY_STATUSES = [408, 425, 429, 500, 502, 503, 504];
3155
+
3156
+ class AccountsApiError extends Error {
3157
+ status;
3158
+ body;
3159
+ constructor(method, path, status, body) {
3160
+ super(`accounts cloud request failed: ${method} ${path} -> ${status}`);
3161
+ this.name = "AccountsApiError";
3162
+ this.status = status;
3163
+ this.body = body;
3164
+ }
3165
+ }
3166
+ function isNotFound(err) {
3167
+ return Boolean(err && typeof err === "object" && err.status === 404);
3168
+ }
3169
+ function sleep(ms) {
3170
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
3171
+ }
3172
+ async function cloudRequest(baseUrl, apiKey, path, query) {
3173
+ const rel = path.startsWith("/") ? path : `/${path}`;
3174
+ const url = query && Object.keys(query).length > 0 ? `${baseUrl}${rel}?${new URLSearchParams(query).toString()}` : `${baseUrl}${rel}`;
3175
+ let lastError;
3176
+ for (let attempt = 0;attempt < 3; attempt += 1) {
3177
+ const controller = new AbortController;
3178
+ const timer = setTimeout(() => controller.abort(), 30000);
3179
+ let res;
3180
+ try {
3181
+ res = await fetch(url, {
3182
+ method: "GET",
3183
+ headers: {
3184
+ "x-api-key": apiKey,
3185
+ Authorization: `Bearer ${apiKey}`,
3186
+ Accept: "application/json"
3187
+ },
3188
+ signal: controller.signal
3189
+ });
3190
+ } catch (error) {
3191
+ lastError = error instanceof Error ? error : new Error(String(error));
3192
+ clearTimeout(timer);
3193
+ if (attempt < 2) {
3194
+ const backoff = Math.min(2000, 200 * 2 ** attempt);
3195
+ const jitter = Math.floor(Math.random() * (backoff / 2 + 1));
3196
+ await sleep(backoff + jitter);
3197
+ }
3198
+ continue;
3199
+ } finally {
3200
+ clearTimeout(timer);
3201
+ }
3202
+ const text = await res.text();
3203
+ let parsed;
3204
+ if (text.length > 0) {
3205
+ try {
3206
+ parsed = JSON.parse(text);
3207
+ } catch {
3208
+ parsed = text;
3209
+ }
3210
+ }
3211
+ if (!res.ok) {
3212
+ lastError = new AccountsApiError("GET", rel, res.status, parsed);
3213
+ if (attempt < 2 && RETRY_STATUSES.includes(res.status)) {
3214
+ const backoff = Math.min(2000, 200 * 2 ** attempt);
3215
+ const jitter = Math.floor(Math.random() * (backoff / 2 + 1));
3216
+ await sleep(backoff + jitter);
3217
+ continue;
3218
+ }
3219
+ break;
3220
+ }
3221
+ return parsed;
3222
+ }
3223
+ throw lastError;
3224
+ }
3225
+ function toProfile(account) {
3226
+ const profile = {
3227
+ name: String(account.name),
3228
+ tool: String(account.tool),
3229
+ dir: typeof account.dir === "string" ? account.dir : "",
3230
+ createdAt: String(account.createdAt)
3231
+ };
3232
+ if (typeof account.email === "string")
3233
+ profile.email = account.email;
3234
+ if (typeof account.displayName === "string")
3235
+ profile.displayName = account.displayName;
3236
+ if (typeof account.description === "string")
3237
+ profile.description = account.description;
3238
+ if (typeof account.lastUsedAt === "string")
3239
+ profile.lastUsedAt = account.lastUsedAt;
3240
+ return profile;
3241
+ }
3242
+
3243
+ class ApiStore {
3244
+ baseUrl;
3245
+ apiKey;
3246
+ transport = "api";
3247
+ constructor(baseUrl, apiKey) {
3248
+ this.baseUrl = baseUrl;
3249
+ this.apiKey = apiKey;
3250
+ }
3251
+ async listProfiles(tool) {
3252
+ const raw = await cloudRequest(this.baseUrl, this.apiKey, "/accounts", tool ? { tool } : undefined);
3253
+ const accounts = Array.isArray(raw?.accounts) ? raw.accounts : [];
3254
+ return accounts.map((account) => toProfile(account)).sort((a, b) => a.tool.localeCompare(b.tool) || a.name.localeCompare(b.name));
3255
+ }
3256
+ async findProfile(name, tool) {
3257
+ if (tool) {
3258
+ try {
3259
+ const account = await cloudRequest(this.baseUrl, this.apiKey, `/accounts/${encodeURIComponent(tool)}/${encodeURIComponent(name)}`);
3260
+ return account ? toProfile(account) : undefined;
3261
+ } catch (err) {
3262
+ if (isNotFound(err))
3263
+ return;
3264
+ throw err;
3265
+ }
3266
+ }
3267
+ const matches = (await this.listProfiles()).filter((profile) => profile.name === name);
3268
+ return matches.length === 1 ? matches[0] : undefined;
3269
+ }
3270
+ async currentProfile(tool) {
3271
+ let current;
3272
+ try {
3273
+ const raw = await cloudRequest(this.baseUrl, this.apiKey, `/current/${encodeURIComponent(tool)}`);
3274
+ current = raw && typeof raw === "object" ? raw : null;
3275
+ } catch (err) {
3276
+ if (isNotFound(err))
3277
+ return;
3278
+ throw err;
3279
+ }
3280
+ if (!current || typeof current.name !== "string")
3281
+ return;
3282
+ return this.findProfile(current.name, tool);
3283
+ }
3284
+ async listTools() {
3285
+ const raw = await cloudRequest(this.baseUrl, this.apiKey, "/tools");
3286
+ const tools = Array.isArray(raw?.tools) ? raw.tools : [];
3287
+ const custom = [];
3288
+ for (const item of tools) {
3289
+ if (item === null || typeof item !== "object")
3290
+ continue;
3291
+ const record = item;
3292
+ if (record.builtin !== false)
3293
+ continue;
3294
+ const parsed = toolDefSchema.safeParse(item);
3295
+ if (!parsed.success) {
3296
+ throw new Error(`invalid custom tool "${String(record.id)}" returned by accounts-serve: ` + parsed.error.issues.map((issue) => issue.message).join("; "));
3297
+ }
3298
+ custom.push(parsed.data);
3299
+ }
3300
+ const byId = new Map;
3301
+ for (const tool of BUILTIN_TOOLS)
3302
+ byId.set(tool.id, tool);
3303
+ for (const tool of custom)
3304
+ byId.set(tool.id, tool);
3305
+ return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
3306
+ }
3307
+ }
3308
+ function resolveStore(env = process.env, options = {}) {
3309
+ assertNoRetiredStorageMode2(env);
3310
+ const clientEnv = env;
3311
+ try {
3312
+ const credential = resolveCredential(ACCOUNTS_APP, clientEnv, options.credentials);
3313
+ if (credential === null)
3314
+ return new LocalStore;
3315
+ const resolution = resolveClientTransport(ACCOUNTS_APP, clientEnv, {
3316
+ credentials: credential ? { ...options.credentials, apiKey: credential.apiKey } : options.credentials
3317
+ });
3318
+ return new ApiStore(resolution.baseUrl, credential.apiKey);
3319
+ } catch (err) {
3320
+ console.warn(`accounts: API configuration unusable (${err instanceof Error ? err.message : String(err)}); using local store`);
3321
+ return new LocalStore;
3322
+ }
3323
+ }
3324
+
2397
3325
  // src/lib/accounts.ts
2398
- import { appliedProfileName, resolveStore } from "@hasna/accounts";
2399
3326
  var AGENT_ACCOUNT_TOOLS = {
2400
3327
  claude: ["claude"],
2401
3328
  takumi: ["takumi", "claude"],
@@ -2467,11 +3394,11 @@ async function profileForEnvDir(store, tool, env) {
2467
3394
  const normalized = normalizeDir(configuredDir);
2468
3395
  return (await store.listProfiles(tool.id)).find((profile) => normalizeDir(profile.dir) === normalized) ?? null;
2469
3396
  }
2470
- async function resolveAccountForAgent(agent, env = process.env) {
3397
+ async function resolveAccountForAgent(agent, env = process.env, options = {}) {
2471
3398
  const override = envOverride(agent, env);
2472
3399
  if (override)
2473
3400
  return override;
2474
- const store = resolveStore(env);
3401
+ const store = resolveStore(env, options);
2475
3402
  const tools = new Map((await store.listTools()).map((tool) => [tool.id, tool]));
2476
3403
  for (const toolId of AGENT_ACCOUNT_TOOLS[agent]) {
2477
3404
  const tool = tools.get(toolId);
@@ -2509,8 +3436,8 @@ function withAccount(record, account) {
2509
3436
  function autoDetectProject(cwd, projects) {
2510
3437
  return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
2511
3438
  }
2512
- var CLAUDE_PROJECTS_DIR = join2(homedir2(), ".claude", "projects");
2513
- var TAKUMI_PROJECTS_DIR = join2(homedir2(), ".takumi", "projects");
3439
+ var CLAUDE_PROJECTS_DIR = process.env["HASNA_ECONOMY_CLAUDE_PROJECTS_DIR"] ?? join4(homedir3(), ".claude", "projects");
3440
+ var TAKUMI_PROJECTS_DIR = process.env["HASNA_ECONOMY_TAKUMI_PROJECTS_DIR"] ?? join4(homedir3(), ".takumi", "projects");
2514
3441
  function dirNameToPath(dirName) {
2515
3442
  return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
2516
3443
  }
@@ -2520,9 +3447,9 @@ function collectJsonlFiles(projectDir) {
2520
3447
  try {
2521
3448
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
2522
3449
  if (entry.isDirectory())
2523
- walk(join2(dir, entry.name));
3450
+ walk(join4(dir, entry.name));
2524
3451
  else if (entry.name.endsWith(".jsonl"))
2525
- files.push(join2(dir, entry.name));
3452
+ files.push(join4(dir, entry.name));
2526
3453
  }
2527
3454
  } catch {}
2528
3455
  }
@@ -2536,7 +3463,7 @@ async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_D
2536
3463
  return ingestJsonlProjects(db, projectsDir, "takumi", verbose);
2537
3464
  }
2538
3465
  async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false) {
2539
- if (!existsSync2(projectsDir)) {
3466
+ if (!existsSync4(projectsDir)) {
2540
3467
  if (verbose)
2541
3468
  console.log(`${agentName} projects dir not found:`, projectsDir);
2542
3469
  return { files: 0, requests: 0, sessions: 0 };
@@ -2547,9 +3474,10 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
2547
3474
  const touchedSessions = new Set;
2548
3475
  const registeredProjects = db.prepare(`SELECT path, name FROM projects ORDER BY LENGTH(path) DESC`).all();
2549
3476
  const account = await resolveAccountForAgent(agentName);
3477
+ const ingestState = loadIngestState(db, agentName);
2550
3478
  const projectDirs = readdirSync2(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
2551
3479
  for (const projectDirEntry of projectDirs) {
2552
- const projectDirPath = join2(projectsDir, projectDirEntry.name);
3480
+ const projectDirPath = join4(projectsDir, projectDirEntry.name);
2553
3481
  const projectPath = dirNameToPath(projectDirEntry.name);
2554
3482
  const jsonlFiles = collectJsonlFiles(projectDirPath);
2555
3483
  for (const filePath of jsonlFiles) {
@@ -2560,12 +3488,12 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
2560
3488
  } catch {
2561
3489
  continue;
2562
3490
  }
2563
- const processed = getIngestState(db, agentName, stateKey);
3491
+ const processed = ingestState.get(stateKey) ?? null;
2564
3492
  if (processed === fileMtime)
2565
3493
  continue;
2566
3494
  let lines;
2567
3495
  try {
2568
- lines = readFileSync(filePath, "utf-8").split(`
3496
+ lines = readFileSync2(filePath, "utf-8").split(`
2569
3497
  `).filter((l) => l.trim());
2570
3498
  } catch {
2571
3499
  continue;
@@ -2685,14 +3613,14 @@ function supportsClaudeDataResidencyPricing(model) {
2685
3613
  // src/ingest/codex.ts
2686
3614
  init_database();
2687
3615
  init_pricing();
2688
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2689
- import { homedir as homedir3 } from "os";
2690
- import { join as join3, basename as basename2 } from "path";
3616
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
3617
+ import { homedir as homedir4 } from "os";
3618
+ import { join as join5, basename as basename2 } from "path";
2691
3619
  import { Database as BunDatabase } from "bun:sqlite";
2692
- var DEFAULT_CODEX_DB_PATH = join3(homedir3(), ".codex", "state_5.sqlite");
2693
- var DEFAULT_CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
2694
- var DEFAULT_CODEWITH_DB_PATH = join3(homedir3(), ".codewith", "state_5.sqlite");
2695
- var DEFAULT_CODEWITH_CONFIG_PATH = join3(homedir3(), ".codewith", "config.toml");
3620
+ var DEFAULT_CODEX_DB_PATH = join5(homedir4(), ".codex", "state_5.sqlite");
3621
+ var DEFAULT_CODEX_CONFIG_PATH = join5(homedir4(), ".codex", "config.toml");
3622
+ var DEFAULT_CODEWITH_DB_PATH = join5(homedir4(), ".codewith", "state_5.sqlite");
3623
+ var DEFAULT_CODEWITH_CONFIG_PATH = join5(homedir4(), ".codewith", "config.toml");
2696
3624
  var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
2697
3625
  function codexDbPath() {
2698
3626
  return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
@@ -2734,10 +3662,10 @@ function codexSources() {
2734
3662
  });
2735
3663
  }
2736
3664
  function readCodexModel(configPath = codexConfigPath()) {
2737
- if (!existsSync3(configPath))
3665
+ if (!existsSync5(configPath))
2738
3666
  return "gpt-5-codex";
2739
3667
  try {
2740
- const content = readFileSync2(configPath, "utf-8");
3668
+ const content = readFileSync3(configPath, "utf-8");
2741
3669
  const match = content.match(/^model\s*=\s*"([^"]+)"/m);
2742
3670
  return match?.[1] ?? "gpt-5-codex";
2743
3671
  } catch {
@@ -2775,12 +3703,12 @@ function openCodexDb(dbPath, verbose) {
2775
3703
  return null;
2776
3704
  }
2777
3705
  function readTokenEvents(rolloutPath) {
2778
- if (!rolloutPath || !existsSync3(rolloutPath))
3706
+ if (!rolloutPath || !existsSync5(rolloutPath))
2779
3707
  return [];
2780
3708
  const fallbackUsages = new Map;
2781
3709
  let fallbackTimestamp;
2782
3710
  let aggregate = null;
2783
- for (const line of readFileSync2(rolloutPath, "utf-8").split(`
3711
+ for (const line of readFileSync3(rolloutPath, "utf-8").split(`
2784
3712
  `)) {
2785
3713
  if (!line.trim())
2786
3714
  continue;
@@ -2856,7 +3784,7 @@ async function ingestCodex(db, verbose = false) {
2856
3784
  let requests = 0;
2857
3785
  const account = await resolveAccountForAgent("codex");
2858
3786
  for (const source of codexSources()) {
2859
- if (!existsSync3(source.dbPath)) {
3787
+ if (!existsSync5(source.dbPath)) {
2860
3788
  if (verbose)
2861
3789
  console.log(`${source.label} DB not found:`, source.dbPath);
2862
3790
  continue;
@@ -2937,11 +3865,11 @@ async function ingestCodex(db, verbose = false) {
2937
3865
  // src/ingest/gemini.ts
2938
3866
  init_database();
2939
3867
  init_pricing();
2940
- import { readdirSync as readdirSync3, readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync3 } from "fs";
2941
- import { homedir as homedir4 } from "os";
2942
- import { join as join4, basename as basename3 } from "path";
2943
- var DEFAULT_GEMINI_TMP_DIR = join4(homedir4(), ".gemini", "tmp");
2944
- var DEFAULT_GEMINI_HISTORY_DIR = join4(homedir4(), ".gemini", "history");
3868
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync6, statSync as statSync3 } from "fs";
3869
+ import { homedir as homedir5 } from "os";
3870
+ import { join as join6, basename as basename3 } from "path";
3871
+ var DEFAULT_GEMINI_TMP_DIR = join6(homedir5(), ".gemini", "tmp");
3872
+ var DEFAULT_GEMINI_HISTORY_DIR = join6(homedir5(), ".gemini", "history");
2945
3873
  function geminiTmpDir() {
2946
3874
  return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
2947
3875
  }
@@ -2958,12 +3886,12 @@ function numberField(...values) {
2958
3886
  function listProjectDirs(...roots) {
2959
3887
  const dirs = new Set;
2960
3888
  for (const root of roots) {
2961
- if (!existsSync4(root))
3889
+ if (!existsSync6(root))
2962
3890
  continue;
2963
3891
  try {
2964
3892
  for (const entry of readdirSync3(root, { withFileTypes: true })) {
2965
3893
  if (entry.isDirectory())
2966
- dirs.add(join4(root, entry.name));
3894
+ dirs.add(join6(root, entry.name));
2967
3895
  }
2968
3896
  } catch {}
2969
3897
  }
@@ -2974,17 +3902,17 @@ function projectRoot(projectDir, chatData) {
2974
3902
  return chatData.projectPath;
2975
3903
  if (chatData.project_path)
2976
3904
  return chatData.project_path;
2977
- const rootFile = join4(projectDir, ".project_root");
3905
+ const rootFile = join6(projectDir, ".project_root");
2978
3906
  try {
2979
- if (existsSync4(rootFile))
2980
- return readFileSync3(rootFile, "utf-8").trim();
3907
+ if (existsSync6(rootFile))
3908
+ return readFileSync4(rootFile, "utf-8").trim();
2981
3909
  } catch {}
2982
3910
  return "";
2983
3911
  }
2984
3912
  async function ingestGemini(db, verbose) {
2985
3913
  const tmpDir = geminiTmpDir();
2986
3914
  const historyDir = geminiHistoryDir();
2987
- if (!existsSync4(tmpDir) && !existsSync4(historyDir)) {
3915
+ if (!existsSync6(tmpDir) && !existsSync6(historyDir)) {
2988
3916
  if (verbose)
2989
3917
  console.log("Gemini tmp/history dirs not found:", tmpDir, historyDir);
2990
3918
  return { sessions: 0, requests: 0 };
@@ -2996,17 +3924,17 @@ async function ingestGemini(db, verbose) {
2996
3924
  const account = await resolveAccountForAgent("gemini");
2997
3925
  const projectDirs = listProjectDirs(tmpDir, historyDir);
2998
3926
  for (const projectDir of projectDirs) {
2999
- const chatsDir = join4(projectDir, "chats");
3000
- if (!existsSync4(chatsDir))
3927
+ const chatsDir = join6(projectDir, "chats");
3928
+ if (!existsSync6(chatsDir))
3001
3929
  continue;
3002
3930
  let chatFiles = [];
3003
3931
  try {
3004
- chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join4(chatsDir, f));
3932
+ chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join6(chatsDir, f));
3005
3933
  } catch {
3006
3934
  continue;
3007
3935
  }
3008
3936
  for (const filePath of chatFiles) {
3009
- const stateKey = filePath.replace(homedir4(), "~");
3937
+ const stateKey = filePath.replace(homedir5(), "~");
3010
3938
  let fileMtime = "0";
3011
3939
  try {
3012
3940
  fileMtime = statSync3(filePath).mtimeMs.toString();
@@ -3018,7 +3946,7 @@ async function ingestGemini(db, verbose) {
3018
3946
  continue;
3019
3947
  let chatData;
3020
3948
  try {
3021
- chatData = JSON.parse(readFileSync3(filePath, "utf-8"));
3949
+ chatData = JSON.parse(readFileSync4(filePath, "utf-8"));
3022
3950
  } catch {
3023
3951
  continue;
3024
3952
  }
@@ -3096,15 +4024,15 @@ async function ingestGemini(db, verbose) {
3096
4024
  // src/ingest/opencode.ts
3097
4025
  init_database();
3098
4026
  init_pricing();
3099
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
3100
- import { homedir as homedir5 } from "os";
3101
- import { join as join5 } from "path";
3102
- var OPENCODE_STORAGE = join5(homedir5(), ".local", "share", "opencode", "storage");
4027
+ import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
4028
+ import { homedir as homedir6 } from "os";
4029
+ import { join as join7 } from "path";
4030
+ var OPENCODE_STORAGE = join7(homedir6(), ".local", "share", "opencode", "storage");
3103
4031
  function walkJsonFiles(dir, acc = []) {
3104
- if (!existsSync5(dir))
4032
+ if (!existsSync7(dir))
3105
4033
  return acc;
3106
4034
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
3107
- const p = join5(dir, entry.name);
4035
+ const p = join7(dir, entry.name);
3108
4036
  if (entry.isDirectory())
3109
4037
  walkJsonFiles(p, acc);
3110
4038
  else if (entry.name.endsWith(".json"))
@@ -3124,12 +4052,12 @@ function parseSessionIdFromPath(filePath) {
3124
4052
  return null;
3125
4053
  }
3126
4054
  async function ingestOpenCode(db, verbose = false) {
3127
- const messageDir = join5(OPENCODE_STORAGE, "message");
4055
+ const messageDir = join7(OPENCODE_STORAGE, "message");
3128
4056
  const files = walkJsonFiles(messageDir);
3129
4057
  let requests = 0;
3130
4058
  const touched = new Set;
3131
4059
  const machineId = getMachineId();
3132
- const now = new Date().toISOString();
4060
+ const now2 = new Date().toISOString();
3133
4061
  const account = await resolveAccountForAgent("opencode");
3134
4062
  for (const file of files) {
3135
4063
  const mtime = statSync4(file).mtimeMs;
@@ -3139,7 +4067,7 @@ async function ingestOpenCode(db, verbose = false) {
3139
4067
  continue;
3140
4068
  let parsed;
3141
4069
  try {
3142
- parsed = JSON.parse(readFileSync4(file, "utf-8"));
4070
+ parsed = JSON.parse(readFileSync5(file, "utf-8"));
3143
4071
  } catch {
3144
4072
  continue;
3145
4073
  }
@@ -3175,7 +4103,7 @@ async function ingestOpenCode(db, verbose = false) {
3175
4103
  timestamp,
3176
4104
  source_request_id: sourceId,
3177
4105
  machine_id: machineId,
3178
- updated_at: now
4106
+ updated_at: now2
3179
4107
  }, account));
3180
4108
  requests++;
3181
4109
  if (!touched.has(sessionId)) {
@@ -3190,7 +4118,7 @@ async function ingestOpenCode(db, verbose = false) {
3190
4118
  total_tokens: 0,
3191
4119
  request_count: 0,
3192
4120
  machine_id: machineId,
3193
- updated_at: now
4121
+ updated_at: now2
3194
4122
  }, account));
3195
4123
  touched.add(sessionId);
3196
4124
  }
@@ -3236,7 +4164,7 @@ async function ingestCursor(db, verbose = false) {
3236
4164
  if (prev)
3237
4165
  return { requests: 0, snapshots: 0 };
3238
4166
  const machineId = getMachineId();
3239
- const now = new Date().toISOString();
4167
+ const now2 = new Date().toISOString();
3240
4168
  let snapshots = 0;
3241
4169
  const account = await resolveAccountForAgent("cursor");
3242
4170
  const usage = await cursorFetch("/api/usage", token);
@@ -3292,12 +4220,12 @@ async function ingestCursor(db, verbose = false) {
3292
4220
  project_path: "",
3293
4221
  project_name: "Cursor subscription",
3294
4222
  started_at: `${today}T00:00:00.000Z`,
3295
- ended_at: now,
4223
+ ended_at: now2,
3296
4224
  total_cost_usd: onDemand + included,
3297
4225
  total_tokens: 0,
3298
4226
  request_count: 1,
3299
4227
  machine_id: machineId,
3300
- updated_at: now
4228
+ updated_at: now2
3301
4229
  }, account));
3302
4230
  upsertRequest(db, withAccount({
3303
4231
  id: `cursor-${today}-${machineId}-usage`,
@@ -3311,14 +4239,14 @@ async function ingestCursor(db, verbose = false) {
3311
4239
  cost_usd: onDemand + included,
3312
4240
  cost_basis: "subscription_included",
3313
4241
  duration_ms: 0,
3314
- timestamp: now,
4242
+ timestamp: now2,
3315
4243
  source_request_id: today,
3316
4244
  machine_id: machineId,
3317
- updated_at: now
4245
+ updated_at: now2
3318
4246
  }, account));
3319
4247
  rollupSession(db, sessionId);
3320
4248
  }
3321
- setIngestState(db, "cursor", `sync-${today}`, now);
4249
+ setIngestState(db, "cursor", `sync-${today}`, now2);
3322
4250
  if (verbose)
3323
4251
  console.log(` cursor: on-demand $${onDemand.toFixed(2)}, included $${included.toFixed(2)}`);
3324
4252
  return { requests: onDemand + included > 0 ? 1 : 0, snapshots };
@@ -3326,15 +4254,15 @@ async function ingestCursor(db, verbose = false) {
3326
4254
 
3327
4255
  // src/ingest/pi.ts
3328
4256
  init_database();
3329
- import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
3330
- import { homedir as homedir6 } from "os";
3331
- import { join as join6 } from "path";
3332
- var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join6(homedir6(), ".pi", "agent", "sessions");
4257
+ import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
4258
+ import { homedir as homedir7 } from "os";
4259
+ import { join as join8 } from "path";
4260
+ var PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join8(homedir7(), ".pi", "agent", "sessions");
3333
4261
  function walkSessions(dir, acc = []) {
3334
- if (!existsSync6(dir))
4262
+ if (!existsSync8(dir))
3335
4263
  return acc;
3336
4264
  for (const entry of readdirSync5(dir, { withFileTypes: true })) {
3337
- const p = join6(dir, entry.name);
4265
+ const p = join8(dir, entry.name);
3338
4266
  if (entry.isDirectory())
3339
4267
  walkSessions(p, acc);
3340
4268
  else if (entry.name.endsWith(".json"))
@@ -3347,7 +4275,7 @@ async function ingestPi(db, verbose = false) {
3347
4275
  let requests = 0;
3348
4276
  const touched = new Set;
3349
4277
  const machineId = getMachineId();
3350
- const now = new Date().toISOString();
4278
+ const now2 = new Date().toISOString();
3351
4279
  const account = await resolveAccountForAgent("pi");
3352
4280
  for (const file of files) {
3353
4281
  const mtime = statSync5(file).mtimeMs;
@@ -3356,7 +4284,7 @@ async function ingestPi(db, verbose = false) {
3356
4284
  continue;
3357
4285
  let data;
3358
4286
  try {
3359
- data = JSON.parse(readFileSync5(file, "utf-8"));
4287
+ data = JSON.parse(readFileSync6(file, "utf-8"));
3360
4288
  } catch {
3361
4289
  continue;
3362
4290
  }
@@ -3389,7 +4317,7 @@ async function ingestPi(db, verbose = false) {
3389
4317
  timestamp,
3390
4318
  source_request_id: `${sessionId}-${i}`,
3391
4319
  machine_id: machineId,
3392
- updated_at: now
4320
+ updated_at: now2
3393
4321
  }, account));
3394
4322
  requests++;
3395
4323
  }
@@ -3399,13 +4327,13 @@ async function ingestPi(db, verbose = false) {
3399
4327
  agent: "pi",
3400
4328
  project_path: "",
3401
4329
  project_name: "",
3402
- started_at: turns[0]?.timestamp ?? now,
4330
+ started_at: turns[0]?.timestamp ?? now2,
3403
4331
  ended_at: null,
3404
4332
  total_cost_usd: 0,
3405
4333
  total_tokens: 0,
3406
4334
  request_count: 0,
3407
4335
  machine_id: machineId,
3408
- updated_at: now
4336
+ updated_at: now2
3409
4337
  }, account));
3410
4338
  touched.add(sessionId);
3411
4339
  }
@@ -3420,10 +4348,10 @@ async function ingestPi(db, verbose = false) {
3420
4348
 
3421
4349
  // src/ingest/hermes.ts
3422
4350
  init_database();
3423
- import { existsSync as existsSync7, statSync as statSync6 } from "fs";
3424
- import { homedir as homedir7 } from "os";
3425
- import { join as join7 } from "path";
3426
- var HERMES_DB = join7(homedir7(), ".hermes", "state.db");
4351
+ import { existsSync as existsSync9, statSync as statSync6 } from "fs";
4352
+ import { homedir as homedir8 } from "os";
4353
+ import { join as join9 } from "path";
4354
+ var HERMES_DB = join9(homedir8(), ".hermes", "state.db");
3427
4355
  function mapCostBasis(billingMode) {
3428
4356
  if (billingMode === "subscription")
3429
4357
  return "subscription_included";
@@ -3432,7 +4360,7 @@ function mapCostBasis(billingMode) {
3432
4360
  return defaultCostBasisForAgent("hermes");
3433
4361
  }
3434
4362
  async function ingestHermes(db, verbose = false) {
3435
- if (!existsSync7(HERMES_DB)) {
4363
+ if (!existsSync9(HERMES_DB)) {
3436
4364
  return { sessions: 0, requests: 0 };
3437
4365
  }
3438
4366
  const { Database: Sqlite } = await import("bun:sqlite");
@@ -3452,7 +4380,7 @@ async function ingestHermes(db, verbose = false) {
3452
4380
  return { sessions: 0, requests: 0 };
3453
4381
  }
3454
4382
  const machineId = getMachineId();
3455
- const now = new Date().toISOString();
4383
+ const now2 = new Date().toISOString();
3456
4384
  let requests = 0;
3457
4385
  const account = await resolveAccountForAgent("hermes");
3458
4386
  for (const row of rows) {
@@ -3472,7 +4400,7 @@ async function ingestHermes(db, verbose = false) {
3472
4400
  total_tokens: tokens,
3473
4401
  request_count: 1,
3474
4402
  machine_id: machineId,
3475
- updated_at: now
4403
+ updated_at: now2
3476
4404
  }, account));
3477
4405
  const reqId = `hermes-${row.id}-rollup`;
3478
4406
  upsertRequest(db, withAccount({
@@ -3490,7 +4418,7 @@ async function ingestHermes(db, verbose = false) {
3490
4418
  timestamp: endedAt ?? startedAt,
3491
4419
  source_request_id: row.id,
3492
4420
  machine_id: machineId,
3493
- updated_at: now
4421
+ updated_at: now2
3494
4422
  }, account));
3495
4423
  requests++;
3496
4424
  rollupSession(db, sessionId);
@@ -3512,19 +4440,78 @@ function statSyncSafe(path) {
3512
4440
  // src/ingest/loops.ts
3513
4441
  init_database();
3514
4442
  init_pricing();
3515
- import { existsSync as existsSync8 } from "fs";
3516
- import { homedir as homedir8 } from "os";
3517
- import { join as join8 } from "path";
4443
+ import { existsSync as existsSync10 } from "fs";
4444
+ import { homedir as homedir9 } from "os";
4445
+ import { join as join10 } from "path";
3518
4446
  import { Database as BunDatabase2 } from "bun:sqlite";
3519
- import { dataDir as dataDir2 } from "@hasna/paths";
4447
+ import { homedir as pathsResolverHomedir2 } from "os";
4448
+ import { join as pathsResolverJoin2 } from "path";
4449
+ var PATHS_RESOLVER_KIND_ENV2 = {
4450
+ config: "HASNA_CONFIG_HOME",
4451
+ data: "HASNA_DATA_HOME",
4452
+ state: "HASNA_STATE_HOME",
4453
+ cache: "HASNA_CACHE_HOME"
4454
+ };
4455
+ var PATHS_RESOLVER_APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4456
+ function pathsResolverAssertApp2(app) {
4457
+ if (typeof app !== "string" || app.length === 0) {
4458
+ throw new TypeError("paths: app must be a non-empty string");
4459
+ }
4460
+ if (!PATHS_RESOLVER_APP_SLUG_RE2.test(app)) {
4461
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
4462
+ }
4463
+ }
4464
+ function pathsResolverAssertKind2(kind) {
4465
+ if (!Object.keys(PATHS_RESOLVER_KIND_ENV2).includes(kind)) {
4466
+ throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV2).join(", ")}`);
4467
+ }
4468
+ }
4469
+ function pathsResolverBaseDir2(kind, options) {
4470
+ pathsResolverAssertKind2(kind);
4471
+ const env = options.env ?? process.env;
4472
+ const override = env[PATHS_RESOLVER_KIND_ENV2[kind]];
4473
+ if (typeof override === "string" && override.length > 0)
4474
+ return override;
4475
+ const home = options.home ?? pathsResolverHomedir2();
4476
+ const platform2 = options.platform ?? process.platform;
4477
+ if (platform2 === "darwin") {
4478
+ switch (kind) {
4479
+ case "config":
4480
+ case "data":
4481
+ return pathsResolverJoin2(home, "Library", "Application Support", "Hasna");
4482
+ case "cache":
4483
+ return pathsResolverJoin2(home, "Library", "Caches", "Hasna");
4484
+ case "state":
4485
+ return pathsResolverJoin2(home, "Library", "Logs", "Hasna");
4486
+ }
4487
+ }
4488
+ switch (kind) {
4489
+ case "config":
4490
+ return pathsResolverJoin2(home, ".config", "hasna");
4491
+ case "data":
4492
+ return pathsResolverJoin2(home, ".local", "share", "hasna");
4493
+ case "state":
4494
+ return pathsResolverJoin2(home, ".local", "state", "hasna");
4495
+ case "cache":
4496
+ return pathsResolverJoin2(home, ".cache", "hasna");
4497
+ }
4498
+ }
4499
+ function pathsResolverResolve2(kind, options) {
4500
+ pathsResolverAssertApp2(options.app);
4501
+ const appSegment = options.internal === true ? pathsResolverJoin2("internal", options.app) : options.app;
4502
+ return pathsResolverJoin2(pathsResolverBaseDir2(kind, options), appSegment);
4503
+ }
4504
+ function dataDir2(options) {
4505
+ return pathsResolverResolve2("data", options);
4506
+ }
3520
4507
  var LOOPS_INGEST_VERSION = "goal-runs-orchestration-v1";
3521
4508
  function defaultLoopsDbPath() {
3522
4509
  const resolved = dataDir2({
3523
4510
  app: "loops",
3524
4511
  home: process.env["HOME"] || process.env["USERPROFILE"] || undefined
3525
4512
  });
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;
4513
+ const legacy = join10(process.env["HOME"] || process.env["USERPROFILE"] || homedir9(), ".hasna", "loops", "loops.db");
4514
+ return existsSync10(join10(resolved, "loops.db")) ? join10(resolved, "loops.db") : legacy;
3528
4515
  }
3529
4516
  function loopsDbPath() {
3530
4517
  return process.env["HASNA_ECONOMY_LOOPS_DB_PATH"] ?? defaultLoopsDbPath();
@@ -3616,7 +4603,7 @@ function readLoopGoalRuns(loopsDb) {
3616
4603
  }
3617
4604
  async function ingestLoops(db, verbose = false) {
3618
4605
  const dbPath = loopsDbPath();
3619
- if (!existsSync8(dbPath)) {
4606
+ if (!existsSync10(dbPath)) {
3620
4607
  if (verbose)
3621
4608
  console.log("OpenLoops DB not found:", dbPath);
3622
4609
  return { sessions: 0, requests: 0 };
@@ -3706,30 +4693,30 @@ async function ingestLoops(db, verbose = false) {
3706
4693
 
3707
4694
  // src/ingest/claude-quota.ts
3708
4695
  init_database();
3709
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
4696
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
3710
4697
 
3711
4698
  // src/lib/paths.ts
3712
- import { homedir as homedir9 } from "os";
3713
- import { join as join9 } from "path";
4699
+ import { homedir as homedir10 } from "os";
4700
+ import { join as join11 } from "path";
3714
4701
  function getHomeDir2() {
3715
- return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir9();
4702
+ return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir10();
3716
4703
  }
3717
4704
  function agentPaths() {
3718
4705
  const home = getHomeDir2();
3719
4706
  return {
3720
- claudeProjects: join9(home, ".claude", "projects"),
3721
- claudeCredentials: join9(home, ".claude", ".credentials.json"),
3722
- takumiProjects: join9(home, ".takumi", "projects"),
3723
- codexDir: join9(home, ".codex"),
3724
- codexDb: join9(home, ".codex", "state_5.sqlite"),
3725
- codexAuth: join9(home, ".codex", "auth.json"),
3726
- codexConfig: join9(home, ".codex", "config.toml"),
3727
- geminiTmp: join9(home, ".gemini", "tmp"),
3728
- geminiHistory: join9(home, ".gemini", "history"),
3729
- opencodeMessages: join9(home, ".local", "share", "opencode", "storage", "message"),
3730
- piSessions: join9(home, ".pi", "agent", "sessions"),
3731
- hermesDir: join9(home, ".hermes"),
3732
- hermesDb: join9(home, ".hermes", "state.db")
4707
+ claudeProjects: join11(home, ".claude", "projects"),
4708
+ claudeCredentials: join11(home, ".claude", ".credentials.json"),
4709
+ takumiProjects: join11(home, ".takumi", "projects"),
4710
+ codexDir: join11(home, ".codex"),
4711
+ codexDb: join11(home, ".codex", "state_5.sqlite"),
4712
+ codexAuth: join11(home, ".codex", "auth.json"),
4713
+ codexConfig: join11(home, ".codex", "config.toml"),
4714
+ geminiTmp: join11(home, ".gemini", "tmp"),
4715
+ geminiHistory: join11(home, ".gemini", "history"),
4716
+ opencodeMessages: join11(home, ".local", "share", "opencode", "storage", "message"),
4717
+ piSessions: join11(home, ".pi", "agent", "sessions"),
4718
+ hermesDir: join11(home, ".hermes"),
4719
+ hermesDb: join11(home, ".hermes", "state.db")
3733
4720
  };
3734
4721
  }
3735
4722
 
@@ -3741,10 +4728,10 @@ function readClaudeToken() {
3741
4728
  const fromEnv = process.env["CLAUDE_OAUTH_TOKEN"] ?? process.env["ANTHROPIC_OAUTH_TOKEN"];
3742
4729
  if (fromEnv)
3743
4730
  return { token: fromEnv };
3744
- if (!existsSync9(CREDENTIALS_PATH))
4731
+ if (!existsSync11(CREDENTIALS_PATH))
3745
4732
  return null;
3746
4733
  try {
3747
- const creds = JSON.parse(readFileSync6(CREDENTIALS_PATH, "utf-8"));
4734
+ const creds = JSON.parse(readFileSync7(CREDENTIALS_PATH, "utf-8"));
3748
4735
  const oauth = creds.claudeAiOauth;
3749
4736
  if (!oauth?.accessToken)
3750
4737
  return null;
@@ -3858,7 +4845,7 @@ async function ingestClaudeQuota(db, verbose = false) {
3858
4845
  snapshots++;
3859
4846
  }
3860
4847
  const monthlyFee = inferMonthlyFee(auth.subscriptionType, auth.rateLimitTier);
3861
- const now = new Date().toISOString();
4848
+ const now2 = new Date().toISOString();
3862
4849
  upsertSubscription(db, {
3863
4850
  id: "anthropic-claude-oauth",
3864
4851
  provider: "anthropic",
@@ -3869,8 +4856,8 @@ async function ingestClaudeQuota(db, verbose = false) {
3869
4856
  billing_cycle_start: null,
3870
4857
  reset_policy: "monthly",
3871
4858
  active: 1,
3872
- created_at: now,
3873
- updated_at: now
4859
+ created_at: now2,
4860
+ updated_at: now2
3874
4861
  });
3875
4862
  setIngestState(db, "claude", `quota-${today}`, new Date().toISOString());
3876
4863
  if (verbose)
@@ -3880,17 +4867,17 @@ async function ingestClaudeQuota(db, verbose = false) {
3880
4867
 
3881
4868
  // src/ingest/codex-quota.ts
3882
4869
  init_database();
3883
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
4870
+ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
3884
4871
  var WHAM_USAGE_URL = process.env["CODEX_USAGE_URL"] ?? "https://chatgpt.com/backend-api/wham/usage";
3885
4872
  function readCodexAuth() {
3886
4873
  const fromEnv = process.env["CODEX_OAUTH_TOKEN"];
3887
4874
  if (fromEnv)
3888
4875
  return { token: fromEnv, authMode: "chatgpt" };
3889
4876
  const authPath = agentPaths().codexAuth;
3890
- if (!existsSync10(authPath))
4877
+ if (!existsSync12(authPath))
3891
4878
  return null;
3892
4879
  try {
3893
- const auth = JSON.parse(readFileSync7(authPath, "utf-8"));
4880
+ const auth = JSON.parse(readFileSync8(authPath, "utf-8"));
3894
4881
  const token = auth.tokens?.access_token;
3895
4882
  if (!token)
3896
4883
  return null;
@@ -3957,7 +4944,7 @@ async function ingestCodexQuota(db, verbose = false) {
3957
4944
  }
3958
4945
  const machineId = getMachineId();
3959
4946
  let snapshots = 0;
3960
- const now = new Date().toISOString();
4947
+ const now2 = new Date().toISOString();
3961
4948
  const windows = [
3962
4949
  ["five_hour_utilization", usage.rate_limit?.primary_window],
3963
4950
  ["seven_day_utilization", usage.rate_limit?.secondary_window]
@@ -4008,10 +4995,10 @@ async function ingestCodexQuota(db, verbose = false) {
4008
4995
  billing_cycle_start: null,
4009
4996
  reset_policy: "monthly",
4010
4997
  active: 1,
4011
- created_at: now,
4012
- updated_at: now
4998
+ created_at: now2,
4999
+ updated_at: now2
4013
5000
  });
4014
- setIngestState(db, "codex", `quota-${today}`, now);
5001
+ setIngestState(db, "codex", `quota-${today}`, now2);
4015
5002
  if (verbose)
4016
5003
  console.log(` codex quota: ${snapshots} snapshots (${usage.plan_type ?? "unknown plan"})`);
4017
5004
  return { snapshots };
@@ -4053,7 +5040,8 @@ async function syncAll(db, opts = {}) {
4053
5040
  // src/lib/cloud-ingest.ts
4054
5041
  init_database();
4055
5042
  init_pricing();
4056
- import { join as join10 } from "path";
5043
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync9, renameSync, writeFileSync } from "fs";
5044
+ import { dirname as dirname2, join as join12 } from "path";
4057
5045
 
4058
5046
  // src/ingest/billing.ts
4059
5047
  init_database();
@@ -4097,15 +5085,39 @@ var CLOUD_INGEST_TABLES = ["requests", "sessions", "billing_daily", "usage_snaps
4097
5085
  function getIngestCachePath() {
4098
5086
  if (process.env["HASNA_ECONOMY_INGEST_CACHE"])
4099
5087
  return process.env["HASNA_ECONOMY_INGEST_CACHE"];
4100
- return join10(getDataDir(), "ingest-cache.db");
5088
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || undefined;
5089
+ return join12(cacheDir({ app: "economy", home }), "ingest-cache.json");
5090
+ }
5091
+ function readIngestCache(path) {
5092
+ let text;
5093
+ try {
5094
+ text = readFileSync9(path, "utf8");
5095
+ } catch {
5096
+ return [];
5097
+ }
5098
+ try {
5099
+ const parsed = JSON.parse(text);
5100
+ const rows = Array.isArray(parsed?.ingest_state) ? parsed.ingest_state : [];
5101
+ return rows.filter((row) => typeof row === "object" && row !== null && typeof row.source === "string" && typeof row.key === "string" && typeof row.value === "string");
5102
+ } catch {
5103
+ return [];
5104
+ }
5105
+ }
5106
+ function writeIngestCache(path, rows) {
5107
+ mkdirSync3(dirname2(path), { recursive: true });
5108
+ const tmp = `${path}.${process.pid}.tmp`;
5109
+ writeFileSync(tmp, JSON.stringify({ version: 1, ingest_state: rows }), { mode: 384 });
5110
+ renameSync(tmp, path);
4101
5111
  }
4102
- function copyIngestState(from, to) {
4103
- const rows = from.prepare(`SELECT source, key, value FROM ingest_state`).all();
4104
- to.prepare(`DELETE FROM ingest_state`).run();
4105
- const insert = to.prepare(`INSERT INTO ingest_state (source, key, value) VALUES (?, ?, ?)`);
5112
+ function loadIngestState2(scratch, rows) {
5113
+ scratch.prepare(`DELETE FROM ingest_state`).run();
5114
+ const insert = scratch.prepare(`INSERT INTO ingest_state (source, key, value) VALUES (?, ?, ?)`);
4106
5115
  for (const row of rows)
4107
5116
  insert.run(row.source, row.key, row.value);
4108
5117
  }
5118
+ function dumpIngestState(scratch) {
5119
+ return scratch.prepare(`SELECT source, key, value FROM ingest_state`).all();
5120
+ }
4109
5121
  function exportIngestRows(db) {
4110
5122
  const body = {};
4111
5123
  for (const table of CLOUD_INGEST_TABLES) {
@@ -4133,11 +5145,9 @@ async function postIngest(cloud, body) {
4133
5145
  async function syncAllToCloud(cloud, opts = {}) {
4134
5146
  const scratch = openDatabase(":memory:", true);
4135
5147
  ensurePricingSeeded(scratch);
4136
- const cache = openDatabase(opts.cachePath ?? getIngestCachePath(), true);
5148
+ const cachePath = opts.cachePath ?? getIngestCachePath();
4137
5149
  try {
4138
- if (opts.force)
4139
- cache.prepare(`DELETE FROM ingest_state`).run();
4140
- copyIngestState(cache, scratch);
5150
+ loadIngestState2(scratch, opts.force ? [] : readIngestCache(cachePath));
4141
5151
  const result = await syncAll(scratch, opts);
4142
5152
  if (opts.backfillMachine)
4143
5153
  backfillMachineId(scratch);
@@ -4145,61 +5155,19 @@ async function syncAllToCloud(cloud, opts = {}) {
4145
5155
  await recalculateZeroCostRequests(scratch);
4146
5156
  const body = exportIngestRows(scratch);
4147
5157
  if (Object.keys(body).length === 0) {
4148
- copyIngestState(scratch, cache);
5158
+ writeIngestCache(cachePath, dumpIngestState(scratch));
4149
5159
  return { ...result, posted: false };
4150
5160
  }
4151
5161
  const response = await postIngest(cloud, body);
4152
- copyIngestState(scratch, cache);
5162
+ writeIngestCache(cachePath, dumpIngestState(scratch));
4153
5163
  return { ...result, posted: true, ingested: response.ingested, total: response.total };
4154
5164
  } finally {
4155
5165
  try {
4156
5166
  scratch.close();
4157
5167
  } catch {}
4158
- try {
4159
- cache.close();
4160
- } catch {}
4161
5168
  }
4162
5169
  }
4163
5170
 
4164
- // src/lib/contracts-client/storage.ts
4165
- import { resolveStorageClient } from "@hasna/contracts/client/storage";
4166
-
4167
- // src/lib/cloud-storage.ts
4168
- var ECONOMY_APP = "economy";
4169
- var cache;
4170
- function resolveEconomyCloudStorage(env = process.env, overrides) {
4171
- const resolved = resolveStorageClient(ECONOMY_APP, env, overrides);
4172
- return resolved.transport === "http" ? { active: true, client: resolved.client } : { active: false, client: null };
4173
- }
4174
- function economyCloudStorage(env = process.env) {
4175
- if (cache === undefined)
4176
- cache = resolveEconomyCloudStorage(env);
4177
- return cache;
4178
- }
4179
- function cleanQuery(query) {
4180
- if (!query)
4181
- return;
4182
- const out = {};
4183
- for (const [k, v] of Object.entries(query)) {
4184
- if (v !== undefined && v !== null && v !== "")
4185
- out[k] = v;
4186
- }
4187
- return Object.keys(out).length > 0 ? out : undefined;
4188
- }
4189
- async function cloudListItems(storage, resource, query) {
4190
- const cleaned = cleanQuery(query);
4191
- const res = await storage.client.list(resource, cleaned ? { query: cleaned } : {});
4192
- return res.items;
4193
- }
4194
- async function cloudObject(storage, path, query) {
4195
- const cleaned = cleanQuery(query);
4196
- const raw = await storage.client.transport.get(path, cleaned ? { query: cleaned } : {});
4197
- if (raw && typeof raw === "object" && "data" in raw) {
4198
- return raw.data;
4199
- }
4200
- return raw;
4201
- }
4202
-
4203
5171
  // src/lib/agents.ts
4204
5172
  var AGENTS = [
4205
5173
  "claude",
@@ -4297,24 +5265,24 @@ function ymd(date) {
4297
5265
  return date.toISOString().substring(0, 10);
4298
5266
  }
4299
5267
  function usageSnapshotFilterForPeriod(period) {
4300
- const now = new Date;
5268
+ const now2 = new Date;
4301
5269
  switch (period) {
4302
5270
  case "today":
4303
- return { date: ymd(now) };
5271
+ return { date: ymd(now2) };
4304
5272
  case "yesterday": {
4305
- const yesterday = new Date(now);
5273
+ const yesterday = new Date(now2);
4306
5274
  yesterday.setUTCDate(yesterday.getUTCDate() - 1);
4307
5275
  return { date: ymd(yesterday) };
4308
5276
  }
4309
5277
  case "week": {
4310
- const weekAgo = new Date(now);
5278
+ const weekAgo = new Date(now2);
4311
5279
  weekAgo.setUTCDate(weekAgo.getUTCDate() - 7);
4312
5280
  return { since: ymd(weekAgo) };
4313
5281
  }
4314
5282
  case "month":
4315
- return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))) };
5283
+ return { since: ymd(new Date(Date.UTC(now2.getUTCFullYear(), now2.getUTCMonth(), 1))) };
4316
5284
  case "year":
4317
- return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), 0, 1))) };
5285
+ return { since: ymd(new Date(Date.UTC(now2.getUTCFullYear(), 0, 1))) };
4318
5286
  case "all":
4319
5287
  return {};
4320
5288
  }
@@ -4343,13 +5311,13 @@ var ZERO_SESSION_TOTALS = {
4343
5311
  cost_usd: 0,
4344
5312
  last_active: null
4345
5313
  };
4346
- function formatAge(timestamp, now) {
5314
+ function formatAge(timestamp, now2) {
4347
5315
  if (!timestamp)
4348
5316
  return "never";
4349
5317
  const t = new Date(timestamp).getTime();
4350
5318
  if (!Number.isFinite(t))
4351
5319
  return "unknown";
4352
- const ageMs = Math.max(0, now.getTime() - t);
5320
+ const ageMs = Math.max(0, now2.getTime() - t);
4353
5321
  const minutes = Math.floor(ageMs / 60000);
4354
5322
  if (minutes < 1)
4355
5323
  return "<1m";
@@ -4498,7 +5466,7 @@ function querySinceTotals(db, since, machine) {
4498
5466
  const sessions = querySessionOnlyTotals(db, "started_at >= ?", [since], machine);
4499
5467
  return combineTotals(requests, sessions);
4500
5468
  }
4501
- function parseSinceDate(input, now) {
5469
+ function parseSinceDate(input, now2) {
4502
5470
  const value = input.trim();
4503
5471
  const match = value.match(/^(\d+(?:\.\d+)?)(m|h|d|w)$/i);
4504
5472
  if (match) {
@@ -4506,7 +5474,7 @@ function parseSinceDate(input, now) {
4506
5474
  const unit = match[2].toLowerCase();
4507
5475
  const multiplier = unit === "m" ? 60000 : unit === "h" ? 60 * 60000 : unit === "d" ? 24 * 60 * 60000 : 7 * 24 * 60 * 60000;
4508
5476
  return {
4509
- timestamp: new Date(now.getTime() - amount * multiplier).toISOString(),
5477
+ timestamp: new Date(now2.getTime() - amount * multiplier).toISOString(),
4510
5478
  label: value
4511
5479
  };
4512
5480
  }
@@ -4847,7 +5815,7 @@ function accountBreakdownToBriefRow(row) {
4847
5815
  last_active: row.last_active
4848
5816
  };
4849
5817
  }
4850
- function queryFreshness(db, now, machine, localSync) {
5818
+ function queryFreshness(db, now2, machine, localSync) {
4851
5819
  const requestFilter = machine ? " AND machine_id = ?" : "";
4852
5820
  const requestRows = db.prepare(`
4853
5821
  SELECT machine_id, MAX(timestamp) as max_request_at
@@ -4861,7 +5829,7 @@ function queryFreshness(db, now, machine, localSync) {
4861
5829
  machines.set(row.machine_id, {
4862
5830
  machine_id: row.machine_id,
4863
5831
  max_request_at: row.max_request_at,
4864
- request_age: formatAge(row.max_request_at, now),
5832
+ request_age: formatAge(row.max_request_at, now2),
4865
5833
  last_merge_sync_at: null,
4866
5834
  merge_sync_age: "never"
4867
5835
  });
@@ -4876,7 +5844,7 @@ function queryFreshness(db, now, machine, localSync) {
4876
5844
  };
4877
5845
  const syncAt = latestTimestamp(row.last_pull_at, row.last_push_at, row.updated_at);
4878
5846
  current.last_merge_sync_at = syncAt;
4879
- current.merge_sync_age = formatAge(syncAt, now);
5847
+ current.merge_sync_age = formatAge(syncAt, now2);
4880
5848
  machines.set(row.machine_id, current);
4881
5849
  }
4882
5850
  if (localSync && (!machine || machine === localSync.machineId)) {
@@ -4888,7 +5856,7 @@ function queryFreshness(db, now, machine, localSync) {
4888
5856
  merge_sync_age: "never"
4889
5857
  };
4890
5858
  current.last_merge_sync_at = latestTimestamp(current.last_merge_sync_at, localSync.syncedAt);
4891
- current.merge_sync_age = formatAge(current.last_merge_sync_at, now);
5859
+ current.merge_sync_age = formatAge(current.last_merge_sync_at, now2);
4892
5860
  machines.set(localSync.machineId, current);
4893
5861
  }
4894
5862
  const rows = [...machines.values()].sort((a, b) => a.machine_id.localeCompare(b.machine_id));
@@ -4899,10 +5867,10 @@ function queryFreshness(db, now, machine, localSync) {
4899
5867
  };
4900
5868
  }
4901
5869
  function buildBrief(db, opts = {}) {
4902
- const now = opts.now ?? new Date;
5870
+ const now2 = opts.now ?? new Date;
4903
5871
  const machine = machineFilter(opts.machine);
4904
5872
  const sinceInput = opts.since ?? "24h";
4905
- const since = parseSinceDate(sinceInput, now);
5873
+ const since = parseSinceDate(sinceInput, now2);
4906
5874
  const summaries = [
4907
5875
  {
4908
5876
  label: "Today",
@@ -4923,10 +5891,10 @@ function buildBrief(db, opts = {}) {
4923
5891
  ];
4924
5892
  const machines = queryMachineSinceRows(db, since.timestamp, machine);
4925
5893
  for (const row of machines) {
4926
- row.last_data_age = formatAge(row.last_data_at, now);
5894
+ row.last_data_age = formatAge(row.last_data_at, now2);
4927
5895
  }
4928
5896
  return {
4929
- generated_at: now.toISOString(),
5897
+ generated_at: now2.toISOString(),
4930
5898
  machine: machine ?? "all",
4931
5899
  since: {
4932
5900
  input: sinceInput,
@@ -4937,7 +5905,7 @@ function buildBrief(db, opts = {}) {
4937
5905
  machines,
4938
5906
  agents: queryAgentSinceRows(db, since.timestamp, machine),
4939
5907
  accounts: queryAccountSinceRows(db, since.timestamp, machine),
4940
- freshness: queryFreshness(db, now, machine, opts.currentMachineId && opts.localSyncAt ? { machineId: opts.currentMachineId, syncedAt: opts.localSyncAt.toISOString() } : undefined)
5908
+ freshness: queryFreshness(db, now2, machine, opts.currentMachineId && opts.localSyncAt ? { machineId: opts.currentMachineId, syncedAt: opts.localSyncAt.toISOString() } : undefined)
4941
5909
  };
4942
5910
  }
4943
5911
 
@@ -5001,14 +5969,14 @@ function queryRangeStats(db, from, to) {
5001
5969
  const s = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE DATE(started_at) BETWEEN ? AND ?`).get(from, to);
5002
5970
  return { cost: r.cost, requests: r.requests, tokens: r.tokens, sessions: s.sessions };
5003
5971
  }
5004
- function queryForecast(db, now = new Date) {
5005
- const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
5006
- const dayOfMonth = now.getDate();
5007
- const monthStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-01`;
5972
+ function queryForecast(db, now2 = new Date) {
5973
+ const daysInMonth = new Date(now2.getFullYear(), now2.getMonth() + 1, 0).getDate();
5974
+ const dayOfMonth = now2.getDate();
5975
+ const monthStart = `${now2.getFullYear()}-${String(now2.getMonth() + 1).padStart(2, "0")}-01`;
5008
5976
  const monthSoFar = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM requests WHERE DATE(timestamp) >= ?`).get(monthStart);
5009
5977
  const dailyAvg = dayOfMonth > 0 ? monthSoFar.cost / dayOfMonth : 0;
5010
5978
  const projected = dailyAvg * daysInMonth;
5011
- const sevenDaysAgo = new Date(now);
5979
+ const sevenDaysAgo = new Date(now2);
5012
5980
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
5013
5981
  const last7 = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM requests WHERE DATE(timestamp) >= ?`).get(sevenDaysAgo.toISOString().substring(0, 10));
5014
5982
  const last7DailyAvg = last7.cost / 7;
@@ -5038,7 +6006,7 @@ function queryModelEfficiency(db) {
5038
6006
  }
5039
6007
 
5040
6008
  // src/lib/store/index.ts
5041
- class LocalStore {
6009
+ class LocalStore2 {
5042
6010
  transport = "local";
5043
6011
  _db;
5044
6012
  db() {
@@ -5154,8 +6122,8 @@ class LocalStore {
5154
6122
  return estimateCostFromRows(listModelPricing(this.db()), input.model, input.inputTokens, input.outputTokens, input.cacheReadTokens ?? 0, input.cacheWriteTokens ?? 0, input.cacheWrite1hTokens ?? 0, input.cacheStorageTokenHours ?? 0);
5155
6123
  }
5156
6124
  async setBudget(input) {
5157
- const now = new Date().toISOString();
5158
- const budget = { id: randomUUID(), ...input, created_at: now, updated_at: now };
6125
+ const now2 = new Date().toISOString();
6126
+ const budget = { id: randomUUID(), ...input, created_at: now2, updated_at: now2 };
5159
6127
  upsertBudget(this.db(), budget);
5160
6128
  return budget.id;
5161
6129
  }
@@ -5163,8 +6131,8 @@ class LocalStore {
5163
6131
  deleteBudget(this.db(), id);
5164
6132
  }
5165
6133
  async setGoal(input) {
5166
- const now = new Date().toISOString();
5167
- upsertGoal(this.db(), { id: randomUUID(), ...input, created_at: now, updated_at: now });
6134
+ const now2 = new Date().toISOString();
6135
+ upsertGoal(this.db(), { id: randomUUID(), ...input, created_at: now2, updated_at: now2 });
5168
6136
  }
5169
6137
  async removeGoal(id) {
5170
6138
  deleteGoal(this.db(), id);
@@ -5176,7 +6144,7 @@ class LocalStore {
5176
6144
  deleteModelPricing(this.db(), model);
5177
6145
  }
5178
6146
  async setSubscription(input) {
5179
- const now = new Date().toISOString();
6147
+ const now2 = new Date().toISOString();
5180
6148
  const row = {
5181
6149
  id: input.id ?? randomUUID(),
5182
6150
  provider: input.provider,
@@ -5187,8 +6155,8 @@ class LocalStore {
5187
6155
  billing_cycle_start: input.billing_cycle_start,
5188
6156
  reset_policy: input.reset_policy,
5189
6157
  active: input.active,
5190
- created_at: now,
5191
- updated_at: now
6158
+ created_at: now2,
6159
+ updated_at: now2
5192
6160
  };
5193
6161
  upsertSubscription(this.db(), row);
5194
6162
  return row;
@@ -5233,7 +6201,7 @@ function q(params) {
5233
6201
  return out;
5234
6202
  }
5235
6203
 
5236
- class ApiStore {
6204
+ class ApiStore2 {
5237
6205
  cloud;
5238
6206
  transport = "cloud-http";
5239
6207
  constructor(cloud) {
@@ -5377,7 +6345,7 @@ class ApiStore {
5377
6345
  await this.cloud.client.delete("pricing", model);
5378
6346
  }
5379
6347
  async setSubscription(input) {
5380
- const now = new Date().toISOString();
6348
+ const now2 = new Date().toISOString();
5381
6349
  const row = {
5382
6350
  id: input.id ?? randomUUID(),
5383
6351
  provider: input.provider,
@@ -5388,8 +6356,8 @@ class ApiStore {
5388
6356
  billing_cycle_start: input.billing_cycle_start,
5389
6357
  reset_policy: input.reset_policy,
5390
6358
  active: input.active,
5391
- created_at: now,
5392
- updated_at: now
6359
+ created_at: now2,
6360
+ updated_at: now2
5393
6361
  };
5394
6362
  await this.cloud.client.create("subscriptions", row);
5395
6363
  return row;
@@ -5417,7 +6385,7 @@ class ApiStore {
5417
6385
  }
5418
6386
  function getStore(env = process.env) {
5419
6387
  const cloud = economyCloudStorage(env);
5420
- return cloud.active ? new ApiStore(cloud) : new LocalStore;
6388
+ return cloud.active ? new ApiStore2(cloud) : new LocalStore2;
5421
6389
  }
5422
6390
  function isCloudStore(env = process.env) {
5423
6391
  return economyCloudStorage(env).active;
@@ -5565,17 +6533,17 @@ function buildServer() {
5565
6533
  function textError(message) {
5566
6534
  return { content: [{ type: "text", text: message }], isError: true };
5567
6535
  }
5568
- server.tool("search_tools", "List tool names matching query. Use first to find relevant tools.", { query: z.string().optional() }, async ({ query }) => {
6536
+ server.tool("search_tools", "List tool names matching query. Use first to find relevant tools.", { query: z3.string().optional() }, async ({ query }) => {
5569
6537
  const q2 = query?.toLowerCase();
5570
6538
  const matches = q2 ? TOOL_NAMES.filter((name) => name.includes(q2)) : [...TOOL_NAMES];
5571
6539
  return text(matches.join(", "));
5572
6540
  });
5573
- server.tool("describe_tools", "Get param hints for specific tools by name.", { names: z.array(z.string()) }, async ({ names }) => {
6541
+ server.tool("describe_tools", "Get param hints for specific tools by name.", { names: z3.array(z3.string()) }, async ({ names }) => {
5574
6542
  const result = names.map((name) => `${name}: ${TOOL_DESCRIPTIONS[name] ?? "see tool schema"}`).join(`
5575
6543
  `);
5576
6544
  return text(result);
5577
6545
  });
5578
- server.tool("get_cost_summary", "Cost summary (total_usd, sessions, requests, tokens, human summary). period: today|week|month|year|all. machine: filter by hostname.", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), machine: z.string().optional() }, async ({ period, machine }) => {
6546
+ server.tool("get_cost_summary", "Cost summary (total_usd, sessions, requests, tokens, human summary). period: today|week|month|year|all. machine: filter by hostname.", { period: z3.enum(["today", "week", "month", "year", "all"]).optional(), machine: z3.string().optional() }, async ({ period, machine }) => {
5579
6547
  const resolved = period ?? "today";
5580
6548
  const s = await store.summary(resolved, machine);
5581
6549
  const machineLabel = machine ? ` on ${machine}` : "";
@@ -5590,11 +6558,11 @@ function buildServer() {
5590
6558
  `));
5591
6559
  });
5592
6560
  server.tool("get_sessions", "List sessions. Returns compact table. Params: agent, project, account, machine, limit(20)", {
5593
- agent: z.enum(AGENTS).optional(),
5594
- project: z.string().optional(),
5595
- account: z.string().optional(),
5596
- machine: z.string().optional(),
5597
- limit: z.number().int().positive().max(100).optional()
6561
+ agent: z3.enum(AGENTS).optional(),
6562
+ project: z3.string().optional(),
6563
+ account: z3.string().optional(),
6564
+ machine: z3.string().optional(),
6565
+ limit: z3.number().int().positive().max(100).optional()
5598
6566
  }, async ({ agent, project, account, machine, limit }) => {
5599
6567
  const sessions = await store.sessions({ agent, project, account, machine, limit: limit ?? 20 });
5600
6568
  const lines = ["id agent cost tokens project"];
@@ -5604,8 +6572,8 @@ function buildServer() {
5604
6572
  `));
5605
6573
  });
5606
6574
  server.tool("get_top_sessions", "Top sessions by cost. Params: n(10), agent", {
5607
- n: z.number().int().positive().max(100).optional(),
5608
- agent: z.enum(AGENTS).optional()
6575
+ n: z3.number().int().positive().max(100).optional(),
6576
+ agent: z3.enum(AGENTS).optional()
5609
6577
  }, async ({ n, agent }) => {
5610
6578
  const sessions = await store.topSessions(n ?? 10, agent);
5611
6579
  const lines = ["rank id agent cost tokens project"];
@@ -5613,7 +6581,7 @@ function buildServer() {
5613
6581
  return text(lines.join(`
5614
6582
  `));
5615
6583
  });
5616
- server.tool("get_model_breakdown", "Cost per model. Params: limit(20), verbose, json.", { limit: z.number().int().positive().max(100).optional(), verbose: z.boolean().optional(), json: z.boolean().optional() }, async ({ limit, verbose, json }) => {
6584
+ server.tool("get_model_breakdown", "Cost per model. Params: limit(20), verbose, json.", { limit: z3.number().int().positive().max(100).optional(), verbose: z3.boolean().optional(), json: z3.boolean().optional() }, async ({ limit, verbose, json }) => {
5617
6585
  const rows = await store.modelBreakdown();
5618
6586
  if (json)
5619
6587
  return text(JSON.stringify(rows, null, 2));
@@ -5628,7 +6596,7 @@ function buildServer() {
5628
6596
  return text(lines.join(`
5629
6597
  `));
5630
6598
  });
5631
- server.tool("get_project_breakdown", "Cost per project. Params: period(today|week|month|year|all), limit(20), verbose, json.", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), limit: z.number().int().positive().max(100).optional(), verbose: z.boolean().optional(), json: z.boolean().optional() }, async ({ period, limit, verbose, json }) => {
6599
+ server.tool("get_project_breakdown", "Cost per project. Params: period(today|week|month|year|all), limit(20), verbose, json.", { period: z3.enum(["today", "week", "month", "year", "all"]).optional(), limit: z3.number().int().positive().max(100).optional(), verbose: z3.boolean().optional(), json: z3.boolean().optional() }, async ({ period, limit, verbose, json }) => {
5632
6600
  const rows = await store.projectBreakdown({ period: period ?? "all" });
5633
6601
  if (json)
5634
6602
  return text(JSON.stringify(rows, null, 2));
@@ -5644,7 +6612,7 @@ function buildServer() {
5644
6612
  return text(lines.join(`
5645
6613
  `));
5646
6614
  });
5647
- server.tool("get_agent_breakdown", "Cost per coding agent. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
6615
+ server.tool("get_agent_breakdown", "Cost per coding agent. Params: period(today|week|month|year|all). Shows API-equivalent, billable API, and subscription-included usage.", { period: z3.enum(["today", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
5648
6616
  const rows = await store.agentBreakdown({ period: period ?? "all" });
5649
6617
  if (rows.length === 0)
5650
6618
  return text("No agent usage yet.");
@@ -5655,7 +6623,7 @@ function buildServer() {
5655
6623
  return text(lines.join(`
5656
6624
  `));
5657
6625
  });
5658
- server.tool("get_account_breakdown", "Cost per account/profile. Params: period(today|week|month|year|all), limit(20), verbose, json. Shows API-equivalent, billable API, and subscription-included usage.", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), limit: z.number().int().positive().max(100).optional(), verbose: z.boolean().optional(), json: z.boolean().optional() }, async ({ period, limit, verbose, json }) => {
6626
+ server.tool("get_account_breakdown", "Cost per account/profile. Params: period(today|week|month|year|all), limit(20), verbose, json. Shows API-equivalent, billable API, and subscription-included usage.", { period: z3.enum(["today", "week", "month", "year", "all"]).optional(), limit: z3.number().int().positive().max(100).optional(), verbose: z3.boolean().optional(), json: z3.boolean().optional() }, async ({ period, limit, verbose, json }) => {
5659
6627
  const rows = await store.accountBreakdown({ period: period ?? "all" });
5660
6628
  if (json)
5661
6629
  return text(JSON.stringify(rows, null, 2));
@@ -5674,8 +6642,8 @@ function buildServer() {
5674
6642
  `));
5675
6643
  });
5676
6644
  server.tool("get_cost_center_breakdown", "Cost per cost center. Params: period(today|week|month|year|all), kind(loop|app|repo|service|team).", {
5677
- period: z.enum(["today", "week", "month", "year", "all"]).optional(),
5678
- kind: z.enum(["loop", "app", "repo", "service", "team"]).optional()
6645
+ period: z3.enum(["today", "week", "month", "year", "all"]).optional(),
6646
+ kind: z3.enum(["loop", "app", "repo", "service", "team"]).optional()
5679
6647
  }, async ({ period, kind }) => {
5680
6648
  const rows = await store.costCenterBreakdown({ period: period ?? "all", kind });
5681
6649
  if (rows.length === 0)
@@ -5687,7 +6655,7 @@ function buildServer() {
5687
6655
  return text(lines.join(`
5688
6656
  `));
5689
6657
  });
5690
- server.tool("get_budget_status", "Budget limits vs spend, percent used, alert flags. Params: limit(20), verbose, json.", { limit: z.number().int().positive().max(100).optional(), verbose: z.boolean().optional(), json: z.boolean().optional() }, async ({ limit, verbose, json }) => {
6658
+ server.tool("get_budget_status", "Budget limits vs spend, percent used, alert flags. Params: limit(20), verbose, json.", { limit: z3.number().int().positive().max(100).optional(), verbose: z3.boolean().optional(), json: z3.boolean().optional() }, async ({ limit, verbose, json }) => {
5691
6659
  const budgets = await store.listBudgets();
5692
6660
  if (json)
5693
6661
  return text(JSON.stringify(budgets, null, 2));
@@ -5708,12 +6676,12 @@ function buildServer() {
5708
6676
  `));
5709
6677
  });
5710
6678
  server.tool("set_budget", "Create a spending budget. period: daily|weekly|monthly. limit_usd must be positive. alert_at_percent defaults to 80.", {
5711
- period: z.enum(["daily", "weekly", "monthly"]),
5712
- limit_usd: z.number().positive(),
5713
- project_path: z.string().optional(),
5714
- agent: z.enum(AGENTS).optional(),
5715
- cost_center_id: z.string().optional(),
5716
- alert_at_percent: z.number().positive().max(100).optional()
6679
+ period: z3.enum(["daily", "weekly", "monthly"]),
6680
+ limit_usd: z3.number().positive(),
6681
+ project_path: z3.string().optional(),
6682
+ agent: z3.enum(AGENTS).optional(),
6683
+ cost_center_id: z3.string().optional(),
6684
+ alert_at_percent: z3.number().positive().max(100).optional()
5717
6685
  }, async ({ period, limit_usd, project_path, agent, cost_center_id, alert_at_percent }) => {
5718
6686
  const id = await store.setBudget({
5719
6687
  project_path: project_path ?? null,
@@ -5725,11 +6693,11 @@ function buildServer() {
5725
6693
  });
5726
6694
  return text(`Budget set: ${id}`);
5727
6695
  });
5728
- server.tool("remove_budget", "Delete a budget by id.", { id: z.string() }, async ({ id }) => {
6696
+ server.tool("remove_budget", "Delete a budget by id.", { id: z3.string() }, async ({ id }) => {
5729
6697
  await store.removeBudget(id);
5730
6698
  return text("Budget removed.");
5731
6699
  });
5732
- server.tool("get_pricing", "Editable model pricing rows. Includes input/output/cache rates and context-cache storage. Params: limit(20), verbose, json.", { limit: z.number().int().positive().max(100).optional(), verbose: z.boolean().optional(), json: z.boolean().optional() }, async ({ limit, verbose, json }) => {
6700
+ server.tool("get_pricing", "Editable model pricing rows. Includes input/output/cache rates and context-cache storage. Params: limit(20), verbose, json.", { limit: z3.number().int().positive().max(100).optional(), verbose: z3.boolean().optional(), json: z3.boolean().optional() }, async ({ limit, verbose, json }) => {
5733
6701
  const rows = await store.listPricing();
5734
6702
  if (json)
5735
6703
  return text(JSON.stringify(rows, null, 2));
@@ -5745,13 +6713,13 @@ function buildServer() {
5745
6713
  `));
5746
6714
  });
5747
6715
  server.tool("set_pricing", "Create or update a model pricing row. Values are USD per 1M tokens except cache_storage_per_1m_hour.", {
5748
- model: z.string().min(1),
5749
- input_per_1m: z.number().nonnegative(),
5750
- output_per_1m: z.number().nonnegative(),
5751
- cache_read_per_1m: z.number().nonnegative().optional(),
5752
- cache_write_per_1m: z.number().nonnegative().optional(),
5753
- cache_write_1h_per_1m: z.number().nonnegative().optional(),
5754
- cache_storage_per_1m_hour: z.number().nonnegative().optional()
6716
+ model: z3.string().min(1),
6717
+ input_per_1m: z3.number().nonnegative(),
6718
+ output_per_1m: z3.number().nonnegative(),
6719
+ cache_read_per_1m: z3.number().nonnegative().optional(),
6720
+ cache_write_per_1m: z3.number().nonnegative().optional(),
6721
+ cache_write_1h_per_1m: z3.number().nonnegative().optional(),
6722
+ cache_storage_per_1m_hour: z3.number().nonnegative().optional()
5755
6723
  }, async (input) => {
5756
6724
  const model = input.model.trim();
5757
6725
  if (!model)
@@ -5767,11 +6735,11 @@ function buildServer() {
5767
6735
  });
5768
6736
  return text(`Pricing set: ${model}`);
5769
6737
  });
5770
- server.tool("remove_pricing", "Delete a model pricing row by model id.", { model: z.string() }, async ({ model }) => {
6738
+ server.tool("remove_pricing", "Delete a model pricing row by model id.", { model: z3.string() }, async ({ model }) => {
5771
6739
  await store.removePricing(model);
5772
6740
  return text("Pricing removed.");
5773
6741
  });
5774
- server.tool("get_daily", "Daily cost table by agent. Params: days(30)", { days: z.number().int().positive().max(365).optional() }, async ({ days }) => {
6742
+ server.tool("get_daily", "Daily cost table by agent. Params: days(30)", { days: z3.number().int().positive().max(365).optional() }, async ({ days }) => {
5775
6743
  const rows = await store.daily(days ?? 30);
5776
6744
  const byDate = new Map;
5777
6745
  for (const row of rows) {
@@ -5795,7 +6763,7 @@ function buildServer() {
5795
6763
  return text(lines.join(`
5796
6764
  `));
5797
6765
  });
5798
- server.tool("get_billing_summary", "Actual provider billing totals from admin API sync. Params: period(today|yesterday|week|month|year|all)", { period: z.enum(["today", "yesterday", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
6766
+ server.tool("get_billing_summary", "Actual provider billing totals from admin API sync. Params: period(today|yesterday|week|month|year|all)", { period: z3.enum(["today", "yesterday", "week", "month", "year", "all"]).optional() }, async ({ period }) => {
5799
6767
  const summary = await store.billingSummary(period ?? "month");
5800
6768
  const lines = ["provider billed"];
5801
6769
  for (const [provider, cost] of Object.entries(summary.by_provider)) {
@@ -5805,7 +6773,7 @@ function buildServer() {
5805
6773
  return text(lines.join(`
5806
6774
  `));
5807
6775
  });
5808
- server.tool("get_session_detail", "Per-request breakdown of a single session. Params: session_id (prefix ok), limit(20), verbose.", { session_id: z.string(), limit: z.number().int().positive().max(100).optional(), verbose: z.boolean().optional() }, async ({ session_id, limit, verbose }) => {
6776
+ server.tool("get_session_detail", "Per-request breakdown of a single session. Params: session_id (prefix ok), limit(20), verbose.", { session_id: z3.string(), limit: z3.number().int().positive().max(100).optional(), verbose: z3.boolean().optional() }, async ({ session_id, limit, verbose }) => {
5809
6777
  const detail = await store.sessionDetail(session_id);
5810
6778
  if (!detail)
5811
6779
  return textError(`Session not found: ${session_id}`);
@@ -5829,13 +6797,13 @@ function buildServer() {
5829
6797
  return text(lines.join(`
5830
6798
  `));
5831
6799
  });
5832
- server.tool("sync", `Ingest new cost data. sources: ${SYNC_SOURCES.join("|")}. Set json=true for the full result object.`, { sources: z.enum([...SYNC_SOURCES]).optional(), json: z.boolean().optional() }, async ({ sources, json }) => {
6800
+ server.tool("sync", `Ingest new cost data. sources: ${SYNC_SOURCES.join("|")}. Set json=true for the full result object.`, { sources: z3.enum([...SYNC_SOURCES]).optional(), json: z3.boolean().optional() }, async ({ sources, json }) => {
5833
6801
  const selected = sources ?? "all";
5834
6802
  const opts = selected === "all" ? {} : { [selected]: true };
5835
6803
  if (isCloudStore()) {
5836
6804
  const cloud = economyCloudStorage();
5837
6805
  if (!cloud.active) {
5838
- return text("cloud mode: sync transport unavailable (set HASNA_ECONOMY_API_URL and HASNA_ECONOMY_API_KEY)");
6806
+ return text("cloud mode: sync transport unavailable (no credential resolved; set HASNA_ECONOMY_API_KEY or write ~/.hasna/economy/config/credentials)");
5839
6807
  }
5840
6808
  const result2 = await syncAllToCloud(cloud, opts);
5841
6809
  if (json)
@@ -5877,7 +6845,7 @@ function buildServer() {
5877
6845
  return text(lines.join(`
5878
6846
  `));
5879
6847
  });
5880
- server.tool("get_usage", "Usage snapshots and fleet summary. period: today|week|month|year|all, agent, limit(20). Set json=true for full data.", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), agent: z.enum(AGENTS).optional(), limit: z.number().int().positive().max(100).optional(), json: z.boolean().optional() }, async ({ period, agent, limit, json }) => {
6848
+ server.tool("get_usage", "Usage snapshots and fleet summary. period: today|week|month|year|all, agent, limit(20). Set json=true for full data.", { period: z3.enum(["today", "week", "month", "year", "all"]).optional(), agent: z3.enum(AGENTS).optional(), limit: z3.number().int().positive().max(100).optional(), json: z3.boolean().optional() }, async ({ period, agent, limit, json }) => {
5881
6849
  const p = period ?? "month";
5882
6850
  const { snapshots: snaps, summary } = await store.usage(p, agent);
5883
6851
  if (json)
@@ -5899,7 +6867,7 @@ function buildServer() {
5899
6867
  return text(lines.join(`
5900
6868
  `));
5901
6869
  });
5902
- server.tool("get_savings", "Subscription vs API savings summary. Params: period, agent, limit(20). Set json=true for full data.", { period: z.enum(["today", "week", "month", "year", "all"]).optional(), agent: z.enum(AGENTS).optional(), limit: z.number().int().positive().max(100).optional(), json: z.boolean().optional() }, async ({ period, agent, limit, json }) => {
6870
+ server.tool("get_savings", "Subscription vs API savings summary. Params: period, agent, limit(20). Set json=true for full data.", { period: z3.enum(["today", "week", "month", "year", "all"]).optional(), agent: z3.enum(AGENTS).optional(), limit: z3.number().int().positive().max(100).optional(), json: z3.boolean().optional() }, async ({ period, agent, limit, json }) => {
5903
6871
  const savings = await store.savings(period ?? "month", agent);
5904
6872
  if (json)
5905
6873
  return text(JSON.stringify(savings, null, 2));
@@ -5936,16 +6904,16 @@ function buildServer() {
5936
6904
  `));
5937
6905
  });
5938
6906
  server.tool("set_subscription", "Create or update a subscription plan used by subscription-vs-API savings calculations.", {
5939
- id: z.string().optional(),
5940
- provider: z.string().min(1),
5941
- plan: z.string().min(1),
5942
- agent: z.enum(AGENTS).optional(),
5943
- monthly_fee_usd: z.number().nonnegative().optional(),
5944
- included_usage_usd: z.number().nonnegative().optional(),
5945
- billing_cycle_start: z.string().optional(),
5946
- reset_policy: z.string().optional(),
5947
- active: z.boolean().optional(),
5948
- json: z.boolean().optional()
6907
+ id: z3.string().optional(),
6908
+ provider: z3.string().min(1),
6909
+ plan: z3.string().min(1),
6910
+ agent: z3.enum(AGENTS).optional(),
6911
+ monthly_fee_usd: z3.number().nonnegative().optional(),
6912
+ included_usage_usd: z3.number().nonnegative().optional(),
6913
+ billing_cycle_start: z3.string().optional(),
6914
+ reset_policy: z3.string().optional(),
6915
+ active: z3.boolean().optional(),
6916
+ json: z3.boolean().optional()
5949
6917
  }, async (input) => {
5950
6918
  const row = await store.setSubscription({
5951
6919
  id: input.id,
@@ -5967,15 +6935,15 @@ agent: ${row.agent ?? "all"}
5967
6935
  fee: ${fmtUsd(row.monthly_fee_usd)}
5968
6936
  included: ${fmtUsd(row.included_usage_usd)}`);
5969
6937
  });
5970
- server.tool("remove_subscription", "Delete a subscription plan by id.", { id: z.string() }, async ({ id }) => {
6938
+ server.tool("remove_subscription", "Delete a subscription plan by id.", { id: z3.string() }, async ({ id }) => {
5971
6939
  await store.removeSubscription(id);
5972
6940
  return text("Subscription removed.");
5973
6941
  });
5974
- server.tool("estimate_cost", "Pre-flight cost estimate for token counts", { model: z.string(), input_tokens: z.number().optional(), output_tokens: z.number().optional() }, async ({ model, input_tokens, output_tokens }) => {
6942
+ server.tool("estimate_cost", "Pre-flight cost estimate for token counts", { model: z3.string(), input_tokens: z3.number().optional(), output_tokens: z3.number().optional() }, async ({ model, input_tokens, output_tokens }) => {
5975
6943
  const cost = await store.estimate({ model, inputTokens: input_tokens ?? 0, outputTokens: output_tokens ?? 0 });
5976
6944
  return text(`${model}: ${fmtUsd(cost)} (${input_tokens ?? 0} in / ${output_tokens ?? 0} out)`);
5977
6945
  });
5978
- server.tool("get_goals", "All spending goals with current progress. Params: limit(20), verbose, json.", { limit: z.number().int().positive().max(100).optional(), verbose: z.boolean().optional(), json: z.boolean().optional() }, async ({ limit, verbose, json }) => {
6946
+ server.tool("get_goals", "All spending goals with current progress. Params: limit(20), verbose, json.", { limit: z3.number().int().positive().max(100).optional(), verbose: z3.boolean().optional(), json: z3.boolean().optional() }, async ({ limit, verbose, json }) => {
5979
6947
  const goals = await store.listGoals();
5980
6948
  if (json)
5981
6949
  return text(JSON.stringify(goals, null, 2));
@@ -5996,10 +6964,10 @@ included: ${fmtUsd(row.included_usage_usd)}`);
5996
6964
  `));
5997
6965
  });
5998
6966
  server.tool("set_goal", "Create/update a spending goal. period(day|week|month|year), limit_usd, project_path?, agent?", {
5999
- period: z.enum(["day", "week", "month", "year"]),
6000
- limit_usd: z.number().positive(),
6001
- project_path: z.string().optional(),
6002
- agent: z.string().optional()
6967
+ period: z3.enum(["day", "week", "month", "year"]),
6968
+ limit_usd: z3.number().positive(),
6969
+ project_path: z3.string().optional(),
6970
+ agent: z3.string().optional()
6003
6971
  }, async ({ period, limit_usd, project_path, agent }) => {
6004
6972
  await store.setGoal({
6005
6973
  period,
@@ -6009,11 +6977,11 @@ included: ${fmtUsd(row.included_usage_usd)}`);
6009
6977
  });
6010
6978
  return text(`Goal set: ${period} $${limit_usd}`);
6011
6979
  });
6012
- server.tool("remove_goal", "Delete a goal by id.", { id: z.string() }, async ({ id }) => {
6980
+ server.tool("remove_goal", "Delete a goal by id.", { id: z3.string() }, async ({ id }) => {
6013
6981
  await store.removeGoal(id);
6014
6982
  return text("Goal removed.");
6015
6983
  });
6016
- server.tool("list_machines", "List all machines that have synced data. Params: limit(20), verbose, json.", { limit: z.number().int().positive().max(100).optional(), verbose: z.boolean().optional(), json: z.boolean().optional() }, async ({ limit, verbose, json }) => {
6984
+ server.tool("list_machines", "List all machines that have synced data. Params: limit(20), verbose, json.", { limit: z3.number().int().positive().max(100).optional(), verbose: z3.boolean().optional(), json: z3.boolean().optional() }, async ({ limit, verbose, json }) => {
6017
6985
  const machines = await store.machines();
6018
6986
  if (json)
6019
6987
  return text(JSON.stringify(machines, null, 2));
@@ -6032,11 +7000,11 @@ current machine: ${getMachineId()}`);
6032
7000
  return text(lines.join(`
6033
7001
  `));
6034
7002
  });
6035
- registerAgentTools(server, { includeFeedback: false });
7003
+ registerAgentTools(server, { service: "economy" });
6036
7004
  server.tool("send_feedback", "Send feedback about this service.", {
6037
- message: z.string(),
6038
- email: z.string().optional(),
6039
- category: z.enum(["bug", "feature", "general"]).optional()
7005
+ message: z3.string(),
7006
+ email: z3.string().optional(),
7007
+ category: z3.enum(["bug", "feature", "general"]).optional()
6040
7008
  }, async ({ message, email, category }) => {
6041
7009
  try {
6042
7010
  await store.sendFeedback({ message, email, category });
@@ -6048,32 +7016,102 @@ current machine: ${getMachineId()}`);
6048
7016
  return server;
6049
7017
  }
6050
7018
 
7019
+ // src/mcp/harness.ts
7020
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
7021
+ var PORT_MIN = 0;
7022
+ var PORT_MAX = 65535;
7023
+ var DEFAULT_MCP_HTTP_PORT2 = 8899;
7024
+ function invalidPortMessage(source, value) {
7025
+ return `Invalid ${source} "${value}". Expected an integer between ${PORT_MIN} and ${PORT_MAX}.`;
7026
+ }
7027
+ function validatePort(port, source) {
7028
+ if (!Number.isInteger(port) || port < PORT_MIN || port > PORT_MAX) {
7029
+ throw new Error(invalidPortMessage(source, String(port)));
7030
+ }
7031
+ return port;
7032
+ }
7033
+ function parsePortValue(value, source) {
7034
+ const trimmed = value.trim();
7035
+ if (!/^\d+$/.test(trimmed)) {
7036
+ throw new Error(invalidPortMessage(source, value));
7037
+ }
7038
+ return validatePort(Number(trimmed), source);
7039
+ }
7040
+ function isStdioMode(argv = process.argv, env = process.env) {
7041
+ return argv.includes("--stdio") || env.MCP_STDIO === "1";
7042
+ }
7043
+ function isHttpMode(argv = process.argv, env = process.env) {
7044
+ return argv.includes("--http") || env.MCP_HTTP === "1";
7045
+ }
7046
+ function parseHttpArgv(argv = process.argv) {
7047
+ const http = isHttpMode(argv);
7048
+ let port;
7049
+ const portEqualsArg = argv.find((arg) => arg.startsWith("--port="));
7050
+ if (portEqualsArg) {
7051
+ port = parsePortValue(portEqualsArg.slice("--port=".length), "--port");
7052
+ }
7053
+ const portIdx = argv.indexOf("--port");
7054
+ if (portIdx !== -1) {
7055
+ const value = argv[portIdx + 1];
7056
+ if (value === undefined) {
7057
+ throw new Error(invalidPortMessage("--port", ""));
7058
+ }
7059
+ port = parsePortValue(value, "--port");
7060
+ }
7061
+ return { http, port };
7062
+ }
7063
+ function resolveMcpHttpPort(opts) {
7064
+ const argv = opts?.argv ?? process.argv;
7065
+ const env = opts?.env ?? process.env;
7066
+ if (opts?.explicit != null) {
7067
+ return validatePort(opts.explicit, "--port");
7068
+ }
7069
+ const { port } = parseHttpArgv(argv);
7070
+ if (port != null)
7071
+ return port;
7072
+ const envPort = env.MCP_HTTP_PORT;
7073
+ if (envPort && envPort.trim() !== "") {
7074
+ return parsePortValue(envPort, "MCP_HTTP_PORT");
7075
+ }
7076
+ return opts?.default ?? DEFAULT_MCP_HTTP_PORT2;
7077
+ }
7078
+ function healthPayload(name) {
7079
+ return { status: "ok", name };
7080
+ }
7081
+ async function handleMcpHttpRequest(req, buildServer2, options) {
7082
+ try {
7083
+ const server = await buildServer2();
7084
+ const transport = new WebStandardStreamableHTTPServerTransport({
7085
+ sessionIdGenerator: undefined,
7086
+ enableJsonResponse: options?.enableJsonResponse
7087
+ });
7088
+ await server.connect(transport);
7089
+ return await transport.handleRequest(req);
7090
+ } catch (error) {
7091
+ console.error("[mcp] HTTP error:", error);
7092
+ return Response.json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null }, { status: 500 });
7093
+ }
7094
+ }
7095
+
6051
7096
  // src/mcp/http.ts
6052
- import {
6053
- healthPayload,
6054
- isHttpMode as harnessIsHttpMode,
6055
- isStdioMode as harnessIsStdioMode,
6056
- resolveMcpHttpPort as harnessResolveMcpHttpPort
6057
- } from "@hasna/mcp-harness";
6058
- import { handleMcpHttpRequest as harnessHandleMcpHttpRequest } from "@hasna/mcp-harness/bun";
6059
7097
  var MCP_HTTP_IDLE_TIMEOUT_SECONDS = 0;
6060
- function isHttpMode(argv = process.argv.slice(2)) {
6061
- return harnessIsHttpMode(argv);
7098
+ function isHttpMode2(argv = process.argv.slice(2)) {
7099
+ return isHttpMode(argv);
6062
7100
  }
6063
- function isStdioMode(argv = process.argv.slice(2)) {
6064
- return harnessIsStdioMode(argv);
7101
+ function isStdioMode2(argv = process.argv.slice(2)) {
7102
+ return isStdioMode(argv);
6065
7103
  }
6066
7104
  function resolveHttpPort(argv = process.argv.slice(2)) {
6067
7105
  const normalized = argv.map((arg) => arg === "-p" ? "--port" : arg);
6068
- return harnessResolveMcpHttpPort({ argv: normalized, default: DEFAULT_MCP_HTTP_PORT });
7106
+ return resolveMcpHttpPort({ argv: normalized, default: DEFAULT_MCP_HTTP_PORT });
6069
7107
  }
6070
- async function handleMcpHttpRequest(req) {
7108
+ async function handleMcpHttpRequest2(req) {
6071
7109
  const url = new URL(req.url);
6072
7110
  if (url.pathname === "/health" && req.method === "GET") {
6073
7111
  return Response.json(healthPayload(MCP_NAME));
6074
7112
  }
6075
7113
  if (url.pathname === "/mcp") {
6076
- return harnessHandleMcpHttpRequest(req, buildServer);
7114
+ return handleMcpHttpRequest(req, buildServer);
6077
7115
  }
6078
7116
  return new Response("Not Found", { status: 404 });
6079
7117
  }
@@ -6085,7 +7123,7 @@ function startHttpServer(options = {}) {
6085
7123
  port,
6086
7124
  hostname: hostname2,
6087
7125
  idleTimeout: MCP_HTTP_IDLE_TIMEOUT_SECONDS,
6088
- fetch: handleMcpHttpRequest
7126
+ fetch: handleMcpHttpRequest2
6089
7127
  });
6090
7128
  const address = `http://${hostname2}:${server.port}`;
6091
7129
  log(`${MCP_NAME}-mcp HTTP listening on ${address}/mcp (health: ${address}/health)`);
@@ -6118,7 +7156,7 @@ if (args.includes("--version") || args.includes("-V")) {
6118
7156
  process.exit(0);
6119
7157
  }
6120
7158
  async function main() {
6121
- if (isStdioMode(args) || !isHttpMode(args)) {
7159
+ if (isStdioMode2(args) || !isHttpMode2(args)) {
6122
7160
  const server = buildServer();
6123
7161
  const transport = new StdioServerTransport;
6124
7162
  await server.connect(transport);
@@ -6128,6 +7166,6 @@ async function main() {
6128
7166
  await new Promise(() => {});
6129
7167
  }
6130
7168
  main().catch((error) => {
6131
- console.error("MCP server error:", error);
7169
+ console.error("MCP server error:", error instanceof Error ? error.message : String(error));
6132
7170
  process.exit(1);
6133
7171
  });