@hasna/evals 0.1.24 → 0.1.26

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 (103) hide show
  1. package/dist/adapters/adapters.test.d.ts +2 -0
  2. package/dist/adapters/adapters.test.d.ts.map +1 -0
  3. package/dist/adapters/anthropic-openai.test.d.ts +2 -0
  4. package/dist/adapters/anthropic-openai.test.d.ts.map +1 -0
  5. package/dist/adapters/anthropic.d.ts +4 -0
  6. package/dist/adapters/anthropic.d.ts.map +1 -0
  7. package/dist/adapters/cli.d.ts +4 -0
  8. package/dist/adapters/cli.d.ts.map +1 -0
  9. package/dist/adapters/function.d.ts +4 -0
  10. package/dist/adapters/function.d.ts.map +1 -0
  11. package/dist/adapters/http-cli-coverage.test.d.ts +2 -0
  12. package/dist/adapters/http-cli-coverage.test.d.ts.map +1 -0
  13. package/dist/adapters/http.d.ts +16 -0
  14. package/dist/adapters/http.d.ts.map +1 -0
  15. package/dist/adapters/mcp-adapter.test.d.ts +2 -0
  16. package/dist/adapters/mcp-adapter.test.d.ts.map +1 -0
  17. package/dist/adapters/mcp.d.ts +4 -0
  18. package/dist/adapters/mcp.d.ts.map +1 -0
  19. package/dist/adapters/openai.d.ts +4 -0
  20. package/dist/adapters/openai.d.ts.map +1 -0
  21. package/dist/cli/adapter-parser.d.ts +3 -0
  22. package/dist/cli/adapter-parser.d.ts.map +1 -0
  23. package/dist/cli/cli.test.d.ts +2 -0
  24. package/dist/cli/cli.test.d.ts.map +1 -0
  25. package/dist/cli/commands/calibrate.d.ts +3 -0
  26. package/dist/cli/commands/calibrate.d.ts.map +1 -0
  27. package/dist/cli/commands/capture.d.ts +3 -0
  28. package/dist/cli/commands/capture.d.ts.map +1 -0
  29. package/dist/cli/commands/ci.d.ts +3 -0
  30. package/dist/cli/commands/ci.d.ts.map +1 -0
  31. package/dist/cli/commands/compare.d.ts +5 -0
  32. package/dist/cli/commands/compare.d.ts.map +1 -0
  33. package/dist/cli/commands/compare.test.d.ts +2 -0
  34. package/dist/cli/commands/compare.test.d.ts.map +1 -0
  35. package/dist/cli/commands/completion.d.ts +3 -0
  36. package/dist/cli/commands/completion.d.ts.map +1 -0
  37. package/dist/cli/commands/doctor.d.ts +3 -0
  38. package/dist/cli/commands/doctor.d.ts.map +1 -0
  39. package/dist/cli/commands/estimate.d.ts +3 -0
  40. package/dist/cli/commands/estimate.d.ts.map +1 -0
  41. package/dist/cli/commands/generate.d.ts +3 -0
  42. package/dist/cli/commands/generate.d.ts.map +1 -0
  43. package/dist/cli/commands/judge.d.ts +3 -0
  44. package/dist/cli/commands/judge.d.ts.map +1 -0
  45. package/dist/cli/commands/mcp.d.ts +3 -0
  46. package/dist/cli/commands/mcp.d.ts.map +1 -0
  47. package/dist/cli/commands/run.d.ts +3 -0
  48. package/dist/cli/commands/run.d.ts.map +1 -0
  49. package/dist/cli/commands/sync.d.ts +3 -0
  50. package/dist/cli/commands/sync.d.ts.map +1 -0
  51. package/dist/cli/index.d.ts +3 -0
  52. package/dist/cli/index.d.ts.map +1 -0
  53. package/dist/cli/index.js +857 -170
  54. package/dist/core/assertions-coverage.test.d.ts +2 -0
  55. package/dist/core/assertions-coverage.test.d.ts.map +1 -0
  56. package/dist/core/assertions.d.ts +18 -0
  57. package/dist/core/assertions.d.ts.map +1 -0
  58. package/dist/core/assertions.test.d.ts +2 -0
  59. package/dist/core/assertions.test.d.ts.map +1 -0
  60. package/dist/core/e2e.test.d.ts +2 -0
  61. package/dist/core/e2e.test.d.ts.map +1 -0
  62. package/dist/core/judge.d.ts +13 -0
  63. package/dist/core/judge.d.ts.map +1 -0
  64. package/dist/core/judge.test.d.ts +2 -0
  65. package/dist/core/judge.test.d.ts.map +1 -0
  66. package/dist/core/reporter.d.ts +21 -0
  67. package/dist/core/reporter.d.ts.map +1 -0
  68. package/dist/core/reporter.test.d.ts +2 -0
  69. package/dist/core/reporter.test.d.ts.map +1 -0
  70. package/dist/core/runner.d.ts +4 -0
  71. package/dist/core/runner.d.ts.map +1 -0
  72. package/dist/core/runner.test.d.ts +2 -0
  73. package/dist/core/runner.test.d.ts.map +1 -0
  74. package/dist/datasets/loader.d.ts +18 -0
  75. package/dist/datasets/loader.d.ts.map +1 -0
  76. package/dist/datasets/loader.test.d.ts +2 -0
  77. package/dist/datasets/loader.test.d.ts.map +1 -0
  78. package/dist/db/store.d.ts +17 -0
  79. package/dist/db/store.d.ts.map +1 -0
  80. package/dist/db/store.test.d.ts +2 -0
  81. package/dist/db/store.test.d.ts.map +1 -0
  82. package/dist/index.d.ts +8 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +24 -4
  85. package/dist/mcp/http.d.ts +13 -0
  86. package/dist/mcp/http.d.ts.map +1 -0
  87. package/dist/mcp/http.test.d.ts +2 -0
  88. package/dist/mcp/http.test.d.ts.map +1 -0
  89. package/dist/mcp/index.d.ts +3 -0
  90. package/dist/mcp/index.d.ts.map +1 -0
  91. package/dist/mcp/index.js +32454 -477
  92. package/dist/mcp/mcp.test.d.ts +2 -0
  93. package/dist/mcp/mcp.test.d.ts.map +1 -0
  94. package/dist/mcp/server.d.ts +5 -0
  95. package/dist/mcp/server.d.ts.map +1 -0
  96. package/dist/server/index.d.ts +3 -0
  97. package/dist/server/index.d.ts.map +1 -0
  98. package/dist/server/index.js +24 -4
  99. package/dist/server/server.test.d.ts +2 -0
  100. package/dist/server/server.test.d.ts.map +1 -0
  101. package/dist/types/index.d.ts +171 -0
  102. package/dist/types/index.d.ts.map +1 -0
  103. package/package.json +3 -2
package/dist/cli/index.js CHANGED
@@ -1019,7 +1019,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1019
1019
  this._exitCallback = (err) => {
1020
1020
  if (err.code !== "commander.executeSubCommandAsync") {
1021
1021
  throw err;
1022
- } else {}
1022
+ }
1023
1023
  };
1024
1024
  }
1025
1025
  return this;
@@ -8559,17 +8559,17 @@ __export(exports_store, {
8559
8559
  });
8560
8560
  import { Database } from "bun:sqlite";
8561
8561
  import { mkdirSync } from "fs";
8562
- import { homedir as homedir2 } from "os";
8563
- import { join as join2 } from "path";
8562
+ import { homedir as homedir3 } from "os";
8563
+ import { join as join3 } from "path";
8564
8564
  function getDbPath() {
8565
- return process.env["EVALS_DB_PATH"] ?? join2(homedir2(), ".hasna", "evals", "evals.db");
8565
+ return process.env["EVALS_DB_PATH"] ?? join3(homedir3(), ".hasna", "evals", "evals.db");
8566
8566
  }
