@hasna/machines 0.0.32 → 0.0.33

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/mcp/index.js CHANGED
@@ -40,527 +40,8 @@ function getPackageVersion() {
40
40
  import { createServer } from "http";
41
41
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
42
42
 
43
- // node_modules/@hasna/events/dist/index.js
44
- import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
45
- import { existsSync as existsSync2 } from "fs";
46
- import { homedir } from "os";
47
- import { join as join2 } from "path";
48
- import { createHmac, timingSafeEqual } from "crypto";
49
- import { randomUUID } from "crypto";
50
- import { spawn } from "child_process";
51
- import { randomUUID as randomUUID2 } from "crypto";
52
- function getPathValue(input, path) {
53
- return path.split(".").reduce((value, part) => {
54
- if (value && typeof value === "object" && part in value) {
55
- return value[part];
56
- }
57
- return;
58
- }, input);
59
- }
60
- function wildcardToRegExp(pattern) {
61
- const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
62
- return new RegExp(`^${escaped}$`);
63
- }
64
- function matchString(value, matcher) {
65
- if (matcher === undefined)
66
- return true;
67
- if (value === undefined)
68
- return false;
69
- const matchers = Array.isArray(matcher) ? matcher : [matcher];
70
- return matchers.some((item) => wildcardToRegExp(item).test(value));
71
- }
72
- function matchRecord(input, matcher) {
73
- if (!matcher)
74
- return true;
75
- return Object.entries(matcher).every(([path, expected]) => {
76
- const actual = getPathValue(input, path);
77
- if (typeof expected === "string" || Array.isArray(expected)) {
78
- return matchString(actual === undefined ? undefined : String(actual), expected);
79
- }
80
- return actual === expected;
81
- });
82
- }
83
- function eventMatchesFilter(event, filter) {
84
- 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);
85
- }
86
- function channelMatchesEvent(channel, event) {
87
- if (!channel.enabled)
88
- return false;
89
- if (!channel.filters || channel.filters.length === 0)
90
- return true;
91
- return channel.filters.some((filter) => eventMatchesFilter(event, filter));
92
- }
93
- var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
94
- var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
95
- function getEventsDataDir(override) {
96
- return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join2(homedir(), ".hasna", "events");
97
- }
98
-
99
- class JsonEventsStore {
100
- dataDir;
101
- channelsPath;
102
- eventsPath;
103
- deliveriesPath;
104
- constructor(dataDir = getEventsDataDir()) {
105
- this.dataDir = dataDir;
106
- this.channelsPath = join2(dataDir, "channels.json");
107
- this.eventsPath = join2(dataDir, "events.json");
108
- this.deliveriesPath = join2(dataDir, "deliveries.json");
109
- }
110
- async init() {
111
- await mkdir(this.dataDir, { recursive: true, mode: 448 });
112
- await chmod(this.dataDir, 448).catch(() => {
113
- return;
114
- });
115
- await this.ensureArrayFile(this.channelsPath);
116
- await this.ensureArrayFile(this.eventsPath);
117
- await this.ensureArrayFile(this.deliveriesPath);
118
- }
119
- async addChannel(channel) {
120
- await this.init();
121
- const channels = await this.readJson(this.channelsPath, []);
122
- const index = channels.findIndex((item) => item.id === channel.id);
123
- if (index >= 0) {
124
- channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
125
- } else {
126
- channels.push(channel);
127
- }
128
- await this.writeJson(this.channelsPath, channels);
129
- return index >= 0 ? channels[index] : channel;
130
- }
131
- async listChannels() {
132
- await this.init();
133
- return this.readJson(this.channelsPath, []);
134
- }
135
- async getChannel(id) {
136
- const channels = await this.listChannels();
137
- return channels.find((channel) => channel.id === id);
138
- }
139
- async removeChannel(id) {
140
- await this.init();
141
- const channels = await this.readJson(this.channelsPath, []);
142
- const next = channels.filter((channel) => channel.id !== id);
143
- await this.writeJson(this.channelsPath, next);
144
- return next.length !== channels.length;
145
- }
146
- async appendEvent(event) {
147
- await this.init();
148
- const events = await this.readJson(this.eventsPath, []);
149
- events.push(event);
150
- await this.writeJson(this.eventsPath, events);
151
- return event;
152
- }
153
- async listEvents() {
154
- await this.init();
155
- return this.readJson(this.eventsPath, []);
156
- }
157
- async findEventByIdentity(identity) {
158
- const events = await this.listEvents();
159
- return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
160
- }
161
- async appendDelivery(result) {
162
- await this.init();
163
- const deliveries = await this.readJson(this.deliveriesPath, []);
164
- deliveries.push(result);
165
- await this.writeJson(this.deliveriesPath, deliveries);
166
- return result;
167
- }
168
- async listDeliveries() {
169
- await this.init();
170
- return this.readJson(this.deliveriesPath, []);
171
- }
172
- async exportData() {
173
- return {
174
- channels: await this.listChannels(),
175
- events: await this.listEvents(),
176
- deliveries: await this.listDeliveries()
177
- };
178
- }
179
- async ensureArrayFile(path) {
180
- if (!existsSync2(path)) {
181
- await writeFile(path, `[]
182
- `, { encoding: "utf-8", mode: 384 });
183
- }
184
- await chmod(path, 384).catch(() => {
185
- return;
186
- });
187
- }
188
- async readJson(path, fallback) {
189
- try {
190
- const raw = await readFile(path, "utf-8");
191
- if (!raw.trim())
192
- return fallback;
193
- return JSON.parse(raw);
194
- } catch (error) {
195
- if (error.code === "ENOENT")
196
- return fallback;
197
- throw error;
198
- }
199
- }
200
- async writeJson(path, value) {
201
- const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
202
- await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
203
- `, { encoding: "utf-8", mode: 384 });
204
- await rename(tempPath, path);
205
- await chmod(path, 384).catch(() => {
206
- return;
207
- });
208
- }
209
- }
210
- var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
211
- function buildSignatureBase(timestamp, body) {
212
- return `${timestamp}.${body}`;
213
- }
214
- function signPayload(secret, timestamp, body) {
215
- const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
216
- return `sha256=${digest}`;
217
- }
218
- function now() {
219
- return new Date().toISOString();
220
- }
221
- function truncate(value, max = 4096) {
222
- return value.length > max ? `${value.slice(0, max)}...` : value;
223
- }
224
- function buildWebhookRequest(event, channel) {
225
- if (!channel.webhook)
226
- throw new Error(`Channel ${channel.id} has no webhook config`);
227
- const body = JSON.stringify(event);
228
- const timestamp = event.time;
229
- const headers = {
230
- "Content-Type": "application/json",
231
- "User-Agent": "@hasna/events",
232
- "X-Hasna-Event-Id": event.id,
233
- "X-Hasna-Event-Type": event.type,
234
- "X-Hasna-Timestamp": timestamp,
235
- ...channel.webhook.headers
236
- };
237
- if (channel.webhook.secret) {
238
- headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
239
- }
240
- return { body, headers };
241
- }
242
- async function dispatchWebhook(event, channel, options = {}) {
243
- if (!channel.webhook)
244
- throw new Error(`Channel ${channel.id} has no webhook config`);
245
- const startedAt = now();
246
- const { body, headers } = buildWebhookRequest(event, channel);
247
- const controller = new AbortController;
248
- const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
249
- try {
250
- const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
251
- method: "POST",
252
- headers,
253
- body,
254
- signal: controller.signal
255
- });
256
- const responseBody = truncate(await response.text());
257
- return {
258
- attempt: 1,
259
- status: response.ok ? "success" : "failed",
260
- startedAt,
261
- completedAt: now(),
262
- responseStatus: response.status,
263
- responseBody,
264
- error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
265
- };
266
- } catch (error) {
267
- return {
268
- attempt: 1,
269
- status: "failed",
270
- startedAt,
271
- completedAt: now(),
272
- error: error instanceof Error ? error.message : String(error)
273
- };
274
- } finally {
275
- clearTimeout(timeout);
276
- }
277
- }
278
- async function dispatchCommand(event, channel) {
279
- if (!channel.command)
280
- throw new Error(`Channel ${channel.id} has no command config`);
281
- const startedAt = now();
282
- const eventJson = JSON.stringify(event);
283
- const env = {
284
- ...process.env,
285
- ...channel.command.env,
286
- HASNA_CHANNEL_ID: channel.id,
287
- HASNA_EVENT_ID: event.id,
288
- HASNA_EVENT_TYPE: event.type,
289
- HASNA_EVENT_SOURCE: event.source,
290
- HASNA_EVENT_SUBJECT: event.subject ?? "",
291
- HASNA_EVENT_SEVERITY: event.severity,
292
- HASNA_EVENT_TIME: event.time,
293
- HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
294
- HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
295
- HASNA_EVENT_JSON: eventJson
296
- };
297
- return new Promise((resolve) => {
298
- const child = spawn(channel.command.command, channel.command.args ?? [], {
299
- cwd: channel.command.cwd,
300
- env,
301
- stdio: ["pipe", "pipe", "pipe"]
302
- });
303
- let stdout = "";
304
- let stderr = "";
305
- const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
306
- child.stdin.end(eventJson);
307
- child.stdout.on("data", (chunk) => {
308
- stdout += chunk.toString();
309
- });
310
- child.stderr.on("data", (chunk) => {
311
- stderr += chunk.toString();
312
- });
313
- child.on("error", (error) => {
314
- clearTimeout(timeout);
315
- resolve({
316
- attempt: 1,
317
- status: "failed",
318
- startedAt,
319
- completedAt: now(),
320
- stdout: truncate(stdout),
321
- stderr: truncate(stderr),
322
- error: error.message
323
- });
324
- });
325
- child.on("close", (code, signal) => {
326
- clearTimeout(timeout);
327
- const success = code === 0;
328
- resolve({
329
- attempt: 1,
330
- status: success ? "success" : "failed",
331
- startedAt,
332
- completedAt: now(),
333
- stdout: truncate(stdout),
334
- stderr: truncate(stderr),
335
- error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
336
- });
337
- });
338
- });
339
- }
340
- async function dispatchChannel(event, channel, options = {}) {
341
- if (channel.transport === "webhook")
342
- return dispatchWebhook(event, channel, options);
343
- if (channel.transport === "command")
344
- return dispatchCommand(event, channel);
345
- return {
346
- attempt: 1,
347
- status: "skipped",
348
- startedAt: now(),
349
- completedAt: now(),
350
- error: `Unsupported transport: ${channel.transport}`
351
- };
352
- }
353
- function createDeliveryResult(event, channel, attempts) {
354
- const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
355
- return {
356
- id: randomUUID(),
357
- eventId: event.id,
358
- channelId: channel.id,
359
- transport: channel.transport,
360
- status,
361
- attempts,
362
- createdAt: attempts[0]?.startedAt ?? now(),
363
- completedAt: attempts.at(-1)?.completedAt ?? now()
364
- };
365
- }
366
- function createEvent(input) {
367
- return {
368
- id: input.id ?? randomUUID2(),
369
- source: input.source,
370
- type: input.type,
371
- time: normalizeTime(input.time),
372
- subject: input.subject,
373
- severity: input.severity ?? "info",
374
- data: input.data ?? {},
375
- message: input.message,
376
- dedupeKey: input.dedupeKey,
377
- schemaVersion: input.schemaVersion ?? "1.0",
378
- metadata: input.metadata ?? {}
379
- };
380
- }
381
-
382
- class EventsClient {
383
- store;
384
- redactors;
385
- transportOptions;
386
- constructor(options = {}) {
387
- this.store = options.store ?? new JsonEventsStore(options.dataDir);
388
- this.redactors = options.redactors ?? [];
389
- this.transportOptions = { fetchImpl: options.fetchImpl };
390
- }
391
- async addChannel(input) {
392
- const timestamp = new Date().toISOString();
393
- return this.store.addChannel({
394
- ...input,
395
- createdAt: input.createdAt ?? timestamp,
396
- updatedAt: input.updatedAt ?? timestamp
397
- });
398
- }
399
- async listChannels() {
400
- return this.store.listChannels();
401
- }
402
- async removeChannel(id) {
403
- return this.store.removeChannel(id);
404
- }
405
- async emit(input, options = {}) {
406
- const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
407
- if (options.dedupe !== false) {
408
- const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
409
- if (existing) {
410
- return { event: existing, deliveries: [], deduped: true };
411
- }
412
- }
413
- await this.store.appendEvent(event);
414
- const deliveries = options.deliver === false ? [] : await this.deliver(event);
415
- return { event, deliveries, deduped: false };
416
- }
417
- async listEvents() {
418
- return this.store.listEvents();
419
- }
420
- async listDeliveries() {
421
- return this.store.listDeliveries();
422
- }
423
- async deliver(event) {
424
- const channels = await this.store.listChannels();
425
- const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
426
- const deliveries = [];
427
- for (const channel of selected) {
428
- const eventForChannel = await this.applyRedaction(event, channel);
429
- const result = await this.deliverWithRetry(eventForChannel, channel);
430
- await this.store.appendDelivery(result);
431
- deliveries.push(result);
432
- }
433
- return deliveries;
434
- }
435
- async testChannel(id, input = {}) {
436
- const channel = await this.store.getChannel(id);
437
- if (!channel)
438
- throw new Error(`Channel not found: ${id}`);
439
- const event = createEvent({
440
- source: input.source ?? "hasna.events",
441
- type: input.type ?? "events.test",
442
- subject: input.subject ?? id,
443
- severity: input.severity ?? "info",
444
- data: input.data ?? { test: true },
445
- message: input.message ?? "Hasna events test delivery",
446
- dedupeKey: input.dedupeKey,
447
- schemaVersion: input.schemaVersion,
448
- metadata: input.metadata,
449
- time: input.time,
450
- id: input.id
451
- });
452
- const eventForChannel = await this.applyRedaction(event, channel);
453
- const result = await this.deliverWithRetry(eventForChannel, channel);
454
- await this.store.appendDelivery(result);
455
- return result;
456
- }
457
- async replay(options = {}) {
458
- const events = (await this.store.listEvents()).filter((event) => {
459
- if (options.eventId && event.id !== options.eventId)
460
- return false;
461
- if (options.source && event.source !== options.source)
462
- return false;
463
- if (options.type && event.type !== options.type)
464
- return false;
465
- return true;
466
- });
467
- if (options.dryRun)
468
- return { events, deliveries: [] };
469
- const deliveries = [];
470
- for (const event of events) {
471
- deliveries.push(...await this.deliver(event));
472
- }
473
- return { events, deliveries };
474
- }
475
- async applyRedaction(event, channel) {
476
- let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
477
- for (const redactor of this.redactors) {
478
- next = await redactor(next, channel);
479
- }
480
- return next;
481
- }
482
- async deliverWithRetry(event, channel) {
483
- const policy = normalizeRetryPolicy(channel.retry);
484
- const attempts = [];
485
- for (let index = 0;index < policy.maxAttempts; index += 1) {
486
- const attempt = await dispatchChannel(event, channel, this.transportOptions);
487
- attempt.attempt = index + 1;
488
- if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
489
- attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
490
- }
491
- attempts.push(attempt);
492
- if (attempt.status !== "failed")
493
- break;
494
- if (attempt.nextBackoffMs)
495
- await Bun.sleep(attempt.nextBackoffMs);
496
- }
497
- return createDeliveryResult(event, channel, attempts);
498
- }
499
- }
500
- function redactPaths(event, paths, replacement = "[REDACTED]") {
501
- if (paths.length === 0)
502
- return event;
503
- const copy = structuredClone(event);
504
- for (const path of paths) {
505
- setPath(copy, path, replacement);
506
- }
507
- return copy;
508
- }
509
- function sanitizeChannelForOutput(channel) {
510
- const copy = structuredClone(channel);
511
- if (copy.webhook?.secret)
512
- copy.webhook.secret = "[REDACTED]";
513
- if (copy.command?.env) {
514
- copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
515
- }
516
- return copy;
517
- }
518
- function sanitizeChannelsForOutput(channels) {
519
- return channels.map(sanitizeChannelForOutput);
520
- }
521
- function redactSensitiveKeys(event, replacement = "[REDACTED]") {
522
- return redactValue(event, replacement);
523
- }
524
- function shouldRedactKey(key) {
525
- return /secret|token|password|api[_-]?key|authorization/i.test(key);
526
- }
527
- function redactValue(value, replacement) {
528
- if (Array.isArray(value))
529
- return value.map((item) => redactValue(item, replacement));
530
- if (!value || typeof value !== "object")
531
- return value;
532
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [
533
- key,
534
- shouldRedactKey(key) ? replacement : redactValue(item, replacement)
535
- ]));
536
- }
537
- function setPath(input, path, replacement) {
538
- const parts = path.split(".");
539
- let cursor = input;
540
- for (const part of parts.slice(0, -1)) {
541
- const next = cursor[part];
542
- if (!next || typeof next !== "object")
543
- return;
544
- cursor = next;
545
- }
546
- const last = parts.at(-1);
547
- if (last && last in cursor)
548
- cursor[last] = replacement;
549
- }
550
- function normalizeTime(value) {
551
- if (!value)
552
- return new Date().toISOString();
553
- return value instanceof Date ? value.toISOString() : value;
554
- }
555
- function normalizeRetryPolicy(policy) {
556
- return {
557
- maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
558
- backoffMs: Math.max(0, policy?.backoffMs ?? 250),
559
- multiplier: Math.max(1, policy?.multiplier ?? 2)
560
- };
561
- }
562
-
563
43
  // src/mcp/server.ts
44
+ import { EventsClient as EventsClient2, sanitizeChannelForOutput, sanitizeChannelsForOutput as sanitizeChannelsForOutput2 } from "@hasna/events";
564
45
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
565
46
 
566
47
  // node_modules/zod/v3/external.js
@@ -4537,8 +4018,8 @@ var coerce = {
4537
4018
  };
4538
4019
  var NEVER = INVALID;
4539
4020
  // src/commands/backup.ts
4540
- import { homedir as homedir2, hostname } from "os";
4541
- import { join as join3 } from "path";
4021
+ import { homedir, hostname } from "os";
4022
+ import { join as join2 } from "path";
4542
4023
  var MACHINES_BACKUP_BUCKET_ENV = "HASNA_MACHINES_S3_BUCKET";
4543
4024
  var MACHINES_BACKUP_BUCKET_FALLBACK_ENV = "MACHINES_S3_BUCKET";
4544
4025
  var MACHINES_BACKUP_PREFIX_ENV = "HASNA_MACHINES_S3_PREFIX";
@@ -4586,16 +4067,16 @@ function resolveBackupTarget(options = {}) {
4586
4067
  };
4587
4068
  }
4588
4069
  function defaultBackupSources() {
4589
- const home = homedir2();
4070
+ const home = homedir();
4590
4071
  return [
4591
- join3(home, ".hasna"),
4592
- join3(home, ".ssh"),
4593
- join3(home, ".secrets")
4072
+ join2(home, ".hasna"),
4073
+ join2(home, ".ssh"),
4074
+ join2(home, ".secrets")
4594
4075
  ];
4595
4076
  }
4596
4077
  function buildBackupPlan(bucket, prefix) {
4597
4078
  const target = resolveBackupTarget({ bucket, prefix });
4598
- const archivePath = join3(homedir2(), ".hasna", "machines", "backup.tgz");
4079
+ const archivePath = join2(homedir(), ".hasna", "machines", "backup.tgz");
4599
4080
  const sources = defaultBackupSources();
4600
4081
  const steps = [
4601
4082
  {
@@ -4646,33 +4127,33 @@ function runBackup(bucket, prefix, options = {}) {
4646
4127
  }
4647
4128
 
4648
4129
  // src/manifests.ts
4649
- import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync } from "fs";
4650
- import { arch, homedir as homedir3, hostname as hostname2, platform, userInfo } from "os";
4130
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
4131
+ import { arch, homedir as homedir2, hostname as hostname2, platform, userInfo } from "os";
4651
4132
  import { dirname as dirname3 } from "path";
4652
4133
 
4653
4134
  // src/paths.ts
4654
- import { existsSync as existsSync3, mkdirSync } from "fs";
4655
- import { dirname as dirname2, join as join4, resolve } from "path";
4135
+ import { existsSync as existsSync2, mkdirSync } from "fs";
4136
+ import { dirname as dirname2, join as join3, resolve } from "path";
4656
4137
  function homeDir() {
4657
4138
  return process.env["HOME"] || process.env["USERPROFILE"] || "~";
4658
4139
  }
4659
4140
  function getDataDir() {
4660
- return process.env["HASNA_MACHINES_DIR"] || join4(homeDir(), ".hasna", "machines");
4141
+ return process.env["HASNA_MACHINES_DIR"] || join3(homeDir(), ".hasna", "machines");
4661
4142
  }
4662
4143
  function getDbPath() {
4663
- return process.env["HASNA_MACHINES_DB_PATH"] || join4(getDataDir(), "machines.db");
4144
+ return process.env["HASNA_MACHINES_DB_PATH"] || join3(getDataDir(), "machines.db");
4664
4145
  }
4665
4146
  function getManifestPath() {
4666
- return process.env["HASNA_MACHINES_MANIFEST_PATH"] || join4(getDataDir(), "machines.json");
4147
+ return process.env["HASNA_MACHINES_MANIFEST_PATH"] || join3(getDataDir(), "machines.json");
4667
4148
  }
4668
4149
  function getNotificationsPath() {
4669
- return process.env["HASNA_MACHINES_NOTIFICATIONS_PATH"] || join4(getDataDir(), "notifications.json");
4150
+ return process.env["HASNA_MACHINES_NOTIFICATIONS_PATH"] || join3(getDataDir(), "notifications.json");
4670
4151
  }
4671
4152
  function ensureParentDir(filePath) {
4672
4153
  if (filePath === ":memory:")
4673
4154
  return;
4674
4155
  const dir = dirname2(resolve(filePath));
4675
- if (!existsSync3(dir)) {
4156
+ if (!existsSync2(dir)) {
4676
4157
  mkdirSync(dir, { recursive: true });
4677
4158
  }
4678
4159
  }
@@ -4714,7 +4195,7 @@ var fleetSchema = exports_external.object({
4714
4195
  machines: exports_external.array(machineSchema)
4715
4196
  });
4716
4197
  function detectWorkspacePath() {
4717
- const home = homedir3();
4198
+ const home = homedir2();
4718
4199
  if (platform() === "darwin") {
4719
4200
  return `${home}/Workspace`;
4720
4201
  }
@@ -4738,7 +4219,7 @@ function getDefaultManifest() {
4738
4219
  };
4739
4220
  }
4740
4221
  function readManifest(path = getManifestPath()) {
4741
- if (!existsSync4(path)) {
4222
+ if (!existsSync3(path)) {
4742
4223
  return getDefaultManifest();
4743
4224
  }
4744
4225
  const raw = JSON.parse(readFileSync2(path, "utf8"));
@@ -4874,19 +4355,19 @@ function countRuns(table) {
4874
4355
  }
4875
4356
  function recordSetupRun(machineId, status, details) {
4876
4357
  const db = getDb();
4877
- const now2 = new Date().toISOString();
4358
+ const now = new Date().toISOString();
4878
4359
  db.query(`INSERT INTO setup_runs (id, machine_id, status, details_json, created_at, updated_at)
4879
- VALUES (?, ?, ?, ?, ?, ?)`).run(crypto.randomUUID(), machineId, status, JSON.stringify(details), now2, now2);
4360
+ VALUES (?, ?, ?, ?, ?, ?)`).run(crypto.randomUUID(), machineId, status, JSON.stringify(details), now, now);
4880
4361
  }
