@hasna/economy 0.2.41 → 0.2.43

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.
@@ -0,0 +1,28 @@
1
+ import type { DbAdapter } from '@hasna/cloud';
2
+ import type { AuthQueryClient } from '@hasna/contracts/auth';
3
+ import pg from 'pg';
4
+ import type { SqliteAdapter as Database } from '@hasna/cloud';
5
+ /** Resolve the cloud Postgres DSN from the standard env aliases. */
6
+ export declare function getCloudDatabaseUrl(): string | undefined;
7
+ /** True when the serve should run in cloud (RDS-direct) mode. */
8
+ export declare function isCloudMode(): boolean;
9
+ /** HMAC signing secret for API-key verification (server-held, never in a token). */
10
+ export declare function resolveSigningSecret(): string | undefined;
11
+ /**
12
+ * Open a pooled Postgres connection to the shared RDS. Returns a value typed as
13
+ * the SQLite `Database` the query layer expects — safe because `PgAdapter`
14
+ * implements the identical `DbAdapter` surface those functions rely on.
15
+ */
16
+ export declare function openCloudDatabase(dsn?: string | undefined): Database;
17
+ /** A raw pg Pool for auxiliary async work (API-key store, migrations). */
18
+ export declare function createCloudPool(dsn?: string | undefined): pg.Pool;
19
+ /**
20
+ * Adapt a pg Pool to the `@hasna/contracts/auth` `AuthQueryClient` surface
21
+ * (many/get/execute). This backs the `ApiKeyStore` without pulling in the
22
+ * generic vendored kit — economy's storage is the `@hasna/cloud` PG pool.
23
+ */
24
+ export declare function authClientFromPool(pool: pg.Pool): AuthQueryClient;
25
+ /** Ordered PG migrations applied by the migration runner (index = version). */
26
+ export declare function cloudMigrations(): string[];
27
+ export type { DbAdapter };
28
+ //# sourceMappingURL=cloud.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/db/cloud.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAI7D,oEAAoE;AACpE,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAOxD;AAED,iEAAiE;AACjE,wBAAgB,WAAW,IAAI,OAAO,CAKrC;AAED,oFAAoF;AACpF,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,SAAS,CAOzD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,qBAAwB,GAAG,QAAQ,CASvE;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,GAAG,qBAAwB,GAAG,EAAE,CAAC,IAAI,CAYpE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,eAAe,CAcjE;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C;AAED,YAAY,EAAE,SAAS,EAAE,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import { SqliteAdapter as Database } from '@hasna/cloud';
2
- import type { EconomyRequest, EconomySession, EconomyProject, Budget, BudgetStatus, CostSummary, ModelBreakdown, ProjectBreakdown, AgentBreakdown, AccountBreakdown, Period, SessionFilter } from '../types/index.js';
2
+ import type { EconomyRequest, EconomySession, EconomyProject, Budget, BudgetStatus, CostSummary, ZeroCostModelBreakdown, ModelBreakdown, ProjectBreakdown, AgentBreakdown, AccountBreakdown, Period, Agent, SessionFilter } from '../types/index.js';
3
3
  export declare function getMachineId(): string;
4
4
  export declare function getDataDir(): string;
5
5
  export declare function getDbPath(): string;
@@ -8,8 +8,9 @@ export declare function upsertRequest(db: Database, req: EconomyRequest): void;
8
8
  export declare function upsertSession(db: Database, session: EconomySession): void;
9
9
  export declare function rollupSession(db: Database, sessionId: string): void;
10
10
  export declare function querySessions(db: Database, filter?: SessionFilter): EconomySession[];
11
- export declare function queryTopSessions(db: Database, n?: number, agent?: string): EconomySession[];
12
- export declare function querySummary(db: Database, period: Period, machine?: string, allMachines?: boolean): CostSummary;
11
+ export declare function queryTopSessions(db: Database, n?: number, agent?: string, since?: string): EconomySession[];
12
+ export declare function querySummary(db: Database, period: Period, machine?: string, allMachines?: boolean, agent?: Agent): CostSummary;
13
+ export declare function queryZeroCostTokenizedModels(db: Database, limit?: number): ZeroCostModelBreakdown[];
13
14
  export declare function queryModelBreakdown(db: Database): ModelBreakdown[];
14
15
  export declare function queryAgentBreakdown(db: Database, period?: Period, machine?: string): AgentBreakdown[];
