@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/index.js CHANGED
@@ -13440,528 +13440,7 @@ function listPorts(machineId) {
13440
13440
  // src/commands/runtime.ts
13441
13441
  import { spawnSync as spawnSync4 } from "child_process";
13442
13442
  import { setTimeout as sleep } from "timers/promises";
13443
-
13444
- // node_modules/@hasna/events/dist/index.js
13445
- import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
13446
- import { existsSync as existsSync7 } from "fs";
13447
- import { homedir as homedir4 } from "os";
13448
- import { join as join6 } from "path";
13449
- import { createHmac, timingSafeEqual } from "crypto";
13450
- import { randomUUID } from "crypto";
13451
- import { spawn } from "child_process";
13452
- import { randomUUID as randomUUID2 } from "crypto";
13453
- function getPathValue(input, path) {
13454
- return path.split(".").reduce((value, part) => {
13455
- if (value && typeof value === "object" && part in value) {
13456
- return value[part];
13457
- }
13458
- return;
13459
- }, input);
13460
- }
13461
- function wildcardToRegExp(pattern) {
13462
- const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
13463
- return new RegExp(`^${escaped}$`);
13464
- }
13465
- function matchString(value, matcher) {
13466
- if (matcher === undefined)
13467
- return true;
13468
- if (value === undefined)
13469
- return false;
13470
- const matchers = Array.isArray(matcher) ? matcher : [matcher];
13471
- return matchers.some((item) => wildcardToRegExp(item).test(value));
13472
- }
13473
- function matchRecord(input, matcher) {
13474
- if (!matcher)
13475
- return true;
13476
- return Object.entries(matcher).every(([path, expected]) => {
13477
- const actual = getPathValue(input, path);
13478
- if (typeof expected === "string" || Array.isArray(expected)) {
13479
- return matchString(actual === undefined ? undefined : String(actual), expected);
13480
- }
13481
- return actual === expected;
13482
- });
13483
- }
13484
- function eventMatchesFilter(event, filter) {
13485
- 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);
13486
- }
13487
- function channelMatchesEvent(channel, event) {
13488
- if (!channel.enabled)
13489
- return false;
13490
- if (!channel.filters || channel.filters.length === 0)
13491
- return true;
13492
- return channel.filters.some((filter) => eventMatchesFilter(event, filter));
13493
- }
13494
- var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
13495
- var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
13496
- function getEventsDataDir(override) {
13497
- return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join6(homedir4(), ".hasna", "events");
13498
- }
13499
-
13500
- class JsonEventsStore {
13501
- dataDir;
13502
- channelsPath;
13503
- eventsPath;
13504
- deliveriesPath;
13505
- constructor(dataDir = getEventsDataDir()) {
13506
- this.dataDir = dataDir;
13507
- this.channelsPath = join6(dataDir, "channels.json");
13508
- this.eventsPath = join6(dataDir, "events.json");
13509
- this.deliveriesPath = join6(dataDir, "deliveries.json");
13510
- }
13511
- async init() {
13512
- await mkdir(this.dataDir, { recursive: true, mode: 448 });
13513
- await chmod(this.dataDir, 448).catch(() => {
13514
- return;
13515
- });
13516
- await this.ensureArrayFile(this.channelsPath);
13517
- await this.ensureArrayFile(this.eventsPath);
13518
- await this.ensureArrayFile(this.deliveriesPath);
13519
- }
13520
- async addChannel(channel) {
13521
- await this.init();
13522
- const channels = await this.readJson(this.channelsPath, []);
13523
- const index = channels.findIndex((item) => item.id === channel.id);
13524
- if (index >= 0) {
13525
- channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
13526
- } else {
13527
- channels.push(channel);
13528
- }
13529
- await this.writeJson(this.channelsPath, channels);
13530
- return index >= 0 ? channels[index] : channel;
13531
- }
13532
- async listChannels() {
13533
- await this.init();
13534
- return this.readJson(this.channelsPath, []);
13535
- }
13536
- async getChannel(id) {
13537
- const channels = await this.listChannels();
13538
- return channels.find((channel) => channel.id === id);
13539
- }
13540
- async removeChannel(id) {
13541
- await this.init();
13542
- const channels = await this.readJson(this.channelsPath, []);
13543
- const next = channels.filter((channel) => channel.id !== id);
13544
- await this.writeJson(this.channelsPath, next);
13545
- return next.length !== channels.length;
13546
- }
13547
- async appendEvent(event) {
13548
- await this.init();
13549
- const events = await this.readJson(this.eventsPath, []);
13550
- events.push(event);
13551
- await this.writeJson(this.eventsPath, events);
13552
- return event;
13553
- }
13554
- async listEvents() {
13555
- await this.init();
13556
- return this.readJson(this.eventsPath, []);
13557
- }
13558
- async findEventByIdentity(identity) {
13559
- const events = await this.listEvents();
13560
- return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
13561
- }
13562
- async appendDelivery(result) {
13563
- await this.init();
13564
- const deliveries = await this.readJson(this.deliveriesPath, []);
13565
- deliveries.push(result);
13566
- await this.writeJson(this.deliveriesPath, deliveries);
13567
- return result;
13568
- }
13569
- async listDeliveries() {
13570
- await this.init();
13571
- return this.readJson(this.deliveriesPath, []);
13572
- }
13573
- async exportData() {
13574
- return {
13575
- channels: await this.listChannels(),
13576
- events: await this.listEvents(),
13577
- deliveries: await this.listDeliveries()
13578
- };
13579
- }
13580
- async ensureArrayFile(path) {
13581
- if (!existsSync7(path)) {
13582
- await writeFile(path, `[]
13583
- `, { encoding: "utf-8", mode: 384 });
13584
- }
13585
- await chmod(path, 384).catch(() => {
13586
- return;
13587
- });
13588
- }
13589
- async readJson(path, fallback) {
13590
- try {
13591
- const raw = await readFile(path, "utf-8");
13592
- if (!raw.trim())
13593
- return fallback;
13594
- return JSON.parse(raw);
13595
- } catch (error) {
13596
- if (error.code === "ENOENT")
13597
- return fallback;
13598
- throw error;
13599
- }
13600
- }
13601
- async writeJson(path, value) {
13602
- const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
13603
- await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
13604
- `, { encoding: "utf-8", mode: 384 });
13605
- await rename(tempPath, path);
13606
- await chmod(path, 384).catch(() => {
13607
- return;
13608
- });
13609
- }
13610
- }
13611
- var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
13612
- function buildSignatureBase(timestamp, body) {
13613
- return `${timestamp}.${body}`;
13614
- }
13615
- function signPayload(secret, timestamp, body) {
13616
- const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
13617
- return `sha256=${digest}`;
13618
- }
13619
- function now() {
13620
- return new Date().toISOString();
13621
- }
13622
- function truncate(value, max = 4096) {
13623
- return value.length > max ? `${value.slice(0, max)}...` : value;
13624
- }
13625
- function buildWebhookRequest(event, channel) {
13626
- if (!channel.webhook)
13627
- throw new Error(`Channel ${channel.id} has no webhook config`);
13628
- const body = JSON.stringify(event);
13629
- const timestamp = event.time;
13630
- const headers = {
13631
- "Content-Type": "application/json",
13632
- "User-Agent": "@hasna/events",
13633
- "X-Hasna-Event-Id": event.id,
13634
- "X-Hasna-Event-Type": event.type,
13635
- "X-Hasna-Timestamp": timestamp,
13636
- ...channel.webhook.headers
13637
- };
13638
- if (channel.webhook.secret) {
13639
- headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
13640
- }
13641
- return { body, headers };
13642
- }
13643
- async function dispatchWebhook2(event, channel, options = {}) {
13644
- if (!channel.webhook)
13645
- throw new Error(`Channel ${channel.id} has no webhook config`);
13646
- const startedAt = now();
13647
- const { body, headers } = buildWebhookRequest(event, channel);
13648
- const controller = new AbortController;
13649
- const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
13650
- try {
13651
- const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
13652
- method: "POST",
13653
- headers,
13654
- body,
13655
- signal: controller.signal
13656
- });
13657
- const responseBody = truncate(await response.text());
13658
- return {
13659
- attempt: 1,
13660
- status: response.ok ? "success" : "failed",
13661
- startedAt,
13662
- completedAt: now(),
13663
- responseStatus: response.status,
13664
- responseBody,
13665
- error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
13666
- };
13667
- } catch (error) {
13668
- return {
13669
- attempt: 1,
13670
- status: "failed",
13671
- startedAt,
13672
- completedAt: now(),
13673
- error: error instanceof Error ? error.message : String(error)
13674
- };
13675
- } finally {
13676
- clearTimeout(timeout);
13677
- }
13678
- }
13679
- async function dispatchCommand2(event, channel) {
13680
- if (!channel.command)
13681
- throw new Error(`Channel ${channel.id} has no command config`);
13682
- const startedAt = now();
13683
- const eventJson = JSON.stringify(event);
13684
- const env = {
13685
- ...process.env,
13686
- ...channel.command.env,
13687
- HASNA_CHANNEL_ID: channel.id,
13688
- HASNA_EVENT_ID: event.id,
13689
- HASNA_EVENT_TYPE: event.type,
13690
- HASNA_EVENT_SOURCE: event.source,
13691
- HASNA_EVENT_SUBJECT: event.subject ?? "",
13692
- HASNA_EVENT_SEVERITY: event.severity,
13693
- HASNA_EVENT_TIME: event.time,
13694
- HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
13695
- HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
13696
- HASNA_EVENT_JSON: eventJson
13697
- };
13698
- return new Promise((resolve2) => {
13699
- const child = spawn(channel.command.command, channel.command.args ?? [], {
13700
- cwd: channel.command.cwd,
13701
- env,
13702
- stdio: ["pipe", "pipe", "pipe"]
13703
- });
13704
- let stdout = "";
13705
- let stderr = "";
13706
- const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
13707
- child.stdin.end(eventJson);
13708
- child.stdout.on("data", (chunk) => {
13709
- stdout += chunk.toString();
13710
- });
13711
- child.stderr.on("data", (chunk) => {
13712
- stderr += chunk.toString();
13713
- });
13714
- child.on("error", (error) => {
13715
- clearTimeout(timeout);
13716
- resolve2({
13717
- attempt: 1,
13718
- status: "failed",
13719
- startedAt,
13720
- completedAt: now(),
13721
- stdout: truncate(stdout),
13722
- stderr: truncate(stderr),
13723
- error: error.message
13724
- });
13725
- });
13726
- child.on("close", (code, signal) => {
13727
- clearTimeout(timeout);
13728
- const success = code === 0;
13729
- resolve2({
13730
- attempt: 1,
13731
- status: success ? "success" : "failed",
13732
- startedAt,
13733
- completedAt: now(),
13734
- stdout: truncate(stdout),
13735
- stderr: truncate(stderr),
13736
- error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
13737
- });
13738
- });
13739
- });
13740
- }
13741
- async function dispatchChannel2(event, channel, options = {}) {
13742
- if (channel.transport === "webhook")
13743
- return dispatchWebhook2(event, channel, options);
13744
- if (channel.transport === "command")
13745
- return dispatchCommand2(event, channel);
13746
- return {
13747
- attempt: 1,
13748
- status: "skipped",
13749
- startedAt: now(),
13750
- completedAt: now(),
13751
- error: `Unsupported transport: ${channel.transport}`
13752
- };
13753
- }
13754
- function createDeliveryResult(event, channel, attempts) {
13755
- const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
13756
- return {
13757
- id: randomUUID(),
13758
- eventId: event.id,
13759
- channelId: channel.id,
13760
- transport: channel.transport,
13761
- status,
13762
- attempts,
13763
- createdAt: attempts[0]?.startedAt ?? now(),
13764
- completedAt: attempts.at(-1)?.completedAt ?? now()
13765
- };
13766
- }
13767
- function createEvent(input) {
13768
- return {
13769
- id: input.id ?? randomUUID2(),
13770
- source: input.source,
13771
- type: input.type,
13772
- time: normalizeTime(input.time),
13773
- subject: input.subject,
13774
- severity: input.severity ?? "info",
13775
- data: input.data ?? {},
13776
- message: input.message,
13777
- dedupeKey: input.dedupeKey,
13778
- schemaVersion: input.schemaVersion ?? "1.0",
13779
- metadata: input.metadata ?? {}
13780
- };
13781
- }
13782
-
13783
- class EventsClient {
13784
- store;
13785
- redactors;
13786
- transportOptions;
13787
- constructor(options = {}) {
13788
- this.store = options.store ?? new JsonEventsStore(options.dataDir);
13789
- this.redactors = options.redactors ?? [];
13790
- this.transportOptions = { fetchImpl: options.fetchImpl };
13791
- }
13792
- async addChannel(input) {
13793
- const timestamp = new Date().toISOString();
13794
- return this.store.addChannel({
13795
- ...input,
13796
- createdAt: input.createdAt ?? timestamp,
13797
- updatedAt: input.updatedAt ?? timestamp
13798
- });
13799
- }
13800
- async listChannels() {
13801
- return this.store.listChannels();
13802
- }
13803
- async removeChannel(id) {
13804
- return this.store.removeChannel(id);
13805
- }
13806
- async emit(input, options = {}) {
13807
- const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
13808
- if (options.dedupe !== false) {
13809
- const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
13810
- if (existing) {
13811
- return { event: existing, deliveries: [], deduped: true };
13812
- }
13813
- }
13814
- await this.store.appendEvent(event);
13815
- const deliveries = options.deliver === false ? [] : await this.deliver(event);
13816
- return { event, deliveries, deduped: false };
13817
- }
13818
- async listEvents() {
13819
- return this.store.listEvents();
13820
- }
13821
- async listDeliveries() {
13822
- return this.store.listDeliveries();
13823
- }
13824
- async deliver(event) {
13825
- const channels = await this.store.listChannels();
13826
- const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
13827
- const deliveries = [];
13828
- for (const channel of selected) {
13829
- const eventForChannel = await this.applyRedaction(event, channel);
13830
- const result = await this.deliverWithRetry(eventForChannel, channel);
13831
- await this.store.appendDelivery(result);
13832
- deliveries.push(result);
13833
- }
13834
- return deliveries;
13835
- }
13836
- async testChannel(id, input = {}) {
13837
- const channel = await this.store.getChannel(id);
13838
- if (!channel)
13839
- throw new Error(`Channel not found: ${id}`);
13840
- const event = createEvent({
13841
- source: input.source ?? "hasna.events",
13842
- type: input.type ?? "events.test",
13843
- subject: input.subject ?? id,
13844
- severity: input.severity ?? "info",
13845
- data: input.data ?? { test: true },
13846
- message: input.message ?? "Hasna events test delivery",
13847
- dedupeKey: input.dedupeKey,
13848
- schemaVersion: input.schemaVersion,
13849
- metadata: input.metadata,
13850
- time: input.time,
13851
- id: input.id
13852
- });
13853
- const eventForChannel = await this.applyRedaction(event, channel);
13854
- const result = await this.deliverWithRetry(eventForChannel, channel);
13855
- await this.store.appendDelivery(result);
13856
- return result;
13857
- }
13858
- async replay(options = {}) {
13859
- const events = (await this.store.listEvents()).filter((event) => {
13860
- if (options.eventId && event.id !== options.eventId)
13861
- return false;
13862
- if (options.source && event.source !== options.source)
13863
- return false;
13864
- if (options.type && event.type !== options.type)
13865
- return false;
13866
- return true;
13867
- });
13868
- if (options.dryRun)
13869
- return { events, deliveries: [] };
13870
- const deliveries = [];
13871
- for (const event of events) {
13872
- deliveries.push(...await this.deliver(event));
13873
- }
13874
- return { events, deliveries };
13875
- }
13876
- async applyRedaction(event, channel) {
13877
- let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
13878
- for (const redactor of this.redactors) {
13879
- next = await redactor(next, channel);
13880
- }
13881
- return next;
13882
- }
13883
- async deliverWithRetry(event, channel) {
13884
- const policy = normalizeRetryPolicy(channel.retry);
13885
- const attempts = [];
13886
- for (let index = 0;index < policy.maxAttempts; index += 1) {
13887
- const attempt = await dispatchChannel2(event, channel, this.transportOptions);
13888
- attempt.attempt = index + 1;
13889
- if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
13890
- attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
13891
- }
13892
- attempts.push(attempt);
13893
- if (attempt.status !== "failed")
13894
- break;
13895
- if (attempt.nextBackoffMs)
13896
- await Bun.sleep(attempt.nextBackoffMs);
13897
- }
13898
- return createDeliveryResult(event, channel, attempts);
13899
- }
13900
- }
13901
- function redactPaths(event, paths, replacement = "[REDACTED]") {
13902
- if (paths.length === 0)
13903
- return event;
13904
- const copy = structuredClone(event);
13905
- for (const path of paths) {
13906
- setPath(copy, path, replacement);
13907
- }
13908
- return copy;
13909
- }
13910
- function sanitizeChannelForOutput(channel) {
13911
- const copy = structuredClone(channel);
13912
- if (copy.webhook?.secret)
13913
- copy.webhook.secret = "[REDACTED]";
13914
- if (copy.command?.env) {
13915
- copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
13916
- }
13917
- return copy;
13918
- }
13919
- function sanitizeChannelsForOutput(channels) {
13920
- return channels.map(sanitizeChannelForOutput);
13921
- }
13922
- function redactSensitiveKeys(event, replacement = "[REDACTED]") {
13923
- return redactValue(event, replacement);
13924
- }
13925
- function shouldRedactKey(key) {
13926
- return /secret|token|password|api[_-]?key|authorization/i.test(key);
13927
- }
13928
- function redactValue(value, replacement) {
13929
- if (Array.isArray(value))
13930
- return value.map((item) => redactValue(item, replacement));
13931
- if (!value || typeof value !== "object")
13932
- return value;
13933
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [
13934
- key,
13935
- shouldRedactKey(key) ? replacement : redactValue(item, replacement)
13936
- ]));
13937
- }
13938
- function setPath(input, path, replacement) {
13939
- const parts = path.split(".");
13940
- let cursor = input;
13941
- for (const part of parts.slice(0, -1)) {
13942
- const next = cursor[part];
13943
- if (!next || typeof next !== "object")
13944
- return;
13945
- cursor = next;
13946
- }
13947
- const last = parts.at(-1);
13948
- if (last && last in cursor)
13949
- cursor[last] = replacement;
13950
- }
13951
- function normalizeTime(value) {
13952
- if (!value)
13953
- return new Date().toISOString();
13954
- return value instanceof Date ? value.toISOString() : value;
13955
- }
13956
- function normalizeRetryPolicy(policy) {
13957
- return {
13958
- maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
13959
- backoffMs: Math.max(0, policy?.backoffMs ?? 250),
13960
- multiplier: Math.max(1, policy?.multiplier ?? 2)
13961
- };
13962
- }
13963
-
13964
- // src/commands/runtime.ts
13443
+ import { EventsClient } from "@hasna/events";
13965
13444
  function probeTmuxPane(target, tmuxCommand = process.env["HASNA_MACHINES_TMUX_BIN"] || "tmux") {
13966
13445
  const checkedAt = new Date().toISOString();
13967
13446
  const result = spawnSync4(tmuxCommand, ["display-message", "-p", "-t", target, "#{pane_id}"], {
@@ -14046,6 +13525,9 @@ async function emitTmuxEvent(client, type, probe, lastPresent, deliver) {
14046
13525
  };
14047
13526
  return client.emit(input, { deliver });
14048
13527
  }
13528
+ // src/commands/serve.ts
13529
+ import { EventsClient as EventsClient2, sanitizeChannelsForOutput } from "@hasna/events";
13530
+
14049
13531
  // src/commands/status.ts
14050
13532
  function getStatus() {
14051
13533
  const manifest = readManifest();
@@ -14271,7 +13753,7 @@ function jsonError(message, status = 400) {
14271
13753
  }
14272
13754
  function startDashboardServer(options = {}) {
14273
13755
  const info = getServeInfo(options);
14274
- const events = new EventsClient;
13756
+ const events = new EventsClient2;
14275
13757
  return Bun.serve({
14276
13758
  hostname: info.host,
14277
13759
  port: info.port,
@@ -14425,7 +13907,7 @@ function runSelfTest() {
14425
13907
  };
14426
13908
  }
14427
13909
  // src/commands/setup.ts
14428
- import { homedir as homedir5 } from "os";
13910
+ import { homedir as homedir4 } from "os";
14429
13911
  function quote3(value) {
14430
13912
  return `'${value.replace(/'/g, `'\\''`)}'`;
