@hasna/conversations 0.2.51 → 0.2.53

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/bin/index.js CHANGED
@@ -6,39 +6,60 @@ var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ function __accessProp(key) {
10
+ return this[key];
11
+ }
12
+ var __toESMCache_node;
13
+ var __toESMCache_esm;
9
14
  var __toESM = (mod, isNodeMode, target) => {
15
+ var canCache = mod != null && typeof mod === "object";
16
+ if (canCache) {
17
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
18
+ var cached = cache.get(mod);
19
+ if (cached)
20
+ return cached;
21
+ }
10
22
  target = mod != null ? __create(__getProtoOf(mod)) : {};
11
23
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
12
24
  for (let key of __getOwnPropNames(mod))
13
25
  if (!__hasOwnProp.call(to, key))
14
26
  __defProp(to, key, {
15
- get: () => mod[key],
27
+ get: __accessProp.bind(mod, key),
16
28
  enumerable: true
17
29
  });
30
+ if (canCache)
31
+ cache.set(mod, to);
18
32
  return to;
19
33
  };
20
- var __moduleCache = /* @__PURE__ */ new WeakMap;
21
34
  var __toCommonJS = (from) => {
22
- var entry = __moduleCache.get(from), desc;
35
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
23
36
  if (entry)
24
37
  return entry;
25
38
  entry = __defProp({}, "__esModule", { value: true });
26
- if (from && typeof from === "object" || typeof from === "function")
27
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
- get: () => from[key],
29
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
- }));
39
+ if (from && typeof from === "object" || typeof from === "function") {
40
+ for (var key of __getOwnPropNames(from))
41
+ if (!__hasOwnProp.call(entry, key))
42
+ __defProp(entry, key, {
43
+ get: __accessProp.bind(from, key),
44
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
45
+ });
46
+ }
31
47
  __moduleCache.set(from, entry);
32
48
  return entry;
33
49
  };
50
+ var __moduleCache;
34
51
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
52
+ var __returnValue = (v) => v;
53
+ function __exportSetter(name, newValue) {
54
+ this[name] = __returnValue.bind(null, newValue);
55
+ }
35
56
  var __export = (target, all) => {
36
57
  for (var name in all)
37
58
  __defProp(target, name, {
38
59
  get: all[name],
39
60
  enumerable: true,
40
61
  configurable: true,
41
- set: (newValue) => all[name] = () => newValue
62
+ set: __exportSetter.bind(all, name)
42
63
  });
43
64
  };
44
65
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -858,7 +879,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
858
879
  this._exitCallback = (err) => {
859
880
  if (err.code !== "commander.executeSubCommandAsync") {
860
881
  throw err;
861
- } else {}
882
+ }
862
883
  };
863
884
  }
864
885
  return this;