15
16
  export declare function queryProjectBreakdown(db: Database, period?: Period, machine?: string): ProjectBreakdown[];
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pg-sync-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-sync-worker.d.ts","sourceRoot":"","sources":["../../src/db/pg-sync-worker.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import { type DbAdapter, type PreparedStatement, type RunResult } from '@hasna/cloud';
2
+ /**
3
+ * Translate the closed set of SQLite date/time idioms the economy query layer
4
+ * uses into Postgres equivalents. Runs AFTER `translateSql` (so `?` are already
5
+ * `$n`). Timestamp columns are stored as ISO-8601 TEXT in both dialects, so bare
6
+ * `DATE(col)` casts through `timestamptz`. Ordered most-specific first.
7
+ */
8
+ export declare function translateSqliteDates(sql: string): string;
9
+ export declare class SyncPgAdapter implements DbAdapter {
10
+ private readonly worker;
11
+ private readonly port;
12
+ private counter;
13
+ constructor(dsn: string);
14
+ private query;
15
+ run(sql: string, ...params: unknown[]): RunResult;
16
+ get(sql: string, ...params: unknown[]): unknown;
17
+ all(sql: string, ...params: unknown[]): unknown[];
18
+ exec(sql: string): void;
19
+ prepare(sql: string): PreparedStatement;
20
+ transaction<T>(fn: () => T): T;
21
+ close(): void;
22
+ }
23
+ //# sourceMappingURL=sync-pg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-pg.d.ts","sourceRoot":"","sources":["../../src/db/sync-pg.ts"],"names":[],"mappings":"AAeA,OAAO,EAAiC,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAIpH;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAiCxD;AAaD,qBAAa,aAAc,YAAW,SAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,OAAO,CAAI;gBAEP,GAAG,EAAE,MAAM;IAYvB,OAAO,CAAC,KAAK;IAsBb,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS;IAIjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO;IAG/C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE;IAGjD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAGvB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB;IASvC,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;IAI9B,KAAK,IAAI,IAAI;CAGd"}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './lib/gatherer.js';
5
5
  export * from './lib/model-config.js';
6
6
  export * from './lib/open-projects.js';
7
7
  export * from './lib/peer-sync.js';
8
+ export * from './lib/fleet-sync.js';
8
9
  export * from './ingest/claude.js';
9
10
  export * from './ingest/codex.js';
10
11
  export * from './ingest/gemini.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
package/dist/index.js CHANGED
@@ -572,6 +572,32 @@ function getDbPath() {
572
572
  return process.env["ECONOMY_DB"];
573
573
  return join(getDataDir(), "economy.db");
574
574
  }