8567
8567
  function getDatabase() {
8568
8568
  if (_db)
8569
8569
  return _db;
8570
8570
  const path3 = getDbPath();
8571
8571
  if (path3 !== ":memory:") {
8572
- mkdirSync(join2(path3, ".."), { recursive: true });
8572
+ mkdirSync(join3(path3, ".."), { recursive: true });
8573
8573
  }
8574
8574
  _db = new Database(path3);
8575
8575
  _db.exec("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;");
@@ -8700,8 +8700,8 @@ __export(exports_dist, {
8700
8700
  ensureConflictsTable: () => ensureConflictsTable,
8701
8701
  ensureAllPgDatabases: () => ensureAllPgDatabases,
8702
8702
  enableAutoSync: () => enableAutoSync,
8703
- discoverSyncableServicesV2: () => discoverSyncableServices,
8704
- discoverSyncableServices: () => discoverSyncableServices2,
8703
+ discoverSyncableServicesV2: () => discoverSyncableServices2,
8704
+ discoverSyncableServices: () => discoverSyncableServices,
8705
8705
  discoverServices: () => discoverServices,
8706
8706
  detectConflicts: () => detectConflicts,
8707
8707
  createDatabase: () => createDatabase,
@@ -8717,28 +8717,28 @@ __export(exports_dist, {
8717
8717
  import { createRequire } from "module";
8718
8718
  import { Database as Database2 } from "bun:sqlite";
8719
8719
  import {
8720
- existsSync as existsSync4,
8720
+ existsSync as existsSync5,
8721
8721
  mkdirSync as mkdirSync2,
8722
8722
  readdirSync,
8723
8723
  copyFileSync
8724
8724
  } from "fs";
8725
- import { homedir as homedir5 } from "os";
8726
- import { join as join5, relative } from "path";
8725
+ import { homedir as homedir6 } from "os";
8726
+ import { join as join6, relative } from "path";
8727
8727
  import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
8728
8728
  import { homedir as homedir22 } from "os";
8729
8729
  import { join as join22 } from "path";
8730
- import { readdirSync as readdirSync2, existsSync as existsSync32 } from "fs";
8731
- import { join as join32 } from "path";
8732
- import { homedir as homedir32 } from "os";
8730
+ import { readdirSync as readdirSync3, existsSync as existsSync6 } from "fs";
8731
+ import { join as join62 } from "path";
8732
+ import { homedir as homedir52 } from "os";
8733
8733
  import { hostname } from "os";
8734
- import { existsSync as existsSync42, readFileSync as readFileSync22 } from "fs";
8735
- import { homedir as homedir42 } from "os";
8734
+ import { existsSync as existsSync32, readFileSync as readFileSync22 } from "fs";
8735
+ import { homedir as homedir32 } from "os";
8736
+ import { join as join32 } from "path";
8737
+ import { existsSync as existsSync42, readdirSync as readdirSync2 } from "fs";
8736
8738
  import { join as join42 } from "path";
8737
- import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
8738
- import { join as join52 } from "path";
8739
- import { join as join6, dirname } from "path";
8740
- import { existsSync as existsSync6, writeFileSync as writeFileSync22, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
8741
- import { homedir as homedir52, platform } from "os";
8739
+ import { join as join52, dirname } from "path";
8740
+ import { existsSync as existsSync52, writeFileSync as writeFileSync22, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
8741
+ import { homedir as homedir42, platform } from "os";
8742
8742
  function __accessProp2(key) {
8743
8743
  return this[key];
8744
8744
  }
@@ -9620,20 +9620,20 @@ function custom(check, _params = {}, fatal) {
9620
9620
  return ZodAny.create();
9621
9621
  }
9622
9622
  function getDataDir(serviceName) {
9623
- const dir = join5(HASNA_DIR, serviceName);
9623
+ const dir = join6(HASNA_DIR, serviceName);
9624
9624
  mkdirSync2(dir, { recursive: true });
9625
9625
  return dir;
9626
9626
  }
9627
9627
  function getDbPath2(serviceName) {
9628
9628
  const dir = getDataDir(serviceName);
9629
- return join5(dir, `${serviceName}.db`);
9629
+ return join6(dir, `${serviceName}.db`);
9630
9630
  }
9631
9631
  function migrateDotfile(serviceName) {
9632
- const legacyDir = join5(homedir5(), `.${serviceName}`);
9633
- const newDir = join5(HASNA_DIR, serviceName);
9634
- if (!existsSync4(legacyDir))
9632
+ const legacyDir = join6(homedir6(), `.${serviceName}`);
9633
+ const newDir = join6(HASNA_DIR, serviceName);
9634
+ if (!existsSync5(legacyDir))
9635
9635
  return [];
9636
- if (existsSync4(newDir))
9636
+ if (existsSync5(newDir))
9637
9637
  return [];
9638
9638
  mkdirSync2(newDir, { recursive: true });
9639
9639
  const migrated = [];
@@ -9643,8 +9643,8 @@ function migrateDotfile(serviceName) {
9643
9643
  function copyDirRecursive(src, dest, root, migrated) {
9644
9644
  const entries = readdirSync(src, { withFileTypes: true });
9645
9645
  for (const entry of entries) {
9646
- const srcPath = join5(src, entry.name);
9647
- const destPath = join5(dest, entry.name);
9646
+ const srcPath = join6(src, entry.name);
9647
+ const destPath = join6(dest, entry.name);
9648
9648
  if (entry.isDirectory()) {
9649
9649
  mkdirSync2(destPath, { recursive: true });
9650
9650
  copyDirRecursive(srcPath, destPath, root, migrated);
@@ -9655,7 +9655,7 @@ function copyDirRecursive(src, dest, root, migrated) {
9655
9655
  }
9656
9656
  }
9657
9657
  function hasLegacyDotfile(serviceName) {
9658
- return existsSync4(join5(homedir5(), `.${serviceName}`));
9658
+ return existsSync5(join6(homedir6(), `.${serviceName}`));
9659
9659
  }
9660
9660
  function getHasnaDir() {
9661
9661
  mkdirSync2(HASNA_DIR, { recursive: true });
@@ -9710,11 +9710,11 @@ function isSyncExcludedTable(table) {
9710
9710
  return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
9711
9711
  }
9712
9712
  function discoverServices() {
9713
- const dataDir = join32(homedir32(), ".hasna");
9714
- if (!existsSync32(dataDir))
9713
+ const dataDir = join62(homedir52(), ".hasna");
9714
+ if (!existsSync6(dataDir))
9715
9715
  return [];
9716
9716
  try {
9717
- const entries = readdirSync2(dataDir, { withFileTypes: true });
9717
+ const entries = readdirSync3(dataDir, { withFileTypes: true });
9718
9718
  return entries.filter((e) => {
9719
9719
  if (!e.isDirectory())
9720
9720
  return false;
@@ -9726,30 +9726,30 @@ function discoverServices() {
9726
9726
  return [];
9727
9727
  }
9728
9728
  }
9729
- function discoverSyncableServices() {
9729
+ function discoverSyncableServices2() {
9730
9730
  const local = discoverServices();
9731
9731
  const pgSet = new Set(KNOWN_PG_SERVICES);
9732
9732
  return local.filter((s) => pgSet.has(s));
9733
9733
  }
9734
9734
  function getServiceDbPath(service) {
9735
- const dataDir = join32(homedir32(), ".hasna", service);
9736
- if (!existsSync32(dataDir))
9735
+ const dataDir = join62(homedir52(), ".hasna", service);
9736
+ if (!existsSync6(dataDir))
9737
9737
  return null;
9738
9738
  const candidates = [
9739
- join32(dataDir, `${service}.db`),
9740
- join32(dataDir, "data.db"),
9741
- join32(dataDir, "database.db")
9739
+ join62(dataDir, `${service}.db`),
9740
+ join62(dataDir, "data.db"),
9741
+ join62(dataDir, "database.db")
9742
9742
  ];
9743
9743
  try {
9744
- const files = readdirSync2(dataDir);
9744
+ const files = readdirSync3(dataDir);
9745
9745
  for (const f of files) {
9746
9746
  if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
9747
- candidates.push(join32(dataDir, f));
9747
+ candidates.push(join62(dataDir, f));
9748
9748
  }
9749
9749
  }
9750
9750
  } catch {}
9751
9751
  for (const p of candidates) {
9752
- if (existsSync32(p))
9752
+ if (existsSync6(p))
9753
9753
  return p;
9754
9754
  }
9755
9755
  return null;
@@ -10056,9 +10056,9 @@ async function syncTransfer(source, target, options, _direction) {
10056
10056
  const batch = rows.slice(offset, offset + batchSize);
10057
10057
  try {
10058
10058
  if (isAsyncAdapter(target)) {
10059
- await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch, columns.includes(conflictColumn) ? conflictColumn : undefined);
10059
+ await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
10060
10060
  } else {
10061
- batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch, columns.includes(conflictColumn) ? conflictColumn : undefined);
10061
+ batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
10062
10062
  }
10063
10063
  result.rowsWritten += batch.length;
10064
10064
  } catch (err) {
@@ -10105,7 +10105,7 @@ async function syncTransfer(source, target, options, _direction) {
10105
10105
  }
10106
10106
  return results;
10107
10107
  }
10108
- async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch, conflictColumn) {
10108
+ async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch) {
10109
10109
  if (batch.length === 0)
10110
10110
  return;
10111
10111
  const colList = columns.map((c) => `"${c}"`).join(", ");
@@ -10115,22 +10115,20 @@ async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, ba
10115
10115
  }).join(", ");
10116
10116
  const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
10117
10117
  const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
10118
- const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
10119
10118
  const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
10120
- ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}${whereClause}`;
10119
+ ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
10121
10120
  const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
10122
10121
  await target.run(sql, ...params);
10123
10122
  }
10124
- function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch, conflictColumn) {
10123
+ function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch) {
10125
10124
  if (batch.length === 0)
10126
10125
  return;
10127
10126
  const colList = columns.map((c) => `"${c}"`).join(", ");
10128
10127
  const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
10129
10128
  const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
10130
10129
  const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
10131
- const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
10132
10130
  const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
10133
- ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}${whereClause}`;
10131
+ ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
10134
10132
  const params = batch.flatMap((row) => columns.map((c) => coerceForSqlite(row[c])));
10135
10133
  target.run(sql, ...params);
10136
10134
  }
@@ -10189,17 +10187,17 @@ function ensureFeedbackTable(db) {
10189
10187
  function saveFeedback(db, feedback) {
10190
10188
  ensureFeedbackTable(db);
10191
10189
  const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
10192
- const now = new Date().toISOString();
10190
+ const now2 = new Date().toISOString();
10193
10191
  const machineId = feedback.machine_id ?? hostname();
10194
10192
  db.run(`INSERT INTO feedback (id, service, version, message, email, machine_id, created_at)
10195
- VALUES (?, ?, ?, ?, ?, ?, ?)`, id, feedback.service, feedback.version ?? "", feedback.message, feedback.email ?? "", machineId, feedback.created_at ?? now);
10193
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, id, feedback.service, feedback.version ?? "", feedback.message, feedback.email ?? "", machineId, feedback.created_at ?? now2);
10196
10194
  return id;
10197
10195
  }
10198
10196
  async function sendFeedback(feedback, db) {
10199
10197
  const config = getCloudConfig();
10200
10198
  const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
10201
10199
  const machineId = feedback.machine_id ?? hostname();
10202
- const now = new Date().toISOString();
10200
+ const now2 = new Date().toISOString();
10203
10201
  const payload = {
10204
10202
  id,
10205
10203
  service: feedback.service,
@@ -10207,7 +10205,7 @@ async function sendFeedback(feedback, db) {
10207
10205
  message: feedback.message,
10208
10206
  email: feedback.email ?? "",
10209
10207
  machine_id: machineId,
10210
- created_at: feedback.created_at ?? now
10208
+ created_at: feedback.created_at ?? now2
10211
10209
  };
10212
10210
  try {
10213
10211
  const res = await fetch(config.feedback_endpoint, {
@@ -10263,8 +10261,8 @@ class SyncProgressTracker {
10263
10261
  }
10264
10262
  start(table, total, direction) {
10265
10263
  const resumed = this.canResume(table);
10266
- const now = Date.now();
10267
- this.startTimes.set(table, now);
10264
+ const now2 = Date.now();
10265
+ this.startTimes.set(table, now2);
10268
10266
  const status = resumed ? "resumed" : "in_progress";
10269
10267
  const info = {
10270
10268
  table,
@@ -10586,10 +10584,10 @@ function incrementalSyncPush(local, remote, tables, options = {}) {
10586
10584
  if (rows.length === 0) {
10587
10585
  stat.skipped_rows = stat.total_rows;
10588
10586
  }
10589
- const now = new Date().toISOString();
10587
+ const now2 = new Date().toISOString();
10590
10588
  upsertSyncMeta(local, {
10591
10589
  table_name: table,
10592
- last_synced_at: now,
10590
+ last_synced_at: now2,
10593
10591
  last_synced_row_count: stat.synced_rows,
10594
10592
  direction: "push"
10595
10593
  });
@@ -10639,10 +10637,10 @@ function incrementalSyncPull(remote, local, tables, options = {}) {
10639
10637
  if (rows.length === 0) {
10640
10638
  stat.skipped_rows = stat.total_rows;
10641
10639
  }
10642
- const now = new Date().toISOString();
10640
+ const now2 = new Date().toISOString();
10643
10641
  upsertSyncMeta(local, {
10644
10642
  table_name: table,
10645
- last_synced_at: now,
10643
+ last_synced_at: now2,
10646
10644
  last_synced_row_count: stat.synced_rows,
10647
10645
  direction: "pull"
10648
10646
  });
@@ -10670,7 +10668,7 @@ function resetAllSyncMeta(db) {
10670
10668
  }
10671
10669
  function getAutoSyncConfig() {
10672
10670
  try {
10673
- if (!existsSync42(AUTO_SYNC_CONFIG_PATH)) {
10671
+ if (!existsSync32(AUTO_SYNC_CONFIG_PATH)) {
10674
10672
  return { ...DEFAULT_AUTO_SYNC_CONFIG };
10675
10673
  }
10676
10674
  const raw = JSON.parse(readFileSync22(AUTO_SYNC_CONFIG_PATH, "utf-8"));
@@ -10682,7 +10680,7 @@ function getAutoSyncConfig() {
10682
10680
  return { ...DEFAULT_AUTO_SYNC_CONFIG };
10683
10681
  }
10684
10682
  }
10685
- async function executeAutoSync(event, serviceName, local, tables) {
10683
+ function executeAutoSync(event, local, remote, tables) {
10686
10684
  const direction = event === "start" ? "pull" : "push";
10687
10685
  const result = {
10688
10686
  event,
@@ -10692,31 +10690,18 @@ async function executeAutoSync(event, serviceName, local, tables) {
10692
10690
  total_rows_synced: 0,
10693
10691
  errors: []
10694
10692
  };
10695
- let remote = null;
10696
10693
  try {
10697
- const connStr = getConnectionString(serviceName);
10698
- remote = new PgAdapterAsync(connStr);
10699
- const syncTables = tables.length > 0 ? tables.filter((t) => !isSyncExcludedTable(t)) : direction === "push" ? listSqliteTables(local).filter((t) => !isSyncExcludedTable(t)) : (await listPgTables(remote)).filter((t) => !isSyncExcludedTable(t));
10700
- if (syncTables.length === 0) {
10701
- result.success = true;
10702
- return result;
10703
- }
10704
- const results = direction === "pull" ? await syncPull(remote, local, { tables: syncTables }) : await syncPush(local, remote, { tables: syncTables });
10705
- for (const r of results) {
10706
- if (r.errors.length === 0)
10694
+ const stats = direction === "pull" ? incrementalSyncPull(remote, local, tables) : incrementalSyncPush(local, remote, tables);
10695
+ for (const s of stats) {
10696
+ if (s.errors.length === 0) {
10707
10697
  result.tables_synced++;
10708
- result.total_rows_synced += r.rowsWritten;
10709
- result.errors.push(...r.errors);
10698
+ }
10699
+ result.total_rows_synced += s.synced_rows;
10700
+ result.errors.push(...s.errors);
10710
10701
  }
10711
10702
  result.success = result.errors.length === 0;
10712
10703
  } catch (err) {
10713
10704
  result.errors.push(err?.message ?? String(err));
10714
- } finally {
10715
- if (remote) {
10716
- try {
10717
- await remote.close();
10718
- } catch {}
10719
- }
10720
10705
  }
10721
10706
  return result;
10722
10707
  }
@@ -10724,76 +10709,80 @@ function installSignalHandlers() {
10724
10709
  if (signalHandlersInstalled)
10725
10710
  return;
10726
10711
  signalHandlersInstalled = true;
10727
- const handleExit = async () => {
10712
+ const handleExit = () => {
10728
10713
  for (const fn of cleanupHandlers) {
10729
10714
  try {
10730
- await fn();
10715
+ fn();
10731
10716
  } catch {}
10732
10717
  }
10733
10718
  };
10734
- process.on("SIGTERM", async () => {
10735
- await handleExit();
10719
+ process.on("SIGTERM", () => {
10720
+ handleExit();
10736
10721
  process.exit(0);
10737
10722
  });
10738
- process.on("SIGINT", async () => {
10739
- await handleExit();
10723
+ process.on("SIGINT", () => {
10724
+ handleExit();
10740
10725
  process.exit(0);
10741
10726
  });
10742
- process.on("beforeExit", async () => {
10743
- await handleExit();
10727
+ process.on("beforeExit", () => {
10728
+ handleExit();
10744
10729
  });
10745
10730
  }
10746
10731
  function setupAutoSync(serviceName, server, local, remote, tables) {
10747
10732
  const config = getAutoSyncConfig();
10748
10733
  const cloudConfig = getCloudConfig();
10749
10734
  const isSyncEnabled = cloudConfig.mode === "hybrid" || cloudConfig.mode === "cloud";
10750
- const syncOnStart = async () => {
10735
+ const syncOnStart = () => {
10751
10736
  if (!config.auto_sync_on_start || !isSyncEnabled)
10752
10737
  return null;
10753
- return executeAutoSync("start", serviceName, local, tables);
10738
+ return executeAutoSync("start", local, remote, tables);
10754
10739
  };
10755
- const syncOnStop = async () => {
10740
+ const syncOnStop = () => {
10756
10741
  if (!config.auto_sync_on_stop || !isSyncEnabled)
10757
10742
  return null;
10758
- return executeAutoSync("stop", serviceName, local, tables);
10743
+ return executeAutoSync("stop", local, remote, tables);
10759
10744
  };
10760
10745
  if (server && typeof server.onconnect === "function") {
10761
10746
  const origOnConnect = server.onconnect;
10762
- server.onconnect = async (...args) => {
10763
- await syncOnStart();
10747
+ server.onconnect = (...args) => {
10748
+ syncOnStart();
10764
10749
  return origOnConnect.apply(server, args);
10765
10750
  };
10766
10751
  } else if (server && typeof server.on === "function") {
10767
- server.on("connect", () => syncOnStart());
10752
+ server.on("connect", () => {
10753
+ syncOnStart();
10754
+ });
10768
10755
  }
10769
10756
  if (server && typeof server.ondisconnect === "function") {
10770
10757
  const origOnDisconnect = server.ondisconnect;
10771
- server.ondisconnect = async (...args) => {
10772
- await syncOnStop();
10758
+ server.ondisconnect = (...args) => {
10759
+ syncOnStop();
10773
10760
  return origOnDisconnect.apply(server, args);
10774
10761
  };
10775
10762
  } else if (server && typeof server.on === "function") {
10776
- server.on("disconnect", () => syncOnStop());
10763
+ server.on("disconnect", () => {
10764
+ syncOnStop();
10765
+ });
10777
10766
  }
10778
10767
  installSignalHandlers();
10779
- cleanupHandlers.push(async () => {
10780
- await syncOnStop();
10768
+ cleanupHandlers.push(() => {
10769
+ syncOnStop();
10781
10770
  });
10782
10771
  return { syncOnStart, syncOnStop, config };
10783
10772
  }
10784
10773
  function enableAutoSync(serviceName, mcpServer, local, remote, tables) {
10785
10774
  setupAutoSync(serviceName, mcpServer, local, remote, tables);
10786
10775
  }
10787
- function discoverSyncableServices2() {
10776
+ function discoverSyncableServices() {
10788
10777
  const hasnaDir = getHasnaDir();
10789
10778
  const services = [];
10790
10779
  try {
10791
- const entries = readdirSync3(hasnaDir, { withFileTypes: true });
10780
+ const entries = readdirSync2(hasnaDir, { withFileTypes: true });
10792
10781
  for (const entry of entries) {
10793
10782
  if (!entry.isDirectory())
10794
10783
  continue;
10795
- const dbPath = join52(hasnaDir, entry.name, `${entry.name}.db`);
10796
- if (existsSync5(dbPath)) {
10784
+ const dbPath = join42(hasnaDir, entry.name, `${entry.name}.db`);
10785
+ if (existsSync42(dbPath)) {
10797
10786
  services.push(entry.name);
10798
10787
  }
10799
10788
  }
@@ -10804,7 +10793,7 @@ async function runScheduledSync() {
10804
10793
  const config = getCloudConfig();
10805
10794
  if (config.mode === "local")
10806
10795
  return [];
10807
- const services = discoverSyncableServices2();
10796
+ const services = discoverSyncableServices();
10808
10797
  const results = [];
10809
10798
  let remote = null;
10810
10799
  for (const service of services) {
@@ -10815,8 +10804,8 @@ async function runScheduledSync() {
10815
10804
  errors: []
10816
10805
  };
10817
10806
  try {
10818
- const dbPath = join52(getDataDir(service), `${service}.db`);
10819
- if (!existsSync5(dbPath)) {
10807
+ const dbPath = join42(getDataDir(service), `${service}.db`);
10808
+ if (!existsSync42(dbPath)) {
10820
10809
  continue;
10821
10810
  }
10822
10811
  const local = new SqliteAdapter(dbPath);
@@ -10897,34 +10886,34 @@ function minutesToCron(minutes) {
10897
10886
  }
10898
10887
  function getWorkerPath() {
10899
10888
  const dir = typeof import.meta.dir === "string" ? import.meta.dir : dirname(import.meta.url.replace("file://", ""));
10900
- const tsPath = join6(dir, "scheduled-sync.ts");
10901
- const jsPath = join6(dir, "scheduled-sync.js");
10889
+ const tsPath = join52(dir, "scheduled-sync.ts");
10890
+ const jsPath = join52(dir, "scheduled-sync.js");
10902
10891
  try {
10903
- if (existsSync6(tsPath))
10892
+ if (existsSync52(tsPath))
10904
10893
  return tsPath;
10905
10894
  } catch {}
10906
10895
  return jsPath;
10907
10896
  }
10908
10897
  function getBunPath() {
10909
10898
  const candidates = [
10910
- join6(homedir52(), ".bun", "bin", "bun"),
10899
+ join52(homedir42(), ".bun", "bin", "bun"),
10911
10900
  "/usr/local/bin/bun",
10912
10901
  "/usr/bin/bun"
10913
10902
  ];
10914
10903
  for (const p of candidates) {
10915
- if (existsSync6(p))
10904
+ if (existsSync52(p))
10916
10905
  return p;
10917
10906
  }
10918
10907
  return "bun";
10919
10908
  }
10920
10909
  function getLaunchdPlistPath() {
10921
- return join6(homedir52(), "Library", "LaunchAgents", `com.hasna.cloud-sync.plist`);
10910
+ return join52(homedir42(), "Library", "LaunchAgents", `com.hasna.cloud-sync.plist`);
10922
10911
  }
10923
10912
  function createLaunchdPlist(intervalMinutes) {
10924
10913
  const workerPath = getWorkerPath();
10925
10914
  const bunPath = getBunPath();
10926
- const logPath = join6(CONFIG_DIR2, "sync.log");
10927
- const errorLogPath = join6(CONFIG_DIR2, "sync-error.log");
10915
+ const logPath = join52(CONFIG_DIR2, "sync.log");
10916
+ const errorLogPath = join52(CONFIG_DIR2, "sync-error.log");
10928
10917
  return `<?xml version="1.0" encoding="UTF-8"?>
10929
10918
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
10930
10919
  <plist version="1.0">
@@ -10950,7 +10939,7 @@ function createLaunchdPlist(intervalMinutes) {
10950
10939
  <key>PATH</key>
10951
10940
  <string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}</string>
10952
10941
  <key>HOME</key>
10953
- <string>${homedir52()}</string>
10942
+ <string>${homedir42()}</string>
10954
10943
  </dict>
10955
10944
  </dict>
10956
10945
  </plist>`;
@@ -10975,7 +10964,7 @@ async function removeLaunchd() {
10975
10964
  } catch {}
10976
10965
  }
10977
10966
  function getSystemdDir() {
10978
- return join6(homedir52(), ".config", "systemd", "user");
10967
+ return join52(homedir42(), ".config", "systemd", "user");
10979
10968
  }
10980
10969
  function createSystemdService() {
10981
10970
  const workerPath = getWorkerPath();
@@ -10987,7 +10976,7 @@ After=network.target
10987
10976
  [Service]
10988
10977
  Type=oneshot
10989
10978
  ExecStart=${bunPath} run ${workerPath}
10990
- Environment=HOME=${homedir52()}
10979
+ Environment=HOME=${homedir42()}
10991
10980
  Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
10992
10981
 
10993
10982
  [Install]
@@ -11010,8 +10999,8 @@ WantedBy=timers.target
11010
10999
  async function registerSystemd(intervalMinutes) {
11011
11000
  const dir = getSystemdDir();
11012
11001
  mkdirSync3(dir, { recursive: true });
11013
- writeFileSync22(join6(dir, `${SERVICE_NAME}.service`), createSystemdService());
11014
- writeFileSync22(join6(dir, `${SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
11002
+ writeFileSync22(join52(dir, `${SERVICE_NAME}.service`), createSystemdService());
11003
+ writeFileSync22(join52(dir, `${SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
11015
11004
  await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
11016
11005
  await Bun.spawn(["systemctl", "--user", "enable", "--now", `${SERVICE_NAME}.timer`]).exited;
11017
11006
  }
@@ -11021,10 +11010,10 @@ async function removeSystemd() {
11021
11010
  } catch {}
11022
11011
  const dir = getSystemdDir();
11023
11012
  try {
11024
- unlinkSync(join6(dir, `${SERVICE_NAME}.service`));
11013
+ unlinkSync(join52(dir, `${SERVICE_NAME}.service`));
11025
11014
  } catch {}
11026
11015
  try {
11027
- unlinkSync(join6(dir, `${SERVICE_NAME}.timer`));
11016
+ unlinkSync(join52(dir, `${SERVICE_NAME}.timer`));
11028
11017
  } catch {}
11029
11018
  try {
11030
11019
  await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
@@ -11061,9 +11050,9 @@ function getSyncScheduleStatus() {
11061
11050
  let mechanism = "none";
11062
11051
  if (registered) {
11063
11052
  if (platform() === "darwin") {
11064
- mechanism = existsSync6(getLaunchdPlistPath()) ? "launchd" : "none";
11053
+ mechanism = existsSync52(getLaunchdPlistPath()) ? "launchd" : "none";
11065
11054
  } else {
11066
- mechanism = existsSync6(join6(getSystemdDir(), `${SERVICE_NAME}.timer`)) ? "systemd" : "none";
11055
+ mechanism = existsSync52(join52(getSystemdDir(), `${SERVICE_NAME}.timer`)) ? "systemd" : "none";
11067
11056
  }
11068
11057
  }
11069
11058
  return {
@@ -11200,7 +11189,7 @@ async function ensureAllPgDatabases() {
11200
11189
  }
11201
11190
  return results;
11202
11191
  }
11203
- function registerCloudTools(server, serviceName, opts = {}) {
11192
+ function registerCloudTools(server, serviceName) {
11204
11193
  server.tool(`${serviceName}_cloud_status`, "Show cloud configuration and connection health", {}, async () => {
11205
11194
  const config = getCloudConfig();
11206
11195
  const lines = [
@@ -11233,13 +11222,8 @@ function registerCloudTools(server, serviceName, opts = {}) {
11233
11222
  isError: true
11234
11223
  };
11235
11224
  }
11236
- const local = new SqliteAdapter(opts.dbPath ?? getDbPath2(serviceName));
11225
+ const local = new SqliteAdapter(getDbPath2(serviceName));
11237
11226
  const cloud = new PgAdapterAsync(getConnectionString(serviceName));
11238
- if (opts.migrations?.length) {
11239
- for (const sql of opts.migrations) {
11240
- await cloud.run(sql);
11241
- }
11242
- }
11243
11227
  const tableList = tablesStr ? tablesStr.split(",").map((t) => t.trim()) : listSqliteTables(local);
11244
11228
  const results = await syncPush(local, cloud, { tables: tableList });
11245
11229
  local.close();
@@ -11261,7 +11245,7 @@ function registerCloudTools(server, serviceName, opts = {}) {
11261
11245
  isError: true
11262
11246
  };
11263
11247
  }
11264
- const local = new SqliteAdapter(opts.dbPath ?? getDbPath2(serviceName));
11248
+ const local = new SqliteAdapter(getDbPath2(serviceName));
11265
11249
  const cloud = new PgAdapterAsync(getConnectionString(serviceName));
11266
11250
  let tableList;
11267
11251
  if (tablesStr) {
@@ -19613,7 +19597,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
19613
19597
  init_external();
19614
19598
  });
19615
19599
  init_dotfile = __esm2(() => {
19616
- HASNA_DIR = join5(homedir5(), ".hasna");
19600
+ HASNA_DIR = join6(homedir6(), ".hasna");
19617
19601
  });
19618
19602
  exports_config = {};
19619
19603
  __export2(exports_config, {
@@ -19651,7 +19635,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
19651
19635
  __export2(exports_discover, {
19652
19636
  isSyncExcludedTable: () => isSyncExcludedTable,
19653
19637
  getServiceDbPath: () => getServiceDbPath,
19654
- discoverSyncableServices: () => discoverSyncableServices,
19638
+ discoverSyncableServices: () => discoverSyncableServices2,
19655
19639
  discoverServices: () => discoverServices,
19656
19640
  SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
19657
19641
  KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
@@ -19706,10 +19690,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
19706
19690
  init_config();
19707
19691
  init_config();
19708
19692
  init_dotfile();
19709
- init_adapter();
19710
19693
  init_config();
19711
- init_discover();
19712
- AUTO_SYNC_CONFIG_PATH = join42(homedir42(), ".hasna", "cloud", "config.json");
19694
+ AUTO_SYNC_CONFIG_PATH = join32(homedir32(), ".hasna", "cloud", "config.json");
19713
19695
  DEFAULT_AUTO_SYNC_CONFIG = {
19714
19696
  auto_sync_on_start: true,
19715
19697
  auto_sync_on_stop: true
@@ -19719,7 +19701,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
19719
19701
  init_adapter();
19720
19702
  init_dotfile();
19721
19703
  init_config();
19722
- CONFIG_DIR2 = join6(homedir52(), ".hasna", "cloud");
19704
+ CONFIG_DIR2 = join52(homedir42(), ".hasna", "cloud");
19723
19705
  init_adapter();
19724
19706
  init_config();
19725
19707
  init_discover();
@@ -19732,6 +19714,690 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
19732
19714
  init_adapter();
19733
19715
  });
19734
19716
 
19717
+ // node_modules/@hasna/events/dist/commander.js
19718
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
19719
+ import { existsSync } from "fs";
19720
+ import { homedir } from "os";
19721
+ import { join } from "path";
19722
+ import { createHmac, timingSafeEqual } from "crypto";
19723
+ import { randomUUID } from "crypto";
19724
+ import { spawn } from "child_process";
19725
+ import { randomUUID as randomUUID2 } from "crypto";
19726
+ function getPathValue(input, path) {
19727
+ return path.split(".").reduce((value, part) => {
19728
+ if (value && typeof value === "object" && part in value) {
19729
+ return value[part];
19730
+ }
19731
+ return;
19732
+ }, input);
19733
+ }
19734
+ function wildcardToRegExp(pattern) {
19735
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
19736
+ return new RegExp(`^${escaped}$`);
19737
+ }
19738
+ function matchString(value, matcher) {
19739
+ if (matcher === undefined)
19740
+ return true;
19741
+ if (value === undefined)
19742
+ return false;
19743
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
19744
+ return matchers.some((item) => wildcardToRegExp(item).test(value));
19745
+ }
19746
+ function matchRecord(input, matcher) {
19747
+ if (!matcher)
19748
+ return true;
19749
+ return Object.entries(matcher).every(([path, expected]) => {
19750
+ const actual = getPathValue(input, path);
19751
+ if (typeof expected === "string" || Array.isArray(expected)) {
19752
+ return matchString(actual === undefined ? undefined : String(actual), expected);
19753
+ }
19754
+ return actual === expected;
19755
+ });
19756
+ }
19757
+ function eventMatchesFilter(event, filter) {
19758
+ return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
19759
+ }
19760
+ function channelMatchesEvent(channel, event) {
19761
+ if (!channel.enabled)
19762
+ return false;
19763
+ if (!channel.filters || channel.filters.length === 0)
19764
+ return true;
19765
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
19766
+ }
19767
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
19768
+ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
19769
+ function getEventsDataDir(override) {
19770
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
19771
+ }
19772
+
19773
+ class JsonEventsStore {
19774
+ dataDir;
19775
+ channelsPath;
19776
+ eventsPath;
19777
+ deliveriesPath;
19778
+ constructor(dataDir = getEventsDataDir()) {
19779
+ this.dataDir = dataDir;
19780
+ this.channelsPath = join(dataDir, "channels.json");
19781
+ this.eventsPath = join(dataDir, "events.json");
19782
+ this.deliveriesPath = join(dataDir, "deliveries.json");
19783
+ }
19784
+ async init() {
19785
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
19786
+ await chmod(this.dataDir, 448).catch(() => {
19787
+ return;
19788
+ });
19789
+ await this.ensureArrayFile(this.channelsPath);
19790
+ await this.ensureArrayFile(this.eventsPath);
19791
+ await this.ensureArrayFile(this.deliveriesPath);
19792
+ }
19793
+ async addChannel(channel) {
19794
+ await this.init();
19795
+ const channels = await this.readJson(this.channelsPath, []);
19796
+ const index = channels.findIndex((item) => item.id === channel.id);
19797
+ if (index >= 0) {
19798
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
19799
+ } else {
19800
+ channels.push(channel);
19801
+ }
19802
+ await this.writeJson(this.channelsPath, channels);
19803
+ return index >= 0 ? channels[index] : channel;
19804
+ }
19805
+ async listChannels() {
19806
+ await this.init();
19807
+ return this.readJson(this.channelsPath, []);
19808
+ }
19809
+ async getChannel(id) {
19810
+ const channels = await this.listChannels();
19811
+ return channels.find((channel) => channel.id === id);
19812
+ }
19813
+ async removeChannel(id) {
19814
+ await this.init();
19815
+ const channels = await this.readJson(this.channelsPath, []);
19816
+ const next = channels.filter((channel) => channel.id !== id);
19817
+ await this.writeJson(this.channelsPath, next);
19818
+ return next.length !== channels.length;
19819
+ }
19820
+ async appendEvent(event) {
19821
+ await this.init();
19822
+ const events = await this.readJson(this.eventsPath, []);
19823
+ events.push(event);
19824
+ await this.writeJson(this.eventsPath, events);
19825
+ return event;
19826
+ }
19827
+ async listEvents() {
19828
+ await this.init();
19829
+ return this.readJson(this.eventsPath, []);
19830
+ }
19831
+ async findEventByIdentity(identity) {
19832
+ const events = await this.listEvents();
19833
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
19834
+ }
19835
+ async appendDelivery(result) {
19836
+ await this.init();
19837
+ const deliveries = await this.readJson(this.deliveriesPath, []);
19838
+ deliveries.push(result);
19839
+ await this.writeJson(this.deliveriesPath, deliveries);
19840
+ return result;
19841
+ }
19842
+ async listDeliveries() {
19843
+ await this.init();
19844
+ return this.readJson(this.deliveriesPath, []);
19845
+ }
19846
+ async exportData() {
19847
+ return {
19848
+ channels: await this.listChannels(),
19849
+ events: await this.listEvents(),
19850
+ deliveries: await this.listDeliveries()
19851
+ };
19852
+ }
19853
+ async ensureArrayFile(path) {
19854
+ if (!existsSync(path)) {
19855
+ await writeFile(path, `[]
19856
+ `, { encoding: "utf-8", mode: 384 });
19857
+ }
19858
+ await chmod(path, 384).catch(() => {
19859
+ return;
19860
+ });
19861
+ }
19862
+ async readJson(path, fallback) {
19863
+ try {
19864
+ const raw = await readFile(path, "utf-8");
19865
+ if (!raw.trim())
19866
+ return fallback;
19867
+ return JSON.parse(raw);
19868
+ } catch (error) {
19869
+ if (error.code === "ENOENT")
19870
+ return fallback;
19871
+ throw error;
19872
+ }
19873
+ }
19874
+ async writeJson(path, value) {
19875
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
19876
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
19877
+ `, { encoding: "utf-8", mode: 384 });
19878
+ await rename(tempPath, path);
19879
+ await chmod(path, 384).catch(() => {
19880
+ return;
19881
+ });
19882
+ }
19883
+ }
19884
+ var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
19885
+ function buildSignatureBase(timestamp, body) {
19886
+ return `${timestamp}.${body}`;
19887
+ }
19888
+ function signPayload(secret, timestamp, body) {
19889
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
19890
+ return `sha256=${digest}`;
19891
+ }
19892
+ function now() {
19893
+ return new Date().toISOString();
19894
+ }
19895
+ function truncate(value, max = 4096) {
19896
+ return value.length > max ? `${value.slice(0, max)}...` : value;
19897
+ }
19898
+ function buildWebhookRequest(event, channel) {
19899
+ if (!channel.webhook)
19900
+ throw new Error(`Channel ${channel.id} has no webhook config`);
19901
+ const body = JSON.stringify(event);
19902
+ const timestamp = event.time;
19903
+ const headers = {
19904
+ "Content-Type": "application/json",
19905
+ "User-Agent": "@hasna/events",
19906
+ "X-Hasna-Event-Id": event.id,
19907
+ "X-Hasna-Event-Type": event.type,
19908
+ "X-Hasna-Timestamp": timestamp,
19909
+ ...channel.webhook.headers
19910
+ };
19911
+ if (channel.webhook.secret) {
19912
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
19913
+ }
19914
+ return { body, headers };
19915
+ }
19916
+ async function dispatchWebhook(event, channel, options = {}) {
19917
+ if (!channel.webhook)
19918
+ throw new Error(`Channel ${channel.id} has no webhook config`);
19919
+ const startedAt = now();
19920
+ const { body, headers } = buildWebhookRequest(event, channel);
19921
+ const controller = new AbortController;
19922
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
19923
+ try {
19924
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
19925
+ method: "POST",
19926
+ headers,
19927
+ body,
19928
+ signal: controller.signal
19929
+ });
19930
+ const responseBody = truncate(await response.text());
19931
+ return {
19932
+ attempt: 1,
19933
+ status: response.ok ? "success" : "failed",
19934
+ startedAt,
19935
+ completedAt: now(),
19936
+ responseStatus: response.status,
19937
+ responseBody,
19938
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
19939
+ };
19940
+ } catch (error) {
19941
+ return {
19942
+ attempt: 1,
19943
+ status: "failed",
19944
+ startedAt,
19945
+ completedAt: now(),
19946
+ error: error instanceof Error ? error.message : String(error)
19947
+ };
19948
+ } finally {
19949
+ clearTimeout(timeout);
19950
+ }
19951
+ }
19952
+ async function dispatchCommand(event, channel) {
19953
+ if (!channel.command)
19954
+ throw new Error(`Channel ${channel.id} has no command config`);
19955
+ const startedAt = now();
19956
+ const eventJson = JSON.stringify(event);
19957
+ const env = {
19958
+ ...process.env,
19959
+ ...channel.command.env,
19960
+ HASNA_CHANNEL_ID: channel.id,
19961
+ HASNA_EVENT_ID: event.id,
19962
+ HASNA_EVENT_TYPE: event.type,
19963
+ HASNA_EVENT_SOURCE: event.source,
19964
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
19965
+ HASNA_EVENT_SEVERITY: event.severity,
19966
+ HASNA_EVENT_TIME: event.time,
19967
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
19968
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
19969
+ HASNA_EVENT_JSON: eventJson
19970
+ };
19971
+ return new Promise((resolve) => {
19972
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
19973
+ cwd: channel.command.cwd,
19974
+ env,
19975
+ stdio: ["pipe", "pipe", "pipe"]
19976
+ });
19977
+ let stdout = "";
19978
+ let stderr = "";
19979
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
19980
+ child.stdin.end(eventJson);
19981
+ child.stdout.on("data", (chunk) => {
19982
+ stdout += chunk.toString();
19983
+ });
19984
+ child.stderr.on("data", (chunk) => {
19985
+ stderr += chunk.toString();
19986
+ });
19987
+ child.on("error", (error) => {
19988
+ clearTimeout(timeout);
19989
+ resolve({
19990
+ attempt: 1,
19991
+ status: "failed",
19992
+ startedAt,
19993
+ completedAt: now(),
19994
+ stdout: truncate(stdout),
19995
+ stderr: truncate(stderr),
19996
+ error: error.message
19997
+ });
19998
+ });
19999
+ child.on("close", (code, signal) => {
20000
+ clearTimeout(timeout);
20001
+ const success = code === 0;
20002
+ resolve({
20003
+ attempt: 1,
20004
+ status: success ? "success" : "failed",
20005
+ startedAt,
20006
+ completedAt: now(),
20007
+ stdout: truncate(stdout),
20008
+ stderr: truncate(stderr),
20009
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
20010
+ });
20011
+ });
20012
+ });
20013
+ }
20014
+ async function dispatchChannel(event, channel, options = {}) {
20015
+ if (channel.transport === "webhook")
20016
+ return dispatchWebhook(event, channel, options);
20017
+ if (channel.transport === "command")
20018
+ return dispatchCommand(event, channel);
20019
+ return {
20020
+ attempt: 1,
20021
+ status: "skipped",
20022
+ startedAt: now(),
20023
+ completedAt: now(),
20024
+ error: `Unsupported transport: ${channel.transport}`
20025
+ };
20026
+ }
20027
+ function createDeliveryResult(event, channel, attempts) {
20028
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
20029
+ return {
20030
+ id: randomUUID(),
20031
+ eventId: event.id,
20032
+ channelId: channel.id,
20033
+ transport: channel.transport,
20034
+ status,
20035
+ attempts,
20036
+ createdAt: attempts[0]?.startedAt ?? now(),
20037
+ completedAt: attempts.at(-1)?.completedAt ?? now()
20038
+ };
20039
+ }
20040
+ function createEvent(input) {
20041
+ return {
20042
+ id: input.id ?? randomUUID2(),
20043
+ source: input.source,
20044
+ type: input.type,
20045
+ time: normalizeTime(input.time),
20046
+ subject: input.subject,
20047
+ severity: input.severity ?? "info",
20048
+ data: input.data ?? {},
20049
+ message: input.message,
20050
+ dedupeKey: input.dedupeKey,
20051
+ schemaVersion: input.schemaVersion ?? "1.0",
20052
+ metadata: input.metadata ?? {}
20053
+ };
20054
+ }
20055
+
20056
+ class EventsClient {
20057
+ store;
20058
+ redactors;
20059
+ transportOptions;
20060
+ constructor(options = {}) {
20061
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
20062
+ this.redactors = options.redactors ?? [];
20063
+ this.transportOptions = { fetchImpl: options.fetchImpl };
20064
+ }
20065
+ async addChannel(input) {
20066
+ const timestamp = new Date().toISOString();
20067
+ return this.store.addChannel({
20068
+ ...input,
20069
+ createdAt: input.createdAt ?? timestamp,
20070
+ updatedAt: input.updatedAt ?? timestamp
20071
+ });
20072
+ }
20073
+ async listChannels() {
20074
+ return this.store.listChannels();
20075
+ }
20076
+ async removeChannel(id) {
20077
+ return this.store.removeChannel(id);
20078
+ }
20079
+ async emit(input, options = {}) {
20080
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
20081
+ if (options.dedupe !== false) {
20082
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
20083
+ if (existing) {
20084
+ return { event: existing, deliveries: [], deduped: true };
20085
+ }
20086
+ }
20087
+ await this.store.appendEvent(event);
20088
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
20089
+ return { event, deliveries, deduped: false };
20090
+ }
20091
+ async listEvents() {
20092
+ return this.store.listEvents();
20093
+ }
20094
+ async listDeliveries() {
20095
+ return this.store.listDeliveries();
20096
+ }
20097
+ async deliver(event) {
20098
+ const channels = await this.store.listChannels();
20099
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
20100
+ const deliveries = [];
20101
+ for (const channel of selected) {
20102
+ const eventForChannel = await this.applyRedaction(event, channel);
20103
+ const result = await this.deliverWithRetry(eventForChannel, channel);
20104
+ await this.store.appendDelivery(result);
20105
+ deliveries.push(result);
20106
+ }
20107
+ return deliveries;
20108
+ }
20109
+ async testChannel(id, input = {}) {
20110
+ const channel = await this.store.getChannel(id);
20111
+ if (!channel)
20112
+ throw new Error(`Channel not found: ${id}`);
20113
+ const event = createEvent({
20114
+ source: input.source ?? "hasna.events",
20115
+ type: input.type ?? "events.test",
20116
+ subject: input.subject ?? id,
20117
+ severity: input.severity ?? "info",
20118
+ data: input.data ?? { test: true },
20119
+ message: input.message ?? "Hasna events test delivery",
20120
+ dedupeKey: input.dedupeKey,
20121
+ schemaVersion: input.schemaVersion,
20122
+ metadata: input.metadata,
20123
+ time: input.time,
20124
+ id: input.id
20125
+ });
20126
+ const eventForChannel = await this.applyRedaction(event, channel);
20127
+ const result = await this.deliverWithRetry(eventForChannel, channel);
20128
+ await this.store.appendDelivery(result);
20129
+ return result;
20130
+ }
20131
+ async replay(options = {}) {
20132
+ const events = (await this.store.listEvents()).filter((event) => {
20133
+ if (options.eventId && event.id !== options.eventId)
20134
+ return false;
20135
+ if (options.source && event.source !== options.source)
20136
+ return false;
20137
+ if (options.type && event.type !== options.type)
20138
+ return false;
20139
+ return true;
20140
+ });
20141
+ if (options.dryRun)
20142
+ return { events, deliveries: [] };
20143
+ const deliveries = [];
20144
+ for (const event of events) {
20145
+ deliveries.push(...await this.deliver(event));
20146
+ }
20147
+ return { events, deliveries };
20148
+ }
20149
+ async applyRedaction(event, channel) {
20150
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
20151
+ for (const redactor of this.redactors) {
20152
+ next = await redactor(next, channel);
20153
+ }
20154
+ return next;
20155
+ }
20156
+ async deliverWithRetry(event, channel) {
20157
+ const policy = normalizeRetryPolicy(channel.retry);
20158
+ const attempts = [];
20159
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
20160
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
20161
+ attempt.attempt = index + 1;
20162
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
20163
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
20164
+ }
20165
+ attempts.push(attempt);
20166
+ if (attempt.status !== "failed")
20167
+ break;
20168
+ if (attempt.nextBackoffMs)
20169
+ await Bun.sleep(attempt.nextBackoffMs);
20170
+ }
20171
+ return createDeliveryResult(event, channel, attempts);
20172
+ }
20173
+ }
20174
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
20175
+ if (paths.length === 0)
20176
+ return event;
20177
+ const copy = structuredClone(event);
20178
+ for (const path of paths) {
20179
+ setPath(copy, path, replacement);
20180
+ }
20181
+ return copy;
20182
+ }
20183
+ function sanitizeChannelForOutput(channel) {
20184
+ const copy = structuredClone(channel);
20185
+ if (copy.webhook?.secret)
20186
+ copy.webhook.secret = "[REDACTED]";
20187
+ if (copy.command?.env) {
20188
+ copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
20189
+ }
20190
+ return copy;
20191
+ }
20192
+ function sanitizeChannelsForOutput(channels) {
20193
+ return channels.map(sanitizeChannelForOutput);
20194
+ }
20195
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
20196
+ return redactValue(event, replacement);
20197
+ }
20198
+ function shouldRedactKey(key) {
20199
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
20200
+ }
20201
+ function redactValue(value, replacement) {
20202
+ if (Array.isArray(value))
20203
+ return value.map((item) => redactValue(item, replacement));
20204
+ if (!value || typeof value !== "object")
20205
+ return value;
20206
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
20207
+ key,
20208
+ shouldRedactKey(key) ? replacement : redactValue(item, replacement)
20209
+ ]));
20210
+ }
20211
+ function setPath(input, path, replacement) {
20212
+ const parts = path.split(".");
20213
+ let cursor = input;
20214
+ for (const part of parts.slice(0, -1)) {
20215
+ const next = cursor[part];
20216
+ if (!next || typeof next !== "object")
20217
+ return;
20218
+ cursor = next;
20219
+ }
20220
+ const last = parts.at(-1);
20221
+ if (last && last in cursor)
20222
+ cursor[last] = replacement;
20223
+ }
20224
+ function normalizeTime(value) {
20225
+ if (!value)
20226
+ return new Date().toISOString();
20227
+ return value instanceof Date ? value.toISOString() : value;
20228
+ }
20229
+ function normalizeRetryPolicy(policy) {
20230
+ return {
20231
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
20232
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
20233
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
20234
+ };
20235
+ }
20236
+ function parseJsonObject(value, fallback) {
20237
+ if (!value)
20238
+ return fallback;
20239
+ const parsed = JSON.parse(value);
20240
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
20241
+ throw new Error("Expected a JSON object");
20242
+ }
20243
+ return parsed;
20244
+ }
20245
+ function parseHeaders(values) {
20246
+ if (!values?.length)
20247
+ return;
20248
+ const headers = {};
20249
+ for (const value of values) {
20250
+ const separator = value.indexOf("=");
20251
+ if (separator === -1)
20252
+ throw new Error(`Invalid header, expected name=value: ${value}`);
20253
+ headers[value.slice(0, separator)] = value.slice(separator + 1);
20254
+ }
20255
+ return headers;
20256
+ }
20257
+ function parseFilter(options) {
20258
+ const filter2 = {};
20259
+ if (options.source)
20260
+ filter2.source = options.source;
20261
+ if (options.type)
20262
+ filter2.type = options.type;
20263
+ if (options.subject)
20264
+ filter2.subject = options.subject;
20265
+ if (options.severity)
20266
+ filter2.severity = options.severity;
20267
+ return Object.keys(filter2).length > 0 ? [filter2] : undefined;
20268
+ }
20269
+ function createClient(options) {
20270
+ if (options.createClient)
20271
+ return options.createClient();
20272
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
20273
+ }
20274
+ function print(value, json, text) {
20275
+ if (json)
20276
+ console.log(JSON.stringify(value, null, 2));
20277
+ else
20278
+ console.log(text);
20279
+ }
20280
+ function hasJsonOption(options) {
20281
+ return Boolean(options?.json || options?.opts?.().json || options?.optsWithGlobals?.().json || options?.parent?.opts?.().json || options?.parent?.optsWithGlobals?.().json);
20282
+ }
20283
+ function wantsJson(actionOptions, command) {
20284
+ return hasJsonOption(actionOptions) || hasJsonOption(command);
20285
+ }
20286
+ function registerWebhookCommands(program, options) {
20287
+ const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
20288
+ webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions, command) => {
20289
+ const timestamp = new Date().toISOString();
20290
+ const channel = {
20291
+ id: actionOptions.id,
20292
+ name: actionOptions.name,
20293
+ enabled: !actionOptions.disabled,
20294
+ transport: actionOptions.transport,
20295
+ filters: parseFilter(actionOptions),
20296
+ retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
20297
+ redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
20298
+ createdAt: timestamp,
20299
+ updatedAt: timestamp
20300
+ };
20301
+ if (actionOptions.transport === "webhook") {
20302
+ channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
20303
+ } else if (actionOptions.transport === "command") {
20304
+ channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
20305
+ } else {
20306
+ throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
20307
+ }
20308
+ const saved = await createClient(options).addChannel(channel);
20309
+ print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
20310
+ });
20311
+ webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
20312
+ const channels = await createClient(options).listChannels();
20313
+ if (wantsJson(actionOptions, command)) {
20314
+ console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
20315
+ return;
20316
+ }
20317
+ if (!channels.length) {
20318
+ console.log("No channels configured.");
20319
+ return;
20320
+ }
20321
+ for (const channel of channels) {
20322
+ console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
20323
+ }
20324
+ });
20325
+ webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
20326
+ const removed = await createClient(options).removeChannel(id);
20327
+ print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
20328
+ });
20329
+ webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
20330
+ const result = await createClient(options).testChannel(id, {
20331
+ source: options.source,
20332
+ type: actionOptions.type,
20333
+ subject: actionOptions.subject ?? id,
20334
+ message: actionOptions.message,
20335
+ data: parseJsonObject(actionOptions.data, { test: true })
20336
+ });
20337
+ print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
20338
+ });
20339
+ return webhooks;
20340
+ }
20341
+ function registerEventCommands(program, options) {
20342
+ const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
20343
+ events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Print JSON output", false).action(async (type, actionOptions, command) => {
20344
+ const result = await createClient(options).emit({
20345
+ source: actionOptions.source ?? options.source,
20346
+ type,
20347
+ subject: actionOptions.subject,
20348
+ severity: actionOptions.severity,
20349
+ message: actionOptions.message,
20350
+ dedupeKey: actionOptions.dedupeKey,
20351
+ data: parseJsonObject(actionOptions.data, {}),
20352
+ metadata: parseJsonObject(actionOptions.metadata, {})
20353
+ }, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
20354
+ print(result, wantsJson(actionOptions, command), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
20355
+ });
20356
+ events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
20357
+ let rows = await createClient(options).listEvents();
20358
+ if (actionOptions.source)
20359
+ rows = rows.filter((event) => event.source === actionOptions.source);
20360
+ if (actionOptions.type)
20361
+ rows = rows.filter((event) => event.type === actionOptions.type);
20362
+ if (actionOptions.limit)
20363
+ rows = rows.slice(-actionOptions.limit);
20364
+ if (wantsJson(actionOptions, command)) {
20365
+ console.log(JSON.stringify(rows, null, 2));
20366
+ return;
20367
+ }
20368
+ if (!rows.length) {
20369
+ console.log("No events recorded.");
20370
+ return;
20371
+ }
20372
+ for (const event of rows)
20373
+ console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
20374
+ });
20375
+ events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
20376
+ const result = await createClient(options).replay({
20377
+ eventId: actionOptions.id,
20378
+ source: actionOptions.source,
20379
+ type: actionOptions.type,
20380
+ dryRun: actionOptions.dryRun
20381
+ });
20382
+ print(result, wantsJson(actionOptions, command), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
20383
+ });
20384
+ return events;
20385
+ }
20386
+ function registerEventsCommands(program, options) {
20387
+ registerWebhookCommands(program, options);
20388
+ registerEventCommands(program, options);
20389
+ }
20390
+ function parseNumber(value) {
20391
+ const parsed = Number(value);
20392
+ if (!Number.isFinite(parsed))
20393
+ throw new Error(`Expected a number, got ${value}`);
20394
+ return parsed;
20395
+ }
20396
+ function collectValues(value, previous) {
20397
+ previous.push(value);
20398
+ return previous;
20399
+ }
20400
+
19735
20401
  // node_modules/commander/esm.mjs
19736
20402
  var import__ = __toESM(require_commander(), 1);
19737
20403
  var {
@@ -19752,7 +20418,7 @@ var {
19752
20418
  init_loader();
19753
20419
 
19754
20420
  // src/core/runner.ts
19755
- import { randomUUID } from "crypto";
20421
+ import { randomUUID as randomUUID3 } from "crypto";
19756
20422
 
19757
20423
  // src/core/assertions.ts
19758
20424
  var import_ajv = __toESM(require_ajv(), 1);
@@ -24356,7 +25022,7 @@ var safeJSON2 = (text) => {
24356
25022
  var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
24357
25023
 
24358
25024
  // node_modules/openai/version.mjs
24359
- var VERSION2 = "6.39.0";
25025
+ var VERSION2 = "6.42.0";
24360
25026
 
24361
25027
  // node_modules/openai/internal/detect-platform.mjs
24362
25028
  var isRunningInBrowser2 = () => {
@@ -30942,7 +31608,10 @@ https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety
30942
31608
  if (isTimeout) {
30943
31609
  throw new APIConnectionTimeoutError2;
30944
31610
  }
30945
- throw new APIConnectionError2({ cause: response });
31611
+ throw new APIConnectionError2({
31612
+ message: getConnectionErrorMessage(response),
31613
+ cause: response
31614
+ });
30946
31615
  }
30947
31616
  const specialHeaders = [...response.headers.entries()].filter(([name]) => name === "x-request-id").map(([name, value]) => ", " + name + ": " + JSON.stringify(value)).join("");
30948
31617
  const responseInfo = `[${requestLogID}${retryLogStr}${specialHeaders}] ${req.method} ${url} ${response.ok ? "succeeded" : "failed"} with status ${response.status} in ${headersTime - startTime}ms`;
@@ -31215,6 +31884,23 @@ OpenAI.Evals = Evals;
31215
31884
  OpenAI.Containers = Containers;
31216
31885
  OpenAI.Skills = Skills2;
31217
31886
  OpenAI.Videos = Videos;
31887
+ function getConnectionErrorMessage(error3) {
31888
+ if (isUndiciDispatcherVersionMismatchError(error3)) {
31889
+ return `Connection error. This may be caused by passing an undici dispatcher, such as ProxyAgent, that is incompatible with the fetch implementation. If you are using undici's ProxyAgent, pass the fetch implementation from the same undici package: import { fetch, ProxyAgent } from 'undici'; new OpenAI({ fetch, fetchOptions: { dispatcher: new ProxyAgent(...) } });`;
31890
+ }
31891
+ return;
31892
+ }
31893
+ function isUndiciDispatcherVersionMismatchError(error3) {
31894
+ let current = error3;
31895
+ for (let i = 0;i < 8 && current && typeof current === "object"; i++) {
31896
+ const err = current;
31897
+ if (err.code === "UND_ERR_INVALID_ARG" && typeof err.message === "string" && err.message.includes("invalid onRequestStart method")) {
31898
+ return true;
31899
+ }
31900
+ current = err.cause;
31901
+ }
31902
+ return false;
31903
+ }
31218
31904
  // node_modules/openai/azure.mjs
31219
31905
  var _deployments_endpoints = new Set([
31220
31906
  "/completions",
@@ -31228,14 +31914,14 @@ var _deployments_endpoints = new Set([
31228
31914
  "/images/edits"
31229
31915
  ]);
31230
31916
  // src/core/judge.ts
31231
- import { existsSync, readFileSync } from "fs";
31232
- import { homedir } from "os";
31233
- import { join } from "path";
31917
+ import { existsSync as existsSync2, readFileSync } from "fs";
31918
+ import { homedir as homedir2 } from "os";
31919
+ import { join as join2 } from "path";
31234
31920
  function resolveKey(envVar, secretsRelPath, secretsKey) {
31235
31921
  if (process.env[envVar])
31236
31922
  return process.env[envVar];
31237
- const p = join(homedir(), ".secrets", secretsRelPath);
31238
- if (existsSync(p)) {
31923
+ const p = join2(homedir2(), ".secrets", secretsRelPath);
31924
+ if (existsSync2(p)) {
31239
31925
  for (const line of readFileSync(p, "utf8").split(`
31240
31926
  `)) {
31241
31927
  if (line.trim().startsWith(secretsKey + "=")) {
@@ -31754,7 +32440,7 @@ async function runEvals(cases, options) {
31754
32440
  }
31755
32441
  const stats = computeStats(results);
31756
32442
  return {
31757
- id: randomUUID(),
32443
+ id: randomUUID3(),
31758
32444
  createdAt: new Date().toISOString(),
31759
32445
  dataset: options.dataset,
31760
32446
  adapterConfig: options.adapter,
@@ -32304,14 +32990,14 @@ function kappaLabel(k) {
32304
32990
  }
32305
32991
 
32306
32992
  // src/cli/commands/doctor.ts
32307
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
32308
- import { homedir as homedir3 } from "os";
32309
- import { join as join3 } from "path";
32993
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
32994
+ import { homedir as homedir4 } from "os";
32995
+ import { join as join4 } from "path";
32310
32996
  function resolveApiKey(envVar, secretsPath, secretsKey) {
32311
32997
  if (process.env[envVar])
32312
32998
  return process.env[envVar];
32313
- const fullPath = join3(homedir3(), ".secrets", secretsPath);
32314
- if (existsSync2(fullPath)) {
32999
+ const fullPath = join4(homedir4(), ".secrets", secretsPath);
33000
+ if (existsSync3(fullPath)) {
32315
33001
  try {
32316
33002
  const content = readFileSync2(fullPath, "utf8");
32317
33003
  for (const line of content.split(`
@@ -32356,17 +33042,17 @@ function doctorCommand() {
32356
33042
  }
32357
33043
  try {
32358
33044
  const { loadDataset: loadDataset2 } = await Promise.resolve().then(() => (init_loader(), exports_loader));
32359
- const { existsSync: existsSync3 } = await import("fs");
32360
- const { join: join4 } = await import("path");
32361
- const { homedir: homedir4 } = await import("os");
33045
+ const { existsSync: existsSync4 } = await import("fs");
33046
+ const { join: join5 } = await import("path");
33047
+ const { homedir: homedir5 } = await import("os");
32362
33048
  const candidates = [
32363
33049
  new URL("../../../datasets/examples/smoke.jsonl", import.meta.url).pathname,
32364
- join4(import.meta.dir, "../../../datasets/examples/smoke.jsonl"),
32365
- join4(import.meta.dir, "../../datasets/examples/smoke.jsonl"),
32366
- join4(import.meta.dir, "../datasets/examples/smoke.jsonl"),
32367
- join4(homedir4(), ".hasna", "evals", "examples", "smoke.jsonl")
33050
+ join5(import.meta.dir, "../../../datasets/examples/smoke.jsonl"),
33051
+ join5(import.meta.dir, "../../datasets/examples/smoke.jsonl"),
33052
+ join5(import.meta.dir, "../datasets/examples/smoke.jsonl"),
33053
+ join5(homedir5(), ".hasna", "evals", "examples", "smoke.jsonl")
32368
33054
  ];
32369
- const found = candidates.find((p) => existsSync3(p));
33055
+ const found = candidates.find((p) => existsSync4(p));
32370
33056
  if (!found)
32371
33057
  throw new Error("not found");
32372
33058
  const { cases } = await loadDataset2(found);
@@ -32401,9 +33087,9 @@ function doctorCommand() {
32401
33087
  }
32402
33088
 
32403
33089
  // src/cli/commands/mcp.ts
32404
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
32405
- import { homedir as homedir4 } from "os";
32406
- import { join as join4 } from "path";
33090
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
33091
+ import { homedir as homedir5 } from "os";
33092
+ import { join as join5 } from "path";
32407
33093
  function mcpCommand() {
32408
33094
  const cmd = new Command("mcp").description("MCP server management");
32409
33095
  cmd.addCommand(new Command("register").description("Register evals-mcp with an agent (Claude Code, Codex, Gemini)").option("--claude", "Register with Claude Code (~/.claude/mcp.json)").option("--codex", "Register with Codex (~/.codex/config.json)").option("--gemini", "Register with Gemini (~/.gemini/settings.json)").option("--all", "Register with all agents").action((opts) => {
@@ -32419,15 +33105,15 @@ function mcpCommand() {
32419
33105
  }));
32420
33106
  cmd.addCommand(new Command("start").description("Start MCP server (stdio)").action(() => {
32421
33107
  const { spawnSync } = __require("child_process");
32422
- spawnSync(process.execPath, [join4(import.meta.dir, "../../mcp/index.js")], { stdio: "inherit" });
33108
+ spawnSync(process.execPath, [join5(import.meta.dir, "../../mcp/index.js")], { stdio: "inherit" });
32423
33109
  }));
32424
33110
  return cmd;
32425
33111
  }
32426
33112
  var ENTRY = { command: "/home/hasna/.bun/bin/evals-mcp", args: [] };
32427
33113
  function registerClaude() {
32428
- const mcpPath = join4(homedir4(), ".claude", "mcp.json");
33114
+ const mcpPath = join5(homedir5(), ".claude", "mcp.json");
32429
33115
  let config = {};
32430
- if (existsSync3(mcpPath)) {
33116
+ if (existsSync4(mcpPath)) {
32431
33117
  config = JSON.parse(readFileSync3(mcpPath, "utf8"));
32432
33118
  }
32433
33119
  config.mcpServers = { ...config.mcpServers ?? {}, evals: ENTRY };
@@ -32437,9 +33123,9 @@ function registerClaude() {
32437
33123
  console.log(" Restart Claude Code to load the new MCP server.");
32438
33124
  }
32439
33125
  function registerCodex() {
32440
- const cfgPath = join4(homedir4(), ".codex", "config.json");
33126
+ const cfgPath = join5(homedir5(), ".codex", "config.json");
32441
33127
  let config = {};
32442
- if (existsSync3(cfgPath)) {
33128
+ if (existsSync4(cfgPath)) {
32443
33129
  config = JSON.parse(readFileSync3(cfgPath, "utf8"));
32444
33130
  }
32445
33131
  config.mcpServers = { ...config.mcpServers ?? {}, evals: { type: "stdio", ...ENTRY, env: {} } };
@@ -32448,9 +33134,9 @@ function registerCodex() {
32448
33134
  console.log("\x1B[32m\u2713 Registered evals-mcp in ~/.codex/config.json\x1B[0m");
32449
33135
  }
32450
33136
  function registerGemini() {
32451
- const cfgPath = join4(homedir4(), ".gemini", "settings.json");
33137
+ const cfgPath = join5(homedir5(), ".gemini", "settings.json");
32452
33138
  let config = {};
32453
- if (existsSync3(cfgPath)) {
33139
+ if (existsSync4(cfgPath)) {
32454
33140
  config = JSON.parse(readFileSync3(cfgPath, "utf8"));
32455
33141
  }
32456
33142
  config.mcpServers = { ...config.mcpServers ?? {}, evals: ENTRY };
@@ -32703,4 +33389,5 @@ program2.addCommand(mcpCommand());
32703
33389
  program2.addCommand(captureCommand());
32704
33390
  program2.addCommand(completionCommand());
32705
33391
  program2.addCommand(syncCommand());
33392
+ registerEventsCommands(program2, { source: "evals" });
32706
33393
  program2.parse(process.argv);