@hasna/domains 0.0.26 → 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +689 -4
- package/dist/index.js +1 -1
- package/dist/mcp/index.js +1 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -993,7 +993,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
993
993
|
this._exitCallback = (err) => {
|
|
994
994
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
995
995
|
throw err;
|
|
996
|
-
}
|
|
996
|
+
}
|
|
997
997
|
};
|
|
998
998
|
}
|
|
999
999
|
return this;
|
|
@@ -15426,7 +15426,7 @@ var init_bowser = __esm(() => {
|
|
|
15426
15426
|
|
|
15427
15427
|
// node_modules/@aws-sdk/core/dist-cjs/submodules/client/index.js
|
|
15428
15428
|
var require_client2 = __commonJS((exports) => {
|
|
15429
|
-
var __dirname = "/home/hasna/
|
|
15429
|
+
var __dirname = "/home/hasna/Workspace/hasna/opensource/open-domains/node_modules/@aws-sdk/core/dist-cjs/submodules/client";
|
|
15430
15430
|
var retry = require_retry();
|
|
15431
15431
|
var protocols = require_protocols();
|
|
15432
15432
|
var lambdaInvokeStore = require_invoke_store();
|
|
@@ -46020,6 +46020,691 @@ var init_interactive = __esm(() => {
|
|
|
46020
46020
|
init_App();
|
|
46021
46021
|
});
|
|
46022
46022
|
|
|
46023
|
+
// node_modules/@hasna/events/dist/commander.js
|
|
46024
|
+
var exports_commander = {};
|
|
46025
|
+
__export(exports_commander, {
|
|
46026
|
+
registerWebhookCommands: () => registerWebhookCommands,
|
|
46027
|
+
registerEventsCommands: () => registerEventsCommands,
|
|
46028
|
+
registerEventCommands: () => registerEventCommands
|
|
46029
|
+
});
|
|
46030
|
+
import { chmod, mkdir, readFile as readFile2, rename, writeFile as writeFile2 } from "fs/promises";
|
|
46031
|
+
import { existsSync as existsSync4 } from "fs";
|
|
46032
|
+
import { homedir as homedir5 } from "os";
|
|
46033
|
+
import { join as join5 } from "path";
|
|
46034
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
46035
|
+
import { randomUUID } from "crypto";
|
|
46036
|
+
import { spawn } from "child_process";
|
|
46037
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
46038
|
+
function getPathValue(input, path) {
|
|
46039
|
+
return path.split(".").reduce((value, part) => {
|
|
46040
|
+
if (value && typeof value === "object" && part in value) {
|
|
46041
|
+
return value[part];
|
|
46042
|
+
}
|
|
46043
|
+
return;
|
|
46044
|
+
}, input);
|
|
46045
|
+
}
|
|
46046
|
+
function wildcardToRegExp(pattern) {
|
|
46047
|
+
const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
|
|
46048
|
+
return new RegExp(`^${escaped}$`);
|
|
46049
|
+
}
|
|
46050
|
+
function matchString(value, matcher) {
|
|
46051
|
+
if (matcher === undefined)
|
|
46052
|
+
return true;
|
|
46053
|
+
if (value === undefined)
|
|
46054
|
+
return false;
|
|
46055
|
+
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
46056
|
+
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
46057
|
+
}
|
|
46058
|
+
function matchRecord(input, matcher) {
|
|
46059
|
+
if (!matcher)
|
|
46060
|
+
return true;
|
|
46061
|
+
return Object.entries(matcher).every(([path, expected]) => {
|
|
46062
|
+
const actual = getPathValue(input, path);
|
|
46063
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
46064
|
+
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
46065
|
+
}
|
|
46066
|
+
return actual === expected;
|
|
46067
|
+
});
|
|
46068
|
+
}
|
|
46069
|
+
function eventMatchesFilter(event, filter) {
|
|
46070
|
+
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);
|
|
46071
|
+
}
|
|
46072
|
+
function channelMatchesEvent(channel, event) {
|
|
46073
|
+
if (!channel.enabled)
|
|
46074
|
+
return false;
|
|
46075
|
+
if (!channel.filters || channel.filters.length === 0)
|
|
46076
|
+
return true;
|
|
46077
|
+
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
46078
|
+
}
|
|
46079
|
+
function getEventsDataDir(override) {
|
|
46080
|
+
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join5(homedir5(), ".hasna", "events");
|
|
46081
|
+
}
|
|
46082
|
+
|
|
46083
|
+
class JsonEventsStore {
|
|
46084
|
+
dataDir;
|
|
46085
|
+
channelsPath;
|
|
46086
|
+
eventsPath;
|
|
46087
|
+
deliveriesPath;
|
|
46088
|
+
constructor(dataDir = getEventsDataDir()) {
|
|
46089
|
+
this.dataDir = dataDir;
|
|
46090
|
+
this.channelsPath = join5(dataDir, "channels.json");
|
|
46091
|
+
this.eventsPath = join5(dataDir, "events.json");
|
|
46092
|
+
this.deliveriesPath = join5(dataDir, "deliveries.json");
|
|
46093
|
+
}
|
|
46094
|
+
async init() {
|
|
46095
|
+
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
46096
|
+
await chmod(this.dataDir, 448).catch(() => {
|
|
46097
|
+
return;
|
|
46098
|
+
});
|
|
46099
|
+
await this.ensureArrayFile(this.channelsPath);
|
|
46100
|
+
await this.ensureArrayFile(this.eventsPath);
|
|
46101
|
+
await this.ensureArrayFile(this.deliveriesPath);
|
|
46102
|
+
}
|
|
46103
|
+
async addChannel(channel) {
|
|
46104
|
+
await this.init();
|
|
46105
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
46106
|
+
const index = channels.findIndex((item) => item.id === channel.id);
|
|
46107
|
+
if (index >= 0) {
|
|
46108
|
+
channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
|
|
46109
|
+
} else {
|
|
46110
|
+
channels.push(channel);
|
|
46111
|
+
}
|
|
46112
|
+
await this.writeJson(this.channelsPath, channels);
|
|
46113
|
+
return index >= 0 ? channels[index] : channel;
|
|
46114
|
+
}
|
|
46115
|
+
async listChannels() {
|
|
46116
|
+
await this.init();
|
|
46117
|
+
return this.readJson(this.channelsPath, []);
|
|
46118
|
+
}
|
|
46119
|
+
async getChannel(id) {
|
|
46120
|
+
const channels = await this.listChannels();
|
|
46121
|
+
return channels.find((channel) => channel.id === id);
|
|
46122
|
+
}
|
|
46123
|
+
async removeChannel(id) {
|
|
46124
|
+
await this.init();
|
|
46125
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
46126
|
+
const next = channels.filter((channel) => channel.id !== id);
|
|
46127
|
+
await this.writeJson(this.channelsPath, next);
|
|
46128
|
+
return next.length !== channels.length;
|
|
46129
|
+
}
|
|
46130
|
+
async appendEvent(event) {
|
|
46131
|
+
await this.init();
|
|
46132
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
46133
|
+
events.push(event);
|
|
46134
|
+
await this.writeJson(this.eventsPath, events);
|
|
46135
|
+
return event;
|
|
46136
|
+
}
|
|
46137
|
+
async listEvents() {
|
|
46138
|
+
await this.init();
|
|
46139
|
+
return this.readJson(this.eventsPath, []);
|
|
46140
|
+
}
|
|
46141
|
+
async findEventByIdentity(identity2) {
|
|
46142
|
+
const events = await this.listEvents();
|
|
46143
|
+
return events.find((event) => identity2.id !== undefined && event.id === identity2.id || identity2.dedupeKey !== undefined && event.dedupeKey === identity2.dedupeKey);
|
|
46144
|
+
}
|
|
46145
|
+
async appendDelivery(result) {
|
|
46146
|
+
await this.init();
|
|
46147
|
+
const deliveries = await this.readJson(this.deliveriesPath, []);
|
|
46148
|
+
deliveries.push(result);
|
|
46149
|
+
await this.writeJson(this.deliveriesPath, deliveries);
|
|
46150
|
+
return result;
|
|
46151
|
+
}
|
|
46152
|
+
async listDeliveries() {
|
|
46153
|
+
await this.init();
|
|
46154
|
+
return this.readJson(this.deliveriesPath, []);
|
|
46155
|
+
}
|
|
46156
|
+
async exportData() {
|
|
46157
|
+
return {
|
|
46158
|
+
channels: await this.listChannels(),
|
|
46159
|
+
events: await this.listEvents(),
|
|
46160
|
+
deliveries: await this.listDeliveries()
|
|
46161
|
+
};
|
|
46162
|
+
}
|
|
46163
|
+
async ensureArrayFile(path) {
|
|
46164
|
+
if (!existsSync4(path)) {
|
|
46165
|
+
await writeFile2(path, `[]
|
|
46166
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
46167
|
+
}
|
|
46168
|
+
await chmod(path, 384).catch(() => {
|
|
46169
|
+
return;
|
|
46170
|
+
});
|
|
46171
|
+
}
|
|
46172
|
+
async readJson(path, fallback) {
|
|
46173
|
+
try {
|
|
46174
|
+
const raw = await readFile2(path, "utf-8");
|
|
46175
|
+
if (!raw.trim())
|
|
46176
|
+
return fallback;
|
|
46177
|
+
return JSON.parse(raw);
|
|
46178
|
+
} catch (error) {
|
|
46179
|
+
if (error.code === "ENOENT")
|
|
46180
|
+
return fallback;
|
|
46181
|
+
throw error;
|
|
46182
|
+
}
|
|
46183
|
+
}
|
|
46184
|
+
async writeJson(path, value) {
|
|
46185
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
46186
|
+
await writeFile2(tempPath, `${JSON.stringify(value, null, 2)}
|
|
46187
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
46188
|
+
await rename(tempPath, path);
|
|
46189
|
+
await chmod(path, 384).catch(() => {
|
|
46190
|
+
return;
|
|
46191
|
+
});
|
|
46192
|
+
}
|
|
46193
|
+
}
|
|
46194
|
+
function buildSignatureBase(timestamp, body) {
|
|
46195
|
+
return `${timestamp}.${body}`;
|
|
46196
|
+
}
|
|
46197
|
+
function signPayload(secret, timestamp, body) {
|
|
46198
|
+
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
46199
|
+
return `sha256=${digest}`;
|
|
46200
|
+
}
|
|
46201
|
+
function now() {
|
|
46202
|
+
return new Date().toISOString();
|
|
46203
|
+
}
|
|
46204
|
+
function truncate(value, max = 4096) {
|
|
46205
|
+
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
46206
|
+
}
|
|
46207
|
+
function buildWebhookRequest(event, channel) {
|
|
46208
|
+
if (!channel.webhook)
|
|
46209
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
46210
|
+
const body = JSON.stringify(event);
|
|
46211
|
+
const timestamp = event.time;
|
|
46212
|
+
const headers = {
|
|
46213
|
+
"Content-Type": "application/json",
|
|
46214
|
+
"User-Agent": "@hasna/events",
|
|
46215
|
+
"X-Hasna-Event-Id": event.id,
|
|
46216
|
+
"X-Hasna-Event-Type": event.type,
|
|
46217
|
+
"X-Hasna-Timestamp": timestamp,
|
|
46218
|
+
...channel.webhook.headers
|
|
46219
|
+
};
|
|
46220
|
+
if (channel.webhook.secret) {
|
|
46221
|
+
headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
|
|
46222
|
+
}
|
|
46223
|
+
return { body, headers };
|
|
46224
|
+
}
|
|
46225
|
+
async function dispatchWebhook(event, channel, options = {}) {
|
|
46226
|
+
if (!channel.webhook)
|
|
46227
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
46228
|
+
const startedAt = now();
|
|
46229
|
+
const { body, headers } = buildWebhookRequest(event, channel);
|
|
46230
|
+
const controller = new AbortController;
|
|
46231
|
+
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
46232
|
+
try {
|
|
46233
|
+
const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
|
|
46234
|
+
method: "POST",
|
|
46235
|
+
headers,
|
|
46236
|
+
body,
|
|
46237
|
+
signal: controller.signal
|
|
46238
|
+
});
|
|
46239
|
+
const responseBody = truncate(await response.text());
|
|
46240
|
+
return {
|
|
46241
|
+
attempt: 1,
|
|
46242
|
+
status: response.ok ? "success" : "failed",
|
|
46243
|
+
startedAt,
|
|
46244
|
+
completedAt: now(),
|
|
46245
|
+
responseStatus: response.status,
|
|
46246
|
+
responseBody,
|
|
46247
|
+
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
46248
|
+
};
|
|
46249
|
+
} catch (error) {
|
|
46250
|
+
return {
|
|
46251
|
+
attempt: 1,
|
|
46252
|
+
status: "failed",
|
|
46253
|
+
startedAt,
|
|
46254
|
+
completedAt: now(),
|
|
46255
|
+
error: error instanceof Error ? error.message : String(error)
|
|
46256
|
+
};
|
|
46257
|
+
} finally {
|
|
46258
|
+
clearTimeout(timeout);
|
|
46259
|
+
}
|
|
46260
|
+
}
|
|
46261
|
+
async function dispatchCommand(event, channel) {
|
|
46262
|
+
if (!channel.command)
|
|
46263
|
+
throw new Error(`Channel ${channel.id} has no command config`);
|
|
46264
|
+
const startedAt = now();
|
|
46265
|
+
const eventJson = JSON.stringify(event);
|
|
46266
|
+
const env = {
|
|
46267
|
+
...process.env,
|
|
46268
|
+
...channel.command.env,
|
|
46269
|
+
HASNA_CHANNEL_ID: channel.id,
|
|
46270
|
+
HASNA_EVENT_ID: event.id,
|
|
46271
|
+
HASNA_EVENT_TYPE: event.type,
|
|
46272
|
+
HASNA_EVENT_SOURCE: event.source,
|
|
46273
|
+
HASNA_EVENT_SUBJECT: event.subject ?? "",
|
|
46274
|
+
HASNA_EVENT_SEVERITY: event.severity,
|
|
46275
|
+
HASNA_EVENT_TIME: event.time,
|
|
46276
|
+
HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
|
|
46277
|
+
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
46278
|
+
HASNA_EVENT_JSON: eventJson
|
|
46279
|
+
};
|
|
46280
|
+
return new Promise((resolve3) => {
|
|
46281
|
+
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
46282
|
+
cwd: channel.command.cwd,
|
|
46283
|
+
env,
|
|
46284
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
46285
|
+
});
|
|
46286
|
+
let stdout = "";
|
|
46287
|
+
let stderr = "";
|
|
46288
|
+
const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
|
|
46289
|
+
child.stdin.end(eventJson);
|
|
46290
|
+
child.stdout.on("data", (chunk) => {
|
|
46291
|
+
stdout += chunk.toString();
|
|
46292
|
+
});
|
|
46293
|
+
child.stderr.on("data", (chunk) => {
|
|
46294
|
+
stderr += chunk.toString();
|
|
46295
|
+
});
|
|
46296
|
+
child.on("error", (error) => {
|
|
46297
|
+
clearTimeout(timeout);
|
|
46298
|
+
resolve3({
|
|
46299
|
+
attempt: 1,
|
|
46300
|
+
status: "failed",
|
|
46301
|
+
startedAt,
|
|
46302
|
+
completedAt: now(),
|
|
46303
|
+
stdout: truncate(stdout),
|
|
46304
|
+
stderr: truncate(stderr),
|
|
46305
|
+
error: error.message
|
|
46306
|
+
});
|
|
46307
|
+
});
|
|
46308
|
+
child.on("close", (code, signal) => {
|
|
46309
|
+
clearTimeout(timeout);
|
|
46310
|
+
const success = code === 0;
|
|
46311
|
+
resolve3({
|
|
46312
|
+
attempt: 1,
|
|
46313
|
+
status: success ? "success" : "failed",
|
|
46314
|
+
startedAt,
|
|
46315
|
+
completedAt: now(),
|
|
46316
|
+
stdout: truncate(stdout),
|
|
46317
|
+
stderr: truncate(stderr),
|
|
46318
|
+
error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
46319
|
+
});
|
|
46320
|
+
});
|
|
46321
|
+
});
|
|
46322
|
+
}
|
|
46323
|
+
async function dispatchChannel(event, channel, options = {}) {
|
|
46324
|
+
if (channel.transport === "webhook")
|
|
46325
|
+
return dispatchWebhook(event, channel, options);
|
|
46326
|
+
if (channel.transport === "command")
|
|
46327
|
+
return dispatchCommand(event, channel);
|
|
46328
|
+
return {
|
|
46329
|
+
attempt: 1,
|
|
46330
|
+
status: "skipped",
|
|
46331
|
+
startedAt: now(),
|
|
46332
|
+
completedAt: now(),
|
|
46333
|
+
error: `Unsupported transport: ${channel.transport}`
|
|
46334
|
+
};
|
|
46335
|
+
}
|
|
46336
|
+
function createDeliveryResult(event, channel, attempts) {
|
|
46337
|
+
const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
|
|
46338
|
+
return {
|
|
46339
|
+
id: randomUUID(),
|
|
46340
|
+
eventId: event.id,
|
|
46341
|
+
channelId: channel.id,
|
|
46342
|
+
transport: channel.transport,
|
|
46343
|
+
status,
|
|
46344
|
+
attempts,
|
|
46345
|
+
createdAt: attempts[0]?.startedAt ?? now(),
|
|
46346
|
+
completedAt: attempts.at(-1)?.completedAt ?? now()
|
|
46347
|
+
};
|
|
46348
|
+
}
|
|
46349
|
+
function createEvent(input) {
|
|
46350
|
+
return {
|
|
46351
|
+
id: input.id ?? randomUUID2(),
|
|
46352
|
+
source: input.source,
|
|
46353
|
+
type: input.type,
|
|
46354
|
+
time: normalizeTime(input.time),
|
|
46355
|
+
subject: input.subject,
|
|
46356
|
+
severity: input.severity ?? "info",
|
|
46357
|
+
data: input.data ?? {},
|
|
46358
|
+
message: input.message,
|
|
46359
|
+
dedupeKey: input.dedupeKey,
|
|
46360
|
+
schemaVersion: input.schemaVersion ?? "1.0",
|
|
46361
|
+
metadata: input.metadata ?? {}
|
|
46362
|
+
};
|
|
46363
|
+
}
|
|
46364
|
+
|
|
46365
|
+
class EventsClient {
|
|
46366
|
+
store;
|
|
46367
|
+
redactors;
|
|
46368
|
+
transportOptions;
|
|
46369
|
+
constructor(options = {}) {
|
|
46370
|
+
this.store = options.store ?? new JsonEventsStore(options.dataDir);
|
|
46371
|
+
this.redactors = options.redactors ?? [];
|
|
46372
|
+
this.transportOptions = { fetchImpl: options.fetchImpl };
|
|
46373
|
+
}
|
|
46374
|
+
async addChannel(input) {
|
|
46375
|
+
const timestamp = new Date().toISOString();
|
|
46376
|
+
return this.store.addChannel({
|
|
46377
|
+
...input,
|
|
46378
|
+
createdAt: input.createdAt ?? timestamp,
|
|
46379
|
+
updatedAt: input.updatedAt ?? timestamp
|
|
46380
|
+
});
|
|
46381
|
+
}
|
|
46382
|
+
async listChannels() {
|
|
46383
|
+
return this.store.listChannels();
|
|
46384
|
+
}
|
|
46385
|
+
async removeChannel(id) {
|
|
46386
|
+
return this.store.removeChannel(id);
|
|
46387
|
+
}
|
|
46388
|
+
async emit(input, options = {}) {
|
|
46389
|
+
const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
|
|
46390
|
+
if (options.dedupe !== false) {
|
|
46391
|
+
const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
|
|
46392
|
+
if (existing) {
|
|
46393
|
+
return { event: existing, deliveries: [], deduped: true };
|
|
46394
|
+
}
|
|
46395
|
+
}
|
|
46396
|
+
await this.store.appendEvent(event);
|
|
46397
|
+
const deliveries = options.deliver === false ? [] : await this.deliver(event);
|
|
46398
|
+
return { event, deliveries, deduped: false };
|
|
46399
|
+
}
|
|
46400
|
+
async listEvents() {
|
|
46401
|
+
return this.store.listEvents();
|
|
46402
|
+
}
|
|
46403
|
+
async listDeliveries() {
|
|
46404
|
+
return this.store.listDeliveries();
|
|
46405
|
+
}
|
|
46406
|
+
async deliver(event) {
|
|
46407
|
+
const channels = await this.store.listChannels();
|
|
46408
|
+
const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
|
|
46409
|
+
const deliveries = [];
|
|
46410
|
+
for (const channel of selected) {
|
|
46411
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
46412
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
46413
|
+
await this.store.appendDelivery(result);
|
|
46414
|
+
deliveries.push(result);
|
|
46415
|
+
}
|
|
46416
|
+
return deliveries;
|
|
46417
|
+
}
|
|
46418
|
+
async testChannel(id, input = {}) {
|
|
46419
|
+
const channel = await this.store.getChannel(id);
|
|
46420
|
+
if (!channel)
|
|
46421
|
+
throw new Error(`Channel not found: ${id}`);
|
|
46422
|
+
const event = createEvent({
|
|
46423
|
+
source: input.source ?? "hasna.events",
|
|
46424
|
+
type: input.type ?? "events.test",
|
|
46425
|
+
subject: input.subject ?? id,
|
|
46426
|
+
severity: input.severity ?? "info",
|
|
46427
|
+
data: input.data ?? { test: true },
|
|
46428
|
+
message: input.message ?? "Hasna events test delivery",
|
|
46429
|
+
dedupeKey: input.dedupeKey,
|
|
46430
|
+
schemaVersion: input.schemaVersion,
|
|
46431
|
+
metadata: input.metadata,
|
|
46432
|
+
time: input.time,
|
|
46433
|
+
id: input.id
|
|
46434
|
+
});
|
|
46435
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
46436
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
46437
|
+
await this.store.appendDelivery(result);
|
|
46438
|
+
return result;
|
|
46439
|
+
}
|
|
46440
|
+
async replay(options = {}) {
|
|
46441
|
+
const events = (await this.store.listEvents()).filter((event) => {
|
|
46442
|
+
if (options.eventId && event.id !== options.eventId)
|
|
46443
|
+
return false;
|
|
46444
|
+
if (options.source && event.source !== options.source)
|
|
46445
|
+
return false;
|
|
46446
|
+
if (options.type && event.type !== options.type)
|
|
46447
|
+
return false;
|
|
46448
|
+
return true;
|
|
46449
|
+
});
|
|
46450
|
+
if (options.dryRun)
|
|
46451
|
+
return { events, deliveries: [] };
|
|
46452
|
+
const deliveries = [];
|
|
46453
|
+
for (const event of events) {
|
|
46454
|
+
deliveries.push(...await this.deliver(event));
|
|
46455
|
+
}
|
|
46456
|
+
return { events, deliveries };
|
|
46457
|
+
}
|
|
46458
|
+
async applyRedaction(event, channel) {
|
|
46459
|
+
let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
|
|
46460
|
+
for (const redactor of this.redactors) {
|
|
46461
|
+
next = await redactor(next, channel);
|
|
46462
|
+
}
|
|
46463
|
+
return next;
|
|
46464
|
+
}
|
|
46465
|
+
async deliverWithRetry(event, channel) {
|
|
46466
|
+
const policy = normalizeRetryPolicy(channel.retry);
|
|
46467
|
+
const attempts = [];
|
|
46468
|
+
for (let index = 0;index < policy.maxAttempts; index += 1) {
|
|
46469
|
+
const attempt = await dispatchChannel(event, channel, this.transportOptions);
|
|
46470
|
+
attempt.attempt = index + 1;
|
|
46471
|
+
if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
|
|
46472
|
+
attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
|
|
46473
|
+
}
|
|
46474
|
+
attempts.push(attempt);
|
|
46475
|
+
if (attempt.status !== "failed")
|
|
46476
|
+
break;
|
|
46477
|
+
if (attempt.nextBackoffMs)
|
|
46478
|
+
await Bun.sleep(attempt.nextBackoffMs);
|
|
46479
|
+
}
|
|
46480
|
+
return createDeliveryResult(event, channel, attempts);
|
|
46481
|
+
}
|
|
46482
|
+
}
|
|
46483
|
+
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
46484
|
+
if (paths.length === 0)
|
|
46485
|
+
return event;
|
|
46486
|
+
const copy = structuredClone(event);
|
|
46487
|
+
for (const path of paths) {
|
|
46488
|
+
setPath(copy, path, replacement);
|
|
46489
|
+
}
|
|
46490
|
+
return copy;
|
|
46491
|
+
}
|
|
46492
|
+
function sanitizeChannelForOutput(channel) {
|
|
46493
|
+
const copy = structuredClone(channel);
|
|
46494
|
+
if (copy.webhook?.secret)
|
|
46495
|
+
copy.webhook.secret = "[REDACTED]";
|
|
46496
|
+
if (copy.command?.env) {
|
|
46497
|
+
copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
|
|
46498
|
+
}
|
|
46499
|
+
return copy;
|
|
46500
|
+
}
|
|
46501
|
+
function sanitizeChannelsForOutput(channels) {
|
|
46502
|
+
return channels.map(sanitizeChannelForOutput);
|
|
46503
|
+
}
|
|
46504
|
+
function redactSensitiveKeys(event, replacement = "[REDACTED]") {
|
|
46505
|
+
return redactValue(event, replacement);
|
|
46506
|
+
}
|
|
46507
|
+
function shouldRedactKey(key) {
|
|
46508
|
+
return /secret|token|password|api[_-]?key|authorization/i.test(key);
|
|
46509
|
+
}
|
|
46510
|
+
function redactValue(value, replacement) {
|
|
46511
|
+
if (Array.isArray(value))
|
|
46512
|
+
return value.map((item) => redactValue(item, replacement));
|
|
46513
|
+
if (!value || typeof value !== "object")
|
|
46514
|
+
return value;
|
|
46515
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
46516
|
+
key,
|
|
46517
|
+
shouldRedactKey(key) ? replacement : redactValue(item, replacement)
|
|
46518
|
+
]));
|
|
46519
|
+
}
|
|
46520
|
+
function setPath(input, path, replacement) {
|
|
46521
|
+
const parts = path.split(".");
|
|
46522
|
+
let cursor = input;
|
|
46523
|
+
for (const part of parts.slice(0, -1)) {
|
|
46524
|
+
const next = cursor[part];
|
|
46525
|
+
if (!next || typeof next !== "object")
|
|
46526
|
+
return;
|
|
46527
|
+
cursor = next;
|
|
46528
|
+
}
|
|
46529
|
+
const last = parts.at(-1);
|
|
46530
|
+
if (last && last in cursor)
|
|
46531
|
+
cursor[last] = replacement;
|
|
46532
|
+
}
|
|
46533
|
+
function normalizeTime(value) {
|
|
46534
|
+
if (!value)
|
|
46535
|
+
return new Date().toISOString();
|
|
46536
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
46537
|
+
}
|
|
46538
|
+
function normalizeRetryPolicy(policy) {
|
|
46539
|
+
return {
|
|
46540
|
+
maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
|
|
46541
|
+
backoffMs: Math.max(0, policy?.backoffMs ?? 250),
|
|
46542
|
+
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
46543
|
+
};
|
|
46544
|
+
}
|
|
46545
|
+
function parseJsonObject(value, fallback) {
|
|
46546
|
+
if (!value)
|
|
46547
|
+
return fallback;
|
|
46548
|
+
const parsed = JSON.parse(value);
|
|
46549
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
46550
|
+
throw new Error("Expected a JSON object");
|
|
46551
|
+
}
|
|
46552
|
+
return parsed;
|
|
46553
|
+
}
|
|
46554
|
+
function parseHeaders(values) {
|
|
46555
|
+
if (!values?.length)
|
|
46556
|
+
return;
|
|
46557
|
+
const headers = {};
|
|
46558
|
+
for (const value of values) {
|
|
46559
|
+
const separator = value.indexOf("=");
|
|
46560
|
+
if (separator === -1)
|
|
46561
|
+
throw new Error(`Invalid header, expected name=value: ${value}`);
|
|
46562
|
+
headers[value.slice(0, separator)] = value.slice(separator + 1);
|
|
46563
|
+
}
|
|
46564
|
+
return headers;
|
|
46565
|
+
}
|
|
46566
|
+
function parseFilter(options) {
|
|
46567
|
+
const filter2 = {};
|
|
46568
|
+
if (options.source)
|
|
46569
|
+
filter2.source = options.source;
|
|
46570
|
+
if (options.type)
|
|
46571
|
+
filter2.type = options.type;
|
|
46572
|
+
if (options.subject)
|
|
46573
|
+
filter2.subject = options.subject;
|
|
46574
|
+
if (options.severity)
|
|
46575
|
+
filter2.severity = options.severity;
|
|
46576
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
46577
|
+
}
|
|
46578
|
+
function createClient(options) {
|
|
46579
|
+
if (options.createClient)
|
|
46580
|
+
return options.createClient();
|
|
46581
|
+
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
46582
|
+
}
|
|
46583
|
+
function print(value, json, text) {
|
|
46584
|
+
if (json)
|
|
46585
|
+
console.log(JSON.stringify(value, null, 2));
|
|
46586
|
+
else
|
|
46587
|
+
console.log(text);
|
|
46588
|
+
}
|
|
46589
|
+
function registerWebhookCommands(program2, options) {
|
|
46590
|
+
const webhooks = program2.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
46591
|
+
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) => {
|
|
46592
|
+
const timestamp = new Date().toISOString();
|
|
46593
|
+
const channel = {
|
|
46594
|
+
id: actionOptions.id,
|
|
46595
|
+
name: actionOptions.name,
|
|
46596
|
+
enabled: !actionOptions.disabled,
|
|
46597
|
+
transport: actionOptions.transport,
|
|
46598
|
+
filters: parseFilter(actionOptions),
|
|
46599
|
+
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
46600
|
+
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
46601
|
+
createdAt: timestamp,
|
|
46602
|
+
updatedAt: timestamp
|
|
46603
|
+
};
|
|
46604
|
+
if (actionOptions.transport === "webhook") {
|
|
46605
|
+
channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
|
|
46606
|
+
} else if (actionOptions.transport === "command") {
|
|
46607
|
+
channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
|
|
46608
|
+
} else {
|
|
46609
|
+
throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
|
|
46610
|
+
}
|
|
46611
|
+
const saved = await createClient(options).addChannel(channel);
|
|
46612
|
+
print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
|
|
46613
|
+
});
|
|
46614
|
+
webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
46615
|
+
const channels = await createClient(options).listChannels();
|
|
46616
|
+
if (actionOptions.json) {
|
|
46617
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
|
|
46618
|
+
return;
|
|
46619
|
+
}
|
|
46620
|
+
if (!channels.length) {
|
|
46621
|
+
console.log("No channels configured.");
|
|
46622
|
+
return;
|
|
46623
|
+
}
|
|
46624
|
+
for (const channel of channels) {
|
|
46625
|
+
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
46626
|
+
}
|
|
46627
|
+
});
|
|
46628
|
+
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
46629
|
+
const removed = await createClient(options).removeChannel(id);
|
|
46630
|
+
print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
46631
|
+
});
|
|
46632
|
+
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) => {
|
|
46633
|
+
const result = await createClient(options).testChannel(id, {
|
|
46634
|
+
source: options.source,
|
|
46635
|
+
type: actionOptions.type,
|
|
46636
|
+
subject: actionOptions.subject ?? id,
|
|
46637
|
+
message: actionOptions.message,
|
|
46638
|
+
data: parseJsonObject(actionOptions.data, { test: true })
|
|
46639
|
+
});
|
|
46640
|
+
print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
|
|
46641
|
+
});
|
|
46642
|
+
return webhooks;
|
|
46643
|
+
}
|
|
46644
|
+
function registerEventCommands(program2, options) {
|
|
46645
|
+
const events = program2.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
46646
|
+
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) => {
|
|
46647
|
+
const result = await createClient(options).emit({
|
|
46648
|
+
source: actionOptions.source ?? options.source,
|
|
46649
|
+
type,
|
|
46650
|
+
subject: actionOptions.subject,
|
|
46651
|
+
severity: actionOptions.severity,
|
|
46652
|
+
message: actionOptions.message,
|
|
46653
|
+
dedupeKey: actionOptions.dedupeKey,
|
|
46654
|
+
data: parseJsonObject(actionOptions.data, {}),
|
|
46655
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
46656
|
+
}, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
|
|
46657
|
+
print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
|
|
46658
|
+
});
|
|
46659
|
+
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) => {
|
|
46660
|
+
let rows = await createClient(options).listEvents();
|
|
46661
|
+
if (actionOptions.source)
|
|
46662
|
+
rows = rows.filter((event) => event.source === actionOptions.source);
|
|
46663
|
+
if (actionOptions.type)
|
|
46664
|
+
rows = rows.filter((event) => event.type === actionOptions.type);
|
|
46665
|
+
if (actionOptions.limit)
|
|
46666
|
+
rows = rows.slice(-actionOptions.limit);
|
|
46667
|
+
if (actionOptions.json) {
|
|
46668
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
46669
|
+
return;
|
|
46670
|
+
}
|
|
46671
|
+
if (!rows.length) {
|
|
46672
|
+
console.log("No events recorded.");
|
|
46673
|
+
return;
|
|
46674
|
+
}
|
|
46675
|
+
for (const event of rows)
|
|
46676
|
+
console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
|
|
46677
|
+
});
|
|
46678
|
+
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) => {
|
|
46679
|
+
const result = await createClient(options).replay({
|
|
46680
|
+
eventId: actionOptions.id,
|
|
46681
|
+
source: actionOptions.source,
|
|
46682
|
+
type: actionOptions.type,
|
|
46683
|
+
dryRun: actionOptions.dryRun
|
|
46684
|
+
});
|
|
46685
|
+
print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
|
|
46686
|
+
});
|
|
46687
|
+
return events;
|
|
46688
|
+
}
|
|
46689
|
+
function registerEventsCommands(program2, options) {
|
|
46690
|
+
registerWebhookCommands(program2, options);
|
|
46691
|
+
registerEventCommands(program2, options);
|
|
46692
|
+
}
|
|
46693
|
+
function parseNumber(value) {
|
|
46694
|
+
const parsed = Number(value);
|
|
46695
|
+
if (!Number.isFinite(parsed))
|
|
46696
|
+
throw new Error(`Expected a number, got ${value}`);
|
|
46697
|
+
return parsed;
|
|
46698
|
+
}
|
|
46699
|
+
function collectValues(value, previous) {
|
|
46700
|
+
previous.push(value);
|
|
46701
|
+
return previous;
|
|
46702
|
+
}
|
|
46703
|
+
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR", HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME", DEFAULT_SIGNATURE_TOLERANCE_MS;
|
|
46704
|
+
var init_commander = __esm(() => {
|
|
46705
|
+
DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
46706
|
+
});
|
|
46707
|
+
|
|
46023
46708
|
// node_modules/commander/esm.mjs
|
|
46024
46709
|
var import__ = __toESM(require_commander(), 1);
|
|
46025
46710
|
var {
|
|
@@ -49248,8 +49933,8 @@ async function registerOptionalCommands(program2, groups) {
|
|
|
49248
49933
|
}
|
|
49249
49934
|
if (groups.has("events")) {
|
|
49250
49935
|
try {
|
|
49251
|
-
const { registerEventsCommands } = await
|
|
49252
|
-
|
|
49936
|
+
const { registerEventsCommands: registerEventsCommands2 } = await Promise.resolve().then(() => (init_commander(), exports_commander));
|
|
49937
|
+
registerEventsCommands2(program2, { source: "domains" });
|
|
49253
49938
|
} catch (error) {
|
|
49254
49939
|
console.error(`Events command group is enabled but @hasna/events could not be loaded: ${error instanceof Error ? error.message : String(error)}`);
|
|
49255
49940
|
}
|
package/dist/index.js
CHANGED
|
@@ -16637,7 +16637,7 @@ var init_bowser = __esm(() => {
|
|
|
16637
16637
|
|
|
16638
16638
|
// node_modules/@aws-sdk/core/dist-cjs/submodules/client/index.js
|
|
16639
16639
|
var require_client4 = __commonJS((exports) => {
|
|
16640
|
-
var __dirname = "/home/hasna/
|
|
16640
|
+
var __dirname = "/home/hasna/Workspace/hasna/opensource/open-domains/node_modules/@aws-sdk/core/dist-cjs/submodules/client";
|
|
16641
16641
|
var retry = require_retry();
|
|
16642
16642
|
var protocols = require_protocols();
|
|
16643
16643
|
var lambdaInvokeStore = require_invoke_store();
|
package/dist/mcp/index.js
CHANGED
|
@@ -11786,7 +11786,7 @@ var init_bowser = __esm(() => {
|
|
|
11786
11786
|
|
|
11787
11787
|
// node_modules/@aws-sdk/core/dist-cjs/submodules/client/index.js
|
|
11788
11788
|
var require_client2 = __commonJS((exports) => {
|
|
11789
|
-
var __dirname = "/home/hasna/
|
|
11789
|
+
var __dirname = "/home/hasna/Workspace/hasna/opensource/open-domains/node_modules/@aws-sdk/core/dist-cjs/submodules/client";
|
|
11790
11790
|
var retry = require_retry();
|
|
11791
11791
|
var protocols = require_protocols();
|
|
11792
11792
|
var lambdaInvokeStore = require_invoke_store();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/domains",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"description": "Domain portfolio, registrar, marketplace, and DNS management for AI agents — CLI + MCP server with SQLite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
},
|
|
103
103
|
"optionalDependencies": {
|
|
104
104
|
"@hasna/contacts": "^0.6.19",
|
|
105
|
-
"@hasna/events": "^0.1.
|
|
105
|
+
"@hasna/events": "^0.1.7"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
108
|
"@types/bun": "^1.3.14",
|