575
+ function isSqliteBusyError(error) {
576
+ const candidate = error;
577
+ const code = typeof candidate.code === "string" ? candidate.code : "";
578
+ const message = typeof candidate.message === "string" ? candidate.message : String(error);
579
+ return code === "SQLITE_BUSY" || code === "SQLITE_BUSY_RECOVERY" || /database is locked|SQLITE_BUSY/i.test(message);
580
+ }
581
+ function retryDelayMs(attempt) {
582
+ return Math.min(1000, 50 * 2 ** attempt);
583
+ }
584
+ function withSqliteBusyRetry(operation, context) {
585
+ const maxAttempts = 8;
586
+ let lastError;
587
+ for (let attempt = 0;attempt < maxAttempts; attempt++) {
588
+ try {
589
+ return operation();
590
+ } catch (error) {
591
+ if (!isSqliteBusyError(error))
592
+ throw error;
593
+ lastError = error;
594
+ if (attempt === maxAttempts - 1)
595
+ break;
596
+ Bun.sleepSync(retryDelayMs(attempt));
597
+ }
598
+ }
599
+ throw new Error(`SQLite database is locked after ${maxAttempts} attempts while ${context}. Another economy sync/merge may be recovering the database; retry shortly.`, { cause: lastError });
600
+ }
575
601
  function openDatabase(dbPath, skipSeed = false) {
576
602
  const path = dbPath ?? getDbPath();
577
603
  if (path !== ":memory:") {
@@ -579,11 +605,21 @@ function openDatabase(dbPath, skipSeed = false) {
579
605
  if (dir && !existsSync(dir))
580
606
  mkdirSync(dir, { recursive: true });
581
607
  }
582
- const db = new Database(path);
583
- db.exec("PRAGMA journal_mode = WAL");
584
- db.exec("PRAGMA busy_timeout = 5000");
585
- db.exec("PRAGMA foreign_keys = ON");
586
- initSchema(db);
608
+ const db = withSqliteBusyRetry(() => {
609
+ const opened = new Database(path);
610
+ try {
611
+ opened.exec("PRAGMA busy_timeout = 10000");
612
+ opened.exec("PRAGMA journal_mode = WAL");
613
+ opened.exec("PRAGMA foreign_keys = ON");
614
+ initSchema(opened);
615
+ return opened;
616
+ } catch (error) {
617
+ try {
618
+ opened.close();
619
+ } catch {}
620
+ throw error;
621
+ }
622
+ }, `opening ${path}`);
587
623
  if (!skipSeed) {
588
624
  Promise.resolve().then(() => (init_pricing(), exports_pricing)).then(({ ensurePricingSeeded: ensurePricingSeeded2 }) => ensurePricingSeeded2(db)).catch(() => {});
589
625
  }
@@ -945,36 +981,50 @@ function querySessions(db, filter = {}) {
945
981
  SELECT * FROM sessions ${where} ORDER BY started_at DESC LIMIT ? OFFSET ?
946
982
  `).all(...params, limit, offset);
947
983
  }
948
- function queryTopSessions(db, n = 10, agent) {
984
+ function queryTopSessions(db, n = 10, agent, since) {
985
+ const conditions = [];
986
+ const params = [];
949
987
  if (agent) {
950
- return db.prepare(`SELECT * FROM sessions WHERE agent = ? ORDER BY total_cost_usd DESC LIMIT ?`).all(agent, n);
988
+ conditions.push("agent = ?");
989
+ params.push(agent);
990
+ }
991
+ if (since) {
992
+ conditions.push("started_at >= ?");
993
+ params.push(since);
951
994
  }
952
- return db.prepare(`SELECT * FROM sessions ORDER BY total_cost_usd DESC LIMIT ?`).all(n);
995
+ const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
996
+ return db.prepare(`SELECT * FROM sessions ${where} ORDER BY total_cost_usd DESC LIMIT ?`).all(...params, n);
953
997
  }
954
- function querySummary(db, period, machine, allMachines = false) {
998
+ function querySummary(db, period, machine, allMachines = false, agent) {
955
999
  const rWhere = periodWhere(period);
956
1000
  const sWhere = sessionPeriodWhere(period);
957
- const machineClause = !allMachines && machine ? ` AND machine_id = '${machine.replace(/'/g, "''")}'` : "";
1001
+ const machineClause = !allMachines && machine ? " AND machine_id = ?" : "";
1002
+ const agentClause = agent ? " AND agent = ?" : "";
1003
+ const params = [];
1004
+ if (!allMachines && machine)
1005
+ params.push(machine);
1006
+ if (agent)
1007
+ params.push(agent);
958
1008
  const r = db.prepare(`
959
1009
  SELECT COALESCE(SUM(cost_usd), 0) as total_usd,
960
1010
  COUNT(*) as requests,
961
1011
  COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as tokens
962
- FROM requests WHERE ${rWhere}${machineClause}
963
- `).get();
1012
+ FROM requests WHERE ${rWhere}${machineClause}${agentClause}
1013
+ `).get(...params);
964
1014
  const codexTotals = db.prepare(`
965
1015
  SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
966
1016
  COALESCE(SUM(total_tokens), 0) as tokens,
967
1017
  COALESCE(SUM(request_count), 0) as requests,
968
1018
  COUNT(*) as sessions
969
1019
  FROM sessions
970
- WHERE ${sWhere}${machineClause}
1020
+ WHERE ${sWhere}${machineClause}${agentClause}
971
1021
  AND id NOT IN (SELECT DISTINCT session_id FROM requests)
972
- `).get();
1022
+ `).get(...params);
973
1023
  const requestSessionCount = db.prepare(`
974
1024
  SELECT COUNT(DISTINCT session_id) as sessions
975
1025
  FROM requests
976
- WHERE ${rWhere}${machineClause}
977
- `).get();
1026
+ WHERE ${rWhere}${machineClause}${agentClause}
1027
+ `).get(...params);
978
1028
  return {
979
1029
  total_usd: r.total_usd + codexTotals.cost_usd,
980
1030
  requests: r.requests + codexTotals.requests,
@@ -983,6 +1033,21 @@ function querySummary(db, period, machine, allMachines = false) {
983
1033
  period
984
1034
  };
985
1035
  }
1036
+ function queryZeroCostTokenizedModels(db, limit = 10) {
1037
+ return db.prepare(`
1038
+ SELECT agent,
1039
+ model,
1040
+ COUNT(*) as requests,
1041
+ COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
1042
+ MAX(timestamp) as last_seen
1043
+ FROM requests
1044
+ WHERE cost_usd = 0
1045
+ AND (input_tokens > 0 OR output_tokens > 0 OR cache_read_tokens > 0 OR cache_create_tokens > 0)
1046
+ GROUP BY agent, model
1047
+ ORDER BY requests DESC, total_tokens DESC
1048
+ LIMIT ?
1049
+ `).all(limit);
1050
+ }
986
1051
  function queryModelBreakdown(db) {
987
1052
  return db.prepare(`
