@hasna/economy 0.2.31 → 0.2.33

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 (64) hide show
  1. package/README.md +6 -6
  2. package/dist/cli/commands/tui.d.ts +1 -1
  3. package/dist/cli/commands/tui.d.ts.map +1 -1
  4. package/dist/cli/index.js +184 -832
  5. package/dist/db/database.d.ts +1 -3
  6. package/dist/db/database.d.ts.map +1 -1
  7. package/dist/index.d.ts +0 -4
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +50 -1049
  10. package/dist/ingest/billing.d.ts +1 -1
  11. package/dist/ingest/billing.d.ts.map +1 -1
  12. package/dist/ingest/claude-quota.d.ts +1 -1
  13. package/dist/ingest/claude-quota.d.ts.map +1 -1
  14. package/dist/ingest/claude.d.ts +1 -1
  15. package/dist/ingest/claude.d.ts.map +1 -1
  16. package/dist/ingest/codex-quota.d.ts +1 -1
  17. package/dist/ingest/codex-quota.d.ts.map +1 -1
  18. package/dist/ingest/codex.d.ts +1 -1
  19. package/dist/ingest/codex.d.ts.map +1 -1
  20. package/dist/ingest/cursor.d.ts +1 -1
  21. package/dist/ingest/cursor.d.ts.map +1 -1
  22. package/dist/ingest/gemini.d.ts +1 -1
  23. package/dist/ingest/gemini.d.ts.map +1 -1
  24. package/dist/ingest/hermes.d.ts +1 -1
  25. package/dist/ingest/hermes.d.ts.map +1 -1
  26. package/dist/ingest/opencode.d.ts +1 -1
  27. package/dist/ingest/opencode.d.ts.map +1 -1
  28. package/dist/ingest/otel.d.ts +1 -1
  29. package/dist/ingest/otel.d.ts.map +1 -1
  30. package/dist/ingest/pi.d.ts +1 -1
  31. package/dist/ingest/pi.d.ts.map +1 -1
  32. package/dist/ingest/plugin.d.ts +1 -1
  33. package/dist/ingest/plugin.d.ts.map +1 -1
  34. package/dist/lib/billing-diff.d.ts +1 -1
  35. package/dist/lib/billing-diff.d.ts.map +1 -1
  36. package/dist/lib/cloud-sync.d.ts +2 -9
  37. package/dist/lib/cloud-sync.d.ts.map +1 -1
  38. package/dist/lib/open-projects.d.ts +2 -3
  39. package/dist/lib/open-projects.d.ts.map +1 -1
  40. package/dist/lib/peer-sync.d.ts +1 -1
  41. package/dist/lib/peer-sync.d.ts.map +1 -1
  42. package/dist/lib/pricing.d.ts +1 -1
  43. package/dist/lib/pricing.d.ts.map +1 -1
  44. package/dist/lib/savings.d.ts +1 -1
  45. package/dist/lib/savings.d.ts.map +1 -1
  46. package/dist/lib/spikes.d.ts +1 -1
  47. package/dist/lib/spikes.d.ts.map +1 -1
  48. package/dist/lib/sync-all.d.ts +1 -1
  49. package/dist/lib/sync-all.d.ts.map +1 -1
  50. package/dist/lib/webhooks.d.ts +1 -1
  51. package/dist/lib/webhooks.d.ts.map +1 -1
  52. package/dist/mcp/index.js +34 -514
  53. package/dist/mcp/server.d.ts.map +1 -1
  54. package/dist/otel/index.js +15 -442
  55. package/dist/server/index.js +46 -492
  56. package/dist/server/serve.d.ts +1 -1
  57. package/dist/server/serve.d.ts.map +1 -1
  58. package/package.json +6 -5
  59. package/dist/db/storage-adapter.d.ts +0 -34
  60. package/dist/db/storage-adapter.d.ts.map +0 -1
  61. package/dist/lib/remote-storage.d.ts +0 -15
  62. package/dist/lib/remote-storage.d.ts.map +0 -1
  63. package/dist/lib/storage-sync.d.ts +0 -27
  64. package/dist/lib/storage-sync.d.ts.map +0 -1
