@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/hook.js +24 -14
- package/bin/index.js +784 -77
- package/bin/mcp.js +43 -21
- package/dist/index.js +38 -17
- package/package.json +3 -2
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: (
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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: (
|
|
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
|
-
}
|
|
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
|
|
2354
|
+
function __accessProp2(key) {
|
|
2334
2355
|
return this[key];
|
|
2335
2356
|
}
|
|
2336
|
-
function
|
|
2337
|
-
this[name] =
|
|
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 =
|
|
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
|
|
3241
|
+
return join2(dir, `${serviceName}.db`);
|
|
3221
3242
|
}
|
|
3222
3243
|
function migrateDotfile(serviceName) {
|
|
3223
|
-
const legacyDir =
|
|
3224
|
-
const newDir =
|
|
3225
|
-
if (!
|
|
3244
|
+
const legacyDir = join2(homedir2(), `.${serviceName}`);
|
|
3245
|
+
const newDir = join2(HASNA_DIR, serviceName);
|
|
3246
|
+
if (!existsSync2(legacyDir))
|
|
3226
3247
|
return [];
|
|
3227
|
-
if (
|
|
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 =
|
|
3238
|
-
const destPath =
|
|
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
|
|
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 (!
|
|
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
|
|
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 ??
|
|
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
|
|
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 ??
|
|
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
|
|
3858
|
-
this.startTimes.set(table,
|
|
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
|
|
4201
|
+
const now2 = new Date().toISOString();
|
|
4181
4202
|
upsertSyncMeta(local, {
|
|
4182
4203
|
table_name: table,
|
|
4183
|
-
last_synced_at:
|
|
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
|
|
4254
|
+
const now2 = new Date().toISOString();
|
|
4234
4255
|
upsertSyncMeta(local, {
|
|
4235
4256
|
table_name: table,
|
|
4236
|
-
last_synced_at:
|
|
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,
|
|
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 ?
|
|
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:
|
|
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),
|
|
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:
|
|
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 =
|
|
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 =
|
|
13239
|
-
CONFIG_PATH =
|
|
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
|
|
14259
|
-
if (cachedConfig &&
|
|
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 =
|
|
14285
|
+
configLoadedAt = now2;
|
|
14265
14286
|
return cachedConfig;
|
|
14266
14287
|
} catch {
|
|
14267
14288
|
cachedConfig = {};
|
|
14268
|
-
configLoadedAt =
|
|
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
|
|
14490
|
+
const now2 = Date.now();
|
|
14470
14491
|
const entry = _rateLimitCounters.get(agentId);
|
|
14471
|
-
if (!entry ||
|
|
14472
|
-
_rateLimitCounters.set(agentId, { count: 1, windowStart:
|
|
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("-")}-${
|
|
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 =
|
|
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.
|
|
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": "
|
|
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
|
|
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 =
|
|
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") {}
|
|
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
|
-
}
|
|
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
|
|
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 &&
|
|
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 ||
|
|
45380
|
-
_sessionInjectRate.set(rateKey, { count: 1, start:
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
48667
|
-
const deadline = new Date(
|
|
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() -
|
|
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();
|