988
1053
  SELECT model, agent,
@@ -2186,12 +2251,192 @@ function mergePeerDatabase(target, sourcePath, opts = {}) {
2186
2251
  tables: tables.filter((t) => t.inserted || t.updated || t.skipped || t.collisions)
2187
2252
  };
2188
2253
  }
2254
+ // src/lib/fleet-sync.ts
2255
+ init_database();
2256
+ import { spawnSync } from "child_process";
2257
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
2258
+ import { join as join3, resolve } from "path";
2259
+ import { homedir as homedir2 } from "os";
2260
+ import { Database as Database2 } from "bun:sqlite";
2261
+ var DEFAULT_TIMEOUT_MS = 120000;
2262
+ var DEFAULT_MACHINES = ["spark02", "apple03"];
2263
+ function runEconomyFleetSync(options = {}) {
2264
+ const generatedAt = (options.now ?? new Date).toISOString();
2265
+ const timeoutMs = normalizePositiveInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS);
2266
+ const runner = options.runner ?? spawnCommand;
2267
+ const dryRun = Boolean(options.dryRun);
2268
+ const machines = ((options.machines?.length) ? options.machines : DEFAULT_MACHINES).filter(Boolean);
2269
+ const cacheDir = resolve(options.cacheDir ?? join3(homedir2(), ".hasna", "economy", "fleet-sync-cache"));
2270
+ const remoteSnapshotDir = options.remoteSnapshotDir ?? "$HOME/.hasna/economy/fleet-sync-snapshots";
2271
+ const localSync = options.localSync !== false;
2272
+ const remoteSync = options.remoteSync !== false;
2273
+ const local = {
2274
+ status: dryRun ? "dry_run" : localSync ? "skipped" : "skipped",
2275
+ db_path: getDbPath()
2276
+ };
2277
+ if (localSync) {
2278
+ if (dryRun) {
2279
+ local.status = "dry_run";
2280
+ } else {
2281
+ local.sync = runner("economy", ["sync"], { timeoutMs });
2282
+ local.status = local.sync.code === 0 ? "synced" : "failed";
2283
+ }
2284
+ }
2285
+ const remote = machines.map((machine) => syncMachine(machine, {
2286
+ generatedAt,
2287
+ timeoutMs,
2288
+ runner,
2289
+ dryRun,
2290
+ remoteSync,
2291
+ cacheDir,
2292
+ remoteSnapshotDir
2293
+ }));
2294
+ return {
2295
+ schema: "open-economy.fleet-sync.v1",
2296
+ generated_at: generatedAt,
2297
+ dry_run: dryRun,
2298
+ local,
2299
+ remote,
2300
+ summary: {
2301
+ machines: remote.length,
2302
+ merged: remote.filter((row) => row.status === "merged").length,
2303
+ failed: remote.filter((row) => row.status === "failed").length,
2304
+ dry_run: remote.filter((row) => row.status === "dry_run").length + (dryRun ? 1 : 0)
2305
+ }
2306
+ };
2307
+ }
2308
+ function syncMachine(machine, context) {
2309
+ const safeMachine = machine.replace(/[^A-Za-z0-9_.-]/g, "_");
2310
+ const stamp = context.generatedAt.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
2311
+ const snapshotName = `${safeMachine}-${stamp}.economy.db`;
2312
+ const remoteSnapshotPath = `${context.remoteSnapshotDir.replace(/\/$/, "")}/${snapshotName}`;
2313
+ const tmpLocal = join3(context.cacheDir, `${snapshotName}.tmp`);
2314
+ const verifiedLocal = join3(context.cacheDir, snapshotName);
2315
+ if (context.dryRun) {
2316
+ return { machine, status: "dry_run", remote_snapshot_path: remoteSnapshotPath };
2317
+ }
2318
+ mkdirSync3(context.cacheDir, { recursive: true });
2319
+ rmSync(tmpLocal, { force: true });
2320
+ const result = {
2321
+ machine,
2322
+ status: "failed",
2323
+ remote_snapshot_path: remoteSnapshotPath
2324
+ };
2325
+ if (context.remoteSync) {
2326
+ result.remote_sync = context.runner("ssh", [machine, "economy sync"], { timeoutMs: context.timeoutMs });
2327
+ if (result.remote_sync.code !== 0) {
2328
+ result.error = "remote economy sync failed";
2329
+ return result;
2330
+ }
2331
+ }
2332
+ result.remote_snapshot = context.runner("ssh", [machine, remoteSnapshotCommand(context.remoteSnapshotDir, snapshotName)], { timeoutMs: context.timeoutMs });
2333
+ if (result.remote_snapshot.code !== 0) {
2334
+ result.error = "remote snapshot failed";
2335
+ return result;
2336
+ }
2337
+ const emittedPath = result.remote_snapshot.stdout.trim().split(/\r?\n/).at(-1)?.trim() || remoteSnapshotPath;
2338
+ if (!isSafeRemoteSnapshotPath(emittedPath, context.remoteSnapshotDir)) {
2339
+ result.error = `remote snapshot path refused: ${emittedPath}`;
2340
+ return result;
2341
+ }
2342
+ result.remote_snapshot_path = emittedPath;
2343
+ result.pull = context.runner("scp", [`${machine}:${emittedPath}`, tmpLocal], { timeoutMs: context.timeoutMs });
2344
+ context.runner("ssh", [machine, `rm -f ${shellQuote(emittedPath)}`], { timeoutMs: context.timeoutMs });
2345
+ if (result.pull.code !== 0) {
2346
+ result.error = "snapshot pull failed; no cached DB merged";
2347
+ rmSync(tmpLocal, { force: true });
2348
+ return result;
2349
+ }
2350
+ const integrity = verifySqliteIntegrity(tmpLocal);
2351
+ result.local_integrity = integrity;
2352
+ if (!integrity.ok) {
2353
+ result.error = "pulled snapshot failed local integrity check";
2354
+ rmSync(tmpLocal, { force: true });
2355
+ return result;
2356
+ }
2357
+ renameSync(tmpLocal, verifiedLocal);
2358
+ const db = openDatabase();
2359
+ result.merge = mergePeerDatabase(db, verifiedLocal, { sourceMachine: machine });
2360
+ result.status = "merged";
2361
+ return result;
2362
+ }
2363
+ function remoteSnapshotCommand(remoteDir, filename) {
2364
+ const dirExpr = remoteDir.startsWith("$HOME/") ? `"${remoteDir.replace(/"/g, "\\\"")}"` : shellQuote(remoteDir);
2365
+ const safeFilename = filename.replace(/[^A-Za-z0-9_.-]/g, "_");
2366
+ return [
2367
+ "set -euo pipefail",
2368
+ 'db="${HASNA_ECONOMY_DB_PATH:-${ECONOMY_DB:-$HOME/.hasna/economy/economy.db}}"',
2369
+ `snapshot_dir=${dirExpr}`,
2370
+ `out="$snapshot_dir/${safeFilename}"`,
2371
+ 'test -f "$db"',
2372
+ 'mkdir -p "$snapshot_dir"',
2373
+ 'rm -f "$out"',
2374
+ `sqlite3 "$db" "VACUUM INTO '$out'"`,
2375
+ 'test "$(sqlite3 "$out" "PRAGMA integrity_check;")" = "ok"',
2376
+ 'printf "%s\\n" "$out"'
2377
+ ].join("; ");
2378
+ }
2379
+ function isSafeRemoteSnapshotPath(path, remoteSnapshotDir = "$HOME/.hasna/economy/fleet-sync-snapshots") {
2380
+ if (!path || path.includes("\x00") || path.includes(`
2381
+ `) || path.includes("\r"))
2382
+ return false;
2383
+ if (!path.endsWith(".db"))
2384
+ return false;
2385
+ if (path.includes(".."))
2386
+ return false;
2387
+ const prefix = remoteSnapshotDir.replace(/\/$/, "");
2388
+ return path.startsWith(`${prefix}/`) || path.startsWith("$HOME/.hasna/economy/fleet-sync-snapshots/") || path.includes("/.hasna/economy/fleet-sync-snapshots/");
2389
+ }
2390
+ function verifySqliteIntegrity(path) {
2391
+ if (!existsSync4(path))
2392
+ return { ok: false, detail: "file does not exist" };
2393
+ let db;
2394
+ try {
2395
+ db = new Database2(path, { readonly: true });
2396
+ const row = db.query("PRAGMA integrity_check").get();
2397
+ const detail = Object.values(row ?? {})[0] ?? "";
2398
+ return { ok: detail.toLowerCase() === "ok", detail: detail || "no integrity result" };
2399
+ } catch (error) {
2400
+ return { ok: false, detail: error instanceof Error ? error.message : String(error) };
2401
+ } finally {
2402
+ db?.close();
2403
+ }
2404
+ }
2405
+ function writeFleetSyncReport(reportDir, result) {
2406
+ mkdirSync3(reportDir, { recursive: true });
2407
+ const path = join3(resolve(reportDir), `${result.generated_at.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z")}.json`);
2408
+ writeFileSync2(path, `${JSON.stringify(result, null, 2)}
2409
+ `);
2410
+ return path;
2411
+ }
2412
+ function spawnCommand(command, args, options) {
2413
+ const result = spawnSync(command, args, {
2414
+ encoding: "utf8",
2415
+ timeout: options.timeoutMs,
2416
+ maxBuffer: 1024 * 1024,
2417
+ stdio: ["ignore", "pipe", "pipe"]
2418
+ });
2419
+ return {
2420
+ code: result.status,
2421
+ stdout: result.stdout ?? "",
2422
+ stderr: result.stderr ?? "",
2423
+ ...result.error ? { error: result.error.message } : {}
2424
+ };
2425
+ }
2426
+ function shellQuote(value) {
2427
+ return `'${value.replace(/'/g, "'\\''")}'`;
2428
+ }
2429
+ function normalizePositiveInteger(value, fallback) {
2430
+ if (value == null || !Number.isFinite(value) || value < 1)
2431
+ return fallback;
2432
+ return Math.floor(value);
2433
+ }
2189
2434
  // src/ingest/claude.ts