4881
4362
  function recordSyncRun(machineId, status, actions) {
4882
4363
  const db = getDb();
4883
- const now2 = new Date().toISOString();
4364
+ const now = new Date().toISOString();
4884
4365
  db.query(`INSERT INTO sync_runs (id, machine_id, status, actions_json, created_at, updated_at)
4885
- VALUES (?, ?, ?, ?, ?, ?)`).run(crypto.randomUUID(), machineId, status, JSON.stringify(actions), now2, now2);
4366
+ VALUES (?, ?, ?, ?, ?, ?)`).run(crypto.randomUUID(), machineId, status, JSON.stringify(actions), now, now);
4886
4367
  }
4887
4368
 
4888
4369
  // src/topology.ts
4889
- import { existsSync as existsSync5 } from "fs";
4370
+ import { existsSync as existsSync4 } from "fs";
4890
4371
  import { arch as arch2, hostname as hostname4, platform as platform2, userInfo as userInfo2 } from "os";
4891
4372
  import { spawnSync } from "child_process";
4892
4373
  var MACHINES_CONSUMER_CONTRACT_VERSION = 1;
@@ -5076,7 +4557,7 @@ function buildEntry(input) {
5076
4557
  };
5077
4558
  }
5078
4559
  function discoverMachineTopology(options = {}) {
5079
- const now2 = options.now ?? new Date;
4560
+ const now = options.now ?? new Date;
5080
4561
  const runner = options.runner ?? defaultRunner;
5081
4562
  const warnings = [];
5082
4563
  const manifest = readManifest();
@@ -5108,11 +4589,11 @@ function discoverMachineTopology(options = {}) {
5108
4589
  version: getPackageVersion()
5109
4590
  },
5110
4591
  capabilities: getMachinesConsumerCapabilities(),
5111
- generated_at: now2.toISOString(),
4592
+ generated_at: now.toISOString(),
5112
4593
  local_machine_id: localMachineId,
5113
4594
  local_hostname: hostname4(),
5114
4595
  current_platform: normalizePlatform2(),
5115
- manifest_path_known: existsSync5(getManifestPath()),
4596
+ manifest_path_known: existsSync4(getManifestPath()),
5116
4597
  machines,
5117
4598
  warnings
5118
4599
  };
@@ -5231,11 +4712,11 @@ function cacheability(input) {
5231
4712
  };
5232
4713
  }
5233
4714
  function resolveMachineRoute(machineId, options = {}) {
5234
- const now2 = options.now ?? new Date;
4715
+ const now = options.now ?? new Date;
5235
4716
  const topology = options.topology ?? discoverMachineTopology(options);
5236
4717
  const warnings = [...topology.warnings];
5237
4718
  const { machine, matchedBy } = findRouteMachine(topology, machineId);
5238
- const generatedAt = now2.toISOString();
4719
+ const generatedAt = now.toISOString();
5239
4720
  if (!machine) {
5240
4721
  warnings.push(`machine_not_found:${machineId}`);
5241
4722
  return {
@@ -5261,8 +4742,8 @@ function resolveMachineRoute(machineId, options = {}) {
5261
4742
  },
5262
4743
  cacheability: cacheability({
5263
4744
  ok: false,
5264
- observedAt: now2,
5265
- now: now2,
4745
+ observedAt: now,
4746
+ now,
5266
4747
  ttlMs: options.resolverTtlMs,
5267
4748
  authority: "unresolved",
5268
4749
  confidence: "none",
@@ -5299,8 +4780,8 @@ function resolveMachineRoute(machineId, options = {}) {
5299
4780
  },
5300
4781
  cacheability: cacheability({
5301
4782
  ok,
5302
- observedAt: now2,
5303
- now: now2,
4783
+ observedAt: now,
4784
+ now,
5304
4785
  ttlMs: options.resolverTtlMs,
5305
4786
  authority: routeAuthority({ machine, selectedHint, matchedBy }),
5306
4787
  confidence,
@@ -5387,7 +4868,7 @@ function canCheckPathForMachine(machine, localMachineId) {
5387
4868
  function checkedPathExists(path, check) {
5388
4869
  if (!path || !check)
5389
4870
  return null;
5390
- return existsSync5(path);
4871
+ return existsSync4(path);
5391
4872
  }
5392
4873
  function repairHint(input) {
5393
4874
  const command = [
@@ -5602,11 +5083,11 @@ function metadataKeysForDiagnostics(metadata) {
5602
5083
  return Object.keys(metadata).filter((key) => !/(secret|token|key|password|credential)/i.test(key)).sort();
5603
5084
  }
5604
5085
  function resolveMachineWorkspace(options) {
5605
- const now2 = options.now ?? new Date;
5086
+ const now = options.now ?? new Date;
5606
5087
  const topology = options.topology ?? discoverMachineTopology(options);
5607
5088
  const warnings = [...topology.warnings];
5608
5089
  const { machine, matchedBy } = findRouteMachine(topology, options.machineId);
5609
- const generatedAt = now2.toISOString();
5090
+ const generatedAt = now.toISOString();
5610
5091
  const repoName = options.repoName ?? options.projectId;
5611
5092
  const openFilesRepoName = options.openFilesRepoName ?? "open-files";
5612
5093
  if (!machine) {
@@ -5635,8 +5116,8 @@ function resolveMachineWorkspace(options) {
5635
5116
  },
5636
5117
  cacheability: cacheability({
5637
5118
  ok: false,
5638
- observedAt: now2,
5639
- now: now2,
5119
+ observedAt: now,
5120
+ now,
5640
5121
  ttlMs: options.resolverTtlMs,
5641
5122
  authority: "unresolved",
5642
5123
  confidence: "none",
@@ -5708,8 +5189,8 @@ function resolveMachineWorkspace(options) {
5708
5189
  },
5709
5190
  cacheability: cacheability({
5710
5191
  ok: workspaceOk,
5711
- observedAt: now2,
5712
- now: now2,
5192
+ observedAt: now,
5193
+ now,
5713
5194
  ttlMs: options.resolverTtlMs,
5714
5195
  authority: workspaceAuthority(workspacePaths),
5715
5196
  confidence: workspaceOk ? "medium" : "none",
@@ -5946,21 +5427,21 @@ function runAppsInstall(machineId, options = {}, runner = runMachineCommand) {
5946
5427
  }
5947
5428
 
5948
5429
  // src/commands/cert.ts
5949
- import { homedir as homedir4, platform as platform3 } from "os";
5950
- import { join as join5 } from "path";
5430
+ import { homedir as homedir3, platform as platform3 } from "os";
5431
+ import { join as join4 } from "path";
5951
5432
  function quote2(value) {
5952
5433
  return `'${value.replace(/'/g, `'\\''`)}'`;