package/dist/cli/index.js CHANGED
@@ -17,60 +17,6 @@ var __export = (target, all) => {
17
17
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
18
18
  var __require = import.meta.require;
19
19
 
20
- // src/db/storage-adapter.ts
21
- import { Database as BunDatabase } from "bun:sqlite";
22
-
23
- class SqliteAdapter {
24
- db;
25
- constructor(path) {
26
- this.db = new BunDatabase(path, { create: true });
27
- }
28
- run(sql, ...params) {
29
- const result = this.db.prepare(sql).run(...params);
30
- return { changes: result.changes, lastInsertRowid: result.lastInsertRowid };
31
- }
32
- get(sql, ...params) {
33
- return this.db.prepare(sql).get(...params);
34
- }
35
- all(sql, ...params) {
36
- return this.db.prepare(sql).all(...params);
37
- }
38
- exec(sql) {
39
- this.db.exec(sql);
40
- }
41
- query(sql) {
42
- return this.db.query(sql);
43
- }
44
- prepare(sql) {
45
- const statement = this.db.prepare(sql);
46
- return {
47
- run(...params) {
48
- const result = statement.run(...params);
49
- return { changes: result.changes, lastInsertRowid: result.lastInsertRowid };
50
- },
51
- get(...params) {
52
- return statement.get(...params);
53
- },
54
- all(...params) {
55
- return statement.all(...params);
56
- },
57
- finalize() {
58
- statement.finalize();
59
- }
60
- };
61
- }
62
- close() {
63
- this.db.close();
64
- }
65
- transaction(fn) {
66
- return this.db.transaction(fn)();
67
- }
68
- get raw() {
69
- return this.db;
70
- }
71
- }
72
- var init_storage_adapter = () => {};
73
-
74
20
  // src/lib/pricing.ts
75
21
  var exports_pricing = {};
76
22
  __export(exports_pricing, {
@@ -615,9 +561,9 @@ __export(exports_database, {
615
561
  deleteGoal: () => deleteGoal,
616
562
  deleteBudget: () => deleteBudget,
617
563
  dedupeRequests: () => dedupeRequests,
618
- clearBillingRange: () => clearBillingRange,
619
- SqliteAdapter: () => SqliteAdapter
564
+ clearBillingRange: () => clearBillingRange
620
565
  });
566
+ import { SqliteAdapter as Database } from "@hasna/cloud";
621
567
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
622
568
  import { hostname } from "os";
623
569
  import { homedir } from "os";
@@ -660,7 +606,7 @@ function openDatabase(dbPath, skipSeed = false) {
660
606
  if (dir && !existsSync(dir))
661
607
  mkdirSync(dir, { recursive: true });
662
608
  }
663
- const db = new SqliteAdapter(path);
609
+ const db = new Database(path);
664
610
  db.exec("PRAGMA journal_mode = WAL");
665
611
  db.exec("PRAGMA busy_timeout = 5000");
666
612
  db.exec("PRAGMA foreign_keys = ON");
@@ -1696,10 +1642,7 @@ function dedupeRequests(db) {
1696
1642
  }
1697
1643
  return removed;
1698
1644
  }
1699
- var init_database = __esm(() => {
1700
- init_storage_adapter();
1701
- init_storage_adapter();
1702
- });
1645
+ var init_database = () => {};
1703
1646
 
1704
1647
  // src/lib/savings.ts
1705
1648
  function periodWhere2(period, column) {
@@ -1965,370 +1908,6 @@ var init_package_metadata = __esm(() => {
1965
1908
  packageMetadata = getPackageMetadata();
1966
1909
  });
1967
1910
 
1968
- // src/lib/remote-storage.ts
1969
- import pg from "pg";
1970
- function translatePlaceholders(sql) {
1971
- let index = 0;
1972
- return sql.replace(/\?/g, () => `$${++index}`);
1973
- }
1974
- function normalizeParams(params) {
1975
- const flat = params.length === 1 && Array.isArray(params[0]) ? params[0] : params;
1976
- return flat.map((value) => value === undefined ? null : value);
1977
- }
1978
- function sslConfigFor(connectionString) {
1979
- return connectionString.includes("sslmode=require") || connectionString.includes("ssl=true") ? { rejectUnauthorized: false } : undefined;
1980
- }
1981
-
1982
- class PgAdapterAsync {
1983
- pool;
1984
- constructor(source) {
1985
- this.pool = typeof source === "string" ? new pg.Pool({ connectionString: source, ssl: sslConfigFor(source) }) : source;
1986
- }
1987
- async run(sql, ...params) {
1988
- const result = await this.pool.query(translatePlaceholders(sql), normalizeParams(params));
1989
- return { changes: result.rowCount ?? 0, lastInsertRowid: 0 };
1990
- }
1991
- async get(sql, ...params) {
1992
- const result = await this.pool.query(translatePlaceholders(sql), normalizeParams(params));
1993
- return result.rows[0] ?? null;
1994
- }
1995
- async all(sql, ...params) {
1996
- const result = await this.pool.query(translatePlaceholders(sql), normalizeParams(params));
1997
- return result.rows;
1998
- }
1999
- async exec(sql) {
2000
- await this.pool.query(translatePlaceholders(sql));
2001
- }
2002
- async close() {
2003
- await this.pool.end();
2004
- }
2005
- async transaction(fn) {
2006
- const client = await this.pool.connect();
2007
- try {
2008
- await client.query("BEGIN");
2009
- const result = await fn(client);
2010
- await client.query("COMMIT");
2011
- return result;
2012
- } catch (error) {
2013
- await client.query("ROLLBACK");
2014
- throw error;
2015
- } finally {
2016
- client.release();
2017
- }
2018
- }
2019
- get raw() {
2020
- return this.pool;
2021
- }
2022
- }
2023
- var init_remote_storage = () => {};
2024
-
2025
- // src/lib/storage-sync.ts
2026
- async function syncPush(local, remote, options) {
2027
- const tables = await getTableOrder(remote, options.tables);
2028
- return syncTransfer(local, remote, { ...options, tables }, "push");
2029
- }
2030
- async function syncPull(remote, local, options) {
2031
- const tables = await getTableOrder(remote, options.tables);
2032
- return syncTransfer(remote, local, { ...options, tables }, "pull");
2033
- }
2034
- function quoteIdent(identifier) {
2035
- return `"${identifier.replace(/"/g, '""')}"`;
2036
- }
2037
- async function getTableOrder(remote, tables) {
2038
- if (tables.length <= 1)
2039
- return tables;
2040
- try {
2041
- const rows = await remote.all(`
2042
- SELECT DISTINCT
2043
- tc.table_name AS source_table,
2044
- ccu.table_name AS referenced_table
2045
- FROM information_schema.table_constraints tc
2046
- JOIN information_schema.constraint_column_usage ccu
2047
- ON tc.constraint_name = ccu.constraint_name
2048
- AND tc.table_schema = ccu.table_schema
2049
- WHERE tc.constraint_type = 'FOREIGN KEY'
2050
- AND tc.table_schema = 'public'
2051
- `);
2052
- if (rows.length > 0)
2053
- return topoSort(tables, rows);
2054
- } catch {}
2055
- return tables;
2056
- }
2057
- function topoSort(tables, foreignKeys) {
2058
- const allowed = new Set(tables);
2059
- const deps = new Map;
2060
- for (const table of tables)
2061
- deps.set(table, new Set);
2062
- for (const fk of foreignKeys) {
2063
- if (allowed.has(fk.source_table) && allowed.has(fk.referenced_table)) {
2064
- deps.get(fk.source_table)?.add(fk.referenced_table);
2065
- }
2066
- }
2067
- const sorted = [];
2068
- const visited = new Set;
2069
- const visiting = new Set;
2070
- function visit(table) {
2071
- if (visited.has(table))
2072
- return;
2073
- if (visiting.has(table)) {
2074
- visited.add(table);
2075
- sorted.push(table);
2076
- return;
2077
- }
2078
- visiting.add(table);
2079
- for (const dep of deps.get(table) ?? [])
2080
- visit(dep);
2081
- visiting.delete(table);
2082
- visited.add(table);
2083
- sorted.push(table);
2084
- }
2085
- for (const table of tables)
2086
- visit(table);
2087
- return sorted;
2088
- }
2089
- async function resolvePrimaryKeys(source, target, table, option) {
2090
- if (option)
2091
- return Array.isArray(option) ? option : [option];
2092
- const sourceKeys = await detectPrimaryKeys(source, table);
2093
- if (sourceKeys.length > 0)
2094
- return sourceKeys;
2095
- return detectPrimaryKeys(target, table);
2096
- }
2097
- async function detectPrimaryKeys(adapter, table) {
2098
- if (isAsyncAdapter(adapter)) {
2099
- try {
2100
- const rows = await adapter.all(`
2101
- SELECT kcu.column_name, kcu.ordinal_position
2102
- FROM information_schema.table_constraints tc
2103
- JOIN information_schema.key_column_usage kcu
2104
- ON tc.constraint_name = kcu.constraint_name
2105
- AND tc.table_schema = kcu.table_schema
2106
- WHERE tc.constraint_type = 'PRIMARY KEY'
2107
- AND tc.table_schema = 'public'
2108
- AND tc.table_name = ?
2109
- ORDER BY kcu.ordinal_position
2110
- `, table);
2111
- return rows.map((row) => row.column_name);
2112
- } catch {
2113
- return [];
2114
- }
2115
- }
2116
- try {
2117
- const rows = adapter.all(`PRAGMA table_info(${quoteIdent(table)})`);
2118
- return rows.filter((row) => row.pk > 0).sort((a, b) => a.pk - b.pk).map((row) => row.name);
2119
- } catch {
2120
- return [];
2121
- }
2122
- }
2123
- async function ensureTablesExist(source, target, tables) {
2124
- if (!isAsyncAdapter(source) || isAsyncAdapter(target))
2125
- return;
2126
- for (const table of tables)
2127
- await ensureTableInSqliteFromPg(target, source, table);
2128
- }
2129
- async function ensureTableInSqliteFromPg(target, source, table) {
2130
- const existing = target.all(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, table);
2131
- if (existing.length > 0)
2132
- return;
2133
- const columns = await source.all(`
2134
- SELECT column_name, data_type, is_nullable
2135
- FROM information_schema.columns
2136
- WHERE table_schema = 'public' AND table_name = ?
2137
- ORDER BY ordinal_position
2138
- `, table);
2139
- if (columns.length === 0)
2140
- return;
2141
- const primaryKeys = new Set(await detectPrimaryKeys(source, table));
2142
- const definitions = columns.filter((column) => !["tsvector", "tsquery"].includes(column.data_type.toLowerCase())).map((column) => {
2143
- const type = pgTypeToSqlite(column.data_type);
2144
- const notNull = column.is_nullable === "NO" && !primaryKeys.has(column.column_name) ? " NOT NULL" : "";
2145
- return `${quoteIdent(column.column_name)} ${type}${notNull}`;
2146
- });
2147
- if (primaryKeys.size > 0) {
2148
- definitions.push(`PRIMARY KEY (${[...primaryKeys].map(quoteIdent).join(", ")})`);
2149
- }
2150
- target.exec(`CREATE TABLE IF NOT EXISTS ${quoteIdent(table)} (${definitions.join(", ")})`);
2151
- }
2152
- function pgTypeToSqlite(pgType) {
2153
- const type = pgType.toLowerCase();
2154
- if (type.includes("int") || ["bigint", "smallint", "serial", "bigserial"].includes(type))
2155
- return "INTEGER";
2156
- if (type.includes("bool"))
2157
- return "INTEGER";
2158
- if (type.includes("float") || type.includes("double") || ["real", "numeric", "decimal"].includes(type))
2159
- return "REAL";
2160
- if (type === "bytea")
2161
- return "BLOB";
2162
- return "TEXT";
2163
- }
2164
- async function filterColumnsForTarget(target, table, columns) {
2165
- if (columns.includes("machine_id") && table !== "machines")
2166
- await ensureMachineIdColumnInTarget(target, table);
2167
- try {
2168
- if (isAsyncAdapter(target)) {
2169
- const rows2 = await target.all(`
2170
- SELECT column_name
2171
- FROM information_schema.columns
2172
- WHERE table_schema = 'public' AND table_name = ?
2173
- `, table);
2174
- if (rows2.length === 0)
2175
- return columns;
2176
- const targetColumns2 = new Set(rows2.map((row) => row.column_name));
2177
- return columns.filter((column) => targetColumns2.has(column));
2178
- }
2179
- const rows = target.all(`PRAGMA table_info(${quoteIdent(table)})`);
2180
- if (rows.length === 0)
2181
- return columns;
2182
- const targetColumns = new Set(rows.map((row) => row.name));
2183
- return columns.filter((column) => targetColumns.has(column));
2184
- } catch {
2185
- return columns;
2186
- }
2187
- }
2188
- async function ensureMachineIdColumnInTarget(target, table) {
2189
- if (isAsyncAdapter(target)) {
2190
- const rows2 = await target.all(`
2191
- SELECT column_name
2192
- FROM information_schema.columns
2193
- WHERE table_schema = 'public' AND table_name = ? AND column_name = 'machine_id'
2194
- `, table);
2195
- if (rows2.length === 0)
2196
- await target.exec(`ALTER TABLE ${quoteIdent(table)} ADD COLUMN machine_id TEXT DEFAULT ''`);
2197
- return;
2198
- }
2199
- const rows = target.all(`PRAGMA table_info(${quoteIdent(table)})`);
2200
- if (!rows.some((row) => row.name === "machine_id")) {
2201
- target.exec(`ALTER TABLE ${quoteIdent(table)} ADD COLUMN machine_id TEXT DEFAULT ''`);
2202
- }
2203
- }
2204
- async function syncTransfer(source, target, options, _direction) {
2205
- const { tables, onProgress, batchSize = 100, conflictColumn = "updated_at", primaryKey } = options;
2206
- const results = [];
2207
- const sqliteTarget = isAsyncAdapter(target) ? null : target;
2208
- await ensureTablesExist(source, target, tables);
2209
- if (sqliteTarget) {
2210
- try {
2211
- sqliteTarget.exec("PRAGMA foreign_keys = OFF");
2212
- } catch {}
2213
- }
2214
- try {
2215
- for (let i = 0;i < tables.length; i++) {
2216
- const table = tables[i];
2217
- const result = { table, rowsRead: 0, rowsWritten: 0, rowsSkipped: 0, errors: [] };
2218
- try {
2219
- onProgress?.({ table, phase: "reading", rowsRead: 0, rowsWritten: 0, totalTables: tables.length, currentTableIndex: i });
2220
- const rows = await readAll(source, `SELECT * FROM ${quoteIdent(table)}`);
2221
- result.rowsRead = rows.length;
2222
- if (rows.length === 0) {
2223
- onProgress?.({ table, phase: "done", rowsRead: 0, rowsWritten: 0, totalTables: tables.length, currentTableIndex: i });
2224
- results.push(result);
2225
- continue;
2226
- }
2227
- const sourceColumns = Object.keys(rows[0]);
2228
- const columns = await filterColumnsForTarget(target, table, sourceColumns);
2229
- const primaryKeys = await resolvePrimaryKeys(source, target, table, primaryKey);
2230
- if (primaryKeys.length === 0) {
2231
- result.errors.push(`Table "${table}" has no primary key; inserted without conflict handling`);
2232
- for (const batch of batches(rows, batchSize)) {
2233
- await insertBatch(target, table, columns, batch);
2234
- result.rowsWritten += batch.length;
2235
- }
2236
- results.push(result);
2237
- continue;
2238
- }
2239
- const missingKeys = primaryKeys.filter((key) => !columns.includes(key));
2240
- if (missingKeys.length > 0) {
2241
- result.errors.push(`Table "${table}" missing primary key column(s): ${missingKeys.join(", ")}`);
2242
- results.push(result);
2243
- continue;
2244
- }
2245
- onProgress?.({ table, phase: "writing", rowsRead: result.rowsRead, rowsWritten: 0, totalTables: tables.length, currentTableIndex: i });
2246
- const updateColumns = columns.filter((column) => !primaryKeys.includes(column));
2247
- const newestWinsColumn = columns.includes(conflictColumn) ? conflictColumn : undefined;
2248
- for (const batch of batches(rows, batchSize)) {
2249
- await upsertBatch(target, table, columns, updateColumns, primaryKeys, batch, newestWinsColumn);
2250
- result.rowsWritten += batch.length;
2251
- onProgress?.({ table, phase: "writing", rowsRead: result.rowsRead, rowsWritten: result.rowsWritten, totalTables: tables.length, currentTableIndex: i });
2252
- }
2253
- onProgress?.({ table, phase: "done", rowsRead: result.rowsRead, rowsWritten: result.rowsWritten, totalTables: tables.length, currentTableIndex: i });
2254
- } catch (error) {
2255
- result.errors.push(error instanceof Error ? error.message : String(error));
2256
- }
2257
- results.push(result);
2258
- }
2259
- } finally {
2260
- if (sqliteTarget) {
2261
- try {
2262
- sqliteTarget.exec("PRAGMA foreign_keys = ON");
2263
- } catch {}
2264
- }
2265
- }
2266
- return results;
2267
- }
2268
- function batches(rows, size) {
2269
- const result = [];
2270
- for (let offset = 0;offset < rows.length; offset += size)
2271
- result.push(rows.slice(offset, offset + size));
2272
- return result;
2273
- }
2274
- async function upsertBatch(target, table, columns, updateColumns, primaryKeys, batch, conflictColumn) {
2275
- if (batch.length === 0 || columns.length === 0)
2276
- return;
2277
- const fallbackKey = primaryKeys[0] ?? columns[0] ?? "id";
2278
- const columnList = columns.map(quoteIdent).join(", ");
2279
- const keyList = primaryKeys.map(quoteIdent).join(", ");
2280
- const setClause = updateColumns.length > 0 ? updateColumns.map((column) => `${quoteIdent(column)} = EXCLUDED.${quoteIdent(column)}`).join(", ") : `${quoteIdent(fallbackKey)} = EXCLUDED.${quoteIdent(fallbackKey)}`;
2281
- const whereClause = conflictColumn && updateColumns.includes(conflictColumn) ? ` WHERE ${quoteIdent(table)}.${quoteIdent(conflictColumn)} IS NULL OR EXCLUDED.${quoteIdent(conflictColumn)} >= ${quoteIdent(table)}.${quoteIdent(conflictColumn)}` : "";
2282
- if (isAsyncAdapter(target)) {
2283
- const placeholders2 = batch.map((_, rowIndex) => `(${columns.map((__, columnIndex) => `$${rowIndex * columns.length + columnIndex + 1}`).join(", ")})`).join(", ");
2284
- const params2 = batch.flatMap((row) => columns.map((column) => row[column] ?? null));
2285
- await target.run(`INSERT INTO ${quoteIdent(table)} (${columnList}) VALUES ${placeholders2}
2286
- ON CONFLICT (${keyList}) DO UPDATE SET ${setClause}${whereClause}`, ...params2);
2287
- return;
2288
- }
2289
- const placeholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
2290
- const params = batch.flatMap((row) => columns.map((column) => coerceForSqlite(row[column])));
2291
- target.run(`INSERT INTO ${quoteIdent(table)} (${columnList}) VALUES ${placeholders}
2292
- ON CONFLICT (${keyList}) DO UPDATE SET ${setClause}${whereClause}`, ...params);
2293
- }
2294
- async function insertBatch(target, table, columns, batch) {
2295
- if (batch.length === 0 || columns.length === 0)
2296
- return;
2297
- const columnList = columns.map(quoteIdent).join(", ");
2298
- if (isAsyncAdapter(target)) {
2299
- const placeholders2 = batch.map((_, rowIndex) => `(${columns.map((__, columnIndex) => `$${rowIndex * columns.length + columnIndex + 1}`).join(", ")})`).join(", ");
2300
- const params2 = batch.flatMap((row) => columns.map((column) => row[column] ?? null));
2301
- await target.run(`INSERT INTO ${quoteIdent(table)} (${columnList}) VALUES ${placeholders2}`, ...params2);
2302
- return;
2303
- }
2304
- const placeholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
2305
- const params = batch.flatMap((row) => columns.map((column) => coerceForSqlite(row[column])));
2306
- target.run(`INSERT INTO ${quoteIdent(table)} (${columnList}) VALUES ${placeholders}`, ...params);
2307
- }
2308
- function coerceForSqlite(value) {
2309
- if (value === null || value === undefined)
2310
- return null;
2311
- if (typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean")
2312
- return value;
2313
- if (value instanceof Date)
2314
- return value.toISOString();
2315
- if (Buffer.isBuffer(value) || value instanceof Uint8Array)
2316
- return value;
2317
- if (typeof value === "object")
2318
- return JSON.stringify(value);
2319
- return String(value);
2320
- }
2321
- function isAsyncAdapter(adapter) {
2322
- return adapter instanceof PgAdapterAsync;
2323
- }
2324
- async function readAll(adapter, sql) {
2325
- const rows = adapter.all(sql);
2326
- return rows instanceof Promise ? await rows : rows;
2327
- }
2328
- var init_storage_sync = __esm(() => {
2329
- init_remote_storage();
2330
- });
2331
-
2332
1911
  // src/db/pg-migrations.ts
2333
1912
  var exports_pg_migrations = {};
2334
1913
  __export(exports_pg_migrations, {
@@ -2518,9 +2097,6 @@ var init_pg_migrations = __esm(() => {
2518
2097
  });
2519
2098
 
2520
2099
  // src/lib/cloud-sync.ts
2521
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
2522
- import { homedir as homedir2, platform } from "os";
2523
- import { dirname as dirname2, join as join4 } from "path";
2524
2100
  function getCloudDatabaseUrl() {
2525
2101
  return process.env["ECONOMY_CLOUD_DATABASE_URL"] ?? process.env["HASNA_ECONOMY_CLOUD_DATABASE_URL"] ?? null;
2526
2102
  }
@@ -2539,6 +2115,7 @@ async function getCloudPg() {
2539
2115
  if (!url) {
2540
2116
  throw new Error("Missing ECONOMY_CLOUD_DATABASE_URL (or HASNA_ECONOMY_CLOUD_DATABASE_URL)");
2541
2117
  }
2118
+ const { PgAdapterAsync } = await import("@hasna/cloud");
2542
2119
  return new PgAdapterAsync(url);
2543
2120
  }
2544
2121
  async function runCloudMigrations(cloud) {
@@ -2548,35 +2125,31 @@ async function runCloudMigrations(cloud) {
2548
2125
  }
2549
2126
  }
2550
2127
  async function cloudPush(opts) {
2128
+ const { syncPush, SqliteAdapter } = await import("@hasna/cloud");
2551
2129
  const cloud = await getCloudPg();
2552
- const local = openDatabase(getDbPath(), true);
2553
- try {
2554
- await runCloudMigrations(cloud);
2555
- touchMachineRegistry(local, "push");
2556
- const tables = resolveCloudTables(opts?.tables);
2557
- const results = await syncPush(local, cloud, { tables, conflictColumn: "updated_at" });
2558
- const rows = results.reduce((sum, result) => sum + result.rowsWritten, 0);
2559
- return { rows, machine: getMachineId() };
2560
- } finally {
2561
- local.close();
2562
- await cloud.close();
2563
- }
2130
+ const local = new SqliteAdapter(getDbPath());
2131
+ await runCloudMigrations(cloud);
2132
+ const tables = opts?.tables ?? [...CLOUD_TABLES];
2133
+ const results = await syncPush(local, cloud, { tables, conflictColumn: "updated_at" });
2134
+ const rows = results.reduce((s, r) => s + r.rowsWritten, 0);
2135
+ touchMachineRegistry(local, "push");
2136
+ local.close();
2137
+ await cloud.close();
2138
+ return { rows, machine: getMachineId() };
2564
2139
  }
2565
2140
  async function cloudPull(opts) {
2141
+ const { syncPull, SqliteAdapter } = await import("@hasna/cloud");
2566
2142
  const cloud = await getCloudPg();
2567
- const local = openDatabase(getDbPath(), true);
2568
- try {
2569
- await runCloudMigrations(cloud);
2570
- const tables = resolveCloudTables(opts?.tables);
2571
- const results = await syncPull(cloud, local, { tables, conflictColumn: "updated_at" });
2572
- const rows = results.reduce((sum, result) => sum + result.rowsWritten, 0);
2573
- touchMachineRegistry(local, "pull");
2574
- setLastCloudPull();
2575
- return { rows, machine: getMachineId() };
2576
- } finally {
2577
- local.close();
2578
- await cloud.close();
2579
- }
2143
+ const local = new SqliteAdapter(getDbPath());
2144
+ await runCloudMigrations(cloud);
2145
+ const tables = opts?.tables ?? [...CLOUD_TABLES];
2146
+ const results = await syncPull(cloud, local, { tables, conflictColumn: "updated_at" });
2147
+ const rows = results.reduce((s, r) => s + r.rowsWritten, 0);
2148
+ touchMachineRegistry(local, "pull");
2149
+ local.close();
2150
+ await cloud.close();
2151
+ setLastCloudPull();
2152
+ return { rows, machine: getMachineId() };
2580
2153
  }
2581
2154
  async function cloudSyncFull() {
2582
2155
  const push = await cloudPush();
@@ -2584,21 +2157,13 @@ async function cloudSyncFull() {
2584
2157
  return { push: push.rows, pull: pull.rows, machine: getMachineId() };
2585
2158
  }
2586
2159
  function setLastCloudPull(at = new Date().toISOString()) {
2587
- const db = openDatabase(undefined, true);
2588
- try {
2589
- db.prepare(`INSERT OR REPLACE INTO ingest_state (source, key, value) VALUES ('cloud', 'last_pull_at', ?)`).run(at);
2590
- } finally {
2591
- db.close();
2592
- }
2160
+ const db = openDatabase();
2161
+ db.prepare(`INSERT OR REPLACE INTO ingest_state (source, key, value) VALUES ('cloud', 'last_pull_at', ?)`).run(at);
2593
2162
  }
2594
2163
  function getLastCloudPull() {
2595
- const db = openDatabase(undefined, true);
2596
- try {
2597
- const row = db.prepare(`SELECT value FROM ingest_state WHERE source = 'cloud' AND key = 'last_pull_at'`).get();
2598
- return row?.value ?? null;
2599
- } finally {
2600
- db.close();
2601
- }
2164
+ const db = openDatabase();
2165
+ const row = db.prepare(`SELECT value FROM ingest_state WHERE source = 'cloud' AND key = 'last_pull_at'`).get();
2166
+ return row?.value ?? null;
2602
2167
  }
2603
2168
  function shouldPullFromCloud() {
2604
2169
  if (!getCloudDatabaseUrl())
@@ -2644,231 +2209,22 @@ function touchMachineRegistry(db, direction) {
2644
2209
  updated_at = excluded.updated_at
2645
2210
  `).run(machine, machine, now, direction === "push" ? now : null, direction === "pull" ? now : null, packageMetadata.version, now, direction, direction);
2646
2211
  }
2647
- function resolveCloudTables(tables) {
2648
- if (!tables || tables.length === 0)
2649
- return [...CLOUD_TABLES];
2650
- const allowed = new Set(CLOUD_TABLES);
2651
- const requested = tables.map((table) => table.trim()).filter(Boolean);
2652
- const invalid = requested.filter((table) => !allowed.has(table));
2653
- if (invalid.length > 0) {
2654
- throw new Error(`Unknown economy sync table(s): ${invalid.join(", ")}`);
2655
- }
2656
- return requested;
2657
- }
2658
2212
  async function registerCloudSchedule(intervalMinutes) {
2659
- if (!Number.isFinite(intervalMinutes) || intervalMinutes <= 0) {
2660
- throw new Error("Cloud sync interval must be greater than 0 minutes");
2661
- }
2662
- mkdirSync3(SCHEDULE_CONFIG_DIR, { recursive: true });
2663
- if (platform() === "darwin") {
2664
- await registerLaunchd(intervalMinutes);
2665
- } else if (platform() === "linux") {
2666
- await registerSystemd(intervalMinutes);
2667
- } else {
2668
- throw new Error(`Automatic economy cloud sync is not supported on ${platform()}`);
2669
- }
2670
- writeFileSync2(SCHEDULE_CONFIG_PATH, JSON.stringify({ intervalMinutes, updatedAt: new Date().toISOString() }, null, 2));
2213
+ const { registerSyncSchedule } = await import("@hasna/cloud");
2214
+ await registerSyncSchedule(intervalMinutes);
2671
2215
  }
2672
2216
  async function removeCloudSchedule() {
2673
- if (platform() === "darwin")
2674
- await removeLaunchd();
2675
- if (platform() === "linux")
2676
- await removeSystemd();
2677
- try {
2678
- unlinkSync(SCHEDULE_CONFIG_PATH);
2679
- } catch {}
2217
+ const { removeSyncSchedule } = await import("@hasna/cloud");
2218
+ await removeSyncSchedule();
2680
2219
  }
2681
2220
  async function getCloudScheduleStatus() {
2682
- const mechanism = platform() === "darwin" ? "launchd" : platform() === "linux" ? "systemd" : "none";
2683
- const interval = readScheduleInterval();
2684
- const registered = mechanism === "launchd" ? existsSync3(getLaunchdPlistPath()) : mechanism === "systemd" ? existsSync3(join4(getSystemdDir(), `${SCHEDULE_SERVICE_NAME}.timer`)) : false;
2685
- return {
2686
- registered,
2687
- schedule_minutes: interval,
2688
- cron_expression: interval > 0 ? minutesToCron(interval) : null,
2689
- mechanism
2690
- };
2221
+ const { getSyncScheduleStatus } = await import("@hasna/cloud");
2222
+ return getSyncScheduleStatus();
2691
2223
  }
2692
- function readScheduleInterval() {
2693
- try {
2694
- const parsed = JSON.parse(readFileSync3(SCHEDULE_CONFIG_PATH, "utf8"));
2695
- return typeof parsed.intervalMinutes === "number" && parsed.intervalMinutes > 0 ? parsed.intervalMinutes : 0;
2696
- } catch {
2697
- return 0;
2698
- }
2699
- }
2700
- function minutesToCron(minutes) {
2701
- if (minutes < 60)
2702
- return `*/${minutes} * * * *`;
2703
- const hours = Math.floor(minutes / 60);
2704
- const remainder = minutes % 60;
2705
- return remainder === 0 && hours <= 24 ? `0 */${hours} * * *` : `*/${minutes} * * * *`;
2706
- }
2707
- function getModuleDir() {
2708
- return typeof import.meta.dir === "string" ? import.meta.dir : dirname2(new URL(import.meta.url).pathname);
2709
- }
2710
- function getBunPath() {
2711
- const candidates = [
2712
- join4(homedir2(), ".bun", "bin", "bun"),
2713
- "/opt/homebrew/bin/bun",
2714
- "/usr/local/bin/bun",
2715
- "/usr/bin/bun"
2716
- ];
2717
- return candidates.find((candidate) => existsSync3(candidate)) ?? "bun";
2718
- }
2719
- function getEconomySyncCommand() {
2720
- const dir = getModuleDir();
2721
- const candidates = [
2722
- join4(dir, "..", "cli", "index.js"),
2723
- join4(dir, "..", "cli", "index.ts")
2724
- ];
2725
- const cliPath = candidates.find((candidate) => existsSync3(candidate));
2726
- return cliPath ? [getBunPath(), "run", cliPath, "cloud", "sync"] : ["economy", "cloud", "sync"];
2727
- }
2728
- function scheduleEnvironment() {
2729
- const keys = [
2730
- "ECONOMY_CLOUD_DATABASE_URL",
2731
- "HASNA_ECONOMY_CLOUD_DATABASE_URL",
2732
- "HASNA_ECONOMY_DB_PATH",
2733
- "ECONOMY_DB",
2734
- "ECONOMY_MACHINE_ID",
2735
- "ECONOMY_CLOUD_AUTO"
2736
- ];
2737
- const env = {
2738
- HOME: homedir2(),
2739
- PATH: process.env.PATH || "/usr/local/bin:/usr/bin:/bin"
2740
- };
2741
- for (const key of keys) {
2742
- const value = process.env[key];
2743
- if (value)
2744
- env[key] = value;
2745
- }
2746
- return env;
2747
- }
2748
- function xmlEscape(value) {
2749
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
2750
- }
2751
- function getLaunchdPlistPath() {
2752
- return join4(homedir2(), "Library", "LaunchAgents", "com.hasna.economy-cloud-sync.plist");
2753
- }
2754
- function createLaunchdPlist(intervalMinutes) {
2755
- const args = getEconomySyncCommand();
2756
- const env = scheduleEnvironment();
2757
- const stdout = join4(SCHEDULE_CONFIG_DIR, "cloud-sync.log");
2758
- const stderr = join4(SCHEDULE_CONFIG_DIR, "cloud-sync-error.log");
2759
- const programArgs = args.map((arg) => ` <string>${xmlEscape(arg)}</string>`).join(`
2760
- `);
2761
- const environment = Object.entries(env).map(([key, value]) => ` <key>${xmlEscape(key)}</key>
2762
- <string>${xmlEscape(value)}</string>`).join(`
2763
- `);
2764
- return `<?xml version="1.0" encoding="UTF-8"?>
2765
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2766
- <plist version="1.0">
2767
- <dict>
2768
- <key>Label</key>
2769
- <string>com.hasna.economy-cloud-sync</string>
2770
- <key>ProgramArguments</key>
2771
- <array>
2772
- ${programArgs}
2773
- </array>
2774
- <key>StartInterval</key>
2775
- <integer>${Math.round(intervalMinutes * 60)}</integer>
2776
- <key>RunAtLoad</key>
2777
- <true/>
2778
- <key>StandardOutPath</key>
2779
- <string>${xmlEscape(stdout)}</string>
2780
- <key>StandardErrorPath</key>
2781
- <string>${xmlEscape(stderr)}</string>
2782
- <key>EnvironmentVariables</key>
2783
- <dict>
2784
- ${environment}
2785
- </dict>
2786
- </dict>
2787
- </plist>`;
2788
- }
2789
- async function registerLaunchd(intervalMinutes) {
2790
- const plistPath = getLaunchdPlistPath();
2791
- mkdirSync3(dirname2(plistPath), { recursive: true });
2792
- try {
2793
- await Bun.spawn(["launchctl", "unload", plistPath]).exited;
2794
- } catch {}
2795
- writeFileSync2(plistPath, createLaunchdPlist(intervalMinutes));
2796
- await Bun.spawn(["launchctl", "load", plistPath]).exited;
2797
- }
2798
- async function removeLaunchd() {
2799
- const plistPath = getLaunchdPlistPath();
2800
- try {
2801
- await Bun.spawn(["launchctl", "unload", plistPath]).exited;
2802
- } catch {}
2803
- try {
2804
- unlinkSync(plistPath);
2805
- } catch {}
2806
- }
2807
- function shellArg(value) {
2808
- return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
2809
- }
2810
- function getSystemdDir() {
2811
- return join4(homedir2(), ".config", "systemd", "user");
2812
- }
2813
- function createSystemdService() {
2814
- const command = getEconomySyncCommand().map(shellArg).join(" ");
2815
- const environment = Object.entries(scheduleEnvironment()).map(([key, value]) => `Environment=${key}=${shellArg(value)}`).join(`
2816
- `);
2817
- return `[Unit]
2818
- Description=Hasna Economy Cloud Sync
2819
- After=network.target
2820
-
2821
- [Service]
2822
- Type=oneshot
2823
- ExecStart=${command}
2824
- ${environment}
2825
-
2826
- [Install]
2827
- WantedBy=default.target
2828
- `;
2829
- }
2830
- function createSystemdTimer(intervalMinutes) {
2831
- return `[Unit]
2832
- Description=Hasna Economy Cloud Sync Timer
2833
-
2834
- [Timer]
2835
- OnBootSec=${intervalMinutes}min
2836
- OnUnitActiveSec=${intervalMinutes}min
2837
- Persistent=true
2838
-
2839
- [Install]
2840
- WantedBy=timers.target
2841
- `;
2842
- }
2843
- async function registerSystemd(intervalMinutes) {
2844
- const dir = getSystemdDir();
2845
- mkdirSync3(dir, { recursive: true });
2846
- writeFileSync2(join4(dir, `${SCHEDULE_SERVICE_NAME}.service`), createSystemdService());
2847
- writeFileSync2(join4(dir, `${SCHEDULE_SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
2848
- await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
2849
- await Bun.spawn(["systemctl", "--user", "enable", "--now", `${SCHEDULE_SERVICE_NAME}.timer`]).exited;
2850
- }
2851
- async function removeSystemd() {
2852
- try {
2853
- await Bun.spawn(["systemctl", "--user", "disable", "--now", `${SCHEDULE_SERVICE_NAME}.timer`]).exited;
2854
- } catch {}
2855
- const dir = getSystemdDir();
2856
- try {
2857
- unlinkSync(join4(dir, `${SCHEDULE_SERVICE_NAME}.service`));
2858
- } catch {}
2859
- try {
2860
- unlinkSync(join4(dir, `${SCHEDULE_SERVICE_NAME}.timer`));
2861
- } catch {}
2862
- try {
2863
- await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
2864
- } catch {}
2865
- }
2866
- var CLOUD_TABLES, SCHEDULE_SERVICE_NAME = "hasna-economy-cloud-sync", SCHEDULE_CONFIG_DIR, SCHEDULE_CONFIG_PATH;
2224
+ var CLOUD_TABLES;
2867
2225
  var init_cloud_sync = __esm(() => {
2868
2226
  init_database();
2869
2227
  init_package_metadata();
2870
- init_remote_storage();
2871
- init_storage_sync();
2872
2228
  CLOUD_TABLES = [
2873
2229
  "requests",
2874
2230
  "sessions",
@@ -2883,8 +2239,6 @@ var init_cloud_sync = __esm(() => {
2883
2239
  "machines",
2884
2240
  "ingest_state"
2885
2241
  ];
2886
- SCHEDULE_CONFIG_DIR = join4(homedir2(), ".hasna", "economy");
2887
- SCHEDULE_CONFIG_PATH = join4(SCHEDULE_CONFIG_DIR, "cloud-sync-schedule.json");
2888
2242
  });
2889
2243
 
2890
2244
  // src/lib/serve-auth.ts
@@ -3054,27 +2408,27 @@ var init_agents = __esm(() => {
3054
2408
  });
3055
2409
 
3056
2410
  // src/lib/paths.ts
3057
- import { homedir as homedir3 } from "os";
3058
- import { join as join5 } from "path";
2411
+ import { homedir as homedir2 } from "os";
2412
+ import { join as join4 } from "path";
3059
2413
  function getHomeDir() {
3060
- return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir3();
2414
+ return process.env["USERPROFILE"] ?? process.env["HOME"] ?? homedir2();
3061
2415
  }
3062
2416
  function agentPaths() {
3063
2417
  const home = getHomeDir();
3064
2418
  return {
3065
- claudeProjects: join5(home, ".claude", "projects"),
3066
- claudeCredentials: join5(home, ".claude", ".credentials.json"),
3067
- takumiProjects: join5(home, ".takumi", "projects"),
3068
- codexDir: join5(home, ".codex"),
3069
- codexDb: join5(home, ".codex", "state_5.sqlite"),
3070
- codexAuth: join5(home, ".codex", "auth.json"),
3071
- codexConfig: join5(home, ".codex", "config.toml"),
3072
- geminiTmp: join5(home, ".gemini", "tmp"),
3073
- geminiHistory: join5(home, ".gemini", "history"),
3074
- opencodeMessages: join5(home, ".local", "share", "opencode", "storage", "message"),
3075
- piSessions: join5(home, ".pi", "agent", "sessions"),
3076
- hermesDir: join5(home, ".hermes"),
3077
- hermesDb: join5(home, ".hermes", "state.db")
2419
+ claudeProjects: join4(home, ".claude", "projects"),
2420
+ claudeCredentials: join4(home, ".claude", ".credentials.json"),
2421
+ takumiProjects: join4(home, ".takumi", "projects"),
2422
+ codexDir: join4(home, ".codex"),
2423
+ codexDb: join4(home, ".codex", "state_5.sqlite"),
2424
+ codexAuth: join4(home, ".codex", "auth.json"),
2425
+ codexConfig: join4(home, ".codex", "config.toml"),
2426
+ geminiTmp: join4(home, ".gemini", "tmp"),
2427
+ geminiHistory: join4(home, ".gemini", "history"),
2428
+ opencodeMessages: join4(home, ".local", "share", "opencode", "storage", "message"),
2429
+ piSessions: join4(home, ".pi", "agent", "sessions"),
2430
+ hermesDir: join4(home, ".hermes"),
2431
+ hermesDb: join4(home, ".hermes", "state.db")
3078
2432
  };
3079
2433
  }
3080
2434
  var init_paths = () => {};
@@ -3215,9 +2569,9 @@ var init_accounts = __esm(() => {
3215
2569
  });
3216
2570
 
3217
2571
  // src/ingest/claude.ts
3218
- import { readdirSync as readdirSync2, readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync2 } from "fs";
3219
- import { homedir as homedir4 } from "os";
3220
- import { join as join7, basename } from "path";
2572
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync2 } from "fs";
2573
+ import { homedir as homedir3 } from "os";
2574
+ import { join as join6, basename } from "path";
3221
2575
  function autoDetectProject(cwd, projects) {
3222
2576
  return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
3223
2577
  }
@@ -3230,9 +2584,9 @@ function collectJsonlFiles(projectDir) {
3230
2584
  try {
3231
2585
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
3232
2586
  if (entry.isDirectory())
3233
- walk(join7(dir, entry.name));
2587
+ walk(join6(dir, entry.name));
3234
2588
  else if (entry.name.endsWith(".jsonl"))
3235
- files.push(join7(dir, entry.name));
2589
+ files.push(join6(dir, entry.name));
3236
2590
  }
3237
2591
  } catch {}
3238
2592
  }
@@ -3246,7 +2600,7 @@ async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_D
3246
2600
  return ingestJsonlProjects(db, projectsDir, "takumi", verbose);
3247
2601
  }
3248
2602
  async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false) {
3249
- if (!existsSync5(projectsDir)) {
2603
+ if (!existsSync4(projectsDir)) {
3250
2604
  if (verbose)
3251
2605
  console.log(`${agentName} projects dir not found:`, projectsDir);
3252
2606
  return { files: 0, requests: 0, sessions: 0 };
@@ -3259,7 +2613,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
3259
2613
  const account = await resolveAccountForAgent(agentName);
3260
2614
  const projectDirs = readdirSync2(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
3261
2615
  for (const projectDirEntry of projectDirs) {
3262
- const projectDirPath = join7(projectsDir, projectDirEntry.name);
2616
+ const projectDirPath = join6(projectsDir, projectDirEntry.name);
3263
2617
  const projectPath = dirNameToPath(projectDirEntry.name);
3264
2618
  const jsonlFiles = collectJsonlFiles(projectDirPath);
3265
2619
  for (const filePath of jsonlFiles) {
@@ -3275,7 +2629,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
3275
2629
  continue;
3276
2630
  let lines;
3277
2631
  try {
3278
- lines = readFileSync4(filePath, "utf-8").split(`
2632
+ lines = readFileSync3(filePath, "utf-8").split(`
3279
2633
  `).filter((l) => l.trim());
3280
2634
  } catch {
3281
2635
  continue;
@@ -3396,15 +2750,15 @@ var init_claude = __esm(() => {
3396
2750
  init_database();
3397
2751
  init_pricing();
3398
2752
  init_accounts();
3399
- CLAUDE_PROJECTS_DIR = join7(homedir4(), ".claude", "projects");
3400
- TAKUMI_PROJECTS_DIR = join7(homedir4(), ".takumi", "projects");
2753
+ CLAUDE_PROJECTS_DIR = join6(homedir3(), ".claude", "projects");
2754
+ TAKUMI_PROJECTS_DIR = join6(homedir3(), ".takumi", "projects");
3401
2755
  });
3402
2756
 
3403
2757
  // src/ingest/codex.ts
3404
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
3405
- import { homedir as homedir5 } from "os";
3406
- import { join as join8, basename as basename2 } from "path";
3407
- import { Database as BunDatabase2 } from "bun:sqlite";
2758
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
2759
+ import { homedir as homedir4 } from "os";
2760
+ import { join as join7, basename as basename2 } from "path";
2761
+ import { Database as BunDatabase } from "bun:sqlite";
3408
2762
  function codexDbPath() {
3409
2763
  return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
3410
2764
  }
@@ -3413,10 +2767,10 @@ function codexConfigPath() {
3413
2767
  }
3414
2768
  function readCodexModel() {
3415
2769
  const configPath = codexConfigPath();
3416
- if (!existsSync6(configPath))
2770
+ if (!existsSync5(configPath))
3417
2771
  return "gpt-5-codex";
3418
2772
  try {
3419
- const content = readFileSync5(configPath, "utf-8");
2773
+ const content = readFileSync4(configPath, "utf-8");
3420
2774
  const match = content.match(/^model\s*=\s*"([^"]+)"/m);
3421
2775
  return match?.[1] ?? "gpt-5-codex";
3422
2776
  } catch {
@@ -3439,7 +2793,7 @@ function openCodexDb(dbPath, verbose) {
3439
2793
  for (const readonly of [true, false]) {
3440
2794
  let codexDb = null;
3441
2795
  try {
3442
- codexDb = readonly ? new BunDatabase2(dbPath, { readonly: true }) : new BunDatabase2(dbPath);
2796
+ codexDb = readonly ? new BunDatabase(dbPath, { readonly: true }) : new BunDatabase(dbPath);
3443
2797
  codexDb.prepare("PRAGMA schema_version").get();
3444
2798
  return codexDb;
3445
2799
  } catch (error) {
@@ -3454,12 +2808,12 @@ function openCodexDb(dbPath, verbose) {
3454
2808
  return null;
3455
2809
  }
3456
2810
  function readTokenEvents(rolloutPath) {
3457
- if (!rolloutPath || !existsSync6(rolloutPath))
2811
+ if (!rolloutPath || !existsSync5(rolloutPath))
3458
2812
  return [];
3459
2813
  const fallbackUsages = new Map;
3460
2814
  let fallbackTimestamp;
3461
2815
  let aggregate = null;
3462
- for (const line of readFileSync5(rolloutPath, "utf-8").split(`
2816
+ for (const line of readFileSync4(rolloutPath, "utf-8").split(`
3463
2817
  `)) {
3464
2818
  if (!line.trim())
3465
2819
  continue;
@@ -3531,7 +2885,7 @@ function fallbackEvents(totalTokens) {
3531
2885
  }
3532
2886
  async function ingestCodex(db, verbose = false) {
3533
2887
  const dbPath = codexDbPath();
3534
- if (!existsSync6(dbPath)) {
2888
+ if (!existsSync5(dbPath)) {
3535
2889
  if (verbose)
3536
2890
  console.log("Codex DB not found:", dbPath);
3537
2891
  return { sessions: 0, requests: 0 };
@@ -3616,14 +2970,14 @@ var init_codex = __esm(() => {
3616
2970
  init_database();
3617
2971
  init_pricing();
3618
2972
  init_accounts();
3619
- DEFAULT_CODEX_DB_PATH = join8(homedir5(), ".codex", "state_5.sqlite");
3620
- DEFAULT_CODEX_CONFIG_PATH = join8(homedir5(), ".codex", "config.toml");
2973
+ DEFAULT_CODEX_DB_PATH = join7(homedir4(), ".codex", "state_5.sqlite");
2974
+ DEFAULT_CODEX_CONFIG_PATH = join7(homedir4(), ".codex", "config.toml");
3621
2975
  });
3622
2976
 
3623
2977
  // src/ingest/gemini.ts
3624
- import { readdirSync as readdirSync3, readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync3 } from "fs";
3625
- import { homedir as homedir6 } from "os";
3626
- import { join as join9, basename as basename3 } from "path";
2978
+ import { readdirSync as readdirSync3, readFileSync as readFileSync5, existsSync as existsSync6, statSync as statSync3 } from "fs";
2979
+ import { homedir as homedir5 } from "os";
2980
+ import { join as join8, basename as basename3 } from "path";
3627
2981
  function geminiTmpDir() {
3628
2982
  return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
3629
2983
  }
@@ -3640,12 +2994,12 @@ function numberField(...values) {
3640
2994
  function listProjectDirs(...roots) {
3641
2995
  const dirs = new Set;
3642
2996
  for (const root of roots) {
3643
- if (!existsSync7(root))
2997
+ if (!existsSync6(root))
3644
2998
  continue;
3645
2999
  try {
3646
3000
  for (const entry of readdirSync3(root, { withFileTypes: true })) {
3647
3001
  if (entry.isDirectory())
3648
- dirs.add(join9(root, entry.name));
3002
+ dirs.add(join8(root, entry.name));
3649
3003
  }
3650
3004
  } catch {}
3651
3005
  }
@@ -3656,17 +3010,17 @@ function projectRoot(projectDir, chatData) {
3656
3010
  return chatData.projectPath;
3657
3011
  if (chatData.project_path)
3658
3012
  return chatData.project_path;
3659
- const rootFile = join9(projectDir, ".project_root");
3013
+ const rootFile = join8(projectDir, ".project_root");
3660
3014
  try {
3661
- if (existsSync7(rootFile))
3662
- return readFileSync6(rootFile, "utf-8").trim();
3015
+ if (existsSync6(rootFile))
3016
+ return readFileSync5(rootFile, "utf-8").trim();
3663
3017
  } catch {}
3664
3018
  return "";
3665
3019
  }
3666
3020
  async function ingestGemini(db, verbose) {
3667
3021
  const tmpDir = geminiTmpDir();
3668
3022
  const historyDir = geminiHistoryDir();
3669
- if (!existsSync7(tmpDir) && !existsSync7(historyDir)) {
3023
+ if (!existsSync6(tmpDir) && !existsSync6(historyDir)) {
3670
3024
  if (verbose)
3671
3025
  console.log("Gemini tmp/history dirs not found:", tmpDir, historyDir);
3672
3026
  return { sessions: 0, requests: 0 };
@@ -3678,17 +3032,17 @@ async function ingestGemini(db, verbose) {
3678
3032
  const account = await resolveAccountForAgent("gemini");
3679
3033
  const projectDirs = listProjectDirs(tmpDir, historyDir);
3680
3034
  for (const projectDir of projectDirs) {
3681
- const chatsDir = join9(projectDir, "chats");
3682
- if (!existsSync7(chatsDir))
3035
+ const chatsDir = join8(projectDir, "chats");
3036
+ if (!existsSync6(chatsDir))
3683
3037
  continue;
3684
3038
  let chatFiles = [];
3685
3039
  try {
3686
- chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join9(chatsDir, f));
3040
+ chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join8(chatsDir, f));
3687
3041
  } catch {
3688
3042
  continue;
3689
3043
  }
3690
3044
  for (const filePath of chatFiles) {
3691
- const stateKey = filePath.replace(homedir6(), "~");
3045
+ const stateKey = filePath.replace(homedir5(), "~");
3692
3046
  let fileMtime = "0";
3693
3047
  try {
3694
3048
  fileMtime = statSync3(filePath).mtimeMs.toString();
@@ -3700,7 +3054,7 @@ async function ingestGemini(db, verbose) {
3700
3054
  continue;
3701
3055
  let chatData;
3702
3056
  try {
3703
- chatData = JSON.parse(readFileSync6(filePath, "utf-8"));
3057
+ chatData = JSON.parse(readFileSync5(filePath, "utf-8"));
3704
3058
  } catch {
3705
3059
  continue;
3706
3060
  }
@@ -3779,19 +3133,19 @@ var init_gemini = __esm(() => {
3779
3133
  init_database();
3780
3134
  init_pricing();
3781
3135
  init_accounts();
3782
- DEFAULT_GEMINI_TMP_DIR = join9(homedir6(), ".gemini", "tmp");
3783
- DEFAULT_GEMINI_HISTORY_DIR = join9(homedir6(), ".gemini", "history");
3136
+ DEFAULT_GEMINI_TMP_DIR = join8(homedir5(), ".gemini", "tmp");
3137
+ DEFAULT_GEMINI_HISTORY_DIR = join8(homedir5(), ".gemini", "history");
3784
3138
  });
3785
3139
 
3786
3140
  // src/ingest/opencode.ts
3787
- import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
3788
- import { homedir as homedir7 } from "os";
3789
- import { join as join10 } from "path";
3141
+ import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
3142
+ import { homedir as homedir6 } from "os";
3143
+ import { join as join9 } from "path";
3790
3144
  function walkJsonFiles(dir, acc = []) {
3791
- if (!existsSync8(dir))
3145
+ if (!existsSync7(dir))
3792
3146
  return acc;
3793
3147
  for (const entry of readdirSync4(dir, { withFileTypes: true })) {
3794
- const p = join10(dir, entry.name);
3148
+ const p = join9(dir, entry.name);
3795
3149
  if (entry.isDirectory())
3796
3150
  walkJsonFiles(p, acc);
3797
3151
  else if (entry.name.endsWith(".json"))
@@ -3811,7 +3165,7 @@ function parseSessionIdFromPath(filePath) {
3811
3165
  return null;
3812
3166
  }
3813
3167
  async function ingestOpenCode(db, verbose = false) {
3814
- const messageDir = join10(OPENCODE_STORAGE, "message");
3168
+ const messageDir = join9(OPENCODE_STORAGE, "message");
3815
3169
  const files = walkJsonFiles(messageDir);
3816
3170
  let requests = 0;
3817
3171
  const touched = new Set;
@@ -3826,7 +3180,7 @@ async function ingestOpenCode(db, verbose = false) {
3826
3180
  continue;
3827
3181
  let parsed;
3828
3182
  try {
3829
- parsed = JSON.parse(readFileSync7(file, "utf-8"));
3183
+ parsed = JSON.parse(readFileSync6(file, "utf-8"));
3830
3184
  } catch {
3831
3185
  continue;
3832
3186
  }
@@ -3894,7 +3248,7 @@ var init_opencode = __esm(() => {
3894
3248
  init_database();
3895
3249
  init_pricing();
3896
3250
  init_accounts();
3897
- OPENCODE_STORAGE = join10(homedir7(), ".local", "share", "opencode", "storage");
3251
+ OPENCODE_STORAGE = join9(homedir6(), ".local", "share", "opencode", "storage");
3898
3252
  });
3899
3253
 
3900
3254
  // src/ingest/cursor.ts
@@ -4022,14 +3376,14 @@ var init_cursor = __esm(() => {
4022
3376
  });
4023
3377
 
4024
3378
  // src/ingest/pi.ts
4025
- import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
4026
- import { homedir as homedir8 } from "os";
4027
- import { join as join11 } from "path";
3379
+ import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
3380
+ import { homedir as homedir7 } from "os";
3381
+ import { join as join10 } from "path";
4028
3382
  function walkSessions(dir, acc = []) {
4029
- if (!existsSync9(dir))
3383
+ if (!existsSync8(dir))
4030
3384
  return acc;
4031
3385
  for (const entry of readdirSync5(dir, { withFileTypes: true })) {
4032
- const p = join11(dir, entry.name);
3386
+ const p = join10(dir, entry.name);
4033
3387
  if (entry.isDirectory())
4034
3388
  walkSessions(p, acc);
4035
3389
  else if (entry.name.endsWith(".json"))
@@ -4051,7 +3405,7 @@ async function ingestPi(db, verbose = false) {
4051
3405
  continue;
4052
3406
  let data;
4053
3407
  try {
4054
- data = JSON.parse(readFileSync8(file, "utf-8"));
3408
+ data = JSON.parse(readFileSync7(file, "utf-8"));
4055
3409
  } catch {
4056
3410
  continue;
4057
3411
  }
@@ -4116,13 +3470,13 @@ var PI_SESSION_DIR;
4116
3470
  var init_pi = __esm(() => {
4117
3471
  init_database();
4118
3472
  init_accounts();
4119
- PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join11(homedir8(), ".pi", "agent", "sessions");
3473
+ PI_SESSION_DIR = process.env["PI_CODING_AGENT_SESSION_DIR"] ?? join10(homedir7(), ".pi", "agent", "sessions");
4120
3474
  });
4121
3475
 
4122
3476
  // src/ingest/hermes.ts
4123
- import { existsSync as existsSync10, statSync as statSync6 } from "fs";
4124
- import { homedir as homedir9 } from "os";
4125
- import { join as join12 } from "path";
3477
+ import { existsSync as existsSync9, statSync as statSync6 } from "fs";
3478
+ import { homedir as homedir8 } from "os";
3479
+ import { join as join11 } from "path";
4126
3480
  function mapCostBasis(billingMode) {
4127
3481
  if (billingMode === "subscription")
4128
3482
  return "subscription_included";
@@ -4131,7 +3485,7 @@ function mapCostBasis(billingMode) {
4131
3485
  return defaultCostBasisForAgent("hermes");
4132
3486
  }
4133
3487
  async function ingestHermes(db, verbose = false) {
4134
- if (!existsSync10(HERMES_DB)) {
3488
+ if (!existsSync9(HERMES_DB)) {
4135
3489
  return { sessions: 0, requests: 0 };
4136
3490
  }
4137
3491
  const { Database: Sqlite } = await import("bun:sqlite");
@@ -4211,19 +3565,19 @@ var HERMES_DB;
4211
3565
  var init_hermes = __esm(() => {
4212
3566
  init_database();
4213
3567
  init_accounts();
4214
- HERMES_DB = join12(homedir9(), ".hermes", "state.db");
3568
+ HERMES_DB = join11(homedir8(), ".hermes", "state.db");
4215
3569
  });
4216
3570
 
4217
3571
  // src/ingest/claude-quota.ts
4218
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
3572
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
4219
3573
  function readClaudeToken() {
4220
3574
  const fromEnv = process.env["CLAUDE_OAUTH_TOKEN"] ?? process.env["ANTHROPIC_OAUTH_TOKEN"];
4221
3575
  if (fromEnv)
4222
3576
  return { token: fromEnv };
4223
- if (!existsSync11(CREDENTIALS_PATH))
3577
+ if (!existsSync10(CREDENTIALS_PATH))
4224
3578
  return null;
4225
3579
  try {
4226
- const creds = JSON.parse(readFileSync9(CREDENTIALS_PATH, "utf-8"));
3580
+ const creds = JSON.parse(readFileSync8(CREDENTIALS_PATH, "utf-8"));
4227
3581
  const oauth = creds.claudeAiOauth;
4228
3582
  if (!oauth?.accessToken)
4229
3583
  return null;
@@ -4364,16 +3718,16 @@ var init_claude_quota = __esm(() => {
4364
3718
  });
4365
3719
 
4366
3720
  // src/ingest/codex-quota.ts
4367
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
3721
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
4368
3722
  function readCodexAuth() {
4369
3723
  const fromEnv = process.env["CODEX_OAUTH_TOKEN"];
4370
3724
  if (fromEnv)
4371
3725
  return { token: fromEnv, authMode: "chatgpt" };
4372
3726
  const authPath = agentPaths().codexAuth;
4373
- if (!existsSync12(authPath))
3727
+ if (!existsSync11(authPath))
4374
3728
  return null;
4375
3729
  try {
4376
- const auth = JSON.parse(readFileSync10(authPath, "utf-8"));
3730
+ const auth = JSON.parse(readFileSync9(authPath, "utf-8"));
4377
3731
  const token = auth.tokens?.access_token;
4378
3732
  if (!token)
4379
3733
  return null;
@@ -4557,15 +3911,15 @@ __export(exports_billing, {
4557
3911
  syncGeminiBilling: () => syncGeminiBilling,
4558
3912
  syncAnthropicBilling: () => syncAnthropicBilling
4559
3913
  });
4560
- import { readFileSync as readFileSync11 } from "fs";
3914
+ import { readFileSync as readFileSync10 } from "fs";
4561
3915
  function getAnthropicAdminKey() {
4562
- return process.env["ANTHROPIC_ADMIN_API_KEY"] ?? null;
3916
+ return process.env["HASNAXYZ_ANTHROPIC_LIVE_ADMIN_API_KEY"] ?? process.env["ANTHROPIC_ADMIN_API_KEY"] ?? null;
4563
3917
  }
4564
3918
  function getOpenAIAdminKey() {
4565
- return process.env["OPENAI_ADMIN_API_KEY"] ?? null;
3919
+ return process.env["HASNAXYZ_OPENAI_LIVE_ADMIN_API_KEY"] ?? process.env["OPENAI_ADMIN_API_KEY"] ?? null;
4566
3920
  }
4567
3921
  function getGeminiBillingExportPath() {
4568
- return process.env["HASNA_ECONOMY_GEMINI_BILLING_EXPORT_PATH"] ?? process.env["GEMINI_BILLING_EXPORT_PATH"] ?? null;
3922
+ return process.env["HASNA_ECONOMY_GEMINI_BILLING_EXPORT_PATH"] ?? process.env["HASNAXYZ_ECONOMY_GEMINI_BILLING_EXPORT_PATH"] ?? process.env["GEMINI_BILLING_EXPORT_PATH"] ?? null;
4569
3923
  }
4570
3924
  function toISODate(d) {
4571
3925
  return d.toISOString().substring(0, 10);
@@ -4641,7 +3995,7 @@ function parseBillingRows(content) {
4641
3995
  async function syncAnthropicBilling(db, opts = {}) {
4642
3996
  const key = getAnthropicAdminKey();
4643
3997
  if (!key)
4644
- throw new Error("Missing Anthropic admin key (ANTHROPIC_ADMIN_API_KEY)");
3998
+ throw new Error("Missing Anthropic admin key (HASNAXYZ_ANTHROPIC_LIVE_ADMIN_API_KEY)");
4645
3999
  const now = new Date;
4646
4000
  const end = opts.toDate ? new Date(opts.toDate) : new Date(now.getTime() + 24 * 3600000);
4647
4001
  const days = opts.days ?? 31;
@@ -4690,7 +4044,7 @@ async function syncAnthropicBilling(db, opts = {}) {
4690
4044
  async function syncOpenAIBilling(db, opts = {}) {
4691
4045
  const key = getOpenAIAdminKey();
4692
4046
  if (!key)
4693
- throw new Error("Missing OpenAI admin key (OPENAI_ADMIN_API_KEY)");
4047
+ throw new Error("Missing OpenAI admin key (HASNAXYZ_OPENAI_LIVE_ADMIN_API_KEY)");
4694
4048
  const now = new Date;
4695
4049
  const end = opts.toDate ? new Date(opts.toDate) : now;
4696
4050
  const days = opts.days ?? 31;
@@ -4742,7 +4096,7 @@ async function syncGeminiBilling(db, opts = {}) {
4742
4096
  return {
4743
4097
  days: 0,
4744
4098
  totalUsd: 0,
4745
- skipped: "Missing Gemini billing export path (HASNA_ECONOMY_GEMINI_BILLING_EXPORT_PATH or GEMINI_BILLING_EXPORT_PATH)"
4099
+ skipped: "Missing Gemini billing export path (HASNA_ECONOMY_GEMINI_BILLING_EXPORT_PATH, HASNAXYZ_ECONOMY_GEMINI_BILLING_EXPORT_PATH, or GEMINI_BILLING_EXPORT_PATH)"
4746
4100
  };
4747
4101
  }
4748
4102
  const now = new Date;
@@ -4751,7 +4105,7 @@ async function syncGeminiBilling(db, opts = {}) {
4751
4105
  const start = opts.fromDate ? new Date(opts.fromDate) : new Date(end.getTime() - days * 24 * 3600000);
4752
4106
  const fromDateStr = toISODate(start);
4753
4107
  const toDateStr = toISODate(end);
4754
- const rows = parseBillingRows(readFileSync11(exportPath, "utf-8"));
4108
+ const rows = parseBillingRows(readFileSync10(exportPath, "utf-8"));
4755
4109
  clearBillingRange(db, "gemini", fromDateStr, toDateStr);
4756
4110
  const updatedAt = new Date().toISOString();
4757
4111
  let totalUsd = 0;
@@ -4783,26 +4137,22 @@ __export(exports_open_projects, {
4783
4137
  syncOpenProjectsRegistry: () => syncOpenProjectsRegistry
4784
4138
  });
4785
4139
  async function syncOpenProjectsRegistry(db, listActiveProjects) {
4786
- let listOpenProjects = listActiveProjects;
4787
- if (!listOpenProjects) {
4140
+ let listProjects2 = listActiveProjects;
4141
+ if (!listProjects2) {
4788
4142
  const projectsApi = await import("@hasna/projects");
4789
- listOpenProjects = projectsApi.listProjects ?? projectsApi.listWorkspaces;
4143
+ listProjects2 = projectsApi.listProjects;
4790
4144
  }
4791
- if (!listOpenProjects) {
4792
- throw new Error("@hasna/projects does not expose listWorkspaces or listProjects");
4793
- }
4794
- const projects = listOpenProjects({ status: "active", limit: 5000 });
4145
+ const projects = listProjects2({ status: "active", limit: 5000 });
4795
4146
  let imported = 0;
4796
4147
  let skipped = 0;
4797
4148
  for (const project of projects) {
4798
- const path = project.path ?? project.primary_path ?? "";
4799
- if (!path) {
4149
+ if (!project.path) {
4800
4150
  skipped++;
4801
4151
  continue;
4802
4152
  }
4803
4153
  upsertProject(db, {
4804
4154
  id: project.id,
4805
- path,
4155
+ path: project.path,
4806
4156
  name: project.name,
4807
4157
  description: project.description,
4808
4158
  tags: project.tags ?? [],
@@ -4824,16 +4174,16 @@ __export(exports_config, {
4824
4174
  loadConfig: () => loadConfig2,
4825
4175
  getConfigValue: () => getConfigValue
4826
4176
  });
4827
- import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
4828
- import { dirname as dirname3, join as join13 } from "path";
4177
+ import { existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
4178
+ import { dirname as dirname2, join as join12 } from "path";
4829
4179
  function getConfigPath() {
4830
- return process.env["HASNA_ECONOMY_CONFIG_PATH"] ?? join13(getDataDir(), "config.json");
4180
+ return process.env["HASNA_ECONOMY_CONFIG_PATH"] ?? join12(getDataDir(), "config.json");
4831
4181
  }
4832
4182
  function loadConfig2() {
4833
4183
  try {
4834
4184
  const configPath = getConfigPath();
4835
- if (existsSync14(configPath)) {
4836
- const raw = readFileSync12(configPath, "utf-8");
4185
+ if (existsSync13(configPath)) {
4186
+ const raw = readFileSync11(configPath, "utf-8");
4837
4187
  return { ...DEFAULTS, ...JSON.parse(raw) };
4838
4188
  }
4839
4189
  } catch {}
@@ -4841,10 +4191,10 @@ function loadConfig2() {
4841
4191
  }
4842
4192
  function saveConfig2(config) {
4843
4193
  const configPath = getConfigPath();
4844
- const dir = dirname3(configPath);
4845
- if (!existsSync14(dir))
4846
- mkdirSync4(dir, { recursive: true });
4847
- writeFileSync3(configPath, JSON.stringify(config, null, 2) + `
4194
+ const dir = dirname2(configPath);
4195
+ if (!existsSync13(dir))
4196
+ mkdirSync3(dir, { recursive: true });
4197
+ writeFileSync2(configPath, JSON.stringify(config, null, 2) + `
4848
4198
  `);
4849
4199
  }
4850
4200
  function getConfigValue(key) {
@@ -4986,7 +4336,7 @@ var init_webhooks = __esm(() => {
4986
4336
  });
4987
4337
 
4988
4338
  // src/lib/watch-paths.ts
4989
- import { existsSync as existsSync15 } from "fs";
4339
+ import { existsSync as existsSync14 } from "fs";
4990
4340
  function getWatchPaths() {
4991
4341
  const p = agentPaths();
4992
4342
  const candidates = [
@@ -4999,7 +4349,7 @@ function getWatchPaths() {
4999
4349
  p.piSessions,
5000
4350
  p.hermesDir
5001
4351
  ];
5002
- return candidates.filter((path) => existsSync15(path));
4352
+ return candidates.filter((path) => existsSync14(path));
5003
4353
  }
5004
4354
  var init_watch_paths = __esm(() => {
5005
4355
  init_paths();
@@ -5169,7 +4519,7 @@ __export(exports_serve, {
5169
4519
  createHandler: () => createHandler
5170
4520
  });
5171
4521
  import { randomUUID as randomUUID2 } from "crypto";
5172
- import { existsSync as existsSync16 } from "fs";
4522
+ import { existsSync as existsSync15 } from "fs";
5173
4523
  import { resolve, sep } from "path";
5174
4524
  function json(data, status = 200) {
5175
4525
  return new Response(JSON.stringify(data), {
@@ -5234,13 +4584,13 @@ function createServerFetch(apiHandler, dashboardDir = DEFAULT_DASHBOARD_DIR) {
5234
4584
  if (url.pathname.startsWith("/api") || url.pathname === "/health") {
5235
4585
  return apiHandler(req);
5236
4586
  }
5237
- if (existsSync16(dashboardDir)) {
4587
+ if (existsSync15(dashboardDir)) {
5238
4588
  const filePath = dashboardPath(dashboardDir, url.pathname);
5239
- if (filePath && existsSync16(filePath)) {
4589
+ if (filePath && existsSync15(filePath)) {
5240
4590
  return new Response(Bun.file(filePath));
5241
4591
  }
5242
4592
  const indexPath = dashboardPath(dashboardDir, "/");
5243
- if (indexPath && existsSync16(indexPath)) {
4593
+ if (indexPath && existsSync15(indexPath)) {
5244
4594
  return new Response(Bun.file(indexPath));
5245
4595
  }
5246
4596
  }
@@ -5655,14 +5005,14 @@ __export(exports_menubar, {
5655
5005
  });
5656
5006
  import chalk6 from "chalk";
5657
5007
  import { execFileSync as execFileSync2 } from "child_process";
5658
- import { cpSync, existsSync as existsSync17, mkdirSync as mkdirSync5, rmSync, writeFileSync as writeFileSync4 } from "fs";
5008
+ import { cpSync, existsSync as existsSync16, mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
5659
5009
  import { tmpdir, arch } from "os";
5660
- import { join as join14 } from "path";
5010
+ import { join as join13 } from "path";
5661
5011
  function getArch() {
5662
5012
  return arch() === "arm64" ? "arm64" : "x86_64";
5663
5013
  }
5664
5014
  function isInstalled() {
5665
- return existsSync17(APP_PATH);
5015
+ return existsSync16(APP_PATH);
5666
5016
  }
5667
5017
  function isRunning() {
5668
5018
  try {
@@ -5699,15 +5049,15 @@ async function menubarInstall(opts) {
5699
5049
  console.error(chalk6.red(`\u2717 Failed to fetch release info: ${e instanceof Error ? e.message : String(e)}`));
5700
5050
  process.exit(1);
5701
5051
  }
5702
- const zipPath = join14(tmpdir(), `economy-bar-${cpuArch}.zip`);
5703
- const extractDir = join14(tmpdir(), "economy-bar-extracted");
5052
+ const zipPath = join13(tmpdir(), `economy-bar-${cpuArch}.zip`);
5053
+ const extractDir = join13(tmpdir(), "economy-bar-extracted");
5704
5054
  console.log(chalk6.cyan(`\u2192 Downloading ${assetUrl}...`));
5705
5055
  try {
5706
5056
  const res = await fetch(assetUrl, { signal: AbortSignal.timeout(60000) });
5707
5057
  if (!res.ok)
5708
5058
  throw new Error(`Download failed: ${res.status}`);
5709
5059
  const buffer = await res.arrayBuffer();
5710
- writeFileSync4(zipPath, Buffer.from(buffer));
5060
+ writeFileSync3(zipPath, Buffer.from(buffer));
5711
5061
  console.log(chalk6.green(`\u2713 Downloaded (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB)`));
5712
5062
  } catch (e) {
5713
5063
  console.error(chalk6.red(`\u2717 Download failed: ${e instanceof Error ? e.message : String(e)}`));
@@ -5716,11 +5066,11 @@ async function menubarInstall(opts) {
5716
5066
  console.log(chalk6.cyan("\u2192 Installing to /Applications..."));
5717
5067
  try {
5718
5068
  rmSync(extractDir, { recursive: true, force: true });
5719
- mkdirSync5(extractDir, { recursive: true });
5069
+ mkdirSync4(extractDir, { recursive: true });
5720
5070
  execFileSync2("unzip", ["-q", zipPath, "-d", extractDir], { stdio: "ignore" });
5721
5071
  if (isInstalled())
5722
5072
  rmSync(APP_PATH, { recursive: true, force: true });
5723
- cpSync(join14(extractDir, "Economy Bar.app"), APP_PATH, { recursive: true });
5073
+ cpSync(join13(extractDir, "Economy Bar.app"), APP_PATH, { recursive: true });
5724
5074
  try {
5725
5075
  execFileSync2("xattr", ["-rd", "com.apple.quarantine", APP_PATH], { stdio: "ignore" });
5726
5076
  } catch {}
@@ -5780,6 +5130,7 @@ var init_menubar = () => {};
5780
5130
 
5781
5131
  // src/cli/index.ts
5782
5132
  import { Command } from "commander";
5133
+ import { registerEventsCommands } from "@hasna/events/commander";
5783
5134
  import chalk7 from "chalk";
5784
5135
 
5785
5136
  // src/cli/brains.ts
@@ -6518,7 +5869,7 @@ var ROADMAP_PHASES = [
6518
5869
  {
6519
5870
  id: "phase-9",
6520
5871
  title: "Multi-machine auto sync (4-machine fleet)",
6521
- summary: "Investigation: machine_id + listMachines + manual cloud push/pull exist; the previous shared cloud package had incremental sync, conflict resolution, and launchd/systemd schedulers \u2014 but economy did not own them, requests/sessions lacked updated_at, ingest was local-only, RDS was hardcoded, and default commands never pulled before query.",
5872
+ summary: "Investigation: machine_id + listMachines + manual cloud push/pull exist; @hasna/cloud has incremental sync, conflict resolution, and launchd/systemd schedulers \u2014 but economy does not use them, requests/sessions lack updated_at, ingest is local-only, RDS is hardcoded, and default commands never pull before query.",
6522
5873
  tasks: [
6523
5874
  {
6524
5875
  id: "9.1",
@@ -6533,7 +5884,7 @@ var ROADMAP_PHASES = [
6533
5884
  },
6534
5885
  {
6535
5886
  id: "9.3",
6536
- title: "Switch cloud sync to repo-native incremental push/pull with _sync_meta (replace full table scans)",
5887
+ title: "Switch cloud sync to @hasna/cloud incrementalSyncPush/Pull with _sync_meta (replace full table scans)",
6537
5888
  status: "done",
6538
5889
  deps: ["9.2"]
6539
5890
  },
@@ -6557,7 +5908,7 @@ var ROADMAP_PHASES = [
6557
5908
  },
6558
5909
  {
6559
5910
  id: "9.7",
6560
- title: "CLI economy cloud schedule install|status|remove \u2014 repo-native launchd/systemd scheduler every 5\u201315m",
5911
+ title: "CLI economy cloud schedule install|status|remove \u2014 wrap @hasna/cloud registerSyncSchedule (launchd/systemd every 5\u201315m)",
6561
5912
  status: "done",
6562
5913
  deps: ["9.5"]
6563
5914
  },
@@ -6968,8 +6319,8 @@ init_agents();
6968
6319
  init_cloud_sync();
6969
6320
  import chalk4 from "chalk";
6970
6321
  import { randomUUID } from "crypto";
6971
- import { existsSync as existsSync4 } from "fs";
6972
- import { join as join6 } from "path";
6322
+ import { existsSync as existsSync3 } from "fs";
6323
+ import { join as join5 } from "path";
6973
6324
 
6974
6325
  // src/cli/commands/completion.ts
6975
6326
  var TOP_LEVEL = [
@@ -7219,13 +6570,13 @@ function registerExtendedCommands(program) {
7219
6570
  const paths = [
7220
6571
  ["claude", agentPaths().claudeProjects],
7221
6572
  ["codex", agentPaths().codexDb],
7222
- ["gemini", join6(agentPaths().geminiTmp, "..")],
7223
- ["opencode", join6(agentPaths().opencodeMessages, "..", "..")],
6573
+ ["gemini", join5(agentPaths().geminiTmp, "..")],
6574
+ ["opencode", join5(agentPaths().opencodeMessages, "..", "..")],
7224
6575
  ["pi", agentPaths().piSessions],
7225
6576
  ["hermes", agentPaths().hermesDb]
7226
6577
  ];
7227
6578
  for (const [agent, path] of paths) {
7228
- checks.push({ ok: existsSync4(path), msg: `${agent}: ${existsSync4(path) ? path : "not found"}` });
6579
+ checks.push({ ok: existsSync3(path), msg: `${agent}: ${existsSync3(path) ? path : "not found"}` });
7229
6580
  }
7230
6581
  checks.push({ ok: Boolean(process.env["CURSOR_SESSION_TOKEN"]), msg: `cursor token: ${process.env["CURSOR_SESSION_TOKEN"] ? "set" : "missing CURSOR_SESSION_TOKEN"}` });
7231
6582
  checks.push({ ok: Boolean(getCloudDatabaseUrl()), msg: `cloud: ${getCloudDatabaseUrl() ? "ECONOMY_CLOUD_DATABASE_URL set" : "not configured"}` });
@@ -7356,8 +6707,8 @@ init_cloud_sync();
7356
6707
  // src/lib/peer-sync.ts
7357
6708
  init_database();
7358
6709
  init_package_metadata();
7359
- import { Database as BunDatabase3 } from "bun:sqlite";
7360
- import { existsSync as existsSync13 } from "fs";
6710
+ import { Database as BunDatabase2 } from "bun:sqlite";
6711
+ import { existsSync as existsSync12 } from "fs";
7361
6712
  var GENERIC_PEER_TABLES = [
7362
6713
  "usage_snapshots",
7363
6714
  "subscriptions",
@@ -7368,7 +6719,7 @@ var GENERIC_PEER_TABLES = [
7368
6719
  "model_pricing",
7369
6720
  "machines"
7370
6721
  ];
7371
- function quoteIdent2(identifier) {
6722
+ function quoteIdent(identifier) {
7372
6723
  return `"${identifier.replace(/"/g, '""')}"`;
7373
6724
  }
7374
6725
  function tableExists(db, table) {
@@ -7378,7 +6729,7 @@ function tableExists(db, table) {
7378
6729
  function tableColumns(db, table) {
7379
6730
  if (!tableExists(db, table))
7380
6731
  return [];
7381
- return db.prepare(`PRAGMA table_info(${quoteIdent2(table)})`).all();
6732
+ return db.prepare(`PRAGMA table_info(${quoteIdent(table)})`).all();
7382
6733
  }
7383
6734
  function commonColumns(source, target, table) {
7384
6735
  const sourceCols = new Set(tableColumns(source, table).map((c) => c.name));
@@ -7390,19 +6741,19 @@ function primaryKeyColumns(db, table) {
7390
6741
  function selectRows(source, table, columns) {
7391
6742
  if (columns.length === 0)
7392
6743
  return [];
7393
- const select = columns.map(quoteIdent2).join(", ");
7394
- return source.prepare(`SELECT ${select} FROM ${quoteIdent2(table)}`).all();
6744
+ const select = columns.map(quoteIdent).join(", ");
6745
+ return source.prepare(`SELECT ${select} FROM ${quoteIdent(table)}`).all();
7395
6746
  }
7396
6747
  function rowByKey(target, table, keyColumns, row) {
7397
6748
  if (keyColumns.length === 0)
7398
6749
  return null;
7399
6750
  if (keyColumns.some((c) => row[c] == null))
7400
6751
  return null;
7401
- const where = keyColumns.map((c) => `${quoteIdent2(c)} = ?`).join(" AND ");
7402
- return target.prepare(`SELECT * FROM ${quoteIdent2(table)} WHERE ${where}`).get(...keyColumns.map((c) => row[c]));
6752
+ const where = keyColumns.map((c) => `${quoteIdent(c)} = ?`).join(" AND ");
6753
+ return target.prepare(`SELECT * FROM ${quoteIdent(table)} WHERE ${where}`).get(...keyColumns.map((c) => row[c]));
7403
6754
  }
7404
6755
  function hasId(target, table, id) {
7405
- return target.prepare(`SELECT id, machine_id FROM ${quoteIdent2(table)} WHERE id = ?`).get(id);
6756
+ return target.prepare(`SELECT id, machine_id FROM ${quoteIdent(table)} WHERE id = ?`).get(id);
7406
6757
  }
7407
6758
  function shouldReplace(source, existing) {
7408
6759
  if (!existing)
@@ -7429,10 +6780,10 @@ function normalizeRow(row, columns, sourceMachine, now) {
7429
6780
  return next;
7430
6781
  }
7431
6782
  function insertOrReplace(target, table, columns, row) {
7432
- const colSql = columns.map(quoteIdent2).join(", ");
6783
+ const colSql = columns.map(quoteIdent).join(", ");
7433
6784
  const placeholders = columns.map(() => "?").join(", ");
7434
6785
  target.prepare(`
7435
- INSERT OR REPLACE INTO ${quoteIdent2(table)} (${colSql})
6786
+ INSERT OR REPLACE INTO ${quoteIdent(table)} (${colSql})
7436
6787
  VALUES (${placeholders})
7437
6788
  `).run(...columns.map((c) => row[c] ?? null));
7438
6789
  }
@@ -7546,7 +6897,7 @@ function detectSourceMachine(source, fallback) {
7546
6897
  continue;
7547
6898
  const rows = source.prepare(`
7548
6899
  SELECT machine_id, COUNT(*) as cnt
7549
- FROM ${quoteIdent2(table)}
6900
+ FROM ${quoteIdent(table)}
7550
6901
  WHERE machine_id != '' AND machine_id IS NOT NULL
7551
6902
  GROUP BY machine_id
7552
6903
  `).all();
@@ -7583,13 +6934,13 @@ function ensureMachineRegistry(target, machine, now) {
7583
6934
  }
7584
6935
  function openSourceDatabase(path) {
7585
6936
  try {
7586
- return new BunDatabase3(path, { readonly: true });
6937
+ return new BunDatabase2(path, { readonly: true });
7587
6938
  } catch {
7588
- return new BunDatabase3(path);
6939
+ return new BunDatabase2(path);
7589
6940
  }
7590
6941
  }
7591
6942
  function mergePeerDatabase(target, sourcePath, opts = {}) {
7592
- if (!existsSync13(sourcePath))
6943
+ if (!existsSync12(sourcePath))
7593
6944
  throw new Error(`source database does not exist: ${sourcePath}`);
7594
6945
  const source = openSourceDatabase(sourcePath);
7595
6946
  const now = opts.now ?? new Date().toISOString();
@@ -8452,8 +7803,8 @@ program.command("dashboard").description("Open the web dashboard (auto-starts se
8452
7803
  if (!serverRunning) {
8453
7804
  console.log(chalk7.cyan(`\u2192 Starting economy server on port ${port}...`));
8454
7805
  const { spawn } = await import("child_process");
8455
- const { resolve: resolve2, dirname: dirname4 } = await import("path");
8456
- const serveScript = resolve2(dirname4(process.argv[1]), "..", "server", "index.js");
7806
+ const { resolve: resolve2, dirname: dirname3 } = await import("path");
7807
+ const serveScript = resolve2(dirname3(process.argv[1]), "..", "server", "index.js");
8457
7808
  const child = spawn(process.execPath, [serveScript], {
8458
7809
  detached: true,
8459
7810
  stdio: "ignore",
@@ -8607,8 +7958,8 @@ program.command("export").description("Export data as CSV").option("--type <type
8607
7958
  }
8608
7959
  }
8609
7960
  if (opts.output) {
8610
- const { writeFileSync: writeFileSync5 } = await import("fs");
8611
- writeFileSync5(opts.output, csv);
7961
+ const { writeFileSync: writeFileSync4 } = await import("fs");
7962
+ writeFileSync4(opts.output, csv);
8612
7963
  console.log(chalk7.green(`\u2713 Exported to ${opts.output}`));
8613
7964
  } else {
8614
7965
  process.stdout.write(csv);
@@ -8960,4 +8311,5 @@ registerBrainsCommand(program);
8960
8311
  registerTodosCommand(program);
8961
8312
  registerExtendedCommands(program);
8962
8313
  registerFleetCommands(program);
8314
+ registerEventsCommands(program, { source: "economy" });
8963
8315
  program.parse();