@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.
- package/dist/adapters/adapters.test.d.ts +2 -0
- package/dist/adapters/adapters.test.d.ts.map +1 -0
- package/dist/adapters/anthropic-openai.test.d.ts +2 -0
- package/dist/adapters/anthropic-openai.test.d.ts.map +1 -0
- package/dist/adapters/anthropic.d.ts +4 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/cli.d.ts +4 -0
- package/dist/adapters/cli.d.ts.map +1 -0
- package/dist/adapters/function.d.ts +4 -0
- package/dist/adapters/function.d.ts.map +1 -0
- package/dist/adapters/http-cli-coverage.test.d.ts +2 -0
- package/dist/adapters/http-cli-coverage.test.d.ts.map +1 -0
- package/dist/adapters/http.d.ts +16 -0
- package/dist/adapters/http.d.ts.map +1 -0
- package/dist/adapters/mcp-adapter.test.d.ts +2 -0
- package/dist/adapters/mcp-adapter.test.d.ts.map +1 -0
- package/dist/adapters/mcp.d.ts +4 -0
- package/dist/adapters/mcp.d.ts.map +1 -0
- package/dist/adapters/openai.d.ts +4 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/cli/adapter-parser.d.ts +3 -0
- package/dist/cli/adapter-parser.d.ts.map +1 -0
- package/dist/cli/cli.test.d.ts +2 -0
- package/dist/cli/cli.test.d.ts.map +1 -0
- package/dist/cli/commands/calibrate.d.ts +3 -0
- package/dist/cli/commands/calibrate.d.ts.map +1 -0
- package/dist/cli/commands/capture.d.ts +3 -0
- package/dist/cli/commands/capture.d.ts.map +1 -0
- package/dist/cli/commands/ci.d.ts +3 -0
- package/dist/cli/commands/ci.d.ts.map +1 -0
- package/dist/cli/commands/compare.d.ts +5 -0
- package/dist/cli/commands/compare.d.ts.map +1 -0
- package/dist/cli/commands/compare.test.d.ts +2 -0
- package/dist/cli/commands/compare.test.d.ts.map +1 -0
- package/dist/cli/commands/completion.d.ts +3 -0
- package/dist/cli/commands/completion.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/estimate.d.ts +3 -0
- package/dist/cli/commands/estimate.d.ts.map +1 -0
- package/dist/cli/commands/generate.d.ts +3 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/judge.d.ts +3 -0
- package/dist/cli/commands/judge.d.ts.map +1 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/run.d.ts +3 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +857 -170
- package/dist/core/assertions-coverage.test.d.ts +2 -0
- package/dist/core/assertions-coverage.test.d.ts.map +1 -0
- package/dist/core/assertions.d.ts +18 -0
- package/dist/core/assertions.d.ts.map +1 -0
- package/dist/core/assertions.test.d.ts +2 -0
- package/dist/core/assertions.test.d.ts.map +1 -0
- package/dist/core/e2e.test.d.ts +2 -0
- package/dist/core/e2e.test.d.ts.map +1 -0
- package/dist/core/judge.d.ts +13 -0
- package/dist/core/judge.d.ts.map +1 -0
- package/dist/core/judge.test.d.ts +2 -0
- package/dist/core/judge.test.d.ts.map +1 -0
- package/dist/core/reporter.d.ts +21 -0
- package/dist/core/reporter.d.ts.map +1 -0
- package/dist/core/reporter.test.d.ts +2 -0
- package/dist/core/reporter.test.d.ts.map +1 -0
- package/dist/core/runner.d.ts +4 -0
- package/dist/core/runner.d.ts.map +1 -0
- package/dist/core/runner.test.d.ts +2 -0
- package/dist/core/runner.test.d.ts.map +1 -0
- package/dist/datasets/loader.d.ts +18 -0
- package/dist/datasets/loader.d.ts.map +1 -0
- package/dist/datasets/loader.test.d.ts +2 -0
- package/dist/datasets/loader.test.d.ts.map +1 -0
- package/dist/db/store.d.ts +17 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.test.d.ts +2 -0
- package/dist/db/store.test.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -4
- package/dist/mcp/http.d.ts +13 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.test.d.ts +2 -0
- package/dist/mcp/http.test.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +32454 -477
- package/dist/mcp/mcp.test.d.ts +2 -0
- package/dist/mcp/mcp.test.d.ts.map +1 -0
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +24 -4
- package/dist/server/server.test.d.ts +2 -0
- package/dist/server/server.test.d.ts.map +1 -0
- package/dist/types/index.d.ts +171 -0
- package/dist/types/index.d.ts.map +1 -0
- 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
|
-
}
|
|
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
|
|
8563
|
-
import { join as
|
|
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"] ??
|
|
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(
|
|
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: () =>
|
|
8704
|
-
discoverSyncableServices: () =>
|
|
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
|
|
8720
|
+
existsSync as existsSync5,
|
|
8721
8721
|
mkdirSync as mkdirSync2,
|
|
8722
8722
|
readdirSync,
|
|
8723
8723
|
copyFileSync
|
|
8724
8724
|
} from "fs";
|
|
8725
|
-
import { homedir as
|
|
8726
|
-
import { join as
|
|
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
|
|
8731
|
-
import { join as
|
|
8732
|
-
import { homedir as
|
|
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
|
|
8735
|
-
import { homedir as
|
|
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 {
|
|
8738
|
-
import {
|
|
8739
|
-
import {
|
|
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 =
|
|
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
|
|
9629
|
+
return join6(dir, `${serviceName}.db`);
|
|
9630
9630
|
}
|
|
9631
9631
|
function migrateDotfile(serviceName) {
|
|
9632
|
-
const legacyDir =
|
|
9633
|
-
const newDir =
|
|
9634
|
-
if (!
|
|
9632
|
+
const legacyDir = join6(homedir6(), `.${serviceName}`);
|
|
9633
|
+
const newDir = join6(HASNA_DIR, serviceName);
|
|
9634
|
+
if (!existsSync5(legacyDir))
|
|
9635
9635
|
return [];
|
|
9636
|
-
if (
|
|
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 =
|
|
9647
|
-
const destPath =
|
|
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
|
|
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 =
|
|
9714
|
-
if (!
|
|
9713
|
+
const dataDir = join62(homedir52(), ".hasna");
|
|
9714
|
+
if (!existsSync6(dataDir))
|
|
9715
9715
|
return [];
|
|
9716
9716
|
try {
|
|
9717
|
-
const entries =
|
|
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
|
|
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 =
|
|
9736
|
-
if (!
|
|
9735
|
+
const dataDir = join62(homedir52(), ".hasna", service);
|
|
9736
|
+
if (!existsSync6(dataDir))
|
|
9737
9737
|
return null;
|
|
9738
9738
|
const candidates = [
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9739
|
+
join62(dataDir, `${service}.db`),
|
|
9740
|
+
join62(dataDir, "data.db"),
|
|
9741
|
+
join62(dataDir, "database.db")
|
|
9742
9742
|
];
|
|
9743
9743
|
try {
|
|
9744
|
-
const files =
|
|
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(
|
|
9747
|
+
candidates.push(join62(dataDir, f));
|
|
9748
9748
|
}
|
|
9749
9749
|
}
|
|
9750
9750
|
} catch {}
|
|
9751
9751
|
for (const p of candidates) {
|
|
9752
|
-
if (
|
|
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
|
|
10059
|
+
await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
|
|
10060
10060
|
} else {
|
|
10061
|
-
batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch
|
|
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
|
|
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}
|
|
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
|
|
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}
|
|
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
|
|
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 ??
|
|
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
|
|
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 ??
|
|
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
|
|
10267
|
-
this.startTimes.set(table,
|
|
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
|
|
10587
|
+
const now2 = new Date().toISOString();
|
|
10590
10588
|
upsertSyncMeta(local, {
|
|
10591
10589
|
table_name: table,
|
|
10592
|
-
last_synced_at:
|
|
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
|
|
10640
|
+
const now2 = new Date().toISOString();
|
|
10643
10641
|
upsertSyncMeta(local, {
|
|
10644
10642
|
table_name: table,
|
|
10645
|
-
last_synced_at:
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
10698
|
-
|
|
10699
|
-
|
|
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
|
-
|
|
10709
|
-
result.
|
|
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 =
|
|
10712
|
+
const handleExit = () => {
|
|
10728
10713
|
for (const fn of cleanupHandlers) {
|
|
10729
10714
|
try {
|
|
10730
|
-
|
|
10715
|
+
fn();
|
|
10731
10716
|
} catch {}
|
|
10732
10717
|
}
|
|
10733
10718
|
};
|
|
10734
|
-
process.on("SIGTERM",
|
|
10735
|
-
|
|
10719
|
+
process.on("SIGTERM", () => {
|
|
10720
|
+
handleExit();
|
|
10736
10721
|
process.exit(0);
|
|
10737
10722
|
});
|
|
10738
|
-
process.on("SIGINT",
|
|
10739
|
-
|
|
10723
|
+
process.on("SIGINT", () => {
|
|
10724
|
+
handleExit();
|
|
10740
10725
|
process.exit(0);
|
|
10741
10726
|
});
|
|
10742
|
-
process.on("beforeExit",
|
|
10743
|
-
|
|
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 =
|
|
10735
|
+
const syncOnStart = () => {
|
|
10751
10736
|
if (!config.auto_sync_on_start || !isSyncEnabled)
|
|
10752
10737
|
return null;
|
|
10753
|
-
return executeAutoSync("start",
|
|
10738
|
+
return executeAutoSync("start", local, remote, tables);
|
|
10754
10739
|
};
|
|
10755
|
-
const syncOnStop =
|
|
10740
|
+
const syncOnStop = () => {
|
|
10756
10741
|
if (!config.auto_sync_on_stop || !isSyncEnabled)
|
|
10757
10742
|
return null;
|
|
10758
|
-
return executeAutoSync("stop",
|
|
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 =
|
|
10763
|
-
|
|
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", () =>
|
|
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 =
|
|
10772
|
-
|
|
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", () =>
|
|
10763
|
+
server.on("disconnect", () => {
|
|
10764
|
+
syncOnStop();
|
|
10765
|
+
});
|
|
10777
10766
|
}
|
|
10778
10767
|
installSignalHandlers();
|
|
10779
|
-
cleanupHandlers.push(
|
|
10780
|
-
|
|
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
|
|
10776
|
+
function discoverSyncableServices() {
|
|
10788
10777
|
const hasnaDir = getHasnaDir();
|
|
10789
10778
|
const services = [];
|
|
10790
10779
|
try {
|
|
10791
|
-
const entries =
|
|
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 =
|
|
10796
|
-
if (
|
|
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 =
|
|
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 =
|
|
10819
|
-
if (!
|
|
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 =
|
|
10901
|
-
const jsPath =
|
|
10889
|
+
const tsPath = join52(dir, "scheduled-sync.ts");
|
|
10890
|
+
const jsPath = join52(dir, "scheduled-sync.js");
|
|
10902
10891
|
try {
|
|
10903
|
-
if (
|
|
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
|
-
|
|
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 (
|
|
10904
|
+
if (existsSync52(p))
|
|
10916
10905
|
return p;
|
|
10917
10906
|
}
|
|
10918
10907
|
return "bun";
|
|
10919
10908
|
}
|
|
10920
10909
|
function getLaunchdPlistPath() {
|
|
10921
|
-
return
|
|
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 =
|
|
10927
|
-
const errorLogPath =
|
|
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>${
|
|
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
|
|
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=${
|
|
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(
|
|
11014
|
-
writeFileSync22(
|
|
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(
|
|
11013
|
+
unlinkSync(join52(dir, `${SERVICE_NAME}.service`));
|
|
11025
11014
|
} catch {}
|
|
11026
11015
|
try {
|
|
11027
|
-
unlinkSync(
|
|
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 =
|
|
11053
|
+
mechanism = existsSync52(getLaunchdPlistPath()) ? "launchd" : "none";
|
|
11065
11054
|
} else {
|
|
11066
|
-
mechanism =
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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: () =>
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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({
|
|
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 =
|
|
31238
|
-
if (
|
|
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:
|
|
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
|
|
32308
|
-
import { homedir as
|
|
32309
|
-
import { join as
|
|
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 =
|
|
32314
|
-
if (
|
|
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:
|
|
32360
|
-
const { join:
|
|
32361
|
-
const { homedir:
|
|
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
|
-
|
|
32365
|
-
|
|
32366
|
-
|
|
32367
|
-
|
|
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) =>
|
|
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
|
|
32405
|
-
import { homedir as
|
|
32406
|
-
import { join as
|
|
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, [
|
|
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 =
|
|
33114
|
+
const mcpPath = join5(homedir5(), ".claude", "mcp.json");
|
|
32429
33115
|
let config = {};
|
|
32430
|
-
if (
|
|
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 =
|
|
33126
|
+
const cfgPath = join5(homedir5(), ".codex", "config.json");
|
|
32441
33127
|
let config = {};
|
|
32442
|
-
if (
|
|
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 =
|
|
33137
|
+
const cfgPath = join5(homedir5(), ".gemini", "settings.json");
|
|
32452
33138
|
let config = {};
|
|
32453
|
-
if (
|
|
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);
|