5953
5434
  }
5954
5435
  function certDir() {
5955
- return join5(homedir4(), ".hasna", "machines", "certs");
5436
+ return join4(homedir3(), ".hasna", "machines", "certs");
5956
5437
  }
5957
5438
  function buildCertPlan(domains) {
5958
5439
  if (domains.length === 0) {
5959
5440
  throw new Error("At least one domain is required.");
5960
5441
  }
5961
5442
  const primary = domains[0];
5962
- const certPath = join5(certDir(), `${primary}.pem`);
5963
- const keyPath = join5(certDir(), `${primary}-key.pem`);
5443
+ const certPath = join4(certDir(), `${primary}.pem`);
5444
+ const keyPath = join4(certDir(), `${primary}-key.pem`);
5964
5445
  const steps = [];
5965
5446
  if (platform3() === "darwin") {
5966
5447
  steps.push({
@@ -6024,14 +5505,14 @@ function runCertPlan(domains, options = {}) {
6024
5505
  }
6025
5506
 
6026
5507
  // src/commands/dns.ts
6027
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
6028
- import { join as join6 } from "path";
5508
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
5509
+ import { join as join5 } from "path";
6029
5510
  function getDnsPath() {
6030
- return join6(getDataDir(), "dns.json");
5511
+ return join5(getDataDir(), "dns.json");
6031
5512
  }
6032
5513
  function readMappings() {
6033
5514
  const path = getDnsPath();
6034
- if (!existsSync6(path))
5515
+ if (!existsSync5(path))
6035
5516
  return [];
6036
5517
  return JSON.parse(readFileSync3(path, "utf8"));
6037
5518
  }
@@ -6060,10 +5541,10 @@ function renderDomainMapping(domain) {
6060
5541
  hostsEntry: `${entry.targetHost} ${entry.domain}`,
6061
5542
  caddySnippet: `${entry.domain} {
6062
5543
  reverse_proxy 127.0.0.1:${entry.port}
6063
- tls ${join6(getDataDir(), "certs", `${entry.domain}.pem`)} ${join6(getDataDir(), "certs", `${entry.domain}-key.pem`)}
5544
+ tls ${join5(getDataDir(), "certs", `${entry.domain}.pem`)} ${join5(getDataDir(), "certs", `${entry.domain}-key.pem`)}
6064
5545
  }`,
6065
- certPath: join6(getDataDir(), "certs", `${entry.domain}.pem`),
6066
- keyPath: join6(getDataDir(), "certs", `${entry.domain}-key.pem`)
5546
+ certPath: join5(getDataDir(), "certs", `${entry.domain}.pem`),
5547
+ keyPath: join5(getDataDir(), "certs", `${entry.domain}-key.pem`)
6067
5548
  };
6068
5549
  }
6069
5550
 
@@ -6333,7 +5814,7 @@ function runTailscaleInstall(machineId, options = {}, runner = runMachineCommand
6333
5814
  }
6334
5815
 
6335
5816
  // src/commands/notifications.ts
6336
- import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
5817
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
6337
5818
  var notificationChannelSchema = exports_external.object({
6338
5819
  id: exports_external.string(),
6339
5820
  type: exports_external.enum(["email", "webhook", "command"]),
@@ -6415,7 +5896,7 @@ ${message}
6415
5896
  }
6416
5897
  throw new Error("No local email transport available. Install sendmail or mail.");
6417
5898
  }
6418
- async function dispatchWebhook2(channel, event, message) {
5899
+ async function dispatchWebhook(channel, event, message) {
6419
5900
  const response = await fetch(channel.target, {
6420
5901
  method: "POST",
6421
5902
  headers: {
@@ -6440,7 +5921,7 @@ async function dispatchWebhook2(channel, event, message) {
6440
5921
  detail: `Webhook accepted with HTTP ${response.status}`
6441
5922
  };
6442
5923
  }
6443
- async function dispatchCommand2(channel, event, message) {
5924
+ async function dispatchCommand(channel, event, message) {
6444
5925
  const result = Bun.spawnSync(["bash", "-lc", channel.target], {
6445
5926
  stdout: "pipe",
6446
5927
  stderr: "pipe",
@@ -6463,7 +5944,7 @@ async function dispatchCommand2(channel, event, message) {
6463
5944
  detail: stdout || "Command completed successfully"
6464
5945
  };
6465
5946
  }
6466
- async function dispatchChannel2(channel, event, message) {
5947
+ async function dispatchChannel(channel, event, message) {
6467
5948
  if (!channel.enabled) {
6468
5949
  return {
6469
5950
  channelId: channel.id,
@@ -6477,9 +5958,9 @@ async function dispatchChannel2(channel, event, message) {
6477
5958
  return dispatchEmail(channel, event, message);
6478
5959
  }
6479
5960
  if (channel.type === "webhook") {
6480
- return dispatchWebhook2(channel, event, message);
5961
+ return dispatchWebhook(channel, event, message);
6481
5962
  }
6482
- return dispatchCommand2(channel, event, message);
5963
+ return dispatchCommand(channel, event, message);
6483
5964
  }
6484
5965
  function getDefaultNotificationConfig() {
6485
5966
  return {
@@ -6489,7 +5970,7 @@ function getDefaultNotificationConfig() {
6489
5970
  };
6490
5971
  }
6491
5972
  function readNotificationConfig(path = getNotificationsPath()) {
6492
- if (!existsSync7(path)) {
5973
+ if (!existsSync6(path)) {
6493
5974
  return getDefaultNotificationConfig();
6494
5975
  }
6495
5976
  return notificationConfigSchema.parse(JSON.parse(readFileSync4(path, "utf8")));
@@ -6534,7 +6015,7 @@ async function dispatchNotificationEvent(event, message, options = {}) {
6534
6015
  const deliveries = [];
6535
6016
  for (const channel of channels) {
6536
6017
  try {
6537
- deliveries.push(await dispatchChannel2(channel, event, message));
6018
+ deliveries.push(await dispatchChannel(channel, event, message));
6538
6019
  } catch (error) {
6539
6020
  deliveries.push({
6540
6021
  channelId: channel.id,
@@ -6569,7 +6050,7 @@ async function testNotificationChannel(channelId, event = "manual.test", message
6569
6050
  if (!options.yes) {
6570
6051
  throw new Error("Notification test execution requires --yes.");
6571
6052
  }
6572
- const delivery = await dispatchChannel2(channel, event, message);
6053
+ const delivery = await dispatchChannel(channel, event, message);
6573
6054
  return {
6574
6055
  channelId,
6575
6056
  mode: "apply",
@@ -6633,6 +6114,9 @@ function listPorts(machineId) {
6633
6114
  };
6634
6115
  }
6635
6116
 
6117
+ // src/commands/serve.ts
6118
+ import { EventsClient, sanitizeChannelsForOutput } from "@hasna/events";
6119
+
6636
6120
  // src/commands/manifest.ts
6637
6121
  function manifestList() {
6638
6122
  return readManifest();
@@ -6910,7 +6394,7 @@ function renderDashboardHtml() {
6910
6394
  }
6911
6395
 
6912
6396
  // src/commands/setup.ts
6913
- import { homedir as homedir5 } from "os";
6397
+ import { homedir as homedir4 } from "os";
6914
6398
  function quote3(value) {
6915
6399
  return `'${value.replace(/'/g, `'\\''`)}'`;
6916
6400
  }
@@ -6983,7 +6467,7 @@ function buildSetupPlan(machineId) {
6983
6467
  const target = selected || {
6984
6468
  id: currentMachineId,
6985
6469
  platform: "linux",
6986
- workspacePath: `${homedir5()}/workspace`
6470
+ workspacePath: `${homedir4()}/workspace`
6987
6471
  };
6988
6472
  const steps = [...buildBaseSteps(target), ...buildPackageSteps(target)];
6989
6473
  return {
@@ -7028,8 +6512,8 @@ function runSetup(machineId, options = {}, runner = runMachineCommand) {
7028
6512
  }
7029
6513
 
7030
6514
  // src/commands/sync.ts
7031
- import { existsSync as existsSync8, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
7032
- import { homedir as homedir6 } from "os";
6515
+ import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
6516
+ import { homedir as homedir5 } from "os";
7033
6517
  function quote4(value) {
7034
6518
  return `'${value.replace(/'/g, `'\\''`)}'`;
7035
6519
  }
@@ -7081,8 +6565,8 @@ function detectFileActions(machine) {
7081
6565
  throw new Error(`Remote file sync planning is not supported for ${machine.id}; refusing to inspect or apply local paths as remote state.`);
7082
6566
  }
7083
6567
  return (machine.files || []).map((file, index) => {
7084
- const sourceExists = existsSync8(file.source);
7085
- const targetExists = existsSync8(file.target);
6568
+ const sourceExists = existsSync7(file.source);
6569
+ const targetExists = existsSync7(file.target);
7086
6570
  let status = "missing";
7087
6571
  if (sourceExists && targetExists) {
7088
6572
  if (file.mode === "symlink") {
@@ -7113,7 +6597,7 @@ function buildSyncPlan(machineId, runner = runMachineCommand) {
7113
6597
  const target = selected || {
7114
6598
  id: currentMachineId,
7115
6599
  platform: "linux",
7116
- workspacePath: `${homedir6()}/workspace`
6600
+ workspacePath: `${homedir5()}/workspace`
7117
6601
  };
7118
6602
  const actions = [
7119
6603
  ...detectPackageActions(target, runner),
@@ -7704,7 +7188,7 @@ function upsertSqlite(db, table, columns, rows) {
7704
7188
  }
7705
7189
  function recordSyncMeta(db, direction, results) {
7706
7190
  ensureSyncMetaTable(db);
7707
- const now2 = new Date().toISOString();
7191
+ const now = new Date().toISOString();
7708
7192
  const statement = db.query(`
7709
7193
  INSERT INTO _machines_sync_meta (table_name, last_synced_at, direction)
7710
7194
  VALUES (?, ?, ?)
@@ -7713,7 +7197,7 @@ function recordSyncMeta(db, direction, results) {
7713
7197
  for (const result of results) {
7714
7198
  if (result.errors.length > 0)
7715
7199
  continue;
7716
- statement.run(result.table, now2, direction);
7200
+ statement.run(result.table, now, direction);
7717
7201
  }
7718
7202
  }
7719
7203
  function ensureSyncMetaTable(db) {
@@ -7763,7 +7247,7 @@ function buildServer(version = getPackageVersion()) {
7763
7247
  }
7764
7248
  function createMcpServer(version) {
7765
7249
  const server = new McpServer({ name: "machines", version });
7766
- const events = new EventsClient;
7250
+ const events = new EventsClient2;
7767
7251
  server.tool("machines_status", "Return local machine fleet status paths and machine identity.", {}, async () => ({
7768
7252
  content: [{ type: "text", text: JSON.stringify(getStatus(), null, 2) }]
7769
7253
  }));
@@ -7917,7 +7401,7 @@ function createMcpServer(version) {
7917
7401
  }));
7918
7402
  server.tool("machines_notifications_dispatch", "Dispatch an event to matching notification channels.", { event: exports_external.string().describe("Event name"), message: exports_external.string().describe("Message body"), channel_id: exports_external.string().optional().describe("Limit delivery to one channel") }, async ({ event, message, channel_id }) => ({ content: [{ type: "text", text: JSON.stringify(await dispatchNotificationEvent(event, message, { channelId: channel_id }), null, 2) }] }));
7919
7403
  server.tool("machines_notifications_remove", "Remove a notification channel.", { channel_id: exports_external.string().describe("Channel identifier") }, async ({ channel_id }) => ({ content: [{ type: "text", text: JSON.stringify(removeNotificationChannel(channel_id), null, 2) }] }));
7920
- server.tool("machines_webhooks_add", "Add or replace a shared Hasna event webhook channel.", {
7404
+ server.tool("machines_webhooks_add", "Add or replace a shared event webhook channel.", {
7921
7405
  channel_id: exports_external.string().describe("Channel identifier"),
7922
7406
  url: exports_external.string().url().describe("Webhook URL"),
7923
7407
  event_type: exports_external.string().optional().describe("Optional event type filter, e.g. machines.*"),
@@ -7925,26 +7409,26 @@ function createMcpServer(version) {
7925
7409
  secret: exports_external.string().optional().describe("Optional HMAC secret"),
7926
7410
  enabled: exports_external.boolean().optional().describe("Whether the channel is enabled")
7927
7411
  }, async ({ channel_id, url, event_type, source, secret, enabled }) => {
7928
- const now2 = new Date().toISOString();
7412
+ const now = new Date().toISOString();
7929
7413
  const channel = await events.addChannel({
7930
7414
  id: channel_id,
7931
7415
  enabled: enabled ?? true,
7932
7416
  transport: "webhook",
7933
7417
  filters: event_type || source ? [{ type: event_type, source }] : undefined,
7934
7418
  webhook: { url, secret },
7935
- createdAt: now2,
7936
- updatedAt: now2
7419
+ createdAt: now,
7420
+ updatedAt: now
7937
7421
  });
7938
7422
  return { content: [{ type: "text", text: JSON.stringify(sanitizeChannelForOutput(channel), null, 2) }] };
7939
7423
  });
7940
- server.tool("machines_webhooks_list", "List shared Hasna event webhook channels.", {}, async () => ({
7941
- content: [{ type: "text", text: JSON.stringify(sanitizeChannelsForOutput(await events.listChannels()), null, 2) }]
7424
+ server.tool("machines_webhooks_list", "List shared event webhook channels.", {}, async () => ({
7425
+ content: [{ type: "text", text: JSON.stringify(sanitizeChannelsForOutput2(await events.listChannels()), null, 2) }]
7942
7426
  }));
7943
- server.tool("machines_webhooks_test", "Send a test event to one shared Hasna event channel.", { channel_id: exports_external.string().describe("Channel identifier"), event_type: exports_external.string().optional().describe("Event type"), message: exports_external.string().optional().describe("Message body") }, async ({ channel_id, event_type, message }) => ({
7427
+ server.tool("machines_webhooks_test", "Send a test event to one shared event channel.", { channel_id: exports_external.string().describe("Channel identifier"), event_type: exports_external.string().optional().describe("Event type"), message: exports_external.string().optional().describe("Message body") }, async ({ channel_id, event_type, message }) => ({
7944
7428
  content: [{ type: "text", text: JSON.stringify(await events.testChannel(channel_id, { source: "machines", type: event_type ?? "events.test", message }), null, 2) }]
7945
7429
  }));
7946
- server.tool("machines_webhooks_remove", "Remove a shared Hasna event channel.", { channel_id: exports_external.string().describe("Channel identifier") }, async ({ channel_id }) => ({ content: [{ type: "text", text: JSON.stringify({ removed: await events.removeChannel(channel_id) }, null, 2) }] }));
7947
- server.tool("machines_events_emit", "Emit a shared Hasna event from machines.", {
7430
+ server.tool("machines_webhooks_remove", "Remove a shared event channel.", { channel_id: exports_external.string().describe("Channel identifier") }, async ({ channel_id }) => ({ content: [{ type: "text", text: JSON.stringify({ removed: await events.removeChannel(channel_id) }, null, 2) }] }));
7431
+ server.tool("machines_events_emit", "Emit a shared event from machines.", {
7948
7432
  event_type: exports_external.string().describe("Event type"),
7949
7433
  subject: exports_external.string().optional().describe("Event subject"),
7950
7434
  severity: exports_external.enum(["debug", "info", "notice", "warning", "error", "critical"]).optional().describe("Event severity"),
@@ -7965,10 +7449,10 @@ function createMcpServer(version) {
7965
7449
  dedupeKey: dedupe_key
7966
7450
  }, { deliver: deliver !== false }), null, 2) }]
7967
7451
  }));
7968
- server.tool("machines_events_list", "List shared Hasna events.", {}, async () => ({
7452
+ server.tool("machines_events_list", "List shared events.", {}, async () => ({
7969
7453
  content: [{ type: "text", text: JSON.stringify(await events.listEvents(), null, 2) }]
7970
7454
  }));
7971
- server.tool("machines_events_replay", "Replay shared Hasna events.", { event_id: exports_external.string().optional().describe("Event id"), source: exports_external.string().optional().describe("Source filter"), event_type: exports_external.string().optional().describe("Event type filter"), dry_run: exports_external.boolean().optional().describe("Preview without delivery") }, async ({ event_id, source, event_type, dry_run }) => ({
7455
+ server.tool("machines_events_replay", "Replay shared events.", { event_id: exports_external.string().optional().describe("Event id"), source: exports_external.string().optional().describe("Source filter"), event_type: exports_external.string().optional().describe("Event type filter"), dry_run: exports_external.boolean().optional().describe("Preview without delivery") }, async ({ event_id, source, event_type, dry_run }) => ({
7972
7456
  content: [{ type: "text", text: JSON.stringify(await events.replay({ eventId: event_id, source, type: event_type, dryRun: dry_run }), null, 2) }]
7973
7457
  }));
7974
7458
  server.tool("machines_serve_info", "Preview the dashboard server bind address and routes.", { host: exports_external.string().optional().describe("Host interface"), port: exports_external.number().optional().describe("Port number") }, async ({ host, port }) => ({ content: [{ type: "text", text: JSON.stringify(getServeInfo({ host, port }), null, 2) }] }));