2190
2435
  init_database();
2191
2436
  init_pricing();
2192
- import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync2 } from "fs";
2193
- import { homedir as homedir2 } from "os";
2194
- import { join as join3, basename } from "path";
2437
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync5, statSync as statSync2 } from "fs";
2438
+ import { homedir as homedir3 } from "os";
2439
+ import { join as join4, basename } from "path";
2195
2440
 
2196
2441
  // src/lib/savings.ts
2197
2442
  function defaultCostBasisForAgent(agent) {
@@ -2338,8 +2583,8 @@ function withAccount(record, account) {
2338
2583
  function autoDetectProject(cwd, projects) {
2339
2584
  return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
2340
2585
  }
2341
- var CLAUDE_PROJECTS_DIR = join3(homedir2(), ".claude", "projects");
2342
- var TAKUMI_PROJECTS_DIR = join3(homedir2(), ".takumi", "projects");
2586
+ var CLAUDE_PROJECTS_DIR = join4(homedir3(), ".claude", "projects");
2587
+ var TAKUMI_PROJECTS_DIR = join4(homedir3(), ".takumi", "projects");
2343
2588
  function dirNameToPath(dirName) {
2344
2589
  return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
2345
2590
  }
@@ -2349,9 +2594,9 @@ function collectJsonlFiles(projectDir) {
2349
2594
  try {
2350
2595
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
2351
2596
  if (entry.isDirectory())
2352
- walk(join3(dir, entry.name));
2597
+ walk(join4(dir, entry.name));
2353
2598
  else if (entry.name.endsWith(".jsonl"))
2354
- files.push(join3(dir, entry.name));
2599
+ files.push(join4(dir, entry.name));
2355
2600
  }
2356
2601
  } catch {}
2357
2602
  }
@@ -2365,7 +2610,7 @@ async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_D
2365
2610
  return ingestJsonlProjects(db, projectsDir, "takumi", verbose);
2366
2611
  }
