@hasna/brains 0.0.26 → 0.0.28

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/cli/index.js CHANGED
@@ -993,7 +993,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
993
993
  this._exitCallback = (err) => {
994
994
  if (err.code !== "commander.executeSubCommandAsync") {
995
995
  throw err;
996
- } else {}
996
+ }
997
997
  };
998
998
  }
999
999
  return this;
@@ -2137,8 +2137,8 @@ __export(exports_dist, {
2137
2137
  ensureConflictsTable: () => ensureConflictsTable,
2138
2138
  ensureAllPgDatabases: () => ensureAllPgDatabases,
2139
2139
  enableAutoSync: () => enableAutoSync,
2140
- discoverSyncableServicesV2: () => discoverSyncableServices,
2141
- discoverSyncableServices: () => discoverSyncableServices2,
2140
+ discoverSyncableServicesV2: () => discoverSyncableServices2,
2141
+ discoverSyncableServices: () => discoverSyncableServices,
2142
2142
  discoverServices: () => discoverServices,
2143
2143
  detectConflicts: () => detectConflicts,
2144
2144
  createDatabase: () => createDatabase,
@@ -2154,28 +2154,28 @@ __export(exports_dist, {
2154
2154
  import { createRequire } from "module";
2155
2155
  import { Database as Database2 } from "bun:sqlite";
2156
2156
  import {
2157
- existsSync,
2157
+ existsSync as existsSync2,
2158
2158
  mkdirSync,
2159
2159
  readdirSync,
2160
2160
  copyFileSync
2161
2161
  } from "fs";
2162
- import { homedir } from "os";
2163
- import { join, relative } from "path";
2164
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
2165
2162
  import { homedir as homedir2 } from "os";
2166
- import { join as join2 } from "path";
2167
- import { readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
2168
- import { join as join3 } from "path";
2169
- import { homedir as homedir3 } from "os";
2163
+ import { join as join2, relative } from "path";
2164
+ import { existsSync as existsSync22, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
2165
+ import { homedir as homedir22 } from "os";
2166
+ import { join as join22 } from "path";
2167
+ import { readdirSync as readdirSync3, existsSync as existsSync6 } from "fs";
2168
+ import { join as join6 } from "path";
2169
+ import { homedir as homedir5 } from "os";
2170
2170
  import { hostname } from "os";
2171
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
2172
- import { homedir as homedir4 } from "os";
2171
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2172
+ import { homedir as homedir3 } from "os";
2173
+ import { join as join3 } from "path";
2174
+ import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
2173
2175
  import { join as join4 } from "path";
2174
- import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
2175
- import { join as join5 } from "path";
2176
- import { join as join6, dirname } from "path";
2177
- import { existsSync as existsSync6, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
2178
- import { homedir as homedir5, platform } from "os";
2176
+ import { join as join5, dirname } from "path";
2177
+ import { existsSync as existsSync5, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
2178
+ import { homedir as homedir4, platform } from "os";
2179
2179
  function __accessProp2(key) {
2180
2180
  return this[key];
2181
2181
  }
@@ -3057,20 +3057,20 @@ function custom(check, _params = {}, fatal) {
3057
3057
  return ZodAny.create();
3058
3058
  }
3059
3059
  function getDataDir(serviceName) {
3060
- const dir = join(HASNA_DIR, serviceName);
3060
+ const dir = join2(HASNA_DIR, serviceName);
3061
3061
  mkdirSync(dir, { recursive: true });
3062
3062
  return dir;
3063
3063
  }
3064
3064
  function getDbPath(serviceName) {
3065
3065
  const dir = getDataDir(serviceName);
3066
- return join(dir, `${serviceName}.db`);
3066
+ return join2(dir, `${serviceName}.db`);
3067
3067
  }
3068
3068
  function migrateDotfile(serviceName) {
3069
- const legacyDir = join(homedir(), `.${serviceName}`);
3070
- const newDir = join(HASNA_DIR, serviceName);
3071
- if (!existsSync(legacyDir))
3069
+ const legacyDir = join2(homedir2(), `.${serviceName}`);
3070
+ const newDir = join2(HASNA_DIR, serviceName);
3071
+ if (!existsSync2(legacyDir))
3072
3072
  return [];
3073
- if (existsSync(newDir))
3073
+ if (existsSync2(newDir))
3074
3074
  return [];
3075
3075
  mkdirSync(newDir, { recursive: true });
3076
3076
  const migrated = [];
@@ -3080,8 +3080,8 @@ function migrateDotfile(serviceName) {
3080
3080
  function copyDirRecursive(src, dest, root, migrated) {
3081
3081
  const entries = readdirSync(src, { withFileTypes: true });
3082
3082
  for (const entry of entries) {
3083
- const srcPath = join(src, entry.name);
3084
- const destPath = join(dest, entry.name);
3083
+ const srcPath = join2(src, entry.name);
3084
+ const destPath = join2(dest, entry.name);
3085
3085
  if (entry.isDirectory()) {
3086
3086
  mkdirSync(destPath, { recursive: true });
3087
3087
  copyDirRecursive(srcPath, destPath, root, migrated);
@@ -3092,7 +3092,7 @@ function copyDirRecursive(src, dest, root, migrated) {
3092
3092
  }
3093
3093
  }
3094
3094
  function hasLegacyDotfile(serviceName) {
3095
- return existsSync(join(homedir(), `.${serviceName}`));
3095
+ return existsSync2(join2(homedir2(), `.${serviceName}`));
3096
3096
  }
3097
3097
  function getHasnaDir() {
3098
3098
  mkdirSync(HASNA_DIR, { recursive: true });
@@ -3105,7 +3105,7 @@ function getConfigPath() {
3105
3105
  return CONFIG_PATH;
3106
3106
  }
3107
3107
  function getCloudConfig() {
3108
- if (!existsSync2(CONFIG_PATH)) {
3108
+ if (!existsSync22(CONFIG_PATH)) {
3109
3109
  return CloudConfigSchema.parse({});
3110
3110
  }
3111
3111
  try {
@@ -3147,11 +3147,11 @@ function isSyncExcludedTable(table) {
3147
3147
  return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
3148
3148
  }
3149
3149
  function discoverServices() {
3150
- const dataDir = join3(homedir3(), ".hasna");
3151
- if (!existsSync3(dataDir))
3150
+ const dataDir = join6(homedir5(), ".hasna");
3151
+ if (!existsSync6(dataDir))
3152
3152
  return [];
3153
3153
  try {
3154
- const entries = readdirSync2(dataDir, { withFileTypes: true });
3154
+ const entries = readdirSync3(dataDir, { withFileTypes: true });
3155
3155
  return entries.filter((e) => {
3156
3156
  if (!e.isDirectory())
3157
3157
  return false;
@@ -3163,30 +3163,30 @@ function discoverServices() {
3163
3163
  return [];
3164
3164
  }
3165
3165
  }
3166
- function discoverSyncableServices() {
3166
+ function discoverSyncableServices2() {
3167
3167
  const local = discoverServices();
3168
3168
  const pgSet = new Set(KNOWN_PG_SERVICES);
3169
3169
  return local.filter((s) => pgSet.has(s));
3170
3170
  }
3171
3171
  function getServiceDbPath(service) {
3172
- const dataDir = join3(homedir3(), ".hasna", service);
3173
- if (!existsSync3(dataDir))
3172
+ const dataDir = join6(homedir5(), ".hasna", service);
3173
+ if (!existsSync6(dataDir))
3174
3174
  return null;
3175
3175
  const candidates = [
3176
- join3(dataDir, `${service}.db`),
3177
- join3(dataDir, "data.db"),
3178
- join3(dataDir, "database.db")
3176
+ join6(dataDir, `${service}.db`),
3177
+ join6(dataDir, "data.db"),
3178
+ join6(dataDir, "database.db")
3179
3179
  ];
3180
3180
  try {
3181
- const files = readdirSync2(dataDir);
3181
+ const files = readdirSync3(dataDir);
3182
3182
  for (const f of files) {
3183
3183
  if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
3184
- candidates.push(join3(dataDir, f));
3184
+ candidates.push(join6(dataDir, f));
3185
3185
  }
3186
3186
  }
3187
3187
  } catch {}
3188
3188
  for (const p of candidates) {
3189
- if (existsSync3(p))
3189
+ if (existsSync6(p))
3190
3190
  return p;
3191
3191
  }
3192
3192
  return null;
@@ -3493,9 +3493,9 @@ async function syncTransfer(source, target, options, _direction) {
3493
3493
  const batch = rows.slice(offset, offset + batchSize);
3494
3494
  try {
3495
3495
  if (isAsyncAdapter(target)) {
3496
- await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch, columns.includes(conflictColumn) ? conflictColumn : undefined);
3496
+ await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
3497
3497
  } else {
3498
- batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch, columns.includes(conflictColumn) ? conflictColumn : undefined);
3498
+ batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
3499
3499
  }
3500
3500
  result.rowsWritten += batch.length;
3501
3501
  } catch (err) {
@@ -3542,7 +3542,7 @@ async function syncTransfer(source, target, options, _direction) {
3542
3542
  }
3543
3543
  return results;
3544
3544
  }
3545
- async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch, conflictColumn) {
3545
+ async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch) {
3546
3546
  if (batch.length === 0)
3547
3547
  return;
3548
3548
  const colList = columns.map((c) => `"${c}"`).join(", ");
@@ -3552,22 +3552,20 @@ async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, ba
3552
3552
  }).join(", ");
3553
3553
  const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
3554
3554
  const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
3555
- const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
3556
3555
  const sql2 = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
3557
- ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}${whereClause}`;
3556
+ ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
3558
3557
  const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
3559
3558
  await target.run(sql2, ...params);
3560
3559
  }
3561
- function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch, conflictColumn) {
3560
+ function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch) {
3562
3561
  if (batch.length === 0)
3563
3562
  return;
3564
3563
  const colList = columns.map((c) => `"${c}"`).join(", ");
3565
3564
  const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
3566
3565
  const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
3567
3566
  const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
3568
- const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
3569
3567
  const sql2 = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
3570
- ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}${whereClause}`;
3568
+ ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
3571
3569
  const params = batch.flatMap((row) => columns.map((c) => coerceForSqlite(row[c])));
3572
3570
  target.run(sql2, ...params);
3573
3571
  }
@@ -3626,17 +3624,17 @@ function ensureFeedbackTable(db) {
3626
3624
  function saveFeedback(db, feedback) {
3627
3625
  ensureFeedbackTable(db);
3628
3626
  const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
3629
- const now = new Date().toISOString();
3627
+ const now2 = new Date().toISOString();
3630
3628
  const machineId = feedback.machine_id ?? hostname();
3631
3629
  db.run(`INSERT INTO feedback (id, service, version, message, email, machine_id, created_at)
3632
- VALUES (?, ?, ?, ?, ?, ?, ?)`, id, feedback.service, feedback.version ?? "", feedback.message, feedback.email ?? "", machineId, feedback.created_at ?? now);
3630
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, id, feedback.service, feedback.version ?? "", feedback.message, feedback.email ?? "", machineId, feedback.created_at ?? now2);
3633
3631
  return id;
3634
3632
  }
3635
3633
  async function sendFeedback(feedback, db) {
3636
3634
  const config = getCloudConfig();
3637
3635
  const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
3638
3636
  const machineId = feedback.machine_id ?? hostname();
3639
- const now = new Date().toISOString();
3637
+ const now2 = new Date().toISOString();
3640
3638
  const payload = {
3641
3639
  id,
3642
3640
  service: feedback.service,
@@ -3644,7 +3642,7 @@ async function sendFeedback(feedback, db) {
3644
3642
  message: feedback.message,
3645
3643
  email: feedback.email ?? "",
3646
3644
  machine_id: machineId,
3647
- created_at: feedback.created_at ?? now
3645
+ created_at: feedback.created_at ?? now2
3648
3646
  };
3649
3647
  try {
3650
3648
  const res = await fetch(config.feedback_endpoint, {
@@ -3700,8 +3698,8 @@ class SyncProgressTracker {
3700
3698
  }
3701
3699
  start(table, total, direction) {
3702
3700
  const resumed = this.canResume(table);
3703
- const now = Date.now();
3704
- this.startTimes.set(table, now);
3701
+ const now2 = Date.now();
3702
+ this.startTimes.set(table, now2);
3705
3703
  const status = resumed ? "resumed" : "in_progress";
3706
3704
  const info = {
3707
3705
  table,
@@ -4023,10 +4021,10 @@ function incrementalSyncPush(local, remote, tables, options = {}) {
4023
4021
  if (rows.length === 0) {
4024
4022
  stat.skipped_rows = stat.total_rows;
4025
4023
  }
4026
- const now = new Date().toISOString();
4024
+ const now2 = new Date().toISOString();
4027
4025
  upsertSyncMeta(local, {
4028
4026
  table_name: table,
4029
- last_synced_at: now,
4027
+ last_synced_at: now2,
4030
4028
  last_synced_row_count: stat.synced_rows,
4031
4029
  direction: "push"
4032
4030
  });
@@ -4076,10 +4074,10 @@ function incrementalSyncPull(remote, local, tables, options = {}) {
4076
4074
  if (rows.length === 0) {
4077
4075
  stat.skipped_rows = stat.total_rows;
4078
4076
  }
4079
- const now = new Date().toISOString();
4077
+ const now2 = new Date().toISOString();
4080
4078
  upsertSyncMeta(local, {
4081
4079
  table_name: table,
4082
- last_synced_at: now,
4080
+ last_synced_at: now2,
4083
4081
  last_synced_row_count: stat.synced_rows,
4084
4082
  direction: "pull"
4085
4083
  });
@@ -4107,7 +4105,7 @@ function resetAllSyncMeta(db) {
4107
4105
  }
4108
4106
  function getAutoSyncConfig() {
4109
4107
  try {
4110
- if (!existsSync4(AUTO_SYNC_CONFIG_PATH)) {
4108
+ if (!existsSync3(AUTO_SYNC_CONFIG_PATH)) {
4111
4109
  return { ...DEFAULT_AUTO_SYNC_CONFIG };
4112
4110
  }
4113
4111
  const raw = JSON.parse(readFileSync2(AUTO_SYNC_CONFIG_PATH, "utf-8"));
@@ -4119,7 +4117,7 @@ function getAutoSyncConfig() {
4119
4117
  return { ...DEFAULT_AUTO_SYNC_CONFIG };
4120
4118
  }
4121
4119
  }
4122
- async function executeAutoSync(event, serviceName, local, tables) {
4120
+ function executeAutoSync(event, local, remote, tables) {
4123
4121
  const direction = event === "start" ? "pull" : "push";
4124
4122
  const result = {
4125
4123
  event,
@@ -4129,31 +4127,18 @@ async function executeAutoSync(event, serviceName, local, tables) {
4129
4127
  total_rows_synced: 0,
4130
4128
  errors: []
4131
4129
  };
4132
- let remote = null;
4133
4130
  try {
4134
- const connStr = getConnectionString(serviceName);
4135
- remote = new PgAdapterAsync(connStr);
4136
- 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));
4137
- if (syncTables.length === 0) {
4138
- result.success = true;
4139
- return result;
4140
- }
4141
- const results = direction === "pull" ? await syncPull(remote, local, { tables: syncTables }) : await syncPush(local, remote, { tables: syncTables });
4142
- for (const r of results) {
4143
- if (r.errors.length === 0)
4131
+ const stats = direction === "pull" ? incrementalSyncPull(remote, local, tables) : incrementalSyncPush(local, remote, tables);
4132
+ for (const s of stats) {
4133
+ if (s.errors.length === 0) {
4144
4134
  result.tables_synced++;
4145
- result.total_rows_synced += r.rowsWritten;
4146
- result.errors.push(...r.errors);
4135
+ }
4136
+ result.total_rows_synced += s.synced_rows;
4137
+ result.errors.push(...s.errors);
4147
4138
  }
4148
4139
  result.success = result.errors.length === 0;
4149
4140
  } catch (err) {
4150
4141
  result.errors.push(err?.message ?? String(err));
4151
- } finally {
4152
- if (remote) {
4153
- try {
4154
- await remote.close();
4155
- } catch {}
4156
- }
4157
4142
  }
4158
4143
  return result;
4159
4144
  }
@@ -4161,76 +4146,80 @@ function installSignalHandlers() {
4161
4146
  if (signalHandlersInstalled)
4162
4147
  return;
4163
4148
  signalHandlersInstalled = true;
4164
- const handleExit = async () => {
4149
+ const handleExit = () => {
4165
4150
  for (const fn of cleanupHandlers) {
4166
4151
  try {
4167
- await fn();
4152
+ fn();
4168
4153
  } catch {}
4169
4154
  }
4170
4155
  };
4171
- process.on("SIGTERM", async () => {
4172
- await handleExit();
4156
+ process.on("SIGTERM", () => {
4157
+ handleExit();
4173
4158
  process.exit(0);
4174
4159
  });
4175
- process.on("SIGINT", async () => {
4176
- await handleExit();
4160
+ process.on("SIGINT", () => {
4161
+ handleExit();
4177
4162
  process.exit(0);
4178
4163
  });
4179
- process.on("beforeExit", async () => {
4180
- await handleExit();
4164
+ process.on("beforeExit", () => {
4165
+ handleExit();
4181
4166
  });
4182
4167
  }
4183
4168
  function setupAutoSync(serviceName, server, local, remote, tables) {
4184
4169
  const config = getAutoSyncConfig();
4185
4170
  const cloudConfig = getCloudConfig();
4186
4171
  const isSyncEnabled = cloudConfig.mode === "hybrid" || cloudConfig.mode === "cloud";
4187
- const syncOnStart = async () => {
4172
+ const syncOnStart = () => {
4188
4173
  if (!config.auto_sync_on_start || !isSyncEnabled)
4189
4174
  return null;
4190
- return executeAutoSync("start", serviceName, local, tables);
4175
+ return executeAutoSync("start", local, remote, tables);
4191
4176
  };
4192
- const syncOnStop = async () => {
4177
+ const syncOnStop = () => {
4193
4178
  if (!config.auto_sync_on_stop || !isSyncEnabled)
4194
4179
  return null;
4195
- return executeAutoSync("stop", serviceName, local, tables);
4180
+ return executeAutoSync("stop", local, remote, tables);
4196
4181
  };
4197
4182
  if (server && typeof server.onconnect === "function") {
4198
4183
  const origOnConnect = server.onconnect;
4199
- server.onconnect = async (...args) => {
4200
- await syncOnStart();
4184
+ server.onconnect = (...args) => {
4185
+ syncOnStart();
4201
4186
  return origOnConnect.apply(server, args);
4202
4187
  };
4203
4188
  } else if (server && typeof server.on === "function") {
4204
- server.on("connect", () => syncOnStart());
4189
+ server.on("connect", () => {
4190
+ syncOnStart();
4191
+ });
4205
4192
  }
4206
4193
  if (server && typeof server.ondisconnect === "function") {
4207
4194
  const origOnDisconnect = server.ondisconnect;
4208
- server.ondisconnect = async (...args) => {
4209
- await syncOnStop();
4195
+ server.ondisconnect = (...args) => {
4196
+ syncOnStop();
4210
4197
  return origOnDisconnect.apply(server, args);
4211
4198
  };
4212
4199
  } else if (server && typeof server.on === "function") {
4213
- server.on("disconnect", () => syncOnStop());
4200
+ server.on("disconnect", () => {
4201
+ syncOnStop();
4202
+ });
4214
4203
  }
4215
4204
  installSignalHandlers();
4216
- cleanupHandlers.push(async () => {
4217
- await syncOnStop();
4205
+ cleanupHandlers.push(() => {
4206
+ syncOnStop();
4218
4207
  });
4219
4208
  return { syncOnStart, syncOnStop, config };
4220
4209
  }
4221
4210
  function enableAutoSync(serviceName, mcpServer, local, remote, tables) {
4222
4211
  setupAutoSync(serviceName, mcpServer, local, remote, tables);
4223
4212
  }
4224
- function discoverSyncableServices2() {
4213
+ function discoverSyncableServices() {
4225
4214
  const hasnaDir = getHasnaDir();
4226
4215
  const services = [];
4227
4216
  try {
4228
- const entries = readdirSync3(hasnaDir, { withFileTypes: true });
4217
+ const entries = readdirSync2(hasnaDir, { withFileTypes: true });
4229
4218
  for (const entry of entries) {
4230
4219
  if (!entry.isDirectory())
4231
4220
  continue;
4232
- const dbPath = join5(hasnaDir, entry.name, `${entry.name}.db`);
4233
- if (existsSync5(dbPath)) {
4221
+ const dbPath = join4(hasnaDir, entry.name, `${entry.name}.db`);
4222
+ if (existsSync4(dbPath)) {
4234
4223
  services.push(entry.name);
4235
4224
  }
4236
4225
  }
@@ -4241,7 +4230,7 @@ async function runScheduledSync() {
4241
4230
  const config = getCloudConfig();
4242
4231
  if (config.mode === "local")
4243
4232
  return [];
4244
- const services = discoverSyncableServices2();
4233
+ const services = discoverSyncableServices();
4245
4234
  const results = [];
4246
4235
  let remote = null;
4247
4236
  for (const service of services) {
@@ -4252,8 +4241,8 @@ async function runScheduledSync() {
4252
4241
  errors: []
4253
4242
  };
4254
4243
  try {
4255
- const dbPath = join5(getDataDir(service), `${service}.db`);
4256
- if (!existsSync5(dbPath)) {
4244
+ const dbPath = join4(getDataDir(service), `${service}.db`);
4245
+ if (!existsSync4(dbPath)) {
4257
4246
  continue;
4258
4247
  }
4259
4248
  const local = new SqliteAdapter(dbPath);
@@ -4334,34 +4323,34 @@ function minutesToCron(minutes) {
4334
4323
  }
4335
4324
  function getWorkerPath() {
4336
4325
  const dir = typeof import.meta.dir === "string" ? import.meta.dir : dirname(import.meta.url.replace("file://", ""));
4337
- const tsPath = join6(dir, "scheduled-sync.ts");
4338
- const jsPath = join6(dir, "scheduled-sync.js");
4326
+ const tsPath = join5(dir, "scheduled-sync.ts");
4327
+ const jsPath = join5(dir, "scheduled-sync.js");
4339
4328
  try {
4340
- if (existsSync6(tsPath))
4329
+ if (existsSync5(tsPath))
4341
4330
  return tsPath;
4342
4331
  } catch {}
4343
4332
  return jsPath;
4344
4333
  }
4345
4334
  function getBunPath() {
4346
4335
  const candidates = [
4347
- join6(homedir5(), ".bun", "bin", "bun"),
4336
+ join5(homedir4(), ".bun", "bin", "bun"),
4348
4337
  "/usr/local/bin/bun",
4349
4338
  "/usr/bin/bun"
4350
4339
  ];
4351
4340
  for (const p of candidates) {
4352
- if (existsSync6(p))
4341
+ if (existsSync5(p))
4353
4342
  return p;
4354
4343
  }
4355
4344
  return "bun";
4356
4345
  }
4357
4346
  function getLaunchdPlistPath() {
4358
- return join6(homedir5(), "Library", "LaunchAgents", `com.hasna.cloud-sync.plist`);
4347
+ return join5(homedir4(), "Library", "LaunchAgents", `com.hasna.cloud-sync.plist`);
4359
4348
  }
4360
4349
  function createLaunchdPlist(intervalMinutes) {
4361
4350
  const workerPath = getWorkerPath();
4362
4351
  const bunPath = getBunPath();
4363
- const logPath = join6(CONFIG_DIR2, "sync.log");
4364
- const errorLogPath = join6(CONFIG_DIR2, "sync-error.log");
4352
+ const logPath = join5(CONFIG_DIR2, "sync.log");
4353
+ const errorLogPath = join5(CONFIG_DIR2, "sync-error.log");
4365
4354
  return `<?xml version="1.0" encoding="UTF-8"?>
4366
4355
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4367
4356
  <plist version="1.0">
@@ -4387,7 +4376,7 @@ function createLaunchdPlist(intervalMinutes) {
4387
4376
  <key>PATH</key>
4388
4377
  <string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}</string>
4389
4378
  <key>HOME</key>
4390
- <string>${homedir5()}</string>
4379
+ <string>${homedir4()}</string>
4391
4380
  </dict>
4392
4381
  </dict>
4393
4382
  </plist>`;
@@ -4412,7 +4401,7 @@ async function removeLaunchd() {
4412
4401
  } catch {}
4413
4402
  }
4414
4403
  function getSystemdDir() {
4415
- return join6(homedir5(), ".config", "systemd", "user");
4404
+ return join5(homedir4(), ".config", "systemd", "user");
4416
4405
  }
4417
4406
  function createSystemdService() {
4418
4407
  const workerPath = getWorkerPath();
@@ -4424,7 +4413,7 @@ After=network.target
4424
4413
  [Service]
4425
4414
  Type=oneshot
4426
4415
  ExecStart=${bunPath} run ${workerPath}
4427
- Environment=HOME=${homedir5()}
4416
+ Environment=HOME=${homedir4()}
4428
4417
  Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
4429
4418
 
4430
4419
  [Install]
@@ -4447,8 +4436,8 @@ WantedBy=timers.target
4447
4436
  async function registerSystemd(intervalMinutes) {
4448
4437
  const dir = getSystemdDir();
4449
4438
  mkdirSync3(dir, { recursive: true });
4450
- writeFileSync2(join6(dir, `${SERVICE_NAME}.service`), createSystemdService());
4451
- writeFileSync2(join6(dir, `${SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
4439
+ writeFileSync2(join5(dir, `${SERVICE_NAME}.service`), createSystemdService());
4440
+ writeFileSync2(join5(dir, `${SERVICE_NAME}.timer`), createSystemdTimer(intervalMinutes));
4452
4441
  await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
4453
4442
  await Bun.spawn(["systemctl", "--user", "enable", "--now", `${SERVICE_NAME}.timer`]).exited;
4454
4443
  }
@@ -4458,10 +4447,10 @@ async function removeSystemd() {
4458
4447
  } catch {}
4459
4448
  const dir = getSystemdDir();
4460
4449
  try {
4461
- unlinkSync(join6(dir, `${SERVICE_NAME}.service`));
4450
+ unlinkSync(join5(dir, `${SERVICE_NAME}.service`));
4462
4451
  } catch {}
4463
4452
  try {
4464
- unlinkSync(join6(dir, `${SERVICE_NAME}.timer`));
4453
+ unlinkSync(join5(dir, `${SERVICE_NAME}.timer`));
4465
4454
  } catch {}
4466
4455
  try {
4467
4456
  await Bun.spawn(["systemctl", "--user", "daemon-reload"]).exited;
@@ -4498,9 +4487,9 @@ function getSyncScheduleStatus() {
4498
4487
  let mechanism = "none";
4499
4488
  if (registered) {
4500
4489
  if (platform() === "darwin") {
4501
- mechanism = existsSync6(getLaunchdPlistPath()) ? "launchd" : "none";
4490
+ mechanism = existsSync5(getLaunchdPlistPath()) ? "launchd" : "none";
4502
4491
  } else {
4503
- mechanism = existsSync6(join6(getSystemdDir(), `${SERVICE_NAME}.timer`)) ? "systemd" : "none";
4492
+ mechanism = existsSync5(join5(getSystemdDir(), `${SERVICE_NAME}.timer`)) ? "systemd" : "none";
4504
4493
  }
4505
4494
  }
4506
4495
  return {
@@ -13045,7 +13034,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
13045
13034
  init_external();
13046
13035
  });
13047
13036
  init_dotfile = __esm2(() => {
13048
- HASNA_DIR = join(homedir(), ".hasna");
13037
+ HASNA_DIR = join2(homedir2(), ".hasna");
13049
13038
  });
13050
13039
  exports_config = {};
13051
13040
  __export2(exports_config, {
@@ -13076,14 +13065,14 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
13076
13065
  schedule_minutes: exports_external.number().default(0)
13077
13066
  }).default({})
13078
13067
  });
13079
- CONFIG_DIR = join2(homedir2(), ".hasna", "cloud");
13080
- CONFIG_PATH = join2(CONFIG_DIR, "config.json");
13068
+ CONFIG_DIR = join22(homedir22(), ".hasna", "cloud");
13069
+ CONFIG_PATH = join22(CONFIG_DIR, "config.json");
13081
13070
  });
13082
13071
  exports_discover = {};
13083
13072
  __export2(exports_discover, {
13084
13073
  isSyncExcludedTable: () => isSyncExcludedTable,
13085
13074
  getServiceDbPath: () => getServiceDbPath,
13086
- discoverSyncableServices: () => discoverSyncableServices,
13075
+ discoverSyncableServices: () => discoverSyncableServices2,
13087
13076
  discoverServices: () => discoverServices,
13088
13077
  SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
13089
13078
  KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
@@ -13138,10 +13127,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
13138
13127
  init_config();
13139
13128
  init_config();
13140
13129
  init_dotfile();
13141
- init_adapter();
13142
13130
  init_config();
13143
- init_discover();
13144
- AUTO_SYNC_CONFIG_PATH = join4(homedir4(), ".hasna", "cloud", "config.json");
13131
+ AUTO_SYNC_CONFIG_PATH = join3(homedir3(), ".hasna", "cloud", "config.json");
13145
13132
  DEFAULT_AUTO_SYNC_CONFIG = {
13146
13133
  auto_sync_on_start: true,
13147
13134
  auto_sync_on_stop: true
@@ -13151,7 +13138,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
13151
13138
  init_adapter();
13152
13139
  init_dotfile();
13153
13140
  init_config();
13154
- CONFIG_DIR2 = join6(homedir5(), ".hasna", "cloud");
13141
+ CONFIG_DIR2 = join5(homedir4(), ".hasna", "cloud");
13155
13142
  init_adapter();
13156
13143
  init_config();
13157
13144
  init_discover();
@@ -13433,7 +13420,7 @@ var exports_sessions2 = {};
13433
13420
  __export(exports_sessions2, {
13434
13421
  gatherFromSessions: () => gatherFromSessions
13435
13422
  });
13436
- import { readdir, readFile, stat } from "fs/promises";
13423
+ import { readdir, readFile as readFile2, stat } from "fs/promises";
13437
13424
  import { existsSync as existsSync10 } from "fs";
13438
13425
  import { join as join12 } from "path";
13439
13426
  import { homedir as homedir11 } from "os";
@@ -13467,7 +13454,7 @@ async function gatherFromSessions(options = {}) {
13467
13454
  if (fileStat && fileStat.mtime < options.since)
13468
13455
  continue;
13469
13456
  }
13470
- const content = await readFile(filePath, "utf-8").catch(() => "");
13457
+ const content = await readFile2(filePath, "utf-8").catch(() => "");
13471
13458
  if (!content.trim())
13472
13459
  continue;
13473
13460
  const lines = content.trim().split(`
@@ -13603,6 +13590,690 @@ var init_pg_migrate = __esm(() => {
13603
13590
  init_pg_migrations();
13604
13591
  });
13605
13592
 
13593
+ // node_modules/@hasna/events/dist/commander.js
13594
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
13595
+ import { existsSync } from "fs";
13596
+ import { homedir } from "os";
13597
+ import { join } from "path";
13598
+ import { createHmac, timingSafeEqual } from "crypto";
13599
+ import { randomUUID } from "crypto";
13600
+ import { spawn } from "child_process";
13601
+ import { randomUUID as randomUUID2 } from "crypto";
13602
+ function getPathValue(input, path) {
13603
+ return path.split(".").reduce((value, part) => {
13604
+ if (value && typeof value === "object" && part in value) {
13605
+ return value[part];
13606
+ }
13607
+ return;
13608
+ }, input);
13609
+ }
13610
+ function wildcardToRegExp(pattern) {
13611
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
13612
+ return new RegExp(`^${escaped}$`);
13613
+ }
13614
+ function matchString(value, matcher) {
13615
+ if (matcher === undefined)
13616
+ return true;
13617
+ if (value === undefined)
13618
+ return false;
13619
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
13620
+ return matchers.some((item) => wildcardToRegExp(item).test(value));
13621
+ }
13622
+ function matchRecord(input, matcher) {
13623
+ if (!matcher)
13624
+ return true;
13625
+ return Object.entries(matcher).every(([path, expected]) => {
13626
+ const actual = getPathValue(input, path);
13627
+ if (typeof expected === "string" || Array.isArray(expected)) {
13628
+ return matchString(actual === undefined ? undefined : String(actual), expected);
13629
+ }
13630
+ return actual === expected;
13631
+ });
13632
+ }
13633
+ function eventMatchesFilter(event, filter) {
13634
+ 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);
13635
+ }
13636
+ function channelMatchesEvent(channel, event) {
13637
+ if (!channel.enabled)
13638
+ return false;
13639
+ if (!channel.filters || channel.filters.length === 0)
13640
+ return true;
13641
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
13642
+ }
13643
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
13644
+ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
13645
+ function getEventsDataDir(override) {
13646
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
13647
+ }
13648
+
13649
+ class JsonEventsStore {
13650
+ dataDir;
13651
+ channelsPath;
13652
+ eventsPath;
13653
+ deliveriesPath;
13654
+ constructor(dataDir = getEventsDataDir()) {
13655
+ this.dataDir = dataDir;
13656
+ this.channelsPath = join(dataDir, "channels.json");
13657
+ this.eventsPath = join(dataDir, "events.json");
13658
+ this.deliveriesPath = join(dataDir, "deliveries.json");
13659
+ }
13660
+ async init() {
13661
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
13662
+ await chmod(this.dataDir, 448).catch(() => {
13663
+ return;
13664
+ });
13665
+ await this.ensureArrayFile(this.channelsPath);
13666
+ await this.ensureArrayFile(this.eventsPath);
13667
+ await this.ensureArrayFile(this.deliveriesPath);
13668
+ }
13669
+ async addChannel(channel) {
13670
+ await this.init();
13671
+ const channels = await this.readJson(this.channelsPath, []);
13672
+ const index = channels.findIndex((item) => item.id === channel.id);
13673
+ if (index >= 0) {
13674
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
13675
+ } else {
13676
+ channels.push(channel);
13677
+ }
13678
+ await this.writeJson(this.channelsPath, channels);
13679
+ return index >= 0 ? channels[index] : channel;
13680
+ }
13681
+ async listChannels() {
13682
+ await this.init();
13683
+ return this.readJson(this.channelsPath, []);
13684
+ }
13685
+ async getChannel(id) {
13686
+ const channels = await this.listChannels();
13687
+ return channels.find((channel) => channel.id === id);
13688
+ }
13689
+ async removeChannel(id) {
13690
+ await this.init();
13691
+ const channels = await this.readJson(this.channelsPath, []);
13692
+ const next = channels.filter((channel) => channel.id !== id);
13693
+ await this.writeJson(this.channelsPath, next);
13694
+ return next.length !== channels.length;
13695
+ }
13696
+ async appendEvent(event) {
13697
+ await this.init();
13698
+ const events = await this.readJson(this.eventsPath, []);
13699
+ events.push(event);
13700
+ await this.writeJson(this.eventsPath, events);
13701
+ return event;
13702
+ }
13703
+ async listEvents() {
13704
+ await this.init();
13705
+ return this.readJson(this.eventsPath, []);
13706
+ }
13707
+ async findEventByIdentity(identity) {
13708
+ const events = await this.listEvents();
13709
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
13710
+ }
13711
+ async appendDelivery(result) {
13712
+ await this.init();
13713
+ const deliveries = await this.readJson(this.deliveriesPath, []);
13714
+ deliveries.push(result);
13715
+ await this.writeJson(this.deliveriesPath, deliveries);
13716
+ return result;
13717
+ }
13718
+ async listDeliveries() {
13719
+ await this.init();
13720
+ return this.readJson(this.deliveriesPath, []);
13721
+ }
13722
+ async exportData() {
13723
+ return {
13724
+ channels: await this.listChannels(),
13725
+ events: await this.listEvents(),
13726
+ deliveries: await this.listDeliveries()
13727
+ };
13728
+ }
13729
+ async ensureArrayFile(path) {
13730
+ if (!existsSync(path)) {
13731
+ await writeFile(path, `[]
13732
+ `, { encoding: "utf-8", mode: 384 });
13733
+ }
13734
+ await chmod(path, 384).catch(() => {
13735
+ return;
13736
+ });
13737
+ }
13738
+ async readJson(path, fallback) {
13739
+ try {
13740
+ const raw = await readFile(path, "utf-8");
13741
+ if (!raw.trim())
13742
+ return fallback;
13743
+ return JSON.parse(raw);
13744
+ } catch (error) {
13745
+ if (error.code === "ENOENT")
13746
+ return fallback;
13747
+ throw error;
13748
+ }
13749
+ }
13750
+ async writeJson(path, value) {
13751
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
13752
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
13753
+ `, { encoding: "utf-8", mode: 384 });
13754
+ await rename(tempPath, path);
13755
+ await chmod(path, 384).catch(() => {
13756
+ return;
13757
+ });
13758
+ }
13759
+ }
13760
+ var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
13761
+ function buildSignatureBase(timestamp, body) {
13762
+ return `${timestamp}.${body}`;
13763
+ }
13764
+ function signPayload(secret, timestamp, body) {
13765
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
13766
+ return `sha256=${digest}`;
13767
+ }
13768
+ function now() {
13769
+ return new Date().toISOString();
13770
+ }
13771
+ function truncate(value, max = 4096) {
13772
+ return value.length > max ? `${value.slice(0, max)}...` : value;
13773
+ }
13774
+ function buildWebhookRequest(event, channel) {
13775
+ if (!channel.webhook)
13776
+ throw new Error(`Channel ${channel.id} has no webhook config`);
13777
+ const body = JSON.stringify(event);
13778
+ const timestamp = event.time;
13779
+ const headers = {
13780
+ "Content-Type": "application/json",
13781
+ "User-Agent": "@hasna/events",
13782
+ "X-Hasna-Event-Id": event.id,
13783
+ "X-Hasna-Event-Type": event.type,
13784
+ "X-Hasna-Timestamp": timestamp,
13785
+ ...channel.webhook.headers
13786
+ };
13787
+ if (channel.webhook.secret) {
13788
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
13789
+ }
13790
+ return { body, headers };
13791
+ }
13792
+ async function dispatchWebhook(event, channel, options = {}) {
13793
+ if (!channel.webhook)
13794
+ throw new Error(`Channel ${channel.id} has no webhook config`);
13795
+ const startedAt = now();
13796
+ const { body, headers } = buildWebhookRequest(event, channel);
13797
+ const controller = new AbortController;
13798
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
13799
+ try {
13800
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
13801
+ method: "POST",
13802
+ headers,
13803
+ body,
13804
+ signal: controller.signal
13805
+ });
13806
+ const responseBody = truncate(await response.text());
13807
+ return {
13808
+ attempt: 1,
13809
+ status: response.ok ? "success" : "failed",
13810
+ startedAt,
13811
+ completedAt: now(),
13812
+ responseStatus: response.status,
13813
+ responseBody,
13814
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
13815
+ };
13816
+ } catch (error) {
13817
+ return {
13818
+ attempt: 1,
13819
+ status: "failed",
13820
+ startedAt,
13821
+ completedAt: now(),
13822
+ error: error instanceof Error ? error.message : String(error)
13823
+ };
13824
+ } finally {
13825
+ clearTimeout(timeout);
13826
+ }
13827
+ }
13828
+ async function dispatchCommand(event, channel) {
13829
+ if (!channel.command)
13830
+ throw new Error(`Channel ${channel.id} has no command config`);
13831
+ const startedAt = now();
13832
+ const eventJson = JSON.stringify(event);
13833
+ const env = {
13834
+ ...process.env,
13835
+ ...channel.command.env,
13836
+ HASNA_CHANNEL_ID: channel.id,
13837
+ HASNA_EVENT_ID: event.id,
13838
+ HASNA_EVENT_TYPE: event.type,
13839
+ HASNA_EVENT_SOURCE: event.source,
13840
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
13841
+ HASNA_EVENT_SEVERITY: event.severity,
13842
+ HASNA_EVENT_TIME: event.time,
13843
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
13844
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
13845
+ HASNA_EVENT_JSON: eventJson
13846
+ };
13847
+ return new Promise((resolve) => {
13848
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
13849
+ cwd: channel.command.cwd,
13850
+ env,
13851
+ stdio: ["pipe", "pipe", "pipe"]
13852
+ });
13853
+ let stdout = "";
13854
+ let stderr = "";
13855
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
13856
+ child.stdin.end(eventJson);
13857
+ child.stdout.on("data", (chunk) => {
13858
+ stdout += chunk.toString();
13859
+ });
13860
+ child.stderr.on("data", (chunk) => {
13861
+ stderr += chunk.toString();
13862
+ });
13863
+ child.on("error", (error) => {
13864
+ clearTimeout(timeout);
13865
+ resolve({
13866
+ attempt: 1,
13867
+ status: "failed",
13868
+ startedAt,
13869
+ completedAt: now(),
13870
+ stdout: truncate(stdout),
13871
+ stderr: truncate(stderr),
13872
+ error: error.message
13873
+ });
13874
+ });
13875
+ child.on("close", (code, signal) => {
13876
+ clearTimeout(timeout);
13877
+ const success = code === 0;
13878
+ resolve({
13879
+ attempt: 1,
13880
+ status: success ? "success" : "failed",
13881
+ startedAt,
13882
+ completedAt: now(),
13883
+ stdout: truncate(stdout),
13884
+ stderr: truncate(stderr),
13885
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
13886
+ });
13887
+ });
13888
+ });
13889
+ }
13890
+ async function dispatchChannel(event, channel, options = {}) {
13891
+ if (channel.transport === "webhook")
13892
+ return dispatchWebhook(event, channel, options);
13893
+ if (channel.transport === "command")
13894
+ return dispatchCommand(event, channel);
13895
+ return {
13896
+ attempt: 1,
13897
+ status: "skipped",
13898
+ startedAt: now(),
13899
+ completedAt: now(),
13900
+ error: `Unsupported transport: ${channel.transport}`
13901
+ };
13902
+ }
13903
+ function createDeliveryResult(event, channel, attempts) {
13904
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
13905
+ return {
13906
+ id: randomUUID(),
13907
+ eventId: event.id,
13908
+ channelId: channel.id,
13909
+ transport: channel.transport,
13910
+ status,
13911
+ attempts,
13912
+ createdAt: attempts[0]?.startedAt ?? now(),
13913
+ completedAt: attempts.at(-1)?.completedAt ?? now()
13914
+ };
13915
+ }
13916
+ function createEvent(input) {
13917
+ return {
13918
+ id: input.id ?? randomUUID2(),
13919
+ source: input.source,
13920
+ type: input.type,
13921
+ time: normalizeTime(input.time),
13922
+ subject: input.subject,
13923
+ severity: input.severity ?? "info",
13924
+ data: input.data ?? {},
13925
+ message: input.message,
13926
+ dedupeKey: input.dedupeKey,
13927
+ schemaVersion: input.schemaVersion ?? "1.0",
13928
+ metadata: input.metadata ?? {}
13929
+ };
13930
+ }
13931
+
13932
+ class EventsClient {
13933
+ store;
13934
+ redactors;
13935
+ transportOptions;
13936
+ constructor(options = {}) {
13937
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
13938
+ this.redactors = options.redactors ?? [];
13939
+ this.transportOptions = { fetchImpl: options.fetchImpl };
13940
+ }
13941
+ async addChannel(input) {
13942
+ const timestamp = new Date().toISOString();
13943
+ return this.store.addChannel({
13944
+ ...input,
13945
+ createdAt: input.createdAt ?? timestamp,
13946
+ updatedAt: input.updatedAt ?? timestamp
13947
+ });
13948
+ }
13949
+ async listChannels() {
13950
+ return this.store.listChannels();
13951
+ }
13952
+ async removeChannel(id) {
13953
+ return this.store.removeChannel(id);
13954
+ }
13955
+ async emit(input, options = {}) {
13956
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
13957
+ if (options.dedupe !== false) {
13958
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
13959
+ if (existing) {
13960
+ return { event: existing, deliveries: [], deduped: true };
13961
+ }
13962
+ }
13963
+ await this.store.appendEvent(event);
13964
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
13965
+ return { event, deliveries, deduped: false };
13966
+ }
13967
+ async listEvents() {
13968
+ return this.store.listEvents();
13969
+ }
13970
+ async listDeliveries() {
13971
+ return this.store.listDeliveries();
13972
+ }
13973
+ async deliver(event) {
13974
+ const channels = await this.store.listChannels();
13975
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
13976
+ const deliveries = [];
13977
+ for (const channel of selected) {
13978
+ const eventForChannel = await this.applyRedaction(event, channel);
13979
+ const result = await this.deliverWithRetry(eventForChannel, channel);
13980
+ await this.store.appendDelivery(result);
13981
+ deliveries.push(result);
13982
+ }
13983
+ return deliveries;
13984
+ }
13985
+ async testChannel(id, input = {}) {
13986
+ const channel = await this.store.getChannel(id);
13987
+ if (!channel)
13988
+ throw new Error(`Channel not found: ${id}`);
13989
+ const event = createEvent({
13990
+ source: input.source ?? "hasna.events",
13991
+ type: input.type ?? "events.test",
13992
+ subject: input.subject ?? id,
13993
+ severity: input.severity ?? "info",
13994
+ data: input.data ?? { test: true },
13995
+ message: input.message ?? "Hasna events test delivery",
13996
+ dedupeKey: input.dedupeKey,
13997
+ schemaVersion: input.schemaVersion,
13998
+ metadata: input.metadata,
13999
+ time: input.time,
14000
+ id: input.id
14001
+ });
14002
+ const eventForChannel = await this.applyRedaction(event, channel);
14003
+ const result = await this.deliverWithRetry(eventForChannel, channel);
14004
+ await this.store.appendDelivery(result);
14005
+ return result;
14006
+ }
14007
+ async replay(options = {}) {
14008
+ const events = (await this.store.listEvents()).filter((event) => {
14009
+ if (options.eventId && event.id !== options.eventId)
14010
+ return false;
14011
+ if (options.source && event.source !== options.source)
14012
+ return false;
14013
+ if (options.type && event.type !== options.type)
14014
+ return false;
14015
+ return true;
14016
+ });
14017
+ if (options.dryRun)
14018
+ return { events, deliveries: [] };
14019
+ const deliveries = [];
14020
+ for (const event of events) {
14021
+ deliveries.push(...await this.deliver(event));
14022
+ }
14023
+ return { events, deliveries };
14024
+ }
14025
+ async applyRedaction(event, channel) {
14026
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
14027
+ for (const redactor of this.redactors) {
14028
+ next = await redactor(next, channel);
14029
+ }
14030
+ return next;
14031
+ }
14032
+ async deliverWithRetry(event, channel) {
14033
+ const policy = normalizeRetryPolicy(channel.retry);
14034
+ const attempts = [];
14035
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
14036
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
14037
+ attempt.attempt = index + 1;
14038
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
14039
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
14040
+ }
14041
+ attempts.push(attempt);
14042
+ if (attempt.status !== "failed")
14043
+ break;
14044
+ if (attempt.nextBackoffMs)
14045
+ await Bun.sleep(attempt.nextBackoffMs);
14046
+ }
14047
+ return createDeliveryResult(event, channel, attempts);
14048
+ }
14049
+ }
14050
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
14051
+ if (paths.length === 0)
14052
+ return event;
14053
+ const copy = structuredClone(event);
14054
+ for (const path of paths) {
14055
+ setPath(copy, path, replacement);
14056
+ }
14057
+ return copy;
14058
+ }
14059
+ function sanitizeChannelForOutput(channel) {
14060
+ const copy = structuredClone(channel);
14061
+ if (copy.webhook?.secret)
14062
+ copy.webhook.secret = "[REDACTED]";
14063
+ if (copy.command?.env) {
14064
+ copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
14065
+ }
14066
+ return copy;
14067
+ }
14068
+ function sanitizeChannelsForOutput(channels) {
14069
+ return channels.map(sanitizeChannelForOutput);
14070
+ }
14071
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
14072
+ return redactValue(event, replacement);
14073
+ }
14074
+ function shouldRedactKey(key) {
14075
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
14076
+ }
14077
+ function redactValue(value, replacement) {
14078
+ if (Array.isArray(value))
14079
+ return value.map((item) => redactValue(item, replacement));
14080
+ if (!value || typeof value !== "object")
14081
+ return value;
14082
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
14083
+ key,
14084
+ shouldRedactKey(key) ? replacement : redactValue(item, replacement)
14085
+ ]));
14086
+ }
14087
+ function setPath(input, path, replacement) {
14088
+ const parts = path.split(".");
14089
+ let cursor = input;
14090
+ for (const part of parts.slice(0, -1)) {
14091
+ const next = cursor[part];
14092
+ if (!next || typeof next !== "object")
14093
+ return;
14094
+ cursor = next;
14095
+ }
14096
+ const last = parts.at(-1);
14097
+ if (last && last in cursor)
14098
+ cursor[last] = replacement;
14099
+ }
14100
+ function normalizeTime(value) {
14101
+ if (!value)
14102
+ return new Date().toISOString();
14103
+ return value instanceof Date ? value.toISOString() : value;
14104
+ }
14105
+ function normalizeRetryPolicy(policy) {
14106
+ return {
14107
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
14108
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
14109
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
14110
+ };
14111
+ }
14112
+ function parseJsonObject(value, fallback) {
14113
+ if (!value)
14114
+ return fallback;
14115
+ const parsed = JSON.parse(value);
14116
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
14117
+ throw new Error("Expected a JSON object");
14118
+ }
14119
+ return parsed;
14120
+ }
14121
+ function parseHeaders(values) {
14122
+ if (!values?.length)
14123
+ return;
14124
+ const headers = {};
14125
+ for (const value of values) {
14126
+ const separator = value.indexOf("=");
14127
+ if (separator === -1)
14128
+ throw new Error(`Invalid header, expected name=value: ${value}`);
14129
+ headers[value.slice(0, separator)] = value.slice(separator + 1);
14130
+ }
14131
+ return headers;
14132
+ }
14133
+ function parseFilter(options) {
14134
+ const filter2 = {};
14135
+ if (options.source)
14136
+ filter2.source = options.source;
14137
+ if (options.type)
14138
+ filter2.type = options.type;
14139
+ if (options.subject)
14140
+ filter2.subject = options.subject;
14141
+ if (options.severity)
14142
+ filter2.severity = options.severity;
14143
+ return Object.keys(filter2).length > 0 ? [filter2] : undefined;
14144
+ }
14145
+ function createClient(options) {
14146
+ if (options.createClient)
14147
+ return options.createClient();
14148
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
14149
+ }
14150
+ function print(value, json, text) {
14151
+ if (json)
14152
+ console.log(JSON.stringify(value, null, 2));
14153
+ else
14154
+ console.log(text);
14155
+ }
14156
+ function hasJsonOption(options) {
14157
+ return Boolean(options?.json || options?.opts?.().json || options?.optsWithGlobals?.().json || options?.parent?.opts?.().json || options?.parent?.optsWithGlobals?.().json);
14158
+ }
14159
+ function wantsJson(actionOptions, command) {
14160
+ return hasJsonOption(actionOptions) || hasJsonOption(command);
14161
+ }
14162
+ function registerWebhookCommands(program, options) {
14163
+ const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
14164
+ 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) => {
14165
+ const timestamp = new Date().toISOString();
14166
+ const channel = {
14167
+ id: actionOptions.id,
14168
+ name: actionOptions.name,
14169
+ enabled: !actionOptions.disabled,
14170
+ transport: actionOptions.transport,
14171
+ filters: parseFilter(actionOptions),
14172
+ retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
14173
+ redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
14174
+ createdAt: timestamp,
14175
+ updatedAt: timestamp
14176
+ };
14177
+ if (actionOptions.transport === "webhook") {
14178
+ channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
14179
+ } else if (actionOptions.transport === "command") {
14180
+ channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
14181
+ } else {
14182
+ throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
14183
+ }
14184
+ const saved = await createClient(options).addChannel(channel);
14185
+ print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
14186
+ });
14187
+ webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
14188
+ const channels = await createClient(options).listChannels();
14189
+ if (wantsJson(actionOptions, command)) {
14190
+ console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
14191
+ return;
14192
+ }
14193
+ if (!channels.length) {
14194
+ console.log("No channels configured.");
14195
+ return;
14196
+ }
14197
+ for (const channel of channels) {
14198
+ console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
14199
+ }
14200
+ });
14201
+ webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
14202
+ const removed = await createClient(options).removeChannel(id);
14203
+ print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
14204
+ });
14205
+ 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) => {
14206
+ const result = await createClient(options).testChannel(id, {
14207
+ source: options.source,
14208
+ type: actionOptions.type,
14209
+ subject: actionOptions.subject ?? id,
14210
+ message: actionOptions.message,
14211
+ data: parseJsonObject(actionOptions.data, { test: true })
14212
+ });
14213
+ print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
14214
+ });
14215
+ return webhooks;
14216
+ }
14217
+ function registerEventCommands(program, options) {
14218
+ const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
14219
+ 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) => {
14220
+ const result = await createClient(options).emit({
14221
+ source: actionOptions.source ?? options.source,
14222
+ type,
14223
+ subject: actionOptions.subject,
14224
+ severity: actionOptions.severity,
14225
+ message: actionOptions.message,
14226
+ dedupeKey: actionOptions.dedupeKey,
14227
+ data: parseJsonObject(actionOptions.data, {}),
14228
+ metadata: parseJsonObject(actionOptions.metadata, {})
14229
+ }, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
14230
+ print(result, wantsJson(actionOptions, command), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
14231
+ });
14232
+ 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) => {
14233
+ let rows = await createClient(options).listEvents();
14234
+ if (actionOptions.source)
14235
+ rows = rows.filter((event) => event.source === actionOptions.source);
14236
+ if (actionOptions.type)
14237
+ rows = rows.filter((event) => event.type === actionOptions.type);
14238
+ if (actionOptions.limit)
14239
+ rows = rows.slice(-actionOptions.limit);
14240
+ if (wantsJson(actionOptions, command)) {
14241
+ console.log(JSON.stringify(rows, null, 2));
14242
+ return;
14243
+ }
14244
+ if (!rows.length) {
14245
+ console.log("No events recorded.");
14246
+ return;
14247
+ }
14248
+ for (const event of rows)
14249
+ console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
14250
+ });
14251
+ 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) => {
14252
+ const result = await createClient(options).replay({
14253
+ eventId: actionOptions.id,
14254
+ source: actionOptions.source,
14255
+ type: actionOptions.type,
14256
+ dryRun: actionOptions.dryRun
14257
+ });
14258
+ print(result, wantsJson(actionOptions, command), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
14259
+ });
14260
+ return events;
14261
+ }
14262
+ function registerEventsCommands(program, options) {
14263
+ registerWebhookCommands(program, options);
14264
+ registerEventCommands(program, options);
14265
+ }
14266
+ function parseNumber(value) {
14267
+ const parsed = Number(value);
14268
+ if (!Number.isFinite(parsed))
14269
+ throw new Error(`Expected a number, got ${value}`);
14270
+ return parsed;
14271
+ }
14272
+ function collectValues(value, previous) {
14273
+ previous.push(value);
14274
+ return previous;
14275
+ }
14276
+
13606
14277
  // node_modules/commander/esm.mjs
13607
14278
  var import__ = __toESM(require_commander(), 1);
13608
14279
  var {
@@ -14198,7 +14869,7 @@ function sql(strings, ...params) {
14198
14869
  return new SQL([new StringChunk(str)]);
14199
14870
  }
14200
14871
  sql2.raw = raw;
14201
- function join(chunks, separator) {
14872
+ function join2(chunks, separator) {
14202
14873
  const result = [];
14203
14874
  for (const [i, chunk] of chunks.entries()) {
14204
14875
  if (i > 0 && separator !== undefined) {
@@ -14208,7 +14879,7 @@ function sql(strings, ...params) {
14208
14879
  }
14209
14880
  return new SQL(result);
14210
14881
  }
14211
- sql2.join = join;
14882
+ sql2.join = join2;
14212
14883
  function identifier(value) {
14213
14884
  return new Name(value);
14214
14885
  }
@@ -16254,7 +16925,7 @@ class SQLiteSelectQueryBuilderBase extends TypedQueryBuilder {
16254
16925
  return (table, on) => {
16255
16926
  const baseTableName = this.tableName;
16256
16927
  const tableName = getTableLikeName(table);
16257
- if (typeof tableName === "string" && this.config.joins?.some((join) => join.alias === tableName)) {
16928
+ if (typeof tableName === "string" && this.config.joins?.some((join2) => join2.alias === tableName)) {
16258
16929
  throw new Error(`Alias "${tableName}" is already used in this query`);
16259
16930
  }
16260
16931
  if (!this.isPartialSelect) {
@@ -16650,7 +17321,7 @@ class SQLiteUpdateBase extends QueryPromise {
16650
17321
  createJoin(joinType) {
16651
17322
  return (table, on) => {
16652
17323
  const tableName = getTableLikeName(table);
16653
- if (typeof tableName === "string" && this.config.joins.some((join) => join.alias === tableName)) {
17324
+ if (typeof tableName === "string" && this.config.joins.some((join2) => join2.alias === tableName)) {
16654
17325
  throw new Error(`Alias "${tableName}" is already used in this query`);
16655
17326
  }
16656
17327
  if (typeof on === "function") {
@@ -17457,7 +18128,7 @@ function printInfo(message) {
17457
18128
  }
17458
18129
 
17459
18130
  // src/cli/commands/models.ts
17460
- import { randomUUID } from "crypto";
18131
+ import { randomUUID as randomUUID3 } from "crypto";
17461
18132
 
17462
18133
  // node_modules/openai/internal/qs/formats.mjs
17463
18134
  var default_format = "RFC3986";
@@ -23397,8 +24068,8 @@ function registerModelsCommands(program2) {
23397
24068
  printInfo(`Model already tracked as: ${existing.id}`);
23398
24069
  return;
23399
24070
  }
23400
- const modelId = randomUUID();
23401
- const now = Date.now();
24071
+ const modelId = randomUUID3();
24072
+ const now2 = Date.now();
23402
24073
  const name = opts.name ?? result.fineTunedModel ?? `imported-${jobId}`;
23403
24074
  await db.insert(fineTunedModels).values({
23404
24075
  id: modelId,
@@ -23407,15 +24078,15 @@ function registerModelsCommands(program2) {
23407
24078
  baseModel: result.baseModel ?? "unknown",
23408
24079
  status: result.status,
23409
24080
  fineTuneJobId: jobId,
23410
- createdAt: now,
23411
- updatedAt: now
24081
+ createdAt: now2,
24082
+ updatedAt: now2
23412
24083
  });
23413
24084
  await db.insert(trainingJobs).values({
23414
- id: randomUUID(),
24085
+ id: randomUUID3(),
23415
24086
  modelId,
23416
24087
  provider: opts.provider,
23417
24088
  status: result.status,
23418
- startedAt: now
24089
+ startedAt: now2
23419
24090
  });
23420
24091
  printSuccess("Model imported successfully.");
23421
24092
  console.log();
@@ -23434,7 +24105,7 @@ function registerModelsCommands(program2) {
23434
24105
  }
23435
24106
 
23436
24107
  // src/cli/commands/finetune.ts
23437
- import { randomUUID as randomUUID2 } from "crypto";
24108
+ import { randomUUID as randomUUID5 } from "crypto";
23438
24109
  import { existsSync as existsSync9 } from "fs";
23439
24110
  function registerFinetuneCommands(program2) {
23440
24111
  const finetuneCmd = program2.command("finetune").description("Manage fine-tuning jobs");
@@ -23476,8 +24147,8 @@ function registerFinetuneCommands(program2) {
23476
24147
  ({ jobId, status: jobStatus } = await tl.createFineTuneJob(fileId, opts.baseModel, opts.name));
23477
24148
  }
23478
24149
  const db = getDb();
23479
- const modelId = randomUUID2();
23480
- const now = Date.now();
24150
+ const modelId = randomUUID5();
24151
+ const now2 = Date.now();
23481
24152
  await db.insert(fineTunedModels).values({
23482
24153
  id: modelId,
23483
24154
  name: opts.name,
@@ -23485,16 +24156,16 @@ function registerFinetuneCommands(program2) {
23485
24156
  baseModel: opts.baseModel,
23486
24157
  status: "running",
23487
24158
  fineTuneJobId: jobId,
23488
- createdAt: now,
23489
- updatedAt: now
24159
+ createdAt: now2,
24160
+ updatedAt: now2
23490
24161
  });
23491
- const trainingJobId = randomUUID2();
24162
+ const trainingJobId = randomUUID5();
23492
24163
  await db.insert(trainingJobs).values({
23493
24164
  id: trainingJobId,
23494
24165
  modelId,
23495
24166
  provider: opts.provider,
23496
24167
  status: jobStatus,
23497
- startedAt: now
24168
+ startedAt: now2
23498
24169
  });
23499
24170
  printSuccess(`Fine-tune job started!`);
23500
24171
  console.log();
@@ -23628,7 +24299,7 @@ function registerFinetuneCommands(program2) {
23628
24299
  }
23629
24300
 
23630
24301
  // src/cli/commands/data.ts
23631
- import { randomUUID as randomUUID3 } from "crypto";
24302
+ import { randomUUID as randomUUID6 } from "crypto";
23632
24303
  import { readFileSync as readFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
23633
24304
  import { dirname as dirname3, join as join13 } from "path";
23634
24305
  import { homedir as homedir12 } from "os";
@@ -23649,7 +24320,7 @@ function registerDataCommands(program2) {
23649
24320
  try {
23650
24321
  mkdirSync6(opts.output, { recursive: true });
23651
24322
  const sources = opts.source === "all" ? ["todos", "mementos", "conversations", "sessions"] : [opts.source];
23652
- const now = Date.now();
24323
+ const now2 = Date.now();
23653
24324
  const db = getDb();
23654
24325
  const gathererMap = {
23655
24326
  todos: (o) => Promise.resolve().then(() => (init_todos(), exports_todos)).then((m) => m.gatherFromTodos(o)),
@@ -23672,17 +24343,17 @@ function registerDataCommands(program2) {
23672
24343
  printInfo(` No examples found in ${source}.`);
23673
24344
  continue;
23674
24345
  }
23675
- const fileName = `${source}-${now}.jsonl`;
24346
+ const fileName = `${source}-${now2}.jsonl`;
23676
24347
  const filePath = join13(opts.output, fileName);
23677
24348
  writeFileSync5(filePath, examples.map((e) => JSON.stringify(e)).join(`
23678
24349
  `) + `
23679
24350
  `, "utf8");
23680
24351
  await db.insert(trainingDatasets).values({
23681
- id: randomUUID3(),
24352
+ id: randomUUID6(),
23682
24353
  source,
23683
24354
  filePath,
23684
24355
  exampleCount: count,
23685
- createdAt: now
24356
+ createdAt: now2
23686
24357
  });
23687
24358
  printSuccess(` \u2713 ${count} examples \u2192 ${filePath}`);
23688
24359
  totalExamples += count;
@@ -23768,7 +24439,7 @@ function registerDataCommands(program2) {
23768
24439
  `, "utf8");
23769
24440
  const db = getDb();
23770
24441
  await db.insert(trainingDatasets).values({
23771
- id: randomUUID3(),
24442
+ id: randomUUID6(),
23772
24443
  source: "mixed",
23773
24444
  filePath: opts.output,
23774
24445
  exampleCount: finalLines.length,
@@ -24229,4 +24900,5 @@ feedbackCmd.command("list").description("List locally saved feedback").option("-
24229
24900
  ]));
24230
24901
  });
24231
24902
  registerCloudCommands2(program2);
24903
+ registerEventsCommands(program2, { source: "brains" });
24232
24904
  program2.parse();