@hasna/contacts 0.6.18 → 0.6.20

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
@@ -1011,7 +1011,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1011
1011
  this._exitCallback = (err) => {
1012
1012
  if (err.code !== "commander.executeSubCommandAsync") {
1013
1013
  throw err;
1014
- } else {}
1014
+ }
1015
1015
  };
1016
1016
  }
1017
1017
  return this;
@@ -2103,24 +2103,24 @@ var require_commander = __commonJS((exports) => {
2103
2103
  import { createRequire } from "module";
2104
2104
  import { Database } from "bun:sqlite";
2105
2105
  import {
2106
- existsSync,
2106
+ existsSync as existsSync2,
2107
2107
  mkdirSync,
2108
2108
  readdirSync,
2109
2109
  copyFileSync
2110
2110
  } from "fs";
2111
- import { homedir } from "os";
2112
- import { join, relative } from "path";
2113
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
2114
2111
  import { homedir as homedir2 } from "os";
2115
- import { join as join2 } from "path";
2116
- import { readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
2117
- import { join as join3 } from "path";
2118
- import { homedir as homedir3 } from "os";
2112
+ import { join as join2, relative } from "path";
2113
+ import { existsSync as existsSync22, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
2114
+ import { homedir as homedir22 } from "os";
2115
+ import { join as join22 } from "path";
2116
+ import { readdirSync as readdirSync3, existsSync as existsSync6 } from "fs";
2117
+ import { join as join6 } from "path";
2118
+ import { homedir as homedir5 } from "os";
2119
2119
  import { hostname } from "os";
2120
- import { homedir as homedir4 } from "os";
2121
- import { join as join4 } from "path";
2122
- import { join as join6, dirname } from "path";
2123
- import { homedir as homedir5, platform } from "os";
2120
+ import { homedir as homedir3 } from "os";
2121
+ import { join as join3 } from "path";
2122
+ import { join as join5, dirname } from "path";
2123
+ import { homedir as homedir4, platform } from "os";
2124
2124
  function __accessProp2(key) {
2125
2125
  return this[key];
2126
2126
  }
@@ -2991,13 +2991,13 @@ function custom(check, _params = {}, fatal) {
2991
2991
  return ZodAny.create();
2992
2992
  }
2993
2993
  function getDataDir(serviceName) {
2994
- const dir = join(HASNA_DIR, serviceName);
2994
+ const dir = join2(HASNA_DIR, serviceName);
2995
2995
  mkdirSync(dir, { recursive: true });
2996
2996
  return dir;
2997
2997
  }
2998
2998
  function getDbPath(serviceName) {
2999
2999
  const dir = getDataDir(serviceName);
3000
- return join(dir, `${serviceName}.db`);
3000
+ return join2(dir, `${serviceName}.db`);
3001
3001
  }
3002
3002
  function getConfigDir() {
3003
3003
  return CONFIG_DIR;
@@ -3006,7 +3006,7 @@ function getConfigPath() {
3006
3006
  return CONFIG_PATH;
3007
3007
  }
3008
3008
  function getCloudConfig() {
3009
- if (!existsSync2(CONFIG_PATH)) {
3009
+ if (!existsSync22(CONFIG_PATH)) {
3010
3010
  return CloudConfigSchema.parse({});
3011
3011
  }
3012
3012
  try {
@@ -3048,11 +3048,11 @@ function isSyncExcludedTable(table) {
3048
3048
  return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
3049
3049
  }
3050
3050
  function discoverServices() {
3051
- const dataDir = join3(homedir3(), ".hasna");
3052
- if (!existsSync3(dataDir))
3051
+ const dataDir = join6(homedir5(), ".hasna");
3052
+ if (!existsSync6(dataDir))
3053
3053
  return [];
3054
3054
  try {
3055
- const entries = readdirSync2(dataDir, { withFileTypes: true });
3055
+ const entries = readdirSync3(dataDir, { withFileTypes: true });
3056
3056
  return entries.filter((e) => {
3057
3057
  if (!e.isDirectory())
3058
3058
  return false;
@@ -3064,30 +3064,30 @@ function discoverServices() {
3064
3064
  return [];
3065
3065
  }
3066
3066
  }
3067
- function discoverSyncableServices() {
3067
+ function discoverSyncableServices2() {
3068
3068
  const local = discoverServices();
3069
3069
  const pgSet = new Set(KNOWN_PG_SERVICES);
3070
3070
  return local.filter((s) => pgSet.has(s));
3071
3071
  }
3072
3072
  function getServiceDbPath(service) {
3073
- const dataDir = join3(homedir3(), ".hasna", service);
3074
- if (!existsSync3(dataDir))
3073
+ const dataDir = join6(homedir5(), ".hasna", service);
3074
+ if (!existsSync6(dataDir))
3075
3075
  return null;
3076
3076
  const candidates = [
3077
- join3(dataDir, `${service}.db`),
3078
- join3(dataDir, "data.db"),
3079
- join3(dataDir, "database.db")
3077
+ join6(dataDir, `${service}.db`),
3078
+ join6(dataDir, "data.db"),
3079
+ join6(dataDir, "database.db")
3080
3080
  ];
3081
3081
  try {
3082
- const files = readdirSync2(dataDir);
3082
+ const files = readdirSync3(dataDir);
3083
3083
  for (const f of files) {
3084
3084
  if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
3085
- candidates.push(join3(dataDir, f));
3085
+ candidates.push(join6(dataDir, f));
3086
3086
  }
3087
3087
  }
3088
3088
  } catch {}
3089
3089
  for (const p of candidates) {
3090
- if (existsSync3(p))
3090
+ if (existsSync6(p))
3091
3091
  return p;
3092
3092
  }
3093
3093
  return null;
@@ -3394,9 +3394,9 @@ async function syncTransfer(source, target, options, _direction) {
3394
3394
  const batch = rows.slice(offset, offset + batchSize);
3395
3395
  try {
3396
3396
  if (isAsyncAdapter(target)) {
3397
- await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch, columns.includes(conflictColumn) ? conflictColumn : undefined);
3397
+ await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
3398
3398
  } else {
3399
- batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch, columns.includes(conflictColumn) ? conflictColumn : undefined);
3399
+ batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
3400
3400
  }
3401
3401
  result.rowsWritten += batch.length;
3402
3402
  } catch (err) {
@@ -3443,7 +3443,7 @@ async function syncTransfer(source, target, options, _direction) {
3443
3443
  }
3444
3444
  return results;
3445
3445
  }
3446
- async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch, conflictColumn) {
3446
+ async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch) {
3447
3447
  if (batch.length === 0)
3448
3448
  return;
3449
3449
  const colList = columns.map((c) => `"${c}"`).join(", ");
@@ -3453,22 +3453,20 @@ async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, ba
3453
3453
  }).join(", ");
3454
3454
  const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
3455
3455
  const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
3456
- const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
3457
3456
  const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
3458
- ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}${whereClause}`;
3457
+ ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
3459
3458
  const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
3460
3459
  await target.run(sql, ...params);
3461
3460
  }
3462
- function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch, conflictColumn) {
3461
+ function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch) {
3463
3462
  if (batch.length === 0)
3464
3463
  return;
3465
3464
  const colList = columns.map((c) => `"${c}"`).join(", ");
3466
3465
  const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
3467
3466
  const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
3468
3467
  const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
3469
- const whereClause = conflictColumn && updateCols.includes(conflictColumn) ? ` WHERE "${table}"."${conflictColumn}" IS NULL OR EXCLUDED."${conflictColumn}" >= "${table}"."${conflictColumn}"` : "";
3470
3468
  const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
3471
- ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}${whereClause}`;
3469
+ ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
3472
3470
  const params = batch.flatMap((row) => columns.map((c) => coerceForSqlite(row[c])));
3473
3471
  target.run(sql, ...params);
3474
3472
  }
@@ -3527,17 +3525,17 @@ function ensureFeedbackTable(db) {
3527
3525
  function saveFeedback(db, feedback) {
3528
3526
  ensureFeedbackTable(db);
3529
3527
  const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
3530
- const now = new Date().toISOString();
3528
+ const now2 = new Date().toISOString();
3531
3529
  const machineId = feedback.machine_id ?? hostname();
3532
3530
  db.run(`INSERT INTO feedback (id, service, version, message, email, machine_id, created_at)
3533
- VALUES (?, ?, ?, ?, ?, ?, ?)`, id, feedback.service, feedback.version ?? "", feedback.message, feedback.email ?? "", machineId, feedback.created_at ?? now);
3531
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, id, feedback.service, feedback.version ?? "", feedback.message, feedback.email ?? "", machineId, feedback.created_at ?? now2);
3534
3532
  return id;
3535
3533
  }
3536
3534
  async function sendFeedback(feedback, db) {
3537
3535
  const config = getCloudConfig();
3538
3536
  const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
3539
3537
  const machineId = feedback.machine_id ?? hostname();
3540
- const now = new Date().toISOString();
3538
+ const now2 = new Date().toISOString();
3541
3539
  const payload = {
3542
3540
  id,
3543
3541
  service: feedback.service,
@@ -3545,7 +3543,7 @@ async function sendFeedback(feedback, db) {
3545
3543
  message: feedback.message,
3546
3544
  email: feedback.email ?? "",
3547
3545
  machine_id: machineId,
3548
- created_at: feedback.created_at ?? now
3546
+ created_at: feedback.created_at ?? now2
3549
3547
  };
3550
3548
  try {
3551
3549
  const res = await fetch(config.feedback_endpoint, {
@@ -3597,8 +3595,8 @@ class SyncProgressTracker {
3597
3595
  }
3598
3596
  start(table, total, direction) {
3599
3597
  const resumed = this.canResume(table);
3600
- const now = Date.now();
3601
- this.startTimes.set(table, now);
3598
+ const now2 = Date.now();
3599
+ this.startTimes.set(table, now2);
3602
3600
  const status = resumed ? "resumed" : "in_progress";
3603
3601
  const info = {
3604
3602
  table,
@@ -3693,7 +3691,7 @@ class SyncProgressTracker {
3693
3691
  }
3694
3692
  }
3695
3693
  }
3696
- function registerCloudTools(server, serviceName, opts = {}) {
3694
+ function registerCloudTools(server, serviceName) {
3697
3695
  server.tool(`${serviceName}_cloud_status`, "Show cloud configuration and connection health", {}, async () => {
3698
3696
  const config = getCloudConfig();
3699
3697
  const lines = [
@@ -3726,13 +3724,8 @@ function registerCloudTools(server, serviceName, opts = {}) {
3726
3724
  isError: true
3727
3725
  };
3728
3726
  }
3729
- const local = new SqliteAdapter(opts.dbPath ?? getDbPath(serviceName));
3727
+ const local = new SqliteAdapter(getDbPath(serviceName));
3730
3728
  const cloud = new PgAdapterAsync(getConnectionString(serviceName));
3731
- if (opts.migrations?.length) {
3732
- for (const sql of opts.migrations) {
3733
- await cloud.run(sql);
3734
- }
3735
- }
3736
3729
  const tableList = tablesStr ? tablesStr.split(",").map((t) => t.trim()) : listSqliteTables(local);
3737
3730
  const results = await syncPush(local, cloud, { tables: tableList });
3738
3731
  local.close();
@@ -3754,7 +3747,7 @@ function registerCloudTools(server, serviceName, opts = {}) {
3754
3747
  isError: true
3755
3748
  };
3756
3749
  }
3757
- const local = new SqliteAdapter(opts.dbPath ?? getDbPath(serviceName));
3750
+ const local = new SqliteAdapter(getDbPath(serviceName));
3758
3751
  const cloud = new PgAdapterAsync(getConnectionString(serviceName));
3759
3752
  let tableList;
3760
3753
  if (tablesStr) {
@@ -12100,7 +12093,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
12100
12093
  init_external();
12101
12094
  });
12102
12095
  init_dotfile = __esm2(() => {
12103
- HASNA_DIR = join(homedir(), ".hasna");
12096
+ HASNA_DIR = join2(homedir2(), ".hasna");
12104
12097
  });
12105
12098
  exports_config = {};
12106
12099
  __export2(exports_config, {
@@ -12131,14 +12124,14 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
12131
12124
  schedule_minutes: exports_external.number().default(0)
12132
12125
  }).default({})
12133
12126
  });
12134
- CONFIG_DIR = join2(homedir2(), ".hasna", "cloud");
12135
- CONFIG_PATH = join2(CONFIG_DIR, "config.json");
12127
+ CONFIG_DIR = join22(homedir22(), ".hasna", "cloud");
12128
+ CONFIG_PATH = join22(CONFIG_DIR, "config.json");
12136
12129
  });
12137
12130
  exports_discover = {};
12138
12131
  __export2(exports_discover, {
12139
12132
  isSyncExcludedTable: () => isSyncExcludedTable,
12140
12133
  getServiceDbPath: () => getServiceDbPath,
12141
- discoverSyncableServices: () => discoverSyncableServices,
12134
+ discoverSyncableServices: () => discoverSyncableServices2,
12142
12135
  discoverServices: () => discoverServices,
12143
12136
  SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
12144
12137
  KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
@@ -12193,15 +12186,13 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
12193
12186
  init_config();
12194
12187
  init_config();
12195
12188
  init_dotfile();
12196
- init_adapter();
12197
12189
  init_config();
12198
- init_discover();
12199
- AUTO_SYNC_CONFIG_PATH = join4(homedir4(), ".hasna", "cloud", "config.json");
12190
+ AUTO_SYNC_CONFIG_PATH = join3(homedir3(), ".hasna", "cloud", "config.json");
12200
12191
  init_config();
12201
12192
  init_adapter();
12202
12193
  init_dotfile();
12203
12194
  init_config();
12204
- CONFIG_DIR2 = join6(homedir5(), ".hasna", "cloud");
12195
+ CONFIG_DIR2 = join5(homedir4(), ".hasna", "cloud");
12205
12196
  init_adapter();
12206
12197
  init_config();
12207
12198
  init_discover();
@@ -12248,23 +12239,23 @@ var exports_database = {};
12248
12239
  __export(exports_database, {
12249
12240
  uuid: () => uuid,
12250
12241
  resetDatabase: () => resetDatabase,
12251
- now: () => now,
12242
+ now: () => now2,
12252
12243
  getDbPath: () => getDbPath2,
12253
12244
  getDatabase: () => getDatabase,
12254
12245
  getDataDir: () => getDataDir2
12255
12246
  });
12256
- import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync3, statSync } from "fs";
12257
- import { dirname as dirname2, join as join5, resolve } from "path";
12247
+ import { copyFileSync as copyFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, statSync } from "fs";
12248
+ import { dirname as dirname2, join as join4, resolve } from "path";
12258
12249
  function getDataDir2() {
12259
12250
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
12260
- const newDir = join5(home, ".hasna", "contacts");
12261
- const oldDir = join5(home, ".contacts");
12262
- if (existsSync4(oldDir) && !existsSync4(newDir)) {
12251
+ const newDir = join4(home, ".hasna", "contacts");
12252
+ const oldDir = join4(home, ".contacts");
12253
+ if (existsSync3(oldDir) && !existsSync3(newDir)) {
12263
12254
  mkdirSync3(newDir, { recursive: true });
12264
- for (const file of readdirSync3(oldDir)) {
12265
- const oldPath = join5(oldDir, file);
12255
+ for (const file of readdirSync2(oldDir)) {
12256
+ const oldPath = join4(oldDir, file);
12266
12257
  if (statSync(oldPath).isFile()) {
12267
- copyFileSync2(oldPath, join5(newDir, file));
12258
+ copyFileSync2(oldPath, join4(newDir, file));
12268
12259
  }
12269
12260
  }
12270
12261
  }
@@ -12276,13 +12267,13 @@ function getDbPath2() {
12276
12267
  return process.env["HASNA_CONTACTS_DB_PATH"];
12277
12268
  if (process.env["CONTACTS_DB_PATH"])
12278
12269
  return process.env["CONTACTS_DB_PATH"];
12279
- return join5(getDataDir2(), "contacts.db");
12270
+ return join4(getDataDir2(), "contacts.db");
12280
12271
  }
12281
12272
  function ensureDir(filePath) {
12282
12273
  if (filePath === ":memory:")
12283
12274
  return;
12284
12275
  const dir = dirname2(resolve(filePath));
12285
- if (!existsSync4(dir))
12276
+ if (!existsSync3(dir))
12286
12277
  mkdirSync3(dir, { recursive: true });
12287
12278
  }
12288
12279
  function getDatabase(path) {
@@ -12306,7 +12297,7 @@ function resetDatabase() {
12306
12297
  function uuid() {
12307
12298
  return crypto.randomUUID();
12308
12299
  }
12309
- function now() {
12300
+ function now2() {
12310
12301
  return new Date().toISOString();
12311
12302
  }
12312
12303
  function runMigrations(db) {
@@ -13004,7 +12995,7 @@ function loadContactDetails(db, contact) {
13004
12995
  function createContact(input, db) {
13005
12996
  const d = db || getDatabase();
13006
12997
  const id = uuid();
13007
- const timestamp = now();
12998
+ const timestamp = now2();
13008
12999
  const firstName = input.first_name ?? "";
13009
13000
  const lastName = input.last_name ?? "";
13010
13001
  const displayName = input.display_name ?? (firstName || lastName ? `${firstName} ${lastName}`.trim() : "Unnamed Contact");
@@ -13150,7 +13141,7 @@ function updateContact(id, input, db) {
13150
13141
  if (!existing)
13151
13142
  throw new ContactNotFoundError(id);
13152
13143
  const setClauses = ["updated_at = ?"];
13153
- const params = [now()];
13144
+ const params = [now2()];
13154
13145
  if (input.first_name !== undefined) {
13155
13146
  setClauses.push("first_name = ?");
13156
13147
  params.push(input.first_name);
@@ -13344,7 +13335,7 @@ function mergeContacts(keepId, mergeId, db) {
13344
13335
  d.run(`UPDATE activity_log SET contact_id = ? WHERE contact_id = ?`, [keepId, mergeId]);
13345
13336
  d.run(`DELETE FROM contacts WHERE id = ?`, [mergeId]);
13346
13337
  const updates = ["updated_at = ?"];
13347
- const params = [now()];
13338
+ const params = [now2()];
13348
13339
  if (!keepRow.notes && mergeRow.notes) {
13349
13340
  updates.push("notes = ?");
13350
13341
  params.push(mergeRow.notes);
@@ -13418,7 +13409,7 @@ function archiveContact(id, db) {
13418
13409
  const row = d.query(`SELECT * FROM contacts WHERE id = ?`).get(id);
13419
13410
  if (!row)
13420
13411
  throw new ContactNotFoundError(id);
13421
- d.run(`UPDATE contacts SET archived = 1, updated_at = ? WHERE id = ?`, [now(), id]);
13412
+ d.run(`UPDATE contacts SET archived = 1, updated_at = ? WHERE id = ?`, [now2(), id]);
13422
13413
  logActivity(d, { contact_id: id, action: "contact.archived", details: `Archived contact: ${row.display_name}` });
13423
13414
  const updated = d.query(`SELECT * FROM contacts WHERE id = ?`).get(id);
13424
13415
  return loadContactDetails(d, rowToContact(updated));
@@ -13428,7 +13419,7 @@ function unarchiveContact(id, db) {
13428
13419
  const row = d.query(`SELECT * FROM contacts WHERE id = ?`).get(id);
13429
13420
  if (!row)
13430
13421
  throw new ContactNotFoundError(id);
13431
- d.run(`UPDATE contacts SET archived = 0, updated_at = ? WHERE id = ?`, [now(), id]);
13422
+ d.run(`UPDATE contacts SET archived = 0, updated_at = ? WHERE id = ?`, [now2(), id]);
13432
13423
  logActivity(d, { contact_id: id, action: "contact.unarchived", details: `Unarchived contact: ${row.display_name}` });
13433
13424
  const updated = d.query(`SELECT * FROM contacts WHERE id = ?`).get(id);
13434
13425
  return loadContactDetails(d, rowToContact(updated));
@@ -13475,7 +13466,7 @@ function autoLinkContactToCompany(contactId, db) {
13475
13466
  const companyRow = d.query(`SELECT id FROM companies WHERE domain = ? LIMIT 1`).get(domain);
13476
13467
  if (!companyRow)
13477
13468
  return null;
13478
- d.run(`UPDATE contacts SET company_id = ?, updated_at = ? WHERE id = ?`, [companyRow.id, now(), contactId]);
13469
+ d.run(`UPDATE contacts SET company_id = ?, updated_at = ? WHERE id = ?`, [companyRow.id, now2(), contactId]);
13479
13470
  logActivity(d, { contact_id: contactId, action: "contact.auto_linked", details: `Auto-linked to company via email domain: ${domain}` });
13480
13471
  const updated = d.query(`SELECT * FROM contacts WHERE id = ?`).get(contactId);
13481
13472
  return loadContactDetails(d, rowToContact(updated));
@@ -13595,7 +13586,7 @@ function loadCompanyDetails(db, company) {
13595
13586
  function createCompany(input, db) {
13596
13587
  const d = db || getDatabase();
13597
13588
  const id = uuid();
13598
- const timestamp = now();
13589
+ const timestamp = now2();
13599
13590
  d.run(`INSERT INTO companies (id, name, domain, logo_url, description, industry, size, founded_year, notes, custom_fields, is_owned_entity, entity_type, created_at, updated_at)
13600
13591
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
13601
13592
  id,
@@ -13684,7 +13675,7 @@ function updateCompany(id, input, db) {
13684
13675
  if (!existing)
13685
13676
  throw new CompanyNotFoundError(id);
13686
13677
  const setClauses = ["updated_at = ?"];
13687
- const params = [now()];
13678
+ const params = [now2()];
13688
13679
  if (input.name !== undefined) {
13689
13680
  setClauses.push("name = ?");
13690
13681
  params.push(input.name);
@@ -13782,7 +13773,7 @@ function archiveCompany(id, db) {
13782
13773
  const row = d.query(`SELECT * FROM companies WHERE id = ?`).get(id);
13783
13774
  if (!row)
13784
13775
  throw new CompanyNotFoundError(id);
13785
- d.run(`UPDATE companies SET archived = 1, updated_at = ? WHERE id = ?`, [now(), id]);
13776
+ d.run(`UPDATE companies SET archived = 1, updated_at = ? WHERE id = ?`, [now2(), id]);
13786
13777
  logActivity(d, { company_id: id, action: "company.archived", details: `Archived company: ${row.name}` });
13787
13778
  const updated = d.query(`SELECT * FROM companies WHERE id = ?`).get(id);
13788
13779
  return loadCompanyDetails(d, rowToCompany2(updated));
@@ -13792,7 +13783,7 @@ function unarchiveCompany(id, db) {
13792
13783
  const row = d.query(`SELECT * FROM companies WHERE id = ?`).get(id);
13793
13784
  if (!row)
13794
13785
  throw new CompanyNotFoundError(id);
13795
- d.run(`UPDATE companies SET archived = 0, updated_at = ? WHERE id = ?`, [now(), id]);
13786
+ d.run(`UPDATE companies SET archived = 0, updated_at = ? WHERE id = ?`, [now2(), id]);
13796
13787
  logActivity(d, { company_id: id, action: "company.unarchived", details: `Unarchived company: ${row.name}` });
13797
13788
  const updated = d.query(`SELECT * FROM companies WHERE id = ?`).get(id);
13798
13789
  return loadCompanyDetails(d, rowToCompany2(updated));
@@ -13948,7 +13939,7 @@ var init_tags = __esm(() => {
13948
13939
  // src/db/groups.ts
13949
13940
  function createGroup(db, input) {
13950
13941
  const id = uuid();
13951
- db.query(`INSERT INTO groups(id, name, description, project_id, created_at, updated_at) VALUES(?,?,?,?,?,?)`).run(id, input.name, input.description ?? null, input.project_id ?? null, now(), now());
13942
+ db.query(`INSERT INTO groups(id, name, description, project_id, created_at, updated_at) VALUES(?,?,?,?,?,?)`).run(id, input.name, input.description ?? null, input.project_id ?? null, now2(), now2());
13952
13943
  return getGroup(db, id);
13953
13944
  }
13954
13945
  function getGroup(db, id) {
@@ -13978,7 +13969,7 @@ function updateGroup(db, id, input) {
13978
13969
  vals.push(input.project_id ?? null);
13979
13970
  }
13980
13971
  fields.push("updated_at = ?");
13981
- vals.push(now());
13972
+ vals.push(now2());
13982
13973
  vals.push(id);
13983
13974
  db.query(`UPDATE groups SET ${fields.join(", ")} WHERE id = ?`).run(...vals);
13984
13975
  return getGroup(db, id);
@@ -14568,10 +14559,10 @@ __export(exports_images, {
14568
14559
  getImageAsBase64: () => getImageAsBase64,
14569
14560
  deleteImage: () => deleteImage
14570
14561
  });
14571
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, copyFileSync as copyFileSync3, unlinkSync, readdirSync as readdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
14572
- import { join as join8, extname, basename } from "path";
14562
+ import { existsSync as existsSync5, mkdirSync as mkdirSync6, copyFileSync as copyFileSync3, unlinkSync, readdirSync as readdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
14563
+ import { join as join9, extname, basename } from "path";
14573
14564
  function ensureImagesDir() {
14574
- if (!existsSync7(IMAGES_DIR))
14565
+ if (!existsSync5(IMAGES_DIR))
14575
14566
  mkdirSync6(IMAGES_DIR, { recursive: true });
14576
14567
  }
14577
14568
  function getImagesDir() {
@@ -14586,17 +14577,17 @@ function saveImage(entityId, source, options) {
14586
14577
  const ext2 = base64Match[1] === "jpeg" ? "jpg" : base64Match[1];
14587
14578
  const data = Buffer.from(base64Match[2], "base64");
14588
14579
  const filename2 = `${entityId}.${ext2}`;
14589
- writeFileSync3(join8(IMAGES_DIR, filename2), data);
14580
+ writeFileSync3(join9(IMAGES_DIR, filename2), data);
14590
14581
  return filename2;
14591
14582
  }
14592
- if (!existsSync7(source) && /^[A-Za-z0-9+/=\n\r]+$/.test(source.trim()) && source.length > 100) {
14583
+ if (!existsSync5(source) && /^[A-Za-z0-9+/=\n\r]+$/.test(source.trim()) && source.length > 100) {
14593
14584
  const ext2 = options?.format || "jpg";
14594
14585
  const data = Buffer.from(source.trim(), "base64");
14595
14586
  const filename2 = `${entityId}.${ext2}`;
14596
- writeFileSync3(join8(IMAGES_DIR, filename2), data);
14587
+ writeFileSync3(join9(IMAGES_DIR, filename2), data);
14597
14588
  return filename2;
14598
14589
  }
14599
- if (!existsSync7(source)) {
14590
+ if (!existsSync5(source)) {
14600
14591
  throw new Error(`Image file not found: ${source}`);
14601
14592
  }
14602
14593
  const ext = extname(source).slice(1).toLowerCase() || "jpg";
@@ -14605,14 +14596,14 @@ function saveImage(entityId, source, options) {
14605
14596
  throw new Error(`Unsupported image format: ${ext}. Supported: ${validExts.join(", ")}`);
14606
14597
  }
14607
14598
  const filename = `${entityId}.${ext === "jpeg" ? "jpg" : ext}`;
14608
- copyFileSync3(source, join8(IMAGES_DIR, filename));
14599
+ copyFileSync3(source, join9(IMAGES_DIR, filename));
14609
14600
  return filename;
14610
14601
  }
14611
14602
  function getImagePath(entityId) {
14612
14603
  ensureImagesDir();
14613
- const files = readdirSync5(IMAGES_DIR);
14604
+ const files = readdirSync4(IMAGES_DIR);
14614
14605
  const match = files.find((f) => f.startsWith(`${entityId}.`));
14615
- return match ? join8(IMAGES_DIR, match) : null;
14606
+ return match ? join9(IMAGES_DIR, match) : null;
14616
14607
  }
14617
14608
  function getImageAsBase64(entityId) {
14618
14609
  const path = getImagePath(entityId);
@@ -14625,11 +14616,11 @@ function getImageAsBase64(entityId) {
14625
14616
  }
14626
14617
  function deleteImage(entityId) {
14627
14618
  ensureImagesDir();
14628
- const files = readdirSync5(IMAGES_DIR);
14619
+ const files = readdirSync4(IMAGES_DIR);
14629
14620
  let deleted = false;
14630
14621
  for (const f of files) {
14631
14622
  if (f.startsWith(`${entityId}.`)) {
14632
- unlinkSync(join8(IMAGES_DIR, f));
14623
+ unlinkSync(join9(IMAGES_DIR, f));
14633
14624
  deleted = true;
14634
14625
  }
14635
14626
  }
@@ -14637,17 +14628,17 @@ function deleteImage(entityId) {
14637
14628
  }
14638
14629
  function listImages() {
14639
14630
  ensureImagesDir();
14640
- const files = readdirSync5(IMAGES_DIR).filter((f) => !f.startsWith("."));
14631
+ const files = readdirSync4(IMAGES_DIR).filter((f) => !f.startsWith("."));
14641
14632
  return files.map((f) => ({
14642
14633
  entity_id: basename(f, extname(f)),
14643
14634
  filename: f,
14644
- path: join8(IMAGES_DIR, f)
14635
+ path: join9(IMAGES_DIR, f)
14645
14636
  }));
14646
14637
  }
14647
14638
  var IMAGES_DIR;
14648
14639
  var init_images = __esm(() => {
14649
14640
  init_database();
14650
- IMAGES_DIR = join8(getDataDir2(), "images");
14641
+ IMAGES_DIR = join9(getDataDir2(), "images");
14651
14642
  });
14652
14643
 
14653
14644
  // src/mcp/http.ts
@@ -14699,8 +14690,8 @@ var DEFAULT_MCP_HTTP_PORT = 8809, MCP_HTTP_HOST = "127.0.0.1", MCP_SERVICE_NAME
14699
14690
  var init_http = () => {};
14700
14691
 
14701
14692
  // src/lib/connector.ts
14702
- import { join as join9 } from "path";
14703
- import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
14693
+ import { join as join10 } from "path";
14694
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
14704
14695
  import { homedir as homedir6 } from "os";
14705
14696
  async function runConnector(name, args, opts = {}) {
14706
14697
  const binary = `connect-${name}`;
@@ -14751,12 +14742,12 @@ async function runConnector(name, args, opts = {}) {
14751
14742
  }
14752
14743
  function getConnectorTokenPath(name, profile = "default") {
14753
14744
  const bases = [
14754
- join9(homedir6(), ".connectors", `connect-${name}`, "profiles", profile, "tokens.json"),
14755
- join9(homedir6(), ".connect", `connect-${name}`, "profiles", profile, "tokens.json"),
14756
- join9(homedir6(), ".connect", `connect-${name}`, "tokens.json")
14745
+ join10(homedir6(), ".connectors", `connect-${name}`, "profiles", profile, "tokens.json"),
14746
+ join10(homedir6(), ".connect", `connect-${name}`, "profiles", profile, "tokens.json"),
14747
+ join10(homedir6(), ".connect", `connect-${name}`, "tokens.json")
14757
14748
  ];
14758
14749
  for (const p of bases) {
14759
- if (existsSync8(p))
14750
+ if (existsSync7(p))
14760
14751
  return p;
14761
14752
  }
14762
14753
  return null;
@@ -15749,7 +15740,7 @@ function rowToOrgMember(row) {
15749
15740
  function addOrgMember(input, db) {
15750
15741
  const d = db || getDatabase();
15751
15742
  const id = uuid();
15752
- const timestamp = now();
15743
+ const timestamp = now2();
15753
15744
  d.run(`INSERT INTO org_members (id, company_id, contact_id, title, specialization, office_phone, response_sla_hours, notes, created_at, updated_at)
15754
15745
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
15755
15746
  id,
@@ -15773,7 +15764,7 @@ function listOrgMembers(companyId, db) {
15773
15764
  function updateOrgMember(id, input, db) {
15774
15765
  const d = db || getDatabase();
15775
15766
  const setClauses = ["updated_at = ?"];
15776
- const params = [now()];
15767
+ const params = [now2()];
15777
15768
  if ("title" in input) {
15778
15769
  setClauses.push("title = ?");
15779
15770
  params.push(input.title ?? null);
@@ -15920,7 +15911,7 @@ function rowToContactTask(row) {
15920
15911
  function createContactTask(input, db) {
15921
15912
  const d = db || getDatabase();
15922
15913
  const id = uuid();
15923
- const timestamp = now();
15914
+ const timestamp = now2();
15924
15915
  d.run(`INSERT INTO contact_tasks
15925
15916
  (id, title, description, contact_id, assigned_by, deadline, status, priority,
15926
15917
  entity_id, linked_todos_task_id, escalation_rules, created_at, updated_at)
@@ -15968,7 +15959,7 @@ function listContactTasks(opts = {}, db) {
15968
15959
  function updateContactTask(id, input, db) {
15969
15960
  const d = db || getDatabase();
15970
15961
  const setClauses = ["updated_at = ?"];
15971
- const params = [now()];
15962
+ const params = [now2()];
15972
15963
  if (input.title !== undefined) {
15973
15964
  setClauses.push("title = ?");
15974
15965
  params.push(input.title);
@@ -16066,7 +16057,7 @@ function rowToApplication(row) {
16066
16057
  function createApplication(input, db) {
16067
16058
  const d = db || getDatabase();
16068
16059
  const id = uuid();
16069
- const timestamp = now();
16060
+ const timestamp = now2();
16070
16061
  d.run(`INSERT INTO applications
16071
16062
  (id, program_name, provider_company_id, type, value_usd, applicant_contact_id, primary_contact_id,
16072
16063
  status, submitted_date, decision_date, follow_up_date, notes, method, form_url, metadata, created_at, updated_at)
@@ -16118,7 +16109,7 @@ function listApplications(opts = {}, db) {
16118
16109
  function updateApplication(id, input, db) {
16119
16110
  const d = db || getDatabase();
16120
16111
  const setClauses = ["updated_at = ?"];
16121
- const params = [now()];
16112
+ const params = [now2()];
16122
16113
  if (input.program_name !== undefined) {
16123
16114
  setClauses.push("program_name = ?");
16124
16115
  params.push(input.program_name);
@@ -16199,9 +16190,9 @@ __export(exports_upcoming, {
16199
16190
  function getUpcomingItems(days = 7, db) {
16200
16191
  const _db2 = db || getDatabase();
16201
16192
  const items = [];
16202
- const now3 = new Date;
16203
- const future = new Date(now3.getTime() + days * 86400000);
16204
- const todayStr = now3.toISOString().slice(0, 10);
16193
+ const now4 = new Date;
16194
+ const future = new Date(now4.getTime() + days * 86400000);
16195
+ const todayStr = now4.toISOString().slice(0, 10);
16205
16196
  const futureStr = future.toISOString().slice(0, 10);
16206
16197
  const followUps = _db2.query(`SELECT c.id, c.display_name, c.follow_up_at FROM contacts c WHERE c.follow_up_at IS NOT NULL AND c.follow_up_at <= ? AND c.do_not_contact = 0`).all(futureStr);
16207
16198
  for (const r of followUps) {
@@ -16222,8 +16213,8 @@ function getUpcomingItems(days = 7, db) {
16222
16213
  const contacts = _db2.query(`SELECT id, display_name, birthday FROM contacts WHERE birthday IS NOT NULL AND do_not_contact = 0`).all();
16223
16214
  for (const c of contacts) {
16224
16215
  const bday = new Date(c.birthday);
16225
- const thisYear = new Date(now3.getFullYear(), bday.getMonth(), bday.getDate());
16226
- const nextBday = thisYear >= now3 ? thisYear : new Date(now3.getFullYear() + 1, bday.getMonth(), bday.getDate());
16216
+ const thisYear = new Date(now4.getFullYear(), bday.getMonth(), bday.getDate());
16217
+ const nextBday = thisYear >= now4 ? thisYear : new Date(now4.getFullYear() + 1, bday.getMonth(), bday.getDate());
16227
16218
  const nextStr = nextBday.toISOString().slice(0, 10);
16228
16219
  if (nextStr <= futureStr) {
16229
16220
  items.push({ date: nextStr, type: "birthday", contact_id: c.id, contact_name: c.display_name, title: `Birthday: ${c.display_name}`, urgency: nextStr === todayStr ? "today" : "upcoming" });
@@ -16364,7 +16355,7 @@ function rowToDeal(row) {
16364
16355
  function createDeal(input, db) {
16365
16356
  const d = db || getDatabase();
16366
16357
  const id = uuid();
16367
- const timestamp = now();
16358
+ const timestamp = now2();
16368
16359
  d.run(`INSERT INTO deals (id, title, contact_id, company_id, stage, value_usd, currency, close_date, notes, created_at, updated_at)
16369
16360
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
16370
16361
  id,
@@ -16412,7 +16403,7 @@ function updateDeal(id, input, db) {
16412
16403
  if (!existing)
16413
16404
  return null;
16414
16405
  const setClauses = ["updated_at = ?"];
16415
- const params = [now()];
16406
+ const params = [now2()];
16416
16407
  if (input.title !== undefined) {
16417
16408
  setClauses.push("title = ?");
16418
16409
  params.push(input.title);
@@ -16500,7 +16491,7 @@ function rowToEvent(row) {
16500
16491
  function logEvent(input, db) {
16501
16492
  const d = db || getDatabase();
16502
16493
  const id = uuid();
16503
- const timestamp = now();
16494
+ const timestamp = now2();
16504
16495
  d.run(`INSERT INTO events (id, title, type, event_date, duration_min, contact_ids, company_id, notes, outcome, deal_id, created_at)
16505
16496
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
16506
16497
  id,
@@ -17212,7 +17203,7 @@ __export(exports_field_history, {
17212
17203
  });
17213
17204
  function recordFieldChange(contactId, fieldName, oldValue, newValue, source, createdBy, db) {
17214
17205
  const _db2 = db || getDatabase();
17215
- _db2.query(`INSERT INTO contact_field_history(id,contact_id,field_name,old_value,new_value,valid_from,source,created_by,created_at) VALUES(?,?,?,?,?,?,?,?,?)`).run(uuid(), contactId, fieldName, oldValue != null ? String(oldValue) : null, newValue != null ? String(newValue) : null, now(), source || null, createdBy || null, now());
17206
+ _db2.query(`INSERT INTO contact_field_history(id,contact_id,field_name,old_value,new_value,valid_from,source,created_by,created_at) VALUES(?,?,?,?,?,?,?,?,?)`).run(uuid(), contactId, fieldName, oldValue != null ? String(oldValue) : null, newValue != null ? String(newValue) : null, now2(), source || null, createdBy || null, now2());
17216
17207
  }
17217
17208
  function getFieldHistory(contactId, fieldName, db) {
17218
17209
  const _db2 = db || getDatabase();
@@ -17245,7 +17236,7 @@ function addJobEntry(contactId, input, db) {
17245
17236
  _db2.query(`UPDATE job_history SET is_current=0, end_date=COALESCE(end_date,?) WHERE contact_id=? AND is_current=1`).run(new Date().toISOString().slice(0, 10), contactId);
17246
17237
  }
17247
17238
  const id = uuid();
17248
- _db2.query(`INSERT INTO job_history(id,contact_id,company_id,company_name,title,start_date,end_date,is_current,inferred,source,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)`).run(id, contactId, input.company_id || null, input.company_name, input.title || null, input.start_date || null, input.end_date || null, input.is_current ? 1 : 0, input.inferred ? 1 : 0, input.source || null, now());
17239
+ _db2.query(`INSERT INTO job_history(id,contact_id,company_id,company_name,title,start_date,end_date,is_current,inferred,source,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)`).run(id, contactId, input.company_id || null, input.company_name, input.title || null, input.start_date || null, input.end_date || null, input.is_current ? 1 : 0, input.inferred ? 1 : 0, input.source || null, now2());
17249
17240
  return rowToJob(_db2.query(`SELECT * FROM job_history WHERE id=?`).get(id));
17250
17241
  }
17251
17242
  function getJobHistory(contactId, db) {
@@ -17272,7 +17263,7 @@ function rowToLearning(r) {
17272
17263
  function saveLearning(contactId, input, db) {
17273
17264
  const _db2 = db || getDatabase();
17274
17265
  const id = uuid();
17275
- _db2.query(`INSERT INTO contact_learnings(id,contact_id,content,type,confidence,importance,learned_by,session_id,visibility,tags,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(id, contactId, input.content, input.type || "fact", input.confidence ?? 70, input.importance ?? 5, input.learned_by || null, input.session_id || null, input.visibility || "shared", JSON.stringify(input.tags || []), now(), now());
17266
+ _db2.query(`INSERT INTO contact_learnings(id,contact_id,content,type,confidence,importance,learned_by,session_id,visibility,tags,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(id, contactId, input.content, input.type || "fact", input.confidence ?? 70, input.importance ?? 5, input.learned_by || null, input.session_id || null, input.visibility || "shared", JSON.stringify(input.tags || []), now2(), now2());
17276
17267
  return rowToLearning(_db2.query(`SELECT * FROM contact_learnings WHERE id=?`).get(id));
17277
17268
  }
17278
17269
  function getLearnings(contactId, opts = {}, db) {
@@ -17311,12 +17302,12 @@ function searchLearnings(query, opts = {}, db) {
17311
17302
  }
17312
17303
  function confirmLearning(learningId, _agentName, db) {
17313
17304
  const _db2 = db || getDatabase();
17314
- _db2.query(`UPDATE contact_learnings SET confirmed_count=confirmed_count+1, confidence=MIN(100,confidence+10), updated_at=? WHERE id=?`).run(now(), learningId);
17305
+ _db2.query(`UPDATE contact_learnings SET confirmed_count=confirmed_count+1, confidence=MIN(100,confidence+10), updated_at=? WHERE id=?`).run(now2(), learningId);
17315
17306
  }
17316
17307
  function decayLearnings(db) {
17317
17308
  const _db2 = db || getDatabase();
17318
17309
  const cutoff = new Date(Date.now() - 30 * 86400000).toISOString();
17319
- const result = _db2.query(`UPDATE contact_learnings SET confidence=MAX(10,confidence-5), updated_at=? WHERE confirmed_count=0 AND created_at<? AND confidence>10`).run(now(), cutoff);
17310
+ const result = _db2.query(`UPDATE contact_learnings SET confidence=MAX(10,confidence-5), updated_at=? WHERE confirmed_count=0 AND created_at<? AND confidence>10`).run(now2(), cutoff);
17320
17311
  return result.changes || 0;
17321
17312
  }
17322
17313
  function deleteLearning(learningId, db) {
@@ -17336,7 +17327,7 @@ function acquireLock(contactId, agentName, ttlSeconds = 300, reason, sessionId,
17336
17327
  return { acquired: false, held_by: existing.agent_name, lock: existing };
17337
17328
  const id = uuid();
17338
17329
  const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
17339
- _db2.query(`INSERT INTO contact_locks(id,contact_id,agent_name,reason,acquired_at,expires_at,session_id) VALUES(?,?,?,?,?,?,?)`).run(id, contactId, agentName, reason || null, now(), expiresAt, sessionId || null);
17330
+ _db2.query(`INSERT INTO contact_locks(id,contact_id,agent_name,reason,acquired_at,expires_at,session_id) VALUES(?,?,?,?,?,?,?)`).run(id, contactId, agentName, reason || null, now2(), expiresAt, sessionId || null);
17340
17331
  return {
17341
17332
  acquired: true,
17342
17333
  lock: _db2.query(`SELECT * FROM contact_locks WHERE id=?`).get(id)
@@ -17354,11 +17345,11 @@ function checkLock(contactId, db) {
17354
17345
  }
17355
17346
  function cleanExpiredLocks(db) {
17356
17347
  const _db2 = db || getDatabase();
17357
- _db2.query(`DELETE FROM contact_locks WHERE expires_at<?`).run(now());
17348
+ _db2.query(`DELETE FROM contact_locks WHERE expires_at<?`).run(now2());
17358
17349
  }
17359
17350
  function logAgentActivity(contactId, agentName, action, details, sessionId, db) {
17360
17351
  const _db2 = db || getDatabase();
17361
- _db2.query(`INSERT INTO contact_agent_activity(id,contact_id,agent_name,action,details,session_id,created_at) VALUES(?,?,?,?,?,?,?)`).run(uuid(), contactId, agentName, action, details || null, sessionId || null, now());
17352
+ _db2.query(`INSERT INTO contact_agent_activity(id,contact_id,agent_name,action,details,session_id,created_at) VALUES(?,?,?,?,?,?,?)`).run(uuid(), contactId, agentName, action, details || null, sessionId || null, now2());
17362
17353
  }
17363
17354
  function getAgentActivity(contactId, limit = 20, db) {
17364
17355
  const _db2 = db || getDatabase();
@@ -17443,7 +17434,7 @@ __export(exports_identity, {
17443
17434
  function addIdentity(contactId, system, externalId, externalUrl, confidence = "inferred", db) {
17444
17435
  const _db2 = db || getDatabase();
17445
17436
  const id = uuid();
17446
- _db2.query(`INSERT OR REPLACE INTO contact_identities(id,contact_id,system,external_id,external_url,confidence,created_at) VALUES(?,?,?,?,?,?,?)`).run(id, contactId, system, externalId, externalUrl || null, confidence, now());
17437
+ _db2.query(`INSERT OR REPLACE INTO contact_identities(id,contact_id,system,external_id,external_url,confidence,created_at) VALUES(?,?,?,?,?,?,?)`).run(id, contactId, system, externalId, externalUrl || null, confidence, now2());
17447
17438
  return _db2.query(`SELECT * FROM contact_identities WHERE id=?`).get(id);
17448
17439
  }
17449
17440
  function resolveIdentity(system, externalId, db) {
@@ -17542,7 +17533,7 @@ async function embedContact(contactId, db) {
17542
17533
  const tokens = tokenize(text);
17543
17534
  const tfidf = buildTfIdf(tokens);
17544
17535
  const embedding = JSON.stringify(Array.from(tfidf.entries()).sort((a, b) => b[1] - a[1]).slice(0, 100));
17545
- _db2.query(`INSERT OR REPLACE INTO contact_embeddings(contact_id, embedding, model, embedded_text, created_at, updated_at) VALUES(?,?,'tfidf',?,?,?)`).run(contactId, embedding, text.slice(0, 500), now(), now());
17536
+ _db2.query(`INSERT OR REPLACE INTO contact_embeddings(contact_id, embedding, model, embedded_text, created_at, updated_at) VALUES(?,?,'tfidf',?,?,?)`).run(contactId, embedding, text.slice(0, 500), now2(), now2());
17546
17537
  }
17547
17538
  async function embedAllContacts(db) {
17548
17539
  const _db2 = db || getDatabase();
@@ -17907,9 +17898,9 @@ function getStaleContacts(threshold = 40, db) {
17907
17898
  function markFieldVerified(contactId, fieldName, source, db) {
17908
17899
  const _db2 = db || getDatabase();
17909
17900
  try {
17910
- _db2.query(`INSERT OR REPLACE INTO field_verifications(contact_id,field_name,verified_at,source) VALUES(?,?,?,?)`).run(contactId, fieldName, now(), source || null);
17901
+ _db2.query(`INSERT OR REPLACE INTO field_verifications(contact_id,field_name,verified_at,source) VALUES(?,?,?,?)`).run(contactId, fieldName, now2(), source || null);
17911
17902
  } catch {
17912
- _db2.query(`INSERT INTO activity_log(id,contact_id,action,details,created_at) VALUES(?,?,?,?,?)`).run(crypto.randomUUID(), contactId, "field.verified", JSON.stringify({ field_name: fieldName, source }), now());
17903
+ _db2.query(`INSERT INTO activity_log(id,contact_id,action,details,created_at) VALUES(?,?,?,?,?)`).run(crypto.randomUUID(), contactId, "field.verified", JSON.stringify({ field_name: fieldName, source }), now2());
17913
17904
  }
17914
17905
  }
17915
17906
  var SCORED_FIELDS;
@@ -17930,7 +17921,7 @@ __export(exports_org_chart, {
17930
17921
  function addOrgChartEdge(companyId, contactAId, contactBId, edgeType, inferred = false, db) {
17931
17922
  const _db2 = db || getDatabase();
17932
17923
  const id = uuid();
17933
- _db2.query(`INSERT OR IGNORE INTO org_chart_edges(id,company_id,contact_a_id,contact_b_id,edge_type,inferred,created_at) VALUES(?,?,?,?,?,?,?)`).run(id, companyId, contactAId, contactBId, edgeType, inferred ? 1 : 0, now());
17924
+ _db2.query(`INSERT OR IGNORE INTO org_chart_edges(id,company_id,contact_a_id,contact_b_id,edge_type,inferred,created_at) VALUES(?,?,?,?,?,?,?)`).run(id, companyId, contactAId, contactBId, edgeType, inferred ? 1 : 0, now2());
17934
17925
  return _db2.query(`SELECT * FROM org_chart_edges WHERE company_id=? AND contact_a_id=? AND contact_b_id=? AND edge_type=?`).get(companyId, contactAId, contactBId, edgeType);
17935
17926
  }
17936
17927
  function listOrgChart(companyId, db) {
@@ -17940,7 +17931,7 @@ function listOrgChart(companyId, db) {
17940
17931
  function setDealContactRole(dealId, contactId, accountRole, db) {
17941
17932
  const _db2 = db || getDatabase();
17942
17933
  const id = uuid();
17943
- _db2.query(`INSERT OR REPLACE INTO deal_contact_roles(id,deal_id,contact_id,account_role,created_at) VALUES(?,?,?,?,?)`).run(id, dealId, contactId, accountRole, now());
17934
+ _db2.query(`INSERT OR REPLACE INTO deal_contact_roles(id,deal_id,contact_id,account_role,created_at) VALUES(?,?,?,?,?)`).run(id, dealId, contactId, accountRole, now2());
17944
17935
  return _db2.query(`SELECT * FROM deal_contact_roles WHERE deal_id=? AND contact_id=?`).get(dealId, contactId);
17945
17936
  }
17946
17937
  function getDealTeam(dealId, db) {
@@ -17987,7 +17978,7 @@ __export(exports_vault, {
17987
17978
  decrypt: () => decrypt
17988
17979
  });
17989
17980
  import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7, unlinkSync as unlinkSync2 } from "fs";
17990
- import { join as join10 } from "path";
17981
+ import { join as join11 } from "path";
17991
17982
  import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync, createHash } from "crypto";
17992
17983
  function deriveKey(passphrase, salt) {
17993
17984
  return pbkdf2Sync(passphrase, salt, 1e5, 32, "sha512");
@@ -18098,7 +18089,7 @@ function storeFile(sourcePath, entityId) {
18098
18089
  if (!existsSync9(DOCUMENTS_DIR))
18099
18090
  mkdirSync7(DOCUMENTS_DIR, { recursive: true });
18100
18091
  const ext = sourcePath.split(".").pop() || "bin";
18101
- const destPath = join10(DOCUMENTS_DIR, `${entityId}.${ext}`);
18092
+ const destPath = join11(DOCUMENTS_DIR, `${entityId}.${ext}`);
18102
18093
  const data = readFileSync5(sourcePath);
18103
18094
  writeFileSync4(destPath, data);
18104
18095
  return destPath;
@@ -18106,10 +18097,10 @@ function storeFile(sourcePath, entityId) {
18106
18097
  function getDocumentFilePath(entityId) {
18107
18098
  if (!existsSync9(DOCUMENTS_DIR))
18108
18099
  return null;
18109
- const { readdirSync: readdirSync4 } = __require("fs");
18110
- const files = readdirSync4(DOCUMENTS_DIR);
18100
+ const { readdirSync: readdirSync5 } = __require("fs");
18101
+ const files = readdirSync5(DOCUMENTS_DIR);
18111
18102
  const match = files.find((f) => f.startsWith(`${entityId}.`) && !f.endsWith(".enc"));
18112
- return match ? join10(DOCUMENTS_DIR, match) : null;
18103
+ return match ? join11(DOCUMENTS_DIR, match) : null;
18113
18104
  }
18114
18105
  function decryptFile(encPath) {
18115
18106
  const key = requireVault();
@@ -18130,9 +18121,9 @@ var VAULT_DIR, VAULT_CONFIG, VAULT_SESSION, DOCUMENTS_DIR, SESSION_TTL_MS, _deri
18130
18121
  var init_vault = __esm(() => {
18131
18122
  init_database();
18132
18123
  VAULT_DIR = getDataDir2();
18133
- VAULT_CONFIG = join10(VAULT_DIR, "vault.json");
18134
- VAULT_SESSION = join10(VAULT_DIR, ".vault-session");
18135
- DOCUMENTS_DIR = join10(VAULT_DIR, "documents");
18124
+ VAULT_CONFIG = join11(VAULT_DIR, "vault.json");
18125
+ VAULT_SESSION = join11(VAULT_DIR, ".vault-session");
18126
+ DOCUMENTS_DIR = join11(VAULT_DIR, "documents");
18136
18127
  SESSION_TTL_MS = 30 * 60 * 1000;
18137
18128
  });
18138
18129
 
@@ -18155,7 +18146,7 @@ function addDocument(input, db) {
18155
18146
  if (input.file_path) {
18156
18147
  filePath = storeFile(input.file_path, id);
18157
18148
  }
18158
- _db2.query(`INSERT INTO contact_documents (id, contact_id, doc_type, label, encrypted_value, iv, encrypted_file_path, metadata, expires_at, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)`).run(id, input.contact_id, input.doc_type, input.label ?? null, ciphertext, iv, filePath, JSON.stringify(input.metadata || {}), input.expires_at ?? null, now(), now());
18149
+ _db2.query(`INSERT INTO contact_documents (id, contact_id, doc_type, label, encrypted_value, iv, encrypted_file_path, metadata, expires_at, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)`).run(id, input.contact_id, input.doc_type, input.label ?? null, ciphertext, iv, filePath, JSON.stringify(input.metadata || {}), input.expires_at ?? null, now2(), now2());
18159
18150
  return getDocument(id, _db2);
18160
18151
  }
18161
18152
  function getDocument(id, db) {
@@ -18290,13 +18281,13 @@ function setHealthData(contactId, input, db) {
18290
18281
  }
18291
18282
  if (sets.length) {
18292
18283
  sets.push("updated_at = ?");
18293
- params.push(now());
18284
+ params.push(now2());
18294
18285
  params.push(contactId);
18295
18286
  _db2.query(`UPDATE contact_health SET ${sets.join(", ")} WHERE contact_id = ?`).run(...params);
18296
18287
  }
18297
18288
  } else {
18298
18289
  const id = uuid();
18299
- _db2.query(`INSERT INTO contact_health (id, contact_id, blood_type, allergies, medical_conditions, medications, emergency_contacts, health_insurance_provider, health_insurance_id, primary_physician, primary_physician_phone, organ_donor, notes, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(id, contactId, input.blood_type ?? null, JSON.stringify(input.allergies || []), JSON.stringify(input.medical_conditions || []), JSON.stringify(input.medications || []), JSON.stringify(input.emergency_contacts || []), input.health_insurance_provider ?? null, input.health_insurance_id ?? null, input.primary_physician ?? null, input.primary_physician_phone ?? null, input.organ_donor ? 1 : 0, input.notes ?? null, now(), now());
18290
+ _db2.query(`INSERT INTO contact_health (id, contact_id, blood_type, allergies, medical_conditions, medications, emergency_contacts, health_insurance_provider, health_insurance_id, primary_physician, primary_physician_phone, organ_donor, notes, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(id, contactId, input.blood_type ?? null, JSON.stringify(input.allergies || []), JSON.stringify(input.medical_conditions || []), JSON.stringify(input.medications || []), JSON.stringify(input.emergency_contacts || []), input.health_insurance_provider ?? null, input.health_insurance_id ?? null, input.primary_physician ?? null, input.primary_physician_phone ?? null, input.organ_donor ? 1 : 0, input.notes ?? null, now2(), now2());
18300
18291
  }
18301
18292
  return getHealthData(contactId, _db2);
18302
18293
  }
@@ -24627,12 +24618,12 @@ var init_register_tools = __esm(() => {
24627
24618
 
24628
24619
  // src/mcp/index.ts
24629
24620
  import { readFileSync as readFileSync7 } from "fs";
24630
- import { join as join11 } from "path";
24621
+ import { join as join12 } from "path";
24631
24622
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24632
24623
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
24633
24624
  function getServerVersion() {
24634
24625
  try {
24635
- const packageJsonPath = join11(import.meta.dir, "..", "..", "package.json");
24626
+ const packageJsonPath = join12(import.meta.dir, "..", "..", "package.json");
24636
24627
  const pkg = JSON.parse(readFileSync7(packageJsonPath, "utf8"));
24637
24628
  return pkg.version ?? "0.0.0";
24638
24629
  } catch {
@@ -24679,7 +24670,7 @@ __export(exports_serve, {
24679
24670
  startServer: () => startServer
24680
24671
  });
24681
24672
  import { existsSync as existsSync12 } from "fs";
24682
- import { join as join12 } from "path";
24673
+ import { join as join13 } from "path";
24683
24674
  function json3(data, status = 200) {
24684
24675
  return new Response(JSON.stringify(data), {
24685
24676
  status,
@@ -24925,7 +24916,7 @@ async function handleImages(req, _url, segments) {
24925
24916
  return apiError("No image file in form data");
24926
24917
  const ext = file.name?.split(".").pop() || "jpg";
24927
24918
  const buffer = Buffer.from(await file.arrayBuffer());
24928
- const tmpPath = join12(getImagesDir(), `_upload_${entityId}.${ext}`);
24919
+ const tmpPath = join13(getImagesDir(), `_upload_${entityId}.${ext}`);
24929
24920
  const { writeFileSync: wfs } = await import("fs");
24930
24921
  wfs(tmpPath, buffer);
24931
24922
  try {
@@ -25012,8 +25003,8 @@ function startServer(port) {
25012
25003
  response = apiError("Not found", 404);
25013
25004
  }
25014
25005
  } else {
25015
- const filePath = join12(DASHBOARD_DIST, url.pathname === "/" ? "index.html" : url.pathname);
25016
- response = serveStaticFile(filePath) ?? serveStaticFile(join12(DASHBOARD_DIST, "index.html")) ?? new Response("Not Found", { status: 404 });
25006
+ const filePath = join13(DASHBOARD_DIST, url.pathname === "/" ? "index.html" : url.pathname);
25007
+ response = serveStaticFile(filePath) ?? serveStaticFile(join13(DASHBOARD_DIST, "index.html")) ?? new Response("Not Found", { status: 404 });
25017
25008
  }
25018
25009
  } catch (err) {
25019
25010
  console.error("Request error:", err);
@@ -25037,9 +25028,693 @@ var init_serve = __esm(() => {
25037
25028
  init_images();
25038
25029
  init_http();
25039
25030
  init_mcp();
25040
- DASHBOARD_DIST = join12(import.meta.dir, "../../dashboard/dist");
25031
+ DASHBOARD_DIST = join13(import.meta.dir, "../../dashboard/dist");
25041
25032
  });
25042
25033
 
25034
+ // node_modules/@hasna/events/dist/commander.js
25035
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
25036
+ import { existsSync } from "fs";
25037
+ import { homedir } from "os";
25038
+ import { join } from "path";
25039
+ import { createHmac, timingSafeEqual } from "crypto";
25040
+ import { randomUUID } from "crypto";
25041
+ import { spawn } from "child_process";
25042
+ import { randomUUID as randomUUID2 } from "crypto";
25043
+ function getPathValue(input, path) {
25044
+ return path.split(".").reduce((value, part) => {
25045
+ if (value && typeof value === "object" && part in value) {
25046
+ return value[part];
25047
+ }
25048
+ return;
25049
+ }, input);
25050
+ }
25051
+ function wildcardToRegExp(pattern) {
25052
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
25053
+ return new RegExp(`^${escaped}$`);
25054
+ }
25055
+ function matchString(value, matcher) {
25056
+ if (matcher === undefined)
25057
+ return true;
25058
+ if (value === undefined)
25059
+ return false;
25060
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
25061
+ return matchers.some((item) => wildcardToRegExp(item).test(value));
25062
+ }
25063
+ function matchRecord(input, matcher) {
25064
+ if (!matcher)
25065
+ return true;
25066
+ return Object.entries(matcher).every(([path, expected]) => {
25067
+ const actual = getPathValue(input, path);
25068
+ if (typeof expected === "string" || Array.isArray(expected)) {
25069
+ return matchString(actual === undefined ? undefined : String(actual), expected);
25070
+ }
25071
+ return actual === expected;
25072
+ });
25073
+ }
25074
+ function eventMatchesFilter(event, filter) {
25075
+ 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);
25076
+ }
25077
+ function channelMatchesEvent(channel, event) {
25078
+ if (!channel.enabled)
25079
+ return false;
25080
+ if (!channel.filters || channel.filters.length === 0)
25081
+ return true;
25082
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
25083
+ }
25084
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
25085
+ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
25086
+ function getEventsDataDir(override) {
25087
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
25088
+ }
25089
+
25090
+ class JsonEventsStore {
25091
+ dataDir;
25092
+ channelsPath;
25093
+ eventsPath;
25094
+ deliveriesPath;
25095
+ constructor(dataDir = getEventsDataDir()) {
25096
+ this.dataDir = dataDir;
25097
+ this.channelsPath = join(dataDir, "channels.json");
25098
+ this.eventsPath = join(dataDir, "events.json");
25099
+ this.deliveriesPath = join(dataDir, "deliveries.json");
25100
+ }
25101
+ async init() {
25102
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
25103
+ await chmod(this.dataDir, 448).catch(() => {
25104
+ return;
25105
+ });
25106
+ await this.ensureArrayFile(this.channelsPath);
25107
+ await this.ensureArrayFile(this.eventsPath);
25108
+ await this.ensureArrayFile(this.deliveriesPath);
25109
+ }
25110
+ async addChannel(channel) {
25111
+ await this.init();
25112
+ const channels = await this.readJson(this.channelsPath, []);
25113
+ const index = channels.findIndex((item) => item.id === channel.id);
25114
+ if (index >= 0) {
25115
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
25116
+ } else {
25117
+ channels.push(channel);
25118
+ }
25119
+ await this.writeJson(this.channelsPath, channels);
25120
+ return index >= 0 ? channels[index] : channel;
25121
+ }
25122
+ async listChannels() {
25123
+ await this.init();
25124
+ return this.readJson(this.channelsPath, []);
25125
+ }
25126
+ async getChannel(id) {
25127
+ const channels = await this.listChannels();
25128
+ return channels.find((channel) => channel.id === id);
25129
+ }
25130
+ async removeChannel(id) {
25131
+ await this.init();
25132
+ const channels = await this.readJson(this.channelsPath, []);
25133
+ const next = channels.filter((channel) => channel.id !== id);
25134
+ await this.writeJson(this.channelsPath, next);
25135
+ return next.length !== channels.length;
25136
+ }
25137
+ async appendEvent(event) {
25138
+ await this.init();
25139
+ const events = await this.readJson(this.eventsPath, []);
25140
+ events.push(event);
25141
+ await this.writeJson(this.eventsPath, events);
25142
+ return event;
25143
+ }
25144
+ async listEvents() {
25145
+ await this.init();
25146
+ return this.readJson(this.eventsPath, []);
25147
+ }
25148
+ async findEventByIdentity(identity) {
25149
+ const events = await this.listEvents();
25150
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
25151
+ }
25152
+ async appendDelivery(result) {
25153
+ await this.init();
25154
+ const deliveries = await this.readJson(this.deliveriesPath, []);
25155
+ deliveries.push(result);
25156
+ await this.writeJson(this.deliveriesPath, deliveries);
25157
+ return result;
25158
+ }
25159
+ async listDeliveries() {
25160
+ await this.init();
25161
+ return this.readJson(this.deliveriesPath, []);
25162
+ }
25163
+ async exportData() {
25164
+ return {
25165
+ channels: await this.listChannels(),
25166
+ events: await this.listEvents(),
25167
+ deliveries: await this.listDeliveries()
25168
+ };
25169
+ }
25170
+ async ensureArrayFile(path) {
25171
+ if (!existsSync(path)) {
25172
+ await writeFile(path, `[]
25173
+ `, { encoding: "utf-8", mode: 384 });
25174
+ }
25175
+ await chmod(path, 384).catch(() => {
25176
+ return;
25177
+ });
25178
+ }
25179
+ async readJson(path, fallback) {
25180
+ try {
25181
+ const raw = await readFile(path, "utf-8");
25182
+ if (!raw.trim())
25183
+ return fallback;
25184
+ return JSON.parse(raw);
25185
+ } catch (error) {
25186
+ if (error.code === "ENOENT")
25187
+ return fallback;
25188
+ throw error;
25189
+ }
25190
+ }
25191
+ async writeJson(path, value) {
25192
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
25193
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
25194
+ `, { encoding: "utf-8", mode: 384 });
25195
+ await rename(tempPath, path);
25196
+ await chmod(path, 384).catch(() => {
25197
+ return;
25198
+ });
25199
+ }
25200
+ }
25201
+ var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
25202
+ function buildSignatureBase(timestamp, body) {
25203
+ return `${timestamp}.${body}`;
25204
+ }
25205
+ function signPayload(secret, timestamp, body) {
25206
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
25207
+ return `sha256=${digest}`;
25208
+ }
25209
+ function now() {
25210
+ return new Date().toISOString();
25211
+ }
25212
+ function truncate(value, max = 4096) {
25213
+ return value.length > max ? `${value.slice(0, max)}...` : value;
25214
+ }
25215
+ function buildWebhookRequest(event, channel) {
25216
+ if (!channel.webhook)
25217
+ throw new Error(`Channel ${channel.id} has no webhook config`);
25218
+ const body = JSON.stringify(event);
25219
+ const timestamp = event.time;
25220
+ const headers = {
25221
+ "Content-Type": "application/json",
25222
+ "User-Agent": "@hasna/events",
25223
+ "X-Hasna-Event-Id": event.id,
25224
+ "X-Hasna-Event-Type": event.type,
25225
+ "X-Hasna-Timestamp": timestamp,
25226
+ ...channel.webhook.headers
25227
+ };
25228
+ if (channel.webhook.secret) {
25229
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
25230
+ }
25231
+ return { body, headers };
25232
+ }
25233
+ async function dispatchWebhook(event, channel, options = {}) {
25234
+ if (!channel.webhook)
25235
+ throw new Error(`Channel ${channel.id} has no webhook config`);
25236
+ const startedAt = now();
25237
+ const { body, headers } = buildWebhookRequest(event, channel);
25238
+ const controller = new AbortController;
25239
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
25240
+ try {
25241
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
25242
+ method: "POST",
25243
+ headers,
25244
+ body,
25245
+ signal: controller.signal
25246
+ });
25247
+ const responseBody = truncate(await response.text());
25248
+ return {
25249
+ attempt: 1,
25250
+ status: response.ok ? "success" : "failed",
25251
+ startedAt,
25252
+ completedAt: now(),
25253
+ responseStatus: response.status,
25254
+ responseBody,
25255
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
25256
+ };
25257
+ } catch (error) {
25258
+ return {
25259
+ attempt: 1,
25260
+ status: "failed",
25261
+ startedAt,
25262
+ completedAt: now(),
25263
+ error: error instanceof Error ? error.message : String(error)
25264
+ };
25265
+ } finally {
25266
+ clearTimeout(timeout);
25267
+ }
25268
+ }
25269
+ async function dispatchCommand(event, channel) {
25270
+ if (!channel.command)
25271
+ throw new Error(`Channel ${channel.id} has no command config`);
25272
+ const startedAt = now();
25273
+ const eventJson = JSON.stringify(event);
25274
+ const env = {
25275
+ ...process.env,
25276
+ ...channel.command.env,
25277
+ HASNA_CHANNEL_ID: channel.id,
25278
+ HASNA_EVENT_ID: event.id,
25279
+ HASNA_EVENT_TYPE: event.type,
25280
+ HASNA_EVENT_SOURCE: event.source,
25281
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
25282
+ HASNA_EVENT_SEVERITY: event.severity,
25283
+ HASNA_EVENT_TIME: event.time,
25284
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
25285
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
25286
+ HASNA_EVENT_JSON: eventJson
25287
+ };
25288
+ return new Promise((resolve) => {
25289
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
25290
+ cwd: channel.command.cwd,
25291
+ env,
25292
+ stdio: ["pipe", "pipe", "pipe"]
25293
+ });
25294
+ let stdout = "";
25295
+ let stderr = "";
25296
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
25297
+ child.stdin.end(eventJson);
25298
+ child.stdout.on("data", (chunk) => {
25299
+ stdout += chunk.toString();
25300
+ });
25301
+ child.stderr.on("data", (chunk) => {
25302
+ stderr += chunk.toString();
25303
+ });
25304
+ child.on("error", (error) => {
25305
+ clearTimeout(timeout);
25306
+ resolve({
25307
+ attempt: 1,
25308
+ status: "failed",
25309
+ startedAt,
25310
+ completedAt: now(),
25311
+ stdout: truncate(stdout),
25312
+ stderr: truncate(stderr),
25313
+ error: error.message
25314
+ });
25315
+ });
25316
+ child.on("close", (code, signal) => {
25317
+ clearTimeout(timeout);
25318
+ const success = code === 0;
25319
+ resolve({
25320
+ attempt: 1,
25321
+ status: success ? "success" : "failed",
25322
+ startedAt,
25323
+ completedAt: now(),
25324
+ stdout: truncate(stdout),
25325
+ stderr: truncate(stderr),
25326
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
25327
+ });
25328
+ });
25329
+ });
25330
+ }
25331
+ async function dispatchChannel(event, channel, options = {}) {
25332
+ if (channel.transport === "webhook")
25333
+ return dispatchWebhook(event, channel, options);
25334
+ if (channel.transport === "command")
25335
+ return dispatchCommand(event, channel);
25336
+ return {
25337
+ attempt: 1,
25338
+ status: "skipped",
25339
+ startedAt: now(),
25340
+ completedAt: now(),
25341
+ error: `Unsupported transport: ${channel.transport}`
25342
+ };
25343
+ }
25344
+ function createDeliveryResult(event, channel, attempts) {
25345
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
25346
+ return {
25347
+ id: randomUUID(),
25348
+ eventId: event.id,
25349
+ channelId: channel.id,
25350
+ transport: channel.transport,
25351
+ status,
25352
+ attempts,
25353
+ createdAt: attempts[0]?.startedAt ?? now(),
25354
+ completedAt: attempts.at(-1)?.completedAt ?? now()
25355
+ };
25356
+ }
25357
+ function createEvent(input) {
25358
+ return {
25359
+ id: input.id ?? randomUUID2(),
25360
+ source: input.source,
25361
+ type: input.type,
25362
+ time: normalizeTime(input.time),
25363
+ subject: input.subject,
25364
+ severity: input.severity ?? "info",
25365
+ data: input.data ?? {},
25366
+ message: input.message,
25367
+ dedupeKey: input.dedupeKey,
25368
+ schemaVersion: input.schemaVersion ?? "1.0",
25369
+ metadata: input.metadata ?? {}
25370
+ };
25371
+ }
25372
+
25373
+ class EventsClient {
25374
+ store;
25375
+ redactors;
25376
+ transportOptions;
25377
+ constructor(options = {}) {
25378
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
25379
+ this.redactors = options.redactors ?? [];
25380
+ this.transportOptions = { fetchImpl: options.fetchImpl };
25381
+ }
25382
+ async addChannel(input) {
25383
+ const timestamp = new Date().toISOString();
25384
+ return this.store.addChannel({
25385
+ ...input,
25386
+ createdAt: input.createdAt ?? timestamp,
25387
+ updatedAt: input.updatedAt ?? timestamp
25388
+ });
25389
+ }
25390
+ async listChannels() {
25391
+ return this.store.listChannels();
25392
+ }
25393
+ async removeChannel(id) {
25394
+ return this.store.removeChannel(id);
25395
+ }
25396
+ async emit(input, options = {}) {
25397
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
25398
+ if (options.dedupe !== false) {
25399
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
25400
+ if (existing) {
25401
+ return { event: existing, deliveries: [], deduped: true };
25402
+ }
25403
+ }
25404
+ await this.store.appendEvent(event);
25405
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
25406
+ return { event, deliveries, deduped: false };
25407
+ }
25408
+ async listEvents() {
25409
+ return this.store.listEvents();
25410
+ }
25411
+ async listDeliveries() {
25412
+ return this.store.listDeliveries();
25413
+ }
25414
+ async deliver(event) {
25415
+ const channels = await this.store.listChannels();
25416
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
25417
+ const deliveries = [];
25418
+ for (const channel of selected) {
25419
+ const eventForChannel = await this.applyRedaction(event, channel);
25420
+ const result = await this.deliverWithRetry(eventForChannel, channel);
25421
+ await this.store.appendDelivery(result);
25422
+ deliveries.push(result);
25423
+ }
25424
+ return deliveries;
25425
+ }
25426
+ async testChannel(id, input = {}) {
25427
+ const channel = await this.store.getChannel(id);
25428
+ if (!channel)
25429
+ throw new Error(`Channel not found: ${id}`);
25430
+ const event = createEvent({
25431
+ source: input.source ?? "hasna.events",
25432
+ type: input.type ?? "events.test",
25433
+ subject: input.subject ?? id,
25434
+ severity: input.severity ?? "info",
25435
+ data: input.data ?? { test: true },
25436
+ message: input.message ?? "Hasna events test delivery",
25437
+ dedupeKey: input.dedupeKey,
25438
+ schemaVersion: input.schemaVersion,
25439
+ metadata: input.metadata,
25440
+ time: input.time,
25441
+ id: input.id
25442
+ });
25443
+ const eventForChannel = await this.applyRedaction(event, channel);
25444
+ const result = await this.deliverWithRetry(eventForChannel, channel);
25445
+ await this.store.appendDelivery(result);
25446
+ return result;
25447
+ }
25448
+ async replay(options = {}) {
25449
+ const events = (await this.store.listEvents()).filter((event) => {
25450
+ if (options.eventId && event.id !== options.eventId)
25451
+ return false;
25452
+ if (options.source && event.source !== options.source)
25453
+ return false;
25454
+ if (options.type && event.type !== options.type)
25455
+ return false;
25456
+ return true;
25457
+ });
25458
+ if (options.dryRun)
25459
+ return { events, deliveries: [] };
25460
+ const deliveries = [];
25461
+ for (const event of events) {
25462
+ deliveries.push(...await this.deliver(event));
25463
+ }
25464
+ return { events, deliveries };
25465
+ }
25466
+ async applyRedaction(event, channel) {
25467
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
25468
+ for (const redactor of this.redactors) {
25469
+ next = await redactor(next, channel);
25470
+ }
25471
+ return next;
25472
+ }
25473
+ async deliverWithRetry(event, channel) {
25474
+ const policy = normalizeRetryPolicy(channel.retry);
25475
+ const attempts = [];
25476
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
25477
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
25478
+ attempt.attempt = index + 1;
25479
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
25480
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
25481
+ }
25482
+ attempts.push(attempt);
25483
+ if (attempt.status !== "failed")
25484
+ break;
25485
+ if (attempt.nextBackoffMs)
25486
+ await Bun.sleep(attempt.nextBackoffMs);
25487
+ }
25488
+ return createDeliveryResult(event, channel, attempts);
25489
+ }
25490
+ }
25491
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
25492
+ if (paths.length === 0)
25493
+ return event;
25494
+ const copy = structuredClone(event);
25495
+ for (const path of paths) {
25496
+ setPath(copy, path, replacement);
25497
+ }
25498
+ return copy;
25499
+ }
25500
+ function sanitizeChannelForOutput(channel) {
25501
+ const copy = structuredClone(channel);
25502
+ if (copy.webhook?.secret)
25503
+ copy.webhook.secret = "[REDACTED]";
25504
+ if (copy.command?.env) {
25505
+ copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
25506
+ }
25507
+ return copy;
25508
+ }
25509
+ function sanitizeChannelsForOutput(channels) {
25510
+ return channels.map(sanitizeChannelForOutput);
25511
+ }
25512
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
25513
+ return redactValue(event, replacement);
25514
+ }
25515
+ function shouldRedactKey(key) {
25516
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
25517
+ }
25518
+ function redactValue(value, replacement) {
25519
+ if (Array.isArray(value))
25520
+ return value.map((item) => redactValue(item, replacement));
25521
+ if (!value || typeof value !== "object")
25522
+ return value;
25523
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
25524
+ key,
25525
+ shouldRedactKey(key) ? replacement : redactValue(item, replacement)
25526
+ ]));
25527
+ }
25528
+ function setPath(input, path, replacement) {
25529
+ const parts = path.split(".");
25530
+ let cursor = input;
25531
+ for (const part of parts.slice(0, -1)) {
25532
+ const next = cursor[part];
25533
+ if (!next || typeof next !== "object")
25534
+ return;
25535
+ cursor = next;
25536
+ }
25537
+ const last = parts.at(-1);
25538
+ if (last && last in cursor)
25539
+ cursor[last] = replacement;
25540
+ }
25541
+ function normalizeTime(value) {
25542
+ if (!value)
25543
+ return new Date().toISOString();
25544
+ return value instanceof Date ? value.toISOString() : value;
25545
+ }
25546
+ function normalizeRetryPolicy(policy) {
25547
+ return {
25548
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
25549
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
25550
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
25551
+ };
25552
+ }
25553
+ function parseJsonObject(value, fallback) {
25554
+ if (!value)
25555
+ return fallback;
25556
+ const parsed = JSON.parse(value);
25557
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
25558
+ throw new Error("Expected a JSON object");
25559
+ }
25560
+ return parsed;
25561
+ }
25562
+ function parseHeaders(values) {
25563
+ if (!values?.length)
25564
+ return;
25565
+ const headers = {};
25566
+ for (const value of values) {
25567
+ const separator = value.indexOf("=");
25568
+ if (separator === -1)
25569
+ throw new Error(`Invalid header, expected name=value: ${value}`);
25570
+ headers[value.slice(0, separator)] = value.slice(separator + 1);
25571
+ }
25572
+ return headers;
25573
+ }
25574
+ function parseFilter(options) {
25575
+ const filter2 = {};
25576
+ if (options.source)
25577
+ filter2.source = options.source;
25578
+ if (options.type)
25579
+ filter2.type = options.type;
25580
+ if (options.subject)
25581
+ filter2.subject = options.subject;
25582
+ if (options.severity)
25583
+ filter2.severity = options.severity;
25584
+ return Object.keys(filter2).length > 0 ? [filter2] : undefined;
25585
+ }
25586
+ function createClient(options) {
25587
+ if (options.createClient)
25588
+ return options.createClient();
25589
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
25590
+ }
25591
+ function print(value, json, text) {
25592
+ if (json)
25593
+ console.log(JSON.stringify(value, null, 2));
25594
+ else
25595
+ console.log(text);
25596
+ }
25597
+ function hasJsonOption(options) {
25598
+ return Boolean(options?.json || options?.opts?.().json || options?.optsWithGlobals?.().json || options?.parent?.opts?.().json || options?.parent?.optsWithGlobals?.().json);
25599
+ }
25600
+ function wantsJson(actionOptions, command) {
25601
+ return hasJsonOption(actionOptions) || hasJsonOption(command);
25602
+ }
25603
+ function registerWebhookCommands(program, options) {
25604
+ const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
25605
+ 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) => {
25606
+ const timestamp = new Date().toISOString();
25607
+ const channel = {
25608
+ id: actionOptions.id,
25609
+ name: actionOptions.name,
25610
+ enabled: !actionOptions.disabled,
25611
+ transport: actionOptions.transport,
25612
+ filters: parseFilter(actionOptions),
25613
+ retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
25614
+ redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
25615
+ createdAt: timestamp,
25616
+ updatedAt: timestamp
25617
+ };
25618
+ if (actionOptions.transport === "webhook") {
25619
+ channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
25620
+ } else if (actionOptions.transport === "command") {
25621
+ channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
25622
+ } else {
25623
+ throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
25624
+ }
25625
+ const saved = await createClient(options).addChannel(channel);
25626
+ print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
25627
+ });
25628
+ webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
25629
+ const channels = await createClient(options).listChannels();
25630
+ if (wantsJson(actionOptions, command)) {
25631
+ console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
25632
+ return;
25633
+ }
25634
+ if (!channels.length) {
25635
+ console.log("No channels configured.");
25636
+ return;
25637
+ }
25638
+ for (const channel of channels) {
25639
+ console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
25640
+ }
25641
+ });
25642
+ webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
25643
+ const removed = await createClient(options).removeChannel(id);
25644
+ print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
25645
+ });
25646
+ 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) => {
25647
+ const result = await createClient(options).testChannel(id, {
25648
+ source: options.source,
25649
+ type: actionOptions.type,
25650
+ subject: actionOptions.subject ?? id,
25651
+ message: actionOptions.message,
25652
+ data: parseJsonObject(actionOptions.data, { test: true })
25653
+ });
25654
+ print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
25655
+ });
25656
+ return webhooks;
25657
+ }
25658
+ function registerEventCommands(program, options) {
25659
+ const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
25660
+ 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) => {
25661
+ const result = await createClient(options).emit({
25662
+ source: actionOptions.source ?? options.source,
25663
+ type,
25664
+ subject: actionOptions.subject,
25665
+ severity: actionOptions.severity,
25666
+ message: actionOptions.message,
25667
+ dedupeKey: actionOptions.dedupeKey,
25668
+ data: parseJsonObject(actionOptions.data, {}),
25669
+ metadata: parseJsonObject(actionOptions.metadata, {})
25670
+ }, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
25671
+ print(result, wantsJson(actionOptions, command), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
25672
+ });
25673
+ 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) => {
25674
+ let rows = await createClient(options).listEvents();
25675
+ if (actionOptions.source)
25676
+ rows = rows.filter((event) => event.source === actionOptions.source);
25677
+ if (actionOptions.type)
25678
+ rows = rows.filter((event) => event.type === actionOptions.type);
25679
+ if (actionOptions.limit)
25680
+ rows = rows.slice(-actionOptions.limit);
25681
+ if (wantsJson(actionOptions, command)) {
25682
+ console.log(JSON.stringify(rows, null, 2));
25683
+ return;
25684
+ }
25685
+ if (!rows.length) {
25686
+ console.log("No events recorded.");
25687
+ return;
25688
+ }
25689
+ for (const event of rows)
25690
+ console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
25691
+ });
25692
+ 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) => {
25693
+ const result = await createClient(options).replay({
25694
+ eventId: actionOptions.id,
25695
+ source: actionOptions.source,
25696
+ type: actionOptions.type,
25697
+ dryRun: actionOptions.dryRun
25698
+ });
25699
+ print(result, wantsJson(actionOptions, command), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
25700
+ });
25701
+ return events;
25702
+ }
25703
+ function registerEventsCommands(program, options) {
25704
+ registerWebhookCommands(program, options);
25705
+ registerEventCommands(program, options);
25706
+ }
25707
+ function parseNumber(value) {
25708
+ const parsed = Number(value);
25709
+ if (!Number.isFinite(parsed))
25710
+ throw new Error(`Expected a number, got ${value}`);
25711
+ return parsed;
25712
+ }
25713
+ function collectValues(value, previous) {
25714
+ previous.push(value);
25715
+ return previous;
25716
+ }
25717
+
25043
25718
  // node_modules/commander/esm.mjs
25044
25719
  var import__ = __toESM(require_commander(), 1);
25045
25720
  var {
@@ -25069,12 +25744,12 @@ import chalk2 from "chalk";
25069
25744
 
25070
25745
  // src/lib/config.ts
25071
25746
  init_database();
25072
- import { existsSync as existsSync6, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync5 } from "fs";
25747
+ import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync5 } from "fs";
25073
25748
  import { join as join7 } from "path";
25074
25749
  var CONFIG_DIR3 = getDataDir2();
25075
25750
  var CONFIG_FILE = join7(CONFIG_DIR3, "config.json");
25076
25751
  function readConfig() {
25077
- if (!existsSync6(CONFIG_FILE))
25752
+ if (!existsSync4(CONFIG_FILE))
25078
25753
  return {};
25079
25754
  try {
25080
25755
  return JSON.parse(readFileSync2(CONFIG_FILE, "utf-8"));
@@ -25085,7 +25760,7 @@ function readConfig() {
25085
25760
 
25086
25761
  // src/cli/commands/core.tsx
25087
25762
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync13, copyFileSync as copyFileSync4, statSync as statSync2, mkdirSync as mkdirSync8, readdirSync as readdirSync6 } from "fs";
25088
- import { extname as extname3, join as join13 } from "path";
25763
+ import { extname as extname3, join as join14 } from "path";
25089
25764
 
25090
25765
  // src/cli/utils.tsx
25091
25766
  import chalk from "chalk";
@@ -25859,7 +26534,7 @@ Group not found: ${groupId}
25859
26534
  });
25860
26535
  program2.command("backup").description("Backup the contacts database").option("--output <path>", "Output path").option("--list", "List existing backups").action((opts) => {
25861
26536
  const { getDataDir: getDataDir3 } = (init_database(), __toCommonJS(exports_database));
25862
- const backupDir = join13(getDataDir3(), "backups");
26537
+ const backupDir = join14(getDataDir3(), "backups");
25863
26538
  if (opts.list) {
25864
26539
  if (!existsSync13(backupDir)) {
25865
26540
  console.log(chalk2.gray(`
@@ -25878,7 +26553,7 @@ No backups found.
25878
26553
  Existing Backups:
25879
26554
  `));
25880
26555
  for (const f of files) {
25881
- const filePath = join13(backupDir, f);
26556
+ const filePath = join14(backupDir, f);
25882
26557
  const size2 = statSync2(filePath).size;
25883
26558
  const mtime = statSync2(filePath).mtime.toISOString().slice(0, 19).replace("T", " ");
25884
26559
  console.log(` ${chalk2.cyan(f)} ${chalk2.gray(`${(size2 / 1024).toFixed(1)} KB ${mtime}`)}`);
@@ -25895,7 +26570,7 @@ Database not found: ${src}
25895
26570
  }
25896
26571
  mkdirSync8(backupDir, { recursive: true });
25897
26572
  const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
25898
- const dest = opts.output || join13(backupDir, `contacts-${ts}.db`);
26573
+ const dest = opts.output || join14(backupDir, `contacts-${ts}.db`);
25899
26574
  copyFileSync4(src, dest);
25900
26575
  const size = statSync2(dest).size;
25901
26576
  console.log(chalk2.green(`
@@ -27577,4 +28252,5 @@ registerCoreCommands(program);
27577
28252
  registerCrmCommands(program);
27578
28253
  registerAdvancedCommands(program);
27579
28254
  registerCloudCommands(program, "contacts");
28255
+ registerEventsCommands(program, { source: "contacts", eventsCommandName: "event-bus" });
27580
28256
  program.parse(process.argv);