@@ -2308,16 +2329,16 @@ __export(exports_dist, {
2308
2329
  import { createRequire } from "module";
2309
2330
  import { Database } from "bun:sqlite";
2310
2331
  import {
2311
- existsSync,
2332
+ existsSync as existsSync2,
2312
2333
  mkdirSync,
2313
2334
  readdirSync,
2314
2335
  copyFileSync
2315
2336
  } from "fs";
2316
- import { homedir } from "os";
2317
- import { join, relative } from "path";
2318
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
2319
2337
  import { homedir as homedir2 } from "os";
2320
- import { join as join2 } from "path";
2338
+ import { join as join2, relative } from "path";
2339
+ import { existsSync as existsSync22, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
2340
+ import { homedir as homedir22 } from "os";
2341
+ import { join as join22 } from "path";
2321
2342
  import { readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
2322
2343
  import { join as join3 } from "path";
2323
2344
  import { homedir as homedir3 } from "os";
@@ -2330,11 +2351,11 @@ import { join as join5 } from "path";
2330
2351
  import { join as join6, dirname } from "path";
2331
2352
  import { existsSync as existsSync6, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
2332
2353
  import { homedir as homedir5, platform } from "os";
2333
- function __accessProp(key) {
2354
+ function __accessProp2(key) {
2334
2355
  return this[key];
2335
2356
  }
2336
- function __exportSetter(name, newValue) {
2337
- this[name] = __returnValue.bind(null, newValue);
2357
+ function __exportSetter2(name, newValue) {
2358
+ this[name] = __returnValue2.bind(null, newValue);
2338
2359
  }
2339
2360
  function translateSql(sql, dialect) {
2340
2361
  if (dialect === "sqlite")
@@ -3211,20 +3232,20 @@ function custom(check, _params = {}, fatal) {
3211
3232
  return ZodAny.create();
3212
3233
  }
3213
3234
  function getDataDir(serviceName) {
3214
- const dir = join(HASNA_DIR, serviceName);
3235
+ const dir = join2(HASNA_DIR, serviceName);
3215
3236
  mkdirSync(dir, { recursive: true });
3216
3237
  return dir;
3217
3238
  }
3218
3239
  function getDbPath(serviceName) {
3219
3240
  const dir = getDataDir(serviceName);
3220
- return join(dir, `${serviceName}.db`);
3241
+ return join2(dir, `${serviceName}.db`);
3221
3242
  }
3222
3243
  function migrateDotfile(serviceName) {
3223
- const legacyDir = join(homedir(), `.${serviceName}`);
3224
- const newDir = join(HASNA_DIR, serviceName);
3225
- if (!existsSync(legacyDir))
3244
+ const legacyDir = join2(homedir2(), `.${serviceName}`);
3245
+ const newDir = join2(HASNA_DIR, serviceName);
3246
+ if (!existsSync2(legacyDir))
3226
3247
  return [];
3227
- if (existsSync(newDir))
3248
+ if (existsSync2(newDir))
3228
3249
  return [];
3229
3250
  mkdirSync(newDir, { recursive: true });
3230
3251
  const migrated = [];
@@ -3234,8 +3255,8 @@ function migrateDotfile(serviceName) {
3234
3255
  function copyDirRecursive(src, dest, root, migrated) {
3235
3256
  const entries = readdirSync(src, { withFileTypes: true });
3236
3257
  for (const entry of entries) {
3237
- const srcPath = join(src, entry.name);
3238
- const destPath = join(dest, entry.name);
3258
+ const srcPath = join2(src, entry.name);
3259
+ const destPath = join2(dest, entry.name);
3239
3260
  if (entry.isDirectory()) {
3240
3261
  mkdirSync(destPath, { recursive: true });
3241
3262
  copyDirRecursive(srcPath, destPath, root, migrated);
@@ -3246,7 +3267,7 @@ function copyDirRecursive(src, dest, root, migrated) {
3246
3267
  }
3247
3268
  }
3248
3269
  function hasLegacyDotfile(serviceName) {
3249
- return existsSync(join(homedir(), `.${serviceName}`));
3270
+ return existsSync2(join2(homedir2(), `.${serviceName}`));
3250
3271
  }
3251
3272
  function getHasnaDir() {
3252
3273
  mkdirSync(HASNA_DIR, { recursive: true });
@@ -3259,7 +3280,7 @@ function getConfigPath() {
3259
3280
  return CONFIG_PATH;
3260
3281
  }
3261
3282
  function getCloudConfig() {
3262
- if (!existsSync2(CONFIG_PATH)) {
3283
+ if (!existsSync22(CONFIG_PATH)) {
3263
3284
  return CloudConfigSchema.parse({});
3264
3285
  }
3265
3286
  try {
@@ -3780,17 +3801,17 @@ function ensureFeedbackTable(db) {
3780
3801
  function saveFeedback(db, feedback) {
3781
3802
  ensureFeedbackTable(db);
3782
3803
  const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
3783
- const now = new Date().toISOString();
3804
+ const now2 = new Date().toISOString();
3784
3805
  const machineId = feedback.machine_id ?? hostname();
3785
3806
  db.run(`INSERT INTO feedback (id, service, version, message, email, machine_id, created_at)
3786
- VALUES (?, ?, ?, ?, ?, ?, ?)`, id, feedback.service, feedback.version ?? "", feedback.message, feedback.email ?? "", machineId, feedback.created_at ?? now);
3807
+ VALUES (?, ?, ?, ?, ?, ?, ?)`, id, feedback.service, feedback.version ?? "", feedback.message, feedback.email ?? "", machineId, feedback.created_at ?? now2);
3787
3808
  return id;
3788
3809
  }
3789
3810
  async function sendFeedback(feedback, db) {
3790
3811
  const config = getCloudConfig();
3791
3812
  const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
3792
3813
  const machineId = feedback.machine_id ?? hostname();
3793
- const now = new Date().toISOString();
3814
+ const now2 = new Date().toISOString();
3794
3815
  const payload = {
3795
3816
  id,
3796
3817
  service: feedback.service,
@@ -3798,7 +3819,7 @@ async function sendFeedback(feedback, db) {
3798
3819
  message: feedback.message,
3799
3820
  email: feedback.email ?? "",
3800
3821
  machine_id: machineId,
3801
- created_at: feedback.created_at ?? now
3822
+ created_at: feedback.created_at ?? now2
3802
3823
  };
3803
3824
  try {
3804
3825
  const res = await fetch(config.feedback_endpoint, {
@@ -3854,8 +3875,8 @@ class SyncProgressTracker {
3854
3875
  }
3855
3876
  start(table, total, direction) {
3856
3877
  const resumed = this.canResume(table);
3857
- const now = Date.now();
3858
- this.startTimes.set(table, now);
3878
+ const now2 = Date.now();
3879
+ this.startTimes.set(table, now2);
3859
3880
  const status = resumed ? "resumed" : "in_progress";
3860
3881
  const info = {
3861
3882
  table,
@@ -4177,10 +4198,10 @@ function incrementalSyncPush(local, remote, tables, options = {}) {
4177
4198
  if (rows.length === 0) {
4178
4199
  stat.skipped_rows = stat.total_rows;
4179
4200
  }
4180
- const now = new Date().toISOString();
4201
+ const now2 = new Date().toISOString();
4181
4202
  upsertSyncMeta(local, {
4182
4203
  table_name: table,
4183
- last_synced_at: now,
4204
+ last_synced_at: now2,
4184
4205
  last_synced_row_count: stat.synced_rows,
4185
4206
  direction: "push"
4186
4207
  });
@@ -4230,10 +4251,10 @@ function incrementalSyncPull(remote, local, tables, options = {}) {
4230
4251
  if (rows.length === 0) {
4231
4252
  stat.skipped_rows = stat.total_rows;
4232
4253
  }
4233
- const now = new Date().toISOString();
4254
+ const now2 = new Date().toISOString();
4234
4255
  upsertSyncMeta(local, {
4235
4256
  table_name: table,
4236
- last_synced_at: now,
4257
+ last_synced_at: now2,
4237
4258
  last_synced_row_count: stat.synced_rows,
4238
4259
  direction: "pull"
4239
4260
  });
@@ -4975,10 +4996,10 @@ function registerCloudCommands(program2, serviceName) {
4975
4996
  }
4976
4997
  });
4977
4998
  }
4978
- var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESMCache_node, __toESMCache_esm, __toESM2 = (mod, isNodeMode, target) => {
4999
+ var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __toESMCache_node2, __toESMCache_esm2, __toESM2 = (mod, isNodeMode, target) => {
4979
5000
  var canCache = mod != null && typeof mod === "object";
4980
5001
  if (canCache) {
4981
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
5002
+ var cache = isNodeMode ? __toESMCache_node2 ??= new WeakMap : __toESMCache_esm2 ??= new WeakMap;
4982
5003
  var cached = cache.get(mod);
4983
5004
  if (cached)
4984
5005
  return cached;
@@ -4988,19 +5009,19 @@ var __create2, __getProtoOf2, __defProp2, __getOwnPropNames2, __hasOwnProp2, __t
4988
5009
  for (let key of __getOwnPropNames2(mod))
4989
5010
  if (!__hasOwnProp2.call(to, key))
4990
5011
  __defProp2(to, key, {
4991
- get: __accessProp.bind(mod, key),
5012
+ get: __accessProp2.bind(mod, key),
4992
5013
  enumerable: true
4993
5014
  });
4994
5015
  if (canCache)
4995
5016
  cache.set(mod, to);
4996
5017
  return to;
4997
- }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue = (v) => v, __export2 = (target, all) => {
5018
+ }, __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports), __returnValue2 = (v) => v, __export2 = (target, all) => {
4998
5019
  for (var name in all)
4999
5020
  __defProp2(target, name, {
5000
5021
  get: all[name],
5001
5022
  enumerable: true,
5002
5023
  configurable: true,
5003
- set: __exportSetter.bind(all, name)
5024
+ set: __exportSetter2.bind(all, name)
5004
5025
  });
5005
5026
  }, __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), __require2, require_postgres_array, require_arrayParser, require_postgres_date, require_mutable, require_postgres_interval, require_postgres_bytea, require_textParsers, require_pg_int8, require_binaryParsers, require_builtins, require_pg_types, require_defaults, require_utils, require_utils_legacy, require_utils_webcrypto, require_utils2, require_cert_signatures, require_sasl, require_type_overrides, require_pg_connection_string, require_connection_parameters, require_result, require_query, require_messages, require_buffer_writer, require_serializer, require_buffer_reader, require_parser, require_dist, require_empty, require_stream, require_connection, require_split2, require_helper, require_lib, require_client, require_pg_pool, require_query2, require_client2, require_lib2, import_lib, Client, Pool, Connection, types, Query, DatabaseError, escapeIdentifier, escapeLiteral, Result, TypeOverrides, defaults, esm_default, init_esm, init_adapter, util, objectUtil, ZodParsedType, getParsedType = (data) => {
5006
5027
  const t = typeof data;
@@ -13204,7 +13225,7 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
13204
13225
  init_external();
13205
13226
  });
13206
13227
  init_dotfile = __esm2(() => {
13207
- HASNA_DIR = join(homedir(), ".hasna");
13228
+ HASNA_DIR = join2(homedir2(), ".hasna");
13208
13229
  });
13209
13230
  exports_config = {};
13210
13231
  __export2(exports_config, {
@@ -13235,8 +13256,8 @@ See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode de
13235
13256
  schedule_minutes: exports_external.number().default(0)
13236
13257
  }).default({})
13237
13258
  });
13238
- CONFIG_DIR = join2(homedir2(), ".hasna", "cloud");
13239
- CONFIG_PATH = join2(CONFIG_DIR, "config.json");
13259
+ CONFIG_DIR = join22(homedir22(), ".hasna", "cloud");
13260
+ CONFIG_PATH = join22(CONFIG_DIR, "config.json");
13240
13261
  });
13241
13262
  exports_discover = {};
13242
13263
  __export2(exports_discover, {
@@ -14255,17 +14276,17 @@ function getConfigPath2() {
14255
14276
  return process.env.CONVERSATIONS_CONFIG_PATH || join10(getDataDir2(), "config.json");
14256
14277
  }
14257
14278
  function loadConfig() {
14258
- const now = Date.now();
14259
- if (cachedConfig && now - configLoadedAt < CONFIG_CACHE_MS)
14279
+ const now2 = Date.now();
14280
+ if (cachedConfig && now2 - configLoadedAt < CONFIG_CACHE_MS)
14260
14281
  return cachedConfig;
14261
14282
  try {
14262
14283
  const raw = readFileSync5(getConfigPath2(), "utf-8");
14263
14284
  cachedConfig = JSON.parse(raw);
14264
- configLoadedAt = now;
14285
+ configLoadedAt = now2;
14265
14286
  return cachedConfig;
14266
14287
  } catch {
14267
14288
  cachedConfig = {};
14268
- configLoadedAt = now;
14289
+ configLoadedAt = now2;
14269
14290
  return cachedConfig;
14270
14291
  }
14271
14292
  }
@@ -14376,7 +14397,7 @@ var init_webhooks = __esm(() => {
14376
14397
  });
14377
14398
 
14378
14399
  // src/lib/messages.ts
14379
- import { randomUUID } from "crypto";
14400
+ import { randomUUID as randomUUID3 } from "crypto";
14380
14401
  import { mkdirSync as mkdirSync8, copyFileSync as copyFileSync3, statSync as statSync2, existsSync as existsSync10, realpathSync } from "fs";
14381
14402
  import { join as join11, basename, resolve } from "path";
14382
14403
  function compactMessage(msg) {
@@ -14466,10 +14487,10 @@ function checkRateLimit(agentId) {
14466
14487
  const dbPath = process.env.CONVERSATIONS_DB_PATH ?? process.env.HASNA_CONVERSATIONS_DB_PATH ?? "";
14467
14488
  if (dbPath === ":memory:" || dbPath.includes("test") || dbPath.includes("tmp"))
14468
14489
  return;
14469
- const now = Date.now();
14490
+ const now2 = Date.now();
14470
14491
  const entry = _rateLimitCounters.get(agentId);
14471
- if (!entry || now - entry.windowStart > RATE_LIMIT_WINDOW_MS) {
14472
- _rateLimitCounters.set(agentId, { count: 1, windowStart: now });
14492
+ if (!entry || now2 - entry.windowStart > RATE_LIMIT_WINDOW_MS) {
14493
+ _rateLimitCounters.set(agentId, { count: 1, windowStart: now2 });
14473
14494
  return;
14474
14495
  }
14475
14496
  entry.count++;
@@ -14484,12 +14505,12 @@ function sendMessage(opts) {
14484
14505
  checkRateLimit(opts.from);
14485
14506
  const db2 = getDb();
14486
14507
  const explicitSession = opts.session_id && opts.session_id.trim().length > 0 ? opts.session_id : undefined;
14487
- const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID().slice(0, 8)}`);
14508
+ const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID3().slice(0, 8)}`);
14488
14509
  const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
14489
14510
  const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
14490
14511
  const blocking = opts.blocking ? 1 : 0;
14491
14512
  const replyTo = opts.reply_to || null;
14492
- const msgUuid = randomUUID().replace(/-/g, "");
14513
+ const msgUuid = randomUUID3().replace(/-/g, "");
14493
14514
  const stmt = db2.prepare(`
14494
14515
  INSERT INTO messages (uuid, session_id, from_agent, to_agent, space, project_id, content, priority, working_dir, repository, branch, metadata, blocking, reply_to)
14495
14516
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -15528,7 +15549,7 @@ var init_space_notifications = __esm(() => {
15528
15549
  var require_package = __commonJS((exports, module) => {
15529
15550
  module.exports = {
15530
15551
  name: "@hasna/conversations",
15531
- version: "0.2.51",
15552
+ version: "0.2.53",
15532
15553
  description: "Real-time CLI messaging for AI agents",
15533
15554
  type: "module",
15534
15555
  bin: {
@@ -15579,7 +15600,8 @@ var require_package = __commonJS((exports, module) => {
15579
15600
  typescript: "^5"
15580
15601
  },
15581
15602
  dependencies: {
15582
- "@hasna/cloud": "^0.1.30",
15603
+ "@hasna/cloud": "0.1.30",
15604
+ "@hasna/events": "^0.1.6",
15583
15605
  "@modelcontextprotocol/sdk": "^1.26.0",
15584
15606
  chalk: "^5.3.0",
15585
15607
  commander: "^12.1.0",
@@ -15610,7 +15632,7 @@ var require_package = __commonJS((exports, module) => {
15610
15632
  });
15611
15633
 
15612
15634
  // src/lib/projects.ts
15613
- import { randomUUID as randomUUID2 } from "crypto";
15635
+ import { randomUUID as randomUUID5 } from "crypto";
15614
15636
  function parseProject(row) {
15615
15637
  let metadata = null;
15616
15638
  if (row.metadata) {
@@ -15652,7 +15674,7 @@ function parseProject(row) {
15652
15674
  }
15653
15675
  function createProject(opts) {
15654
15676
  const db2 = getDb();
15655
- const id = randomUUID2();
15677
+ const id = randomUUID5();
15656
15678
  const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
15657
15679
  const tags = opts.tags ? JSON.stringify(opts.tags) : null;
15658
15680
  const settings = opts.settings ? JSON.stringify(opts.settings) : null;
@@ -30961,7 +30983,7 @@ function finalize(ctx, schema) {
30961
30983
  result.$schema = "http://json-schema.org/draft-07/schema#";
30962
30984
  } else if (ctx.target === "draft-04") {
30963
30985
  result.$schema = "http://json-schema.org/draft-04/schema#";
30964
- } else if (ctx.target === "openapi-3.0") {} else {}
30986
+ } else if (ctx.target === "openapi-3.0") {}
30965
30987
  if (ctx.external?.uri) {
30966
30988
  const id = ctx.external.registry.get(schema)?.id;
30967
30989
  if (!id)
@@ -31226,7 +31248,7 @@ var formatMap, stringProcessor = (schema, ctx, _json, _params) => {
31226
31248
  if (val === undefined) {
31227
31249
  if (ctx.unrepresentable === "throw") {
31228
31250
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
31229
- } else {}
31251
+ }
31230
31252
  } else if (typeof val === "bigint") {
31231
31253
  if (ctx.unrepresentable === "throw") {
31232
31254
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -45366,18 +45388,18 @@ function registerMessagingTools(server, resolveProjectId) {
45366
45388
  const { target_session_id, content, from: fromParam, priority } = args;
45367
45389
  const from = resolveIdentity(fromParam);
45368
45390
  const rateKey = `session:${from}:${target_session_id}`;
45369
- const now = Date.now();
45391
+ const now2 = Date.now();
45370
45392
  const windowMs = 60000;
45371
45393
  const maxPerWindow = 10;
45372
45394
  const rateEntry = _sessionInjectRate.get(rateKey);
45373
- if (rateEntry && now - rateEntry.start < windowMs && rateEntry.count >= maxPerWindow) {
45395
+ if (rateEntry && now2 - rateEntry.start < windowMs && rateEntry.count >= maxPerWindow) {
45374
45396
  return {
45375
45397
  content: [{ type: "text", text: `Rate limit: max ${maxPerWindow} session injections per minute. Try again soon.` }],
45376
45398
  isError: true
45377
45399
  };
45378
45400
  }
45379
- if (!rateEntry || now - rateEntry.start >= windowMs) {
45380
- _sessionInjectRate.set(rateKey, { count: 1, start: now });
45401
+ if (!rateEntry || now2 - rateEntry.start >= windowMs) {
45402
+ _sessionInjectRate.set(rateKey, { count: 1, start: now2 });
45381
45403
  } else {
45382
45404
  rateEntry.count++;
45383
45405
  }
@@ -48102,7 +48124,7 @@ var init_tmux2 = __esm(() => {
48102
48124
  });
48103
48125
 
48104
48126
  // src/lib/tasks.ts
48105
- import { randomUUID as randomUUID3 } from "crypto";
48127
+ import { randomUUID as randomUUID6 } from "crypto";
48106
48128
  function parseTask(row) {
48107
48129
  let dependsOn = null;
48108
48130
  if (row.depends_on) {
@@ -48172,7 +48194,7 @@ function emitTaskEvent(task, action, agent, oldStatus, detail) {
48172
48194
  }
48173
48195
  function createTask(opts) {
48174
48196
  const db2 = getDb();
48175
- const uuid3 = randomUUID3().replace(/-/g, "");
48197
+ const uuid3 = randomUUID6().replace(/-/g, "");
48176
48198
  const priority = opts.priority || "medium";
48177
48199
  const description = opts.description || null;
48178
48200
  const assignee = opts.assignee || null;
@@ -48321,9 +48343,9 @@ function startTask(id, agent) {
48321
48343
  if (incompleteDeps.length > 0) {
48322
48344
  throw new Error(`Cannot start: blocked by ${incompleteDeps.length} incomplete task(s): ${incompleteDeps.map((d) => `#${d.depends_on_id} "${d.subject}" (${d.status})`).join(", ")}`);
48323
48345
  }
48324
- const now = new Date().toISOString();
48346
+ const now2 = new Date().toISOString();
48325
48347
  const oldStatus = task.status;
48326
- db2.prepare("UPDATE tasks SET status = 'in_progress', started_at = ? WHERE id = ?").run(now, task.id);
48348
+ db2.prepare("UPDATE tasks SET status = 'in_progress', started_at = ? WHERE id = ?").run(now2, task.id);
48327
48349
  logActivity(task.id, agent || task.reporter, "started");
48328
48350
  const updated = getTaskById(task.id);
48329
48351
  if (updated)
@@ -48335,9 +48357,9 @@ function completeTask(id, agent, opts) {
48335
48357
  const task = resolveTask(id);
48336
48358
  if (!task)
48337
48359
  return null;
48338
- const now = new Date().toISOString();
48360
+ const now2 = new Date().toISOString();
48339
48361
  const oldStatus = task.status;
48340
- db2.prepare("UPDATE tasks SET status = 'completed', completed_at = ? WHERE id = ?").run(now, task.id);
48362
+ db2.prepare("UPDATE tasks SET status = 'completed', completed_at = ? WHERE id = ?").run(now2, task.id);
48341
48363
  logActivity(task.id, agent || task.reporter, "completed", opts?.evidence);
48342
48364
  unblockDependents(task.id);
48343
48365
  const updated = getTaskById(task.id);
@@ -48350,9 +48372,9 @@ function cancelTask(id, agent, opts) {
48350
48372
  const task = resolveTask(id);
48351
48373
  if (!task)
48352
48374
  return null;
48353
- const now = new Date().toISOString();
48375
+ const now2 = new Date().toISOString();
48354
48376
  const oldStatus = task.status;
48355
- db2.prepare("UPDATE tasks SET status = 'cancelled', cancelled_at = ? WHERE id = ?").run(now, task.id);
48377
+ db2.prepare("UPDATE tasks SET status = 'cancelled', cancelled_at = ? WHERE id = ?").run(now2, task.id);
48356
48378
  logActivity(task.id, agent || task.reporter, "cancelled", opts?.reason);
48357
48379
  const updated = getTaskById(task.id);
48358
48380
  if (updated)
@@ -48663,8 +48685,8 @@ function searchTasks(opts) {
48663
48685
  function getDueTasks(opts = {}) {
48664
48686
  const db2 = getDb();
48665
48687
  const windowHours = opts.window_hours ?? 24;
48666
- const now = new Date;
48667
- const deadline = new Date(now.getTime() + windowHours * 60 * 60 * 1000);
48688
+ const now2 = new Date;
48689
+ const deadline = new Date(now2.getTime() + windowHours * 60 * 60 * 1000);
48668
48690
  const rows = db2.prepare(`
48669
48691
  SELECT t.* FROM tasks t
48670
48692
  WHERE t.due_at IS NOT NULL
@@ -48675,7 +48697,7 @@ function getDueTasks(opts = {}) {
48675
48697
  return rows.map((row) => {
48676
48698
  const task = enrichTask(row);
48677
48699
  const dueAt = new Date(task.due_at);
48678
- const hoursUntilDue = (dueAt.getTime() - now.getTime()) / (1000 * 60 * 60);
48700
+ const hoursUntilDue = (dueAt.getTime() - now2.getTime()) / (1000 * 60 * 60);
48679
48701
  let urgency;
48680
48702
  if (hoursUntilDue < 0)
48681
48703
  urgency = "overdue";
@@ -50379,6 +50401,690 @@ var init_serve = __esm(() => {
50379
50401
  }
50380
50402
  });
50381
50403
 
50404
+ // node_modules/@hasna/events/dist/commander.js
50405
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
50406
+ import { existsSync } from "fs";
50407
+ import { homedir } from "os";
50408
+ import { join } from "path";
50409
+ import { createHmac, timingSafeEqual } from "crypto";
50410
+ import { randomUUID } from "crypto";
50411
+ import { spawn } from "child_process";
50412
+ import { randomUUID as randomUUID2 } from "crypto";
50413
+ function getPathValue(input, path) {
50414
+ return path.split(".").reduce((value, part) => {
50415
+ if (value && typeof value === "object" && part in value) {
50416
+ return value[part];
50417
+ }
50418
+ return;
50419
+ }, input);
50420
+ }
50421
+ function wildcardToRegExp(pattern) {
50422
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
50423
+ return new RegExp(`^${escaped}$`);
50424
+ }
50425
+ function matchString(value, matcher) {
50426
+ if (matcher === undefined)
50427
+ return true;
50428
+ if (value === undefined)
50429
+ return false;
50430
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
50431
+ return matchers.some((item) => wildcardToRegExp(item).test(value));
50432
+ }
50433
+ function matchRecord(input, matcher) {
50434
+ if (!matcher)
50435
+ return true;
50436
+ return Object.entries(matcher).every(([path, expected]) => {
50437
+ const actual = getPathValue(input, path);
50438
+ if (typeof expected === "string" || Array.isArray(expected)) {
50439
+ return matchString(actual === undefined ? undefined : String(actual), expected);
50440
+ }
50441
+ return actual === expected;
50442
+ });
50443
+ }
50444
+ function eventMatchesFilter(event, filter) {
50445
+ 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);
50446
+ }
50447
+ function channelMatchesEvent(channel, event) {
50448
+ if (!channel.enabled)
50449
+ return false;
50450
+ if (!channel.filters || channel.filters.length === 0)
50451
+ return true;
50452
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
50453
+ }
50454
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
50455
+ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
50456
+ function getEventsDataDir(override) {
50457
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
50458
+ }
50459
+
50460
+ class JsonEventsStore {
50461
+ dataDir;
50462
+ channelsPath;
50463
+ eventsPath;
50464
+ deliveriesPath;
50465
+ constructor(dataDir = getEventsDataDir()) {
50466
+ this.dataDir = dataDir;
50467
+ this.channelsPath = join(dataDir, "channels.json");
50468
+ this.eventsPath = join(dataDir, "events.json");
50469
+ this.deliveriesPath = join(dataDir, "deliveries.json");
50470
+ }
50471
+ async init() {
50472
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
50473
+ await chmod(this.dataDir, 448).catch(() => {
50474
+ return;
50475
+ });
50476
+ await this.ensureArrayFile(this.channelsPath);
50477
+ await this.ensureArrayFile(this.eventsPath);
50478
+ await this.ensureArrayFile(this.deliveriesPath);
50479
+ }
50480
+ async addChannel(channel) {
50481
+ await this.init();
50482
+ const channels = await this.readJson(this.channelsPath, []);
50483
+ const index = channels.findIndex((item) => item.id === channel.id);
50484
+ if (index >= 0) {
50485
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
50486
+ } else {
50487
+ channels.push(channel);
50488
+ }
50489
+ await this.writeJson(this.channelsPath, channels);
50490
+ return index >= 0 ? channels[index] : channel;
50491
+ }
50492
+ async listChannels() {
50493
+ await this.init();
50494
+ return this.readJson(this.channelsPath, []);
50495
+ }
50496
+ async getChannel(id) {
50497
+ const channels = await this.listChannels();
50498
+ return channels.find((channel) => channel.id === id);
50499
+ }
50500
+ async removeChannel(id) {
50501
+ await this.init();
50502
+ const channels = await this.readJson(this.channelsPath, []);
50503
+ const next = channels.filter((channel) => channel.id !== id);
50504
+ await this.writeJson(this.channelsPath, next);
50505
+ return next.length !== channels.length;
50506
+ }
50507
+ async appendEvent(event) {
50508
+ await this.init();
50509
+ const events = await this.readJson(this.eventsPath, []);
50510
+ events.push(event);
50511
+ await this.writeJson(this.eventsPath, events);
50512
+ return event;
50513
+ }
50514
+ async listEvents() {
50515
+ await this.init();
50516
+ return this.readJson(this.eventsPath, []);
50517
+ }
50518
+ async findEventByIdentity(identity) {
50519
+ const events = await this.listEvents();
50520
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
50521
+ }
50522
+ async appendDelivery(result) {
50523
+ await this.init();
50524
+ const deliveries = await this.readJson(this.deliveriesPath, []);
50525
+ deliveries.push(result);
50526
+ await this.writeJson(this.deliveriesPath, deliveries);
50527
+ return result;
50528
+ }
50529
+ async listDeliveries() {
50530
+ await this.init();
50531
+ return this.readJson(this.deliveriesPath, []);
50532
+ }
50533
+ async exportData() {
50534
+ return {
50535
+ channels: await this.listChannels(),
50536
+ events: await this.listEvents(),
50537
+ deliveries: await this.listDeliveries()
50538
+ };
50539
+ }
50540
+ async ensureArrayFile(path) {
50541
+ if (!existsSync(path)) {
50542
+ await writeFile(path, `[]
50543
+ `, { encoding: "utf-8", mode: 384 });
50544
+ }
50545
+ await chmod(path, 384).catch(() => {
50546
+ return;
50547
+ });
50548
+ }
50549
+ async readJson(path, fallback) {
50550
+ try {
50551
+ const raw = await readFile(path, "utf-8");
50552
+ if (!raw.trim())
50553
+ return fallback;
50554
+ return JSON.parse(raw);
50555
+ } catch (error) {
50556
+ if (error.code === "ENOENT")
50557
+ return fallback;
50558
+ throw error;
50559
+ }
50560
+ }
50561
+ async writeJson(path, value) {
50562
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
50563
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
50564
+ `, { encoding: "utf-8", mode: 384 });
50565
+ await rename(tempPath, path);
50566
+ await chmod(path, 384).catch(() => {
50567
+ return;
50568
+ });
50569
+ }
50570
+ }
50571
+ var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
50572
+ function buildSignatureBase(timestamp, body) {
50573
+ return `${timestamp}.${body}`;
50574
+ }
50575
+ function signPayload(secret, timestamp, body) {
50576
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
50577
+ return `sha256=${digest}`;
50578
+ }
50579
+ function now() {
50580
+ return new Date().toISOString();
50581
+ }
50582
+ function truncate(value, max = 4096) {
50583
+ return value.length > max ? `${value.slice(0, max)}...` : value;
50584
+ }
50585
+ function buildWebhookRequest(event, channel) {
50586
+ if (!channel.webhook)
50587
+ throw new Error(`Channel ${channel.id} has no webhook config`);
50588
+ const body = JSON.stringify(event);
50589
+ const timestamp = event.time;
50590
+ const headers = {
50591
+ "Content-Type": "application/json",
50592
+ "User-Agent": "@hasna/events",
50593
+ "X-Hasna-Event-Id": event.id,
50594
+ "X-Hasna-Event-Type": event.type,
50595
+ "X-Hasna-Timestamp": timestamp,
50596
+ ...channel.webhook.headers
50597
+ };
50598
+ if (channel.webhook.secret) {
50599
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
50600
+ }
50601
+ return { body, headers };
50602
+ }
50603
+ async function dispatchWebhook(event, channel, options = {}) {
50604
+ if (!channel.webhook)
50605
+ throw new Error(`Channel ${channel.id} has no webhook config`);
50606
+ const startedAt = now();
50607
+ const { body, headers } = buildWebhookRequest(event, channel);
50608
+ const controller = new AbortController;
50609
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
50610
+ try {
50611
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
50612
+ method: "POST",
50613
+ headers,
50614
+ body,
50615
+ signal: controller.signal
50616
+ });
50617
+ const responseBody = truncate(await response.text());
50618
+ return {
50619
+ attempt: 1,
50620
+ status: response.ok ? "success" : "failed",
50621
+ startedAt,
50622
+ completedAt: now(),
50623
+ responseStatus: response.status,
50624
+ responseBody,
50625
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
50626
+ };
50627
+ } catch (error) {
50628
+ return {
50629
+ attempt: 1,
50630
+ status: "failed",
50631
+ startedAt,
50632
+ completedAt: now(),
50633
+ error: error instanceof Error ? error.message : String(error)
50634
+ };
50635
+ } finally {
50636
+ clearTimeout(timeout);
50637
+ }
50638
+ }
50639
+ async function dispatchCommand(event, channel) {
50640
+ if (!channel.command)
50641
+ throw new Error(`Channel ${channel.id} has no command config`);
50642
+ const startedAt = now();
50643
+ const eventJson = JSON.stringify(event);
50644
+ const env = {
50645
+ ...process.env,
50646
+ ...channel.command.env,
50647
+ HASNA_CHANNEL_ID: channel.id,
50648
+ HASNA_EVENT_ID: event.id,
50649
+ HASNA_EVENT_TYPE: event.type,
50650
+ HASNA_EVENT_SOURCE: event.source,
50651
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
50652
+ HASNA_EVENT_SEVERITY: event.severity,
50653
+ HASNA_EVENT_TIME: event.time,
50654
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
50655
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
50656
+ HASNA_EVENT_JSON: eventJson
50657
+ };
50658
+ return new Promise((resolve) => {
50659
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
50660
+ cwd: channel.command.cwd,
50661
+ env,
50662
+ stdio: ["pipe", "pipe", "pipe"]
50663
+ });
50664
+ let stdout = "";
50665
+ let stderr = "";
50666
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
50667
+ child.stdin.end(eventJson);
50668
+ child.stdout.on("data", (chunk) => {
50669
+ stdout += chunk.toString();
50670
+ });
50671
+ child.stderr.on("data", (chunk) => {
50672
+ stderr += chunk.toString();
50673
+ });
50674
+ child.on("error", (error) => {
50675
+ clearTimeout(timeout);
50676
+ resolve({
50677
+ attempt: 1,
50678
+ status: "failed",
50679
+ startedAt,
50680
+ completedAt: now(),
50681
+ stdout: truncate(stdout),
50682
+ stderr: truncate(stderr),
50683
+ error: error.message
50684
+ });
50685
+ });
50686
+ child.on("close", (code, signal) => {
50687
+ clearTimeout(timeout);
50688
+ const success = code === 0;
50689
+ resolve({
50690
+ attempt: 1,
50691
+ status: success ? "success" : "failed",
50692
+ startedAt,
50693
+ completedAt: now(),
50694
+ stdout: truncate(stdout),
50695
+ stderr: truncate(stderr),
50696
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
50697
+ });
50698
+ });
50699
+ });
50700
+ }
50701
+ async function dispatchChannel(event, channel, options = {}) {
50702
+ if (channel.transport === "webhook")
50703
+ return dispatchWebhook(event, channel, options);
50704
+ if (channel.transport === "command")
50705
+ return dispatchCommand(event, channel);
50706
+ return {
50707
+ attempt: 1,
50708
+ status: "skipped",
50709
+ startedAt: now(),
50710
+ completedAt: now(),
50711
+ error: `Unsupported transport: ${channel.transport}`
50712
+ };
50713
+ }
50714
+ function createDeliveryResult(event, channel, attempts) {
50715
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
50716
+ return {
50717
+ id: randomUUID(),
50718
+ eventId: event.id,
50719
+ channelId: channel.id,
50720
+ transport: channel.transport,
50721
+ status,
50722
+ attempts,
50723
+ createdAt: attempts[0]?.startedAt ?? now(),
50724
+ completedAt: attempts.at(-1)?.completedAt ?? now()
50725
+ };
50726
+ }
50727
+ function createEvent(input) {
50728
+ return {
50729
+ id: input.id ?? randomUUID2(),
50730
+ source: input.source,
50731
+ type: input.type,
50732
+ time: normalizeTime(input.time),
50733
+ subject: input.subject,
50734
+ severity: input.severity ?? "info",
50735
+ data: input.data ?? {},
50736
+ message: input.message,
50737
+ dedupeKey: input.dedupeKey,
50738
+ schemaVersion: input.schemaVersion ?? "1.0",
50739
+ metadata: input.metadata ?? {}
50740
+ };
50741
+ }
50742
+
50743
+ class EventsClient {
50744
+ store;
50745
+ redactors;
50746
+ transportOptions;
50747
+ constructor(options = {}) {
50748
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
50749
+ this.redactors = options.redactors ?? [];
50750
+ this.transportOptions = { fetchImpl: options.fetchImpl };
50751
+ }
50752
+ async addChannel(input) {
50753
+ const timestamp = new Date().toISOString();
50754
+ return this.store.addChannel({
50755
+ ...input,
50756
+ createdAt: input.createdAt ?? timestamp,
50757
+ updatedAt: input.updatedAt ?? timestamp
50758
+ });
50759
+ }
50760
+ async listChannels() {
50761
+ return this.store.listChannels();
50762
+ }
50763
+ async removeChannel(id) {
50764
+ return this.store.removeChannel(id);
50765
+ }
50766
+ async emit(input, options = {}) {
50767
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
50768
+ if (options.dedupe !== false) {
50769
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
50770
+ if (existing) {
50771
+ return { event: existing, deliveries: [], deduped: true };
50772
+ }
50773
+ }
50774
+ await this.store.appendEvent(event);
50775
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
50776
+ return { event, deliveries, deduped: false };
50777
+ }
50778
+ async listEvents() {
50779
+ return this.store.listEvents();
50780
+ }
50781
+ async listDeliveries() {
50782
+ return this.store.listDeliveries();
50783
+ }
50784
+ async deliver(event) {
50785
+ const channels = await this.store.listChannels();
50786
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
50787
+ const deliveries = [];
50788
+ for (const channel of selected) {
50789
+ const eventForChannel = await this.applyRedaction(event, channel);
50790
+ const result = await this.deliverWithRetry(eventForChannel, channel);
50791
+ await this.store.appendDelivery(result);
50792
+ deliveries.push(result);
50793
+ }
50794
+ return deliveries;
50795
+ }
50796
+ async testChannel(id, input = {}) {
50797
+ const channel = await this.store.getChannel(id);
50798
+ if (!channel)
50799
+ throw new Error(`Channel not found: ${id}`);
50800
+ const event = createEvent({
50801
+ source: input.source ?? "hasna.events",
50802
+ type: input.type ?? "events.test",
50803
+ subject: input.subject ?? id,
50804
+ severity: input.severity ?? "info",
50805
+ data: input.data ?? { test: true },
50806
+ message: input.message ?? "Hasna events test delivery",
50807
+ dedupeKey: input.dedupeKey,
50808
+ schemaVersion: input.schemaVersion,
50809
+ metadata: input.metadata,
50810
+ time: input.time,
50811
+ id: input.id
50812
+ });
50813
+ const eventForChannel = await this.applyRedaction(event, channel);
50814
+ const result = await this.deliverWithRetry(eventForChannel, channel);
50815
+ await this.store.appendDelivery(result);
50816
+ return result;
50817
+ }
50818
+ async replay(options = {}) {
50819
+ const events = (await this.store.listEvents()).filter((event) => {
50820
+ if (options.eventId && event.id !== options.eventId)
50821
+ return false;
50822
+ if (options.source && event.source !== options.source)
50823
+ return false;
50824
+ if (options.type && event.type !== options.type)
50825
+ return false;
50826
+ return true;
50827
+ });
50828
+ if (options.dryRun)
50829
+ return { events, deliveries: [] };
50830
+ const deliveries = [];
50831
+ for (const event of events) {
50832
+ deliveries.push(...await this.deliver(event));
50833
+ }
50834
+ return { events, deliveries };
50835
+ }
50836
+ async applyRedaction(event, channel) {
50837
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
50838
+ for (const redactor of this.redactors) {
50839
+ next = await redactor(next, channel);
50840
+ }
50841
+ return next;
50842
+ }
50843
+ async deliverWithRetry(event, channel) {
50844
+ const policy = normalizeRetryPolicy(channel.retry);
50845
+ const attempts = [];
50846
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
50847
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
50848
+ attempt.attempt = index + 1;
50849
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
50850
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
50851
+ }
50852
+ attempts.push(attempt);
50853
+ if (attempt.status !== "failed")
50854
+ break;
50855
+ if (attempt.nextBackoffMs)
50856
+ await Bun.sleep(attempt.nextBackoffMs);
50857
+ }
50858
+ return createDeliveryResult(event, channel, attempts);
50859
+ }
50860
+ }
50861
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
50862
+ if (paths.length === 0)
50863
+ return event;
50864
+ const copy = structuredClone(event);
50865
+ for (const path of paths) {
50866
+ setPath(copy, path, replacement);
50867
+ }
50868
+ return copy;
50869
+ }
50870
+ function sanitizeChannelForOutput(channel) {
50871
+ const copy = structuredClone(channel);
50872
+ if (copy.webhook?.secret)
50873
+ copy.webhook.secret = "[REDACTED]";
50874
+ if (copy.command?.env) {
50875
+ copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
50876
+ }
50877
+ return copy;
50878
+ }
50879
+ function sanitizeChannelsForOutput(channels) {
50880
+ return channels.map(sanitizeChannelForOutput);
50881
+ }
50882
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
50883
+ return redactValue(event, replacement);
50884
+ }
50885
+ function shouldRedactKey(key) {
50886
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
50887
+ }
50888
+ function redactValue(value, replacement) {
50889
+ if (Array.isArray(value))
50890
+ return value.map((item) => redactValue(item, replacement));
50891
+ if (!value || typeof value !== "object")
50892
+ return value;
50893
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
50894
+ key,
50895
+ shouldRedactKey(key) ? replacement : redactValue(item, replacement)
50896
+ ]));
50897
+ }
50898
+ function setPath(input, path, replacement) {
50899
+ const parts = path.split(".");
50900
+ let cursor = input;
50901
+ for (const part of parts.slice(0, -1)) {
50902
+ const next = cursor[part];
50903
+ if (!next || typeof next !== "object")
50904
+ return;
50905
+ cursor = next;
50906
+ }
50907
+ const last = parts.at(-1);
50908
+ if (last && last in cursor)
50909
+ cursor[last] = replacement;
50910
+ }
50911
+ function normalizeTime(value) {
50912
+ if (!value)
50913
+ return new Date().toISOString();
50914
+ return value instanceof Date ? value.toISOString() : value;
50915
+ }
50916
+ function normalizeRetryPolicy(policy) {
50917
+ return {
50918
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
50919
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
50920
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
50921
+ };
50922
+ }
50923
+ function parseJsonObject(value, fallback) {
50924
+ if (!value)
50925
+ return fallback;
50926
+ const parsed = JSON.parse(value);
50927
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
50928
+ throw new Error("Expected a JSON object");
50929
+ }
50930
+ return parsed;
50931
+ }
50932
+ function parseHeaders(values) {
50933
+ if (!values?.length)
50934
+ return;
50935
+ const headers = {};
50936
+ for (const value of values) {
50937
+ const separator = value.indexOf("=");
50938
+ if (separator === -1)
50939
+ throw new Error(`Invalid header, expected name=value: ${value}`);
50940
+ headers[value.slice(0, separator)] = value.slice(separator + 1);
50941
+ }
50942
+ return headers;
50943
+ }
50944
+ function parseFilter(options) {
50945
+ const filter2 = {};
50946
+ if (options.source)
50947
+ filter2.source = options.source;
50948
+ if (options.type)
50949
+ filter2.type = options.type;
50950
+ if (options.subject)
50951
+ filter2.subject = options.subject;
50952
+ if (options.severity)
50953
+ filter2.severity = options.severity;
50954
+ return Object.keys(filter2).length > 0 ? [filter2] : undefined;
50955
+ }
50956
+ function createClient(options) {
50957
+ if (options.createClient)
50958
+ return options.createClient();
50959
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
50960
+ }
50961
+ function print(value, json, text) {
50962
+ if (json)
50963
+ console.log(JSON.stringify(value, null, 2));
50964
+ else
50965
+ console.log(text);
50966
+ }
50967
+ function hasJsonOption(options) {
50968
+ return Boolean(options?.json || options?.opts?.().json || options?.optsWithGlobals?.().json || options?.parent?.opts?.().json || options?.parent?.optsWithGlobals?.().json);
50969
+ }
50970
+ function wantsJson(actionOptions, command) {
50971
+ return hasJsonOption(actionOptions) || hasJsonOption(command);
50972
+ }
50973
+ function registerWebhookCommands(program, options) {
50974
+ const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
50975
+ 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) => {
50976
+ const timestamp = new Date().toISOString();
50977
+ const channel = {
50978
+ id: actionOptions.id,
50979
+ name: actionOptions.name,
50980
+ enabled: !actionOptions.disabled,
50981
+ transport: actionOptions.transport,
50982
+ filters: parseFilter(actionOptions),
50983
+ retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
50984
+ redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
50985
+ createdAt: timestamp,
50986
+ updatedAt: timestamp
50987
+ };
50988
+ if (actionOptions.transport === "webhook") {
50989
+ channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
50990
+ } else if (actionOptions.transport === "command") {
50991
+ channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
50992
+ } else {
50993
+ throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
50994
+ }
50995
+ const saved = await createClient(options).addChannel(channel);
50996
+ print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
50997
+ });
50998
+ webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
50999
+ const channels = await createClient(options).listChannels();
51000
+ if (wantsJson(actionOptions, command)) {
51001
+ console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
51002
+ return;
51003
+ }
51004
+ if (!channels.length) {
51005
+ console.log("No channels configured.");
51006
+ return;
51007
+ }
51008
+ for (const channel of channels) {
51009
+ console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
51010
+ }
51011
+ });
51012
+ webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
51013
+ const removed = await createClient(options).removeChannel(id);
51014
+ print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
51015
+ });
51016
+ 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) => {
51017
+ const result = await createClient(options).testChannel(id, {
51018
+ source: options.source,
51019
+ type: actionOptions.type,
51020
+ subject: actionOptions.subject ?? id,
51021
+ message: actionOptions.message,
51022
+ data: parseJsonObject(actionOptions.data, { test: true })
51023
+ });
51024
+ print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
51025
+ });
51026
+ return webhooks;
51027
+ }
51028
+ function registerEventCommands(program, options) {
51029
+ const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
51030
+ 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) => {
51031
+ const result = await createClient(options).emit({
51032
+ source: actionOptions.source ?? options.source,
51033
+ type,
51034
+ subject: actionOptions.subject,
51035
+ severity: actionOptions.severity,
51036
+ message: actionOptions.message,
51037
+ dedupeKey: actionOptions.dedupeKey,
51038
+ data: parseJsonObject(actionOptions.data, {}),
51039
+ metadata: parseJsonObject(actionOptions.metadata, {})
51040
+ }, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
51041
+ print(result, wantsJson(actionOptions, command), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
51042
+ });
51043
+ 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) => {
51044
+ let rows = await createClient(options).listEvents();
51045
+ if (actionOptions.source)
51046
+ rows = rows.filter((event) => event.source === actionOptions.source);
51047
+ if (actionOptions.type)
51048
+ rows = rows.filter((event) => event.type === actionOptions.type);
51049
+ if (actionOptions.limit)
51050
+ rows = rows.slice(-actionOptions.limit);
51051
+ if (wantsJson(actionOptions, command)) {
51052
+ console.log(JSON.stringify(rows, null, 2));
51053
+ return;
51054
+ }
51055
+ if (!rows.length) {
51056
+ console.log("No events recorded.");
51057
+ return;
51058
+ }
51059
+ for (const event of rows)
51060
+ console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
51061
+ });
51062
+ 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) => {
51063
+ const result = await createClient(options).replay({
51064
+ eventId: actionOptions.id,
51065
+ source: actionOptions.source,
51066
+ type: actionOptions.type,
51067
+ dryRun: actionOptions.dryRun
51068
+ });
51069
+ print(result, wantsJson(actionOptions, command), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
51070
+ });
51071
+ return events;
51072
+ }
51073
+ function registerEventsCommands(program, options) {
51074
+ registerWebhookCommands(program, options);
51075
+ registerEventCommands(program, options);
51076
+ }
51077
+ function parseNumber(value) {
51078
+ const parsed = Number(value);
51079
+ if (!Number.isFinite(parsed))
51080
+ throw new Error(`Expected a number, got ${value}`);
51081
+ return parsed;
51082
+ }
51083
+ function collectValues(value, previous) {
51084
+ previous.push(value);
51085
+ return previous;
51086
+ }
51087
+
50382
51088
  // node_modules/commander/esm.mjs
50383
51089
  var import__ = __toESM(require_commander(), 1);
50384
51090
  var {
@@ -53357,4 +54063,5 @@ program2.action(() => {
53357
54063
  const agent = resolveIdentity();
53358
54064
  render(React8.createElement(App, { agent }));
53359
54065
  });
54066
+ registerEventsCommands(program2, { source: "conversations" });
53360
54067
  program2.parse();