2367
2612
  async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false) {
2368
- if (!existsSync4(projectsDir)) {
2613
+ if (!existsSync5(projectsDir)) {
2369
2614
  if (verbose)
2370
2615
  console.log(`${agentName} projects dir not found:`, projectsDir);
2371
2616
  return { files: 0, requests: 0, sessions: 0 };
@@ -2378,7 +2623,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
2378
2623
  const account = await resolveAccountForAgent(agentName);
2379
2624
  const projectDirs = readdirSync2(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
2380
2625
  for (const projectDirEntry of projectDirs) {
2381
- const projectDirPath = join3(projectsDir, projectDirEntry.name);
2626
+ const projectDirPath = join4(projectsDir, projectDirEntry.name);
2382
2627
  const projectPath = dirNameToPath(projectDirEntry.name);
2383
2628
  const jsonlFiles = collectJsonlFiles(projectDirPath);
2384
2629
  for (const filePath of jsonlFiles) {
@@ -2513,14 +2758,14 @@ function supportsClaudeDataResidencyPricing(model) {
2513
2758
  // src/ingest/codex.ts
2514
2759
  init_database();
2515
2760
  init_pricing();
2516
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
2517
- import { homedir as homedir3 } from "os";
2518
- import { join as join4, basename as basename2 } from "path";
2761
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
2762
+ import { homedir as homedir4 } from "os";
2763
+ import { join as join5, basename as basename2 } from "path";
2519
2764
  import { Database as BunDatabase2 } from "bun:sqlite";
2520
- var DEFAULT_CODEX_DB_PATH = join4(homedir3(), ".codex", "state_5.sqlite");
2521
- var DEFAULT_CODEX_CONFIG_PATH = join4(homedir3(), ".codex", "config.toml");
2522
- var DEFAULT_CODEWITH_DB_PATH = join4(homedir3(), ".codewith", "state_5.sqlite");
2523
- var DEFAULT_CODEWITH_CONFIG_PATH = join4(homedir3(), ".codewith", "config.toml");
2765
+ var DEFAULT_CODEX_DB_PATH = join5(homedir4(), ".codex", "state_5.sqlite");
2766
+ var DEFAULT_CODEX_CONFIG_PATH = join5(homedir4(), ".codex", "config.toml");
2767
+ var DEFAULT_CODEWITH_DB_PATH = join5(homedir4(), ".codewith", "state_5.sqlite");
2768
+ var DEFAULT_CODEWITH_CONFIG_PATH = join5(homedir4(), ".codewith", "config.toml");
2524
2769
  var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
2525
2770
  function codexDbPath() {
2526
2771
  return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
@@ -2562,7 +2807,7 @@ function codexSources() {
2562
2807
  });
2563
2808
  }
2564
2809
  function readCodexModel(configPath = codexConfigPath()) {
2565
- if (!existsSync5(configPath))
2810
+ if (!existsSync6(configPath))
2566
2811
  return "gpt-5-codex";
2567
2812
  try {
2568
2813
  const content = readFileSync4(configPath, "utf-8");
@@ -2603,7 +2848,7 @@ function openCodexDb(dbPath, verbose) {
2603
2848
  return null;
2604
2849
  }
2605
2850
  function readTokenEvents(rolloutPath) {
2606
- if (!rolloutPath || !existsSync5(rolloutPath))
2851
+ if (!rolloutPath || !existsSync6(rolloutPath))
2607
2852
  return [];
2608
2853
  const fallbackUsages = new Map;
2609
2854
  let fallbackTimestamp;
@@ -2684,7 +2929,7 @@ async function ingestCodex(db, verbose = false) {
2684
2929
  let requests = 0;
2685
2930
  const account = await resolveAccountForAgent("codex");
2686
2931
  for (const source of codexSources()) {
2687
- if (!existsSync5(source.dbPath)) {
2932
+ if (!existsSync6(source.dbPath)) {
2688
2933
  if (verbose)
2689
2934
  console.log(`${source.label} DB not found:`, source.dbPath);
2690
2935
  continue;
@@ -2764,11 +3009,11 @@ async function ingestCodex(db, verbose = false) {
2764
3009
  // src/ingest/gemini.ts
2765
3010
  init_database();
2766
3011
  init_pricing();
2767
- import { readdirSync as readdirSync3, readFileSync as readFileSync5, existsSync as existsSync6, statSync as statSync3 } from "fs";
2768
- import { homedir as homedir4 } from "os";
2769
- import { join as join5, basename as basename3 } from "path";
2770
- var DEFAULT_GEMINI_TMP_DIR = join5(homedir4(), ".gemini", "tmp");
2771
- var DEFAULT_GEMINI_HISTORY_DIR = join5(homedir4(), ".gemini", "history");
3012
+ import { readdirSync as readdirSync3, readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync3 } from "fs";
3013
+ import { homedir as homedir5 } from "os";
3014
+ import { join as join6, basename as basename3 } from "path";
3015
+ var DEFAULT_GEMINI_TMP_DIR = join6(homedir5(), ".gemini", "tmp");
3016
+ var DEFAULT_GEMINI_HISTORY_DIR = join6(homedir5(), ".gemini", "history");
2772
3017
  function geminiTmpDir() {
2773
3018
  return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
2774
3019
  }
@@ -2785,12 +3030,12 @@ function numberField(...values) {
2785
3030
  function listProjectDirs(...roots) {
2786
3031
  const dirs = new Set;
2787
3032
  for (const root of roots) {
2788
- if (!existsSync6(root))
3033
+ if (!existsSync7(root))
2789
3034
  continue;
2790
3035
  try {
2791
3036
  for (const entry of readdirSync3(root, { withFileTypes: true })) {
2792
3037
  if (entry.isDirectory())
2793
- dirs.add(join5(root, entry.name));
3038
+ dirs.add(join6(root, entry.name));
2794
3039
  }
2795
3040
  } catch {}
2796
3041
  }
@@ -2801,9 +3046,9 @@ function projectRoot(projectDir, chatData) {
2801
3046
  return chatData.projectPath;
2802
3047
  if (chatData.project_path)
2803
3048
  return chatData.project_path;
2804
- const rootFile = join5(projectDir, ".project_root");
3049
+ const rootFile = join6(projectDir, ".project_root");
2805
3050
  try {
2806
- if (existsSync6(rootFile))
3051
+ if (existsSync7(rootFile))
2807
3052
  return readFileSync5(rootFile, "utf-8").trim();
2808
3053
  } catch {}
2809
3054
  return "";
@@ -2811,7 +3056,7 @@ function projectRoot(projectDir, chatData) {
2811
3056
  async function ingestGemini(db, verbose) {
2812
3057
  const tmpDir = geminiTmpDir();
2813
3058
  const historyDir = geminiHistoryDir();
2814
- if (!existsSync6(tmpDir) && !existsSync6(historyDir)) {
3059
+ if (!existsSync7(tmpDir) && !existsSync7(historyDir)) {
2815
3060
  if (verbose)
2816
3061
  console.log("Gemini tmp/history dirs not found:", tmpDir, historyDir);
2817
3062
  return { sessions: 0, requests: 0 };
@@ -2823,17 +3068,17 @@ async function ingestGemini(db, verbose) {
2823
3068
  const account = await resolveAccountForAgent("gemini");
2824
3069
  const projectDirs = listProjectDirs(tmpDir, historyDir);
2825
3070
  for (const projectDir of projectDirs) {
2826
- const chatsDir = join5(projectDir, "chats");
2827
- if (!existsSync6(chatsDir))
3071
+ const chatsDir = join6(projectDir, "chats");
3072
+ if (!existsSync7(chatsDir))
2828
3073
  continue;
2829
3074
  let chatFiles = [];
2830
3075
  try {
2831
- chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join5(chatsDir, f));
3076
+ chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join6(chatsDir, f));
2832
3077
  } catch {
2833
3078
  continue;
2834
3079
  }
2835
3080
  for (const filePath of chatFiles) {
2836
- const stateKey = filePath.replace(homedir4(), "~");
3081
+ const stateKey = filePath.replace(homedir5(), "~");
2837
3082
  let fileMtime = "0";
2838
3083
  try {
2839
3084
  fileMtime = statSync3(filePath).mtimeMs.toString();
@@ -2920,6 +3165,8 @@ async function ingestGemini(db, verbose) {
2920
3165
  return { sessions: totalSessions, requests: totalRequests };
2921
3166
  }
2922
3167
  export {
3168
+ writeFleetSyncReport,
3169
+ verifySqliteIntegrity,
2923
3170
  upsertUsageSnapshot,
2924
3171
  upsertSubscription,
2925
3172
  upsertSession,
@@ -2933,8 +3180,10 @@ export {
2933
3180
  setIngestState,
2934
3181
  setActiveModel,
2935
3182
  seedModelPricing,
3183
+ runEconomyFleetSync,
2936
3184
  rollupSession,
2937
3185
  readCodexModel,
3186
+ queryZeroCostTokenizedModels,
2938
3187
  queryUsageSnapshots,
2939
3188
  queryTopSessions,
2940
3189
  querySummary,
@@ -2957,6 +3206,7 @@ export {
2957
3206
  listMachineRegistry,
2958
3207
  listGoals,
2959
3208
  listBudgets,
3209
+ isSafeRemoteSnapshotPath,
2960
3210
  isAgent,
2961
3211
  ingestTakumi,
2962
3212
  ingestJsonlProjects,