14431
13913
  }
@@ -14498,7 +13980,7 @@ function buildSetupPlan(machineId) {
14498
13980
  const target = selected || {
14499
13981
  id: currentMachineId,
14500
13982
  platform: "linux",
14501
- workspacePath: `${homedir5()}/workspace`
13983
+ workspacePath: `${homedir4()}/workspace`
14502
13984
  };
14503
13985
  const steps = [...buildBaseSteps(target), ...buildPackageSteps(target)];
14504
13986
  return {
@@ -14542,7 +14024,8 @@ function runSetup(machineId, options = {}, runner = runMachineCommand) {
14542
14024
  return summary;
14543
14025
  }
14544
14026
  // src/commands/screen.ts
14545
- var DEFAULT_SCREEN_SECRET_NAMESPACE = "hasna/xyz/opensource/machines/prod";
14027
+ var SCREEN_SECRET_NAMESPACE_ENV = "HASNA_MACHINES_SCREEN_SECRET_NAMESPACE";
14028
+ var DEFAULT_SCREEN_SECRET_NAMESPACE = "machines/screen-sharing";
14546
14029
  function shellQuote7(value) {
14547
14030
  return `'${value.replace(/'/g, "'\\''")}'`;
14548
14031
  }
@@ -14566,7 +14049,8 @@ function splitTarget(target) {
14566
14049
  return [target.slice(0, at), target.slice(at + 1)];
14567
14050
  }
14568
14051
  function defaultScreenPasswordSecretKey(machineId) {
14569
- return `${DEFAULT_SCREEN_SECRET_NAMESPACE}/screen-${machineId}-vnc-password`;
14052
+ const namespace = process.env[SCREEN_SECRET_NAMESPACE_ENV]?.trim() || DEFAULT_SCREEN_SECRET_NAMESPACE;
14053
+ return `${namespace}/screen-${machineId}-vnc-password`;
14570
14054
  }
14571
14055
  function resolveScreenTarget(machineId, options = {}) {
14572
14056
  const resolved = resolveMachineRoute(machineId, options);
@@ -14663,8 +14147,8 @@ function buildScreenEnableCommand(machineId, options = {}) {
14663
14147
  };
14664
14148
  }
14665
14149
  // src/commands/sync.ts
14666
- import { existsSync as existsSync8, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
14667
- import { homedir as homedir6 } from "os";
14150
+ import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
14151
+ import { homedir as homedir5 } from "os";
14668
14152
  function quote4(value) {
14669
14153
  return `'${value.replace(/'/g, `'\\''`)}'`;
14670
14154
  }
@@ -14716,8 +14200,8 @@ function detectFileActions(machine) {
14716
14200
  throw new Error(`Remote file sync planning is not supported for ${machine.id}; refusing to inspect or apply local paths as remote state.`);
14717
14201
  }
14718
14202
  return (machine.files || []).map((file, index) => {
14719
- const sourceExists = existsSync8(file.source);
14720
- const targetExists = existsSync8(file.target);
14203
+ const sourceExists = existsSync7(file.source);
14204
+ const targetExists = existsSync7(file.target);
14721
14205
  let status = "missing";
14722
14206
  if (sourceExists && targetExists) {
14723
14207
  if (file.mode === "symlink") {
@@ -14748,7 +14232,7 @@ function buildSyncPlan(machineId, runner = runMachineCommand) {
14748
14232
  const target = selected || {
14749
14233
  id: currentMachineId,
14750
14234
  platform: "linux",
14751
- workspacePath: `${homedir6()}/workspace`
14235
+ workspacePath: `${homedir5()}/workspace`
14752
14236
  };
14753
14237
  const actions = [
14754
14238
  ...detectPackageActions(target, runner),
@@ -14968,6 +14452,9 @@ function repairWorkspaceManifestMappings(options) {
14968
14452
  warnings
14969
14453
  };
14970
14454
  }
14455
+ // src/mcp/server.ts
14456
+ import { EventsClient as EventsClient3, sanitizeChannelForOutput, sanitizeChannelsForOutput as sanitizeChannelsForOutput2 } from "@hasna/events";
14457
+
14971
14458
  // node_modules/zod/v4/core/core.js
14972
14459
  var NEVER2 = Object.freeze({
14973
14460
  status: "aborted"
@@ -23940,7 +23427,7 @@ function buildServer(version2 = getPackageVersion()) {
23940
23427
  }
23941
23428
  function createMcpServer(version2) {
23942
23429
  const server = new McpServer({ name: "machines", version: version2 });
23943
- const events = new EventsClient;
23430
+ const events = new EventsClient3;
23944
23431
  server.tool("machines_status", "Return local machine fleet status paths and machine identity.", {}, async () => ({
23945
23432
  content: [{ type: "text", text: JSON.stringify(getStatus(), null, 2) }]
23946
23433
  }));
@@ -24094,7 +23581,7 @@ function createMcpServer(version2) {
24094
23581
  }));
24095
23582
  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) }] }));
24096
23583
  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) }] }));
24097
- server.tool("machines_webhooks_add", "Add or replace a shared Hasna event webhook channel.", {
23584
+ server.tool("machines_webhooks_add", "Add or replace a shared event webhook channel.", {
24098
23585
  channel_id: exports_external.string().describe("Channel identifier"),
24099
23586
  url: exports_external.string().url().describe("Webhook URL"),
24100
23587
  event_type: exports_external.string().optional().describe("Optional event type filter, e.g. machines.*"),
@@ -24102,26 +23589,26 @@ function createMcpServer(version2) {
24102
23589
  secret: exports_external.string().optional().describe("Optional HMAC secret"),
24103
23590
  enabled: exports_external.boolean().optional().describe("Whether the channel is enabled")
24104
23591
  }, async ({ channel_id, url, event_type, source, secret, enabled }) => {
24105
- const now2 = new Date().toISOString();
23592
+ const now = new Date().toISOString();
24106
23593
  const channel = await events.addChannel({
24107
23594
  id: channel_id,
24108
23595
  enabled: enabled ?? true,
24109
23596
  transport: "webhook",
24110
23597
  filters: event_type || source ? [{ type: event_type, source }] : undefined,
24111
23598
  webhook: { url, secret },
24112
- createdAt: now2,
24113
- updatedAt: now2
23599
+ createdAt: now,
23600
+ updatedAt: now
24114
23601
  });
24115
23602
  return { content: [{ type: "text", text: JSON.stringify(sanitizeChannelForOutput(channel), null, 2) }] };
24116
23603
  });
24117
- server.tool("machines_webhooks_list", "List shared Hasna event webhook channels.", {}, async () => ({
24118
- content: [{ type: "text", text: JSON.stringify(sanitizeChannelsForOutput(await events.listChannels()), null, 2) }]
23604
+ server.tool("machines_webhooks_list", "List shared event webhook channels.", {}, async () => ({
23605
+ content: [{ type: "text", text: JSON.stringify(sanitizeChannelsForOutput2(await events.listChannels()), null, 2) }]
24119
23606
  }));
24120
- 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 }) => ({
23607
+ 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 }) => ({
24121
23608
  content: [{ type: "text", text: JSON.stringify(await events.testChannel(channel_id, { source: "machines", type: event_type ?? "events.test", message }), null, 2) }]
24122
23609
  }));
24123
- 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) }] }));
24124
- server.tool("machines_events_emit", "Emit a shared Hasna event from machines.", {
23610
+ 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) }] }));
23611
+ server.tool("machines_events_emit", "Emit a shared event from machines.", {
24125
23612
  event_type: exports_external.string().describe("Event type"),
24126
23613
  subject: exports_external.string().optional().describe("Event subject"),
24127
23614
  severity: exports_external.enum(["debug", "info", "notice", "warning", "error", "critical"]).optional().describe("Event severity"),
@@ -24142,10 +23629,10 @@ function createMcpServer(version2) {
24142
23629
  dedupeKey: dedupe_key
24143
23630
  }, { deliver: deliver !== false }), null, 2) }]
24144
23631
  }));
24145
- server.tool("machines_events_list", "List shared Hasna events.", {}, async () => ({
23632
+ server.tool("machines_events_list", "List shared events.", {}, async () => ({
24146
23633
  content: [{ type: "text", text: JSON.stringify(await events.listEvents(), null, 2) }]
24147
23634
  }));
24148
- 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 }) => ({
23635
+ 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 }) => ({
24149
23636
  content: [{ type: "text", text: JSON.stringify(await events.replay({ eventId: event_id, source, type: event_type, dryRun: dry_run }), null, 2) }]
24150
23637
  }));
24151
23638
  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) }] }));
@@ -24276,6 +23763,7 @@ export {
24276
23763
  STORAGE_TABLES,
24277
23764
  STORAGE_MODE_ENV,
24278
23765
  STORAGE_DATABASE_ENV,
23766
+ SCREEN_SECRET_NAMESPACE_ENV,
24279
23767
  MACHINE_MCP_TOOL_NAMES,
24280
23768
  MACHINES_STORAGE_TABLES,
24281
23769
  MACHINES_STORAGE_MODE_FALLBACK_ENV,