@hasna/machines 0.0.31 → 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
@@ -12645,7 +12645,14 @@ function buildAppSteps(machine) {
12645
12645
  }));
12646
12646
  }
12647
12647
  function resolveMachine(machineId) {
12648
- return (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
12648
+ if (!machineId)
12649
+ return detectCurrentMachineManifest();
12650
+ return getManifestMachine(machineId) || {
12651
+ id: machineId,
12652
+ platform: "linux",
12653
+ workspacePath: "",
12654
+ apps: []
12655
+ };
12649
12656
  }
12650
12657
  function parseProbeOutput(app, machine, stdout) {
12651
12658
  const lines = stdout.trim().split(`
@@ -12995,7 +13002,13 @@ function buildInstallSteps(machine, tools) {
12995
13002
  }));
12996
13003
  }
12997
13004
  function resolveMachine2(machineId) {
12998
- return (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
13005
+ if (!machineId)
13006
+ return detectCurrentMachineManifest();
13007
+ return getManifestMachine(machineId) || {
13008
+ id: machineId,
13009
+ platform: "linux",
13010
+ workspacePath: ""
13011
+ };
12999
13012
  }
13000
13013
  function buildProbeCommand(tool) {
13001
13014
  const binary = getToolBinary(tool);
@@ -13095,7 +13108,10 @@ function buildInstallSteps2(machine) {
13095
13108
  ];
13096
13109
  }
13097
13110
  function buildTailscaleInstallPlan(machineId) {
13098
- const machine = (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
13111
+ const machine = machineId ? getManifestMachine(machineId) : detectCurrentMachineManifest();
13112
+ if (!machine) {
13113
+ throw new Error(`Machine not found in manifest: ${machineId}`);
13114
+ }
13099
13115
  return {
13100
13116
  machineId: machine.id,
13101
13117
  mode: "plan",
@@ -13424,528 +13440,7 @@ function listPorts(machineId) {
13424
13440
  // src/commands/runtime.ts
13425
13441
  import { spawnSync as spawnSync4 } from "child_process";
13426
13442
  import { setTimeout as sleep } from "timers/promises";
13427
-
13428
- // node_modules/@hasna/events/dist/index.js
13429
- import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
13430
- import { existsSync as existsSync7 } from "fs";
13431
- import { homedir as homedir4 } from "os";
13432
- import { join as join6 } from "path";
13433
- import { createHmac, timingSafeEqual } from "crypto";
13434
- import { randomUUID } from "crypto";
13435
- import { spawn } from "child_process";
13436
- import { randomUUID as randomUUID2 } from "crypto";
13437
- function getPathValue(input, path) {
13438
- return path.split(".").reduce((value, part) => {
13439
- if (value && typeof value === "object" && part in value) {
13440
- return value[part];
13441
- }
13442
- return;
13443
- }, input);
13444
- }
13445
- function wildcardToRegExp(pattern) {
13446
- const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
13447
- return new RegExp(`^${escaped}$`);
13448
- }
13449
- function matchString(value, matcher) {
13450
- if (matcher === undefined)
13451
- return true;
13452
- if (value === undefined)
13453
- return false;
13454
- const matchers = Array.isArray(matcher) ? matcher : [matcher];
13455
- return matchers.some((item) => wildcardToRegExp(item).test(value));
13456
- }
13457
- function matchRecord(input, matcher) {
13458
- if (!matcher)
13459
- return true;
13460
- return Object.entries(matcher).every(([path, expected]) => {
13461
- const actual = getPathValue(input, path);
13462
- if (typeof expected === "string" || Array.isArray(expected)) {
13463
- return matchString(actual === undefined ? undefined : String(actual), expected);
13464
- }
13465
- return actual === expected;
13466
- });
13467
- }
13468
- function eventMatchesFilter(event, filter) {
13469
- 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);
13470
- }
13471
- function channelMatchesEvent(channel, event) {
13472
- if (!channel.enabled)
13473
- return false;
13474
- if (!channel.filters || channel.filters.length === 0)
13475
- return true;
13476
- return channel.filters.some((filter) => eventMatchesFilter(event, filter));
13477
- }
13478
- var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
13479
- var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
13480
- function getEventsDataDir(override) {
13481
- return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join6(homedir4(), ".hasna", "events");
13482
- }
13483
-
13484
- class JsonEventsStore {
13485
- dataDir;
13486
- channelsPath;
13487
- eventsPath;
13488
- deliveriesPath;
13489
- constructor(dataDir = getEventsDataDir()) {
13490
- this.dataDir = dataDir;
13491
- this.channelsPath = join6(dataDir, "channels.json");
13492
- this.eventsPath = join6(dataDir, "events.json");
13493
- this.deliveriesPath = join6(dataDir, "deliveries.json");
13494
- }
13495
- async init() {
13496
- await mkdir(this.dataDir, { recursive: true, mode: 448 });
13497
- await chmod(this.dataDir, 448).catch(() => {
13498
- return;
13499
- });
13500
- await this.ensureArrayFile(this.channelsPath);
13501
- await this.ensureArrayFile(this.eventsPath);
13502
- await this.ensureArrayFile(this.deliveriesPath);
13503
- }
13504
- async addChannel(channel) {
13505
- await this.init();
13506
- const channels = await this.readJson(this.channelsPath, []);
13507
- const index = channels.findIndex((item) => item.id === channel.id);
13508
- if (index >= 0) {
13509
- channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
13510
- } else {
13511
- channels.push(channel);
13512
- }
13513
- await this.writeJson(this.channelsPath, channels);
13514
- return index >= 0 ? channels[index] : channel;
13515
- }
13516
- async listChannels() {
13517
- await this.init();
13518
- return this.readJson(this.channelsPath, []);
13519
- }
13520
- async getChannel(id) {
13521
- const channels = await this.listChannels();
13522
- return channels.find((channel) => channel.id === id);
13523
- }
13524
- async removeChannel(id) {
13525
- await this.init();
13526
- const channels = await this.readJson(this.channelsPath, []);
13527
- const next = channels.filter((channel) => channel.id !== id);
13528
- await this.writeJson(this.channelsPath, next);
13529
- return next.length !== channels.length;
13530
- }
13531
- async appendEvent(event) {
13532
- await this.init();
13533
- const events = await this.readJson(this.eventsPath, []);
13534
- events.push(event);
13535
- await this.writeJson(this.eventsPath, events);
13536
- return event;
13537
- }
13538
- async listEvents() {
13539
- await this.init();
13540
- return this.readJson(this.eventsPath, []);
13541
- }
13542
- async findEventByIdentity(identity) {
13543
- const events = await this.listEvents();
13544
- return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
13545
- }
13546
- async appendDelivery(result) {
13547
- await this.init();
13548
- const deliveries = await this.readJson(this.deliveriesPath, []);
13549
- deliveries.push(result);
13550
- await this.writeJson(this.deliveriesPath, deliveries);
13551
- return result;
13552
- }
13553
- async listDeliveries() {
13554
- await this.init();
13555
- return this.readJson(this.deliveriesPath, []);
13556
- }
13557
- async exportData() {
13558
- return {
13559
- channels: await this.listChannels(),
13560
- events: await this.listEvents(),
13561
- deliveries: await this.listDeliveries()
13562
- };
13563
- }
13564
- async ensureArrayFile(path) {
13565
- if (!existsSync7(path)) {
13566
- await writeFile(path, `[]
13567
- `, { encoding: "utf-8", mode: 384 });
13568
- }
13569
- await chmod(path, 384).catch(() => {
13570
- return;
13571
- });
13572
- }
13573
- async readJson(path, fallback) {
13574
- try {
13575
- const raw = await readFile(path, "utf-8");
13576
- if (!raw.trim())
13577
- return fallback;
13578
- return JSON.parse(raw);
13579
- } catch (error) {
13580
- if (error.code === "ENOENT")
13581
- return fallback;
13582
- throw error;
13583
- }
13584
- }
13585
- async writeJson(path, value) {
13586
- const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
13587
- await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
13588
- `, { encoding: "utf-8", mode: 384 });
13589
- await rename(tempPath, path);
13590
- await chmod(path, 384).catch(() => {
13591
- return;
13592
- });
13593
- }
13594
- }
13595
- var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
13596
- function buildSignatureBase(timestamp, body) {
13597
- return `${timestamp}.${body}`;
13598
- }
13599
- function signPayload(secret, timestamp, body) {
13600
- const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
13601
- return `sha256=${digest}`;
13602
- }
13603
- function now() {
13604
- return new Date().toISOString();
13605
- }
13606
- function truncate(value, max = 4096) {
13607
- return value.length > max ? `${value.slice(0, max)}...` : value;
13608
- }
13609
- function buildWebhookRequest(event, channel) {
13610
- if (!channel.webhook)
13611
- throw new Error(`Channel ${channel.id} has no webhook config`);
13612
- const body = JSON.stringify(event);
13613
- const timestamp = event.time;
13614
- const headers = {
13615
- "Content-Type": "application/json",
13616
- "User-Agent": "@hasna/events",
13617
- "X-Hasna-Event-Id": event.id,
13618
- "X-Hasna-Event-Type": event.type,
13619
- "X-Hasna-Timestamp": timestamp,
13620
- ...channel.webhook.headers
13621
- };
13622
- if (channel.webhook.secret) {
13623
- headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
13624
- }
13625
- return { body, headers };
13626
- }
13627
- async function dispatchWebhook2(event, channel, options = {}) {
13628
- if (!channel.webhook)
13629
- throw new Error(`Channel ${channel.id} has no webhook config`);
13630
- const startedAt = now();
13631
- const { body, headers } = buildWebhookRequest(event, channel);
13632
- const controller = new AbortController;
13633
- const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
13634
- try {
13635
- const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
13636
- method: "POST",
13637
- headers,
13638
- body,
13639
- signal: controller.signal
13640
- });
13641
- const responseBody = truncate(await response.text());
13642
- return {
13643
- attempt: 1,
13644
- status: response.ok ? "success" : "failed",
13645
- startedAt,
13646
- completedAt: now(),
13647
- responseStatus: response.status,
13648
- responseBody,
13649
- error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
13650
- };
13651
- } catch (error) {
13652
- return {
13653
- attempt: 1,
13654
- status: "failed",
13655
- startedAt,
13656
- completedAt: now(),
13657
- error: error instanceof Error ? error.message : String(error)
13658
- };
13659
- } finally {
13660
- clearTimeout(timeout);
13661
- }
13662
- }
13663
- async function dispatchCommand2(event, channel) {
13664
- if (!channel.command)
13665
- throw new Error(`Channel ${channel.id} has no command config`);
13666
- const startedAt = now();
13667
- const eventJson = JSON.stringify(event);
13668
- const env = {
13669
- ...process.env,
13670
- ...channel.command.env,
13671
- HASNA_CHANNEL_ID: channel.id,
13672
- HASNA_EVENT_ID: event.id,
13673
- HASNA_EVENT_TYPE: event.type,
13674
- HASNA_EVENT_SOURCE: event.source,
13675
- HASNA_EVENT_SUBJECT: event.subject ?? "",
13676
- HASNA_EVENT_SEVERITY: event.severity,
13677
- HASNA_EVENT_TIME: event.time,
13678
- HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
13679
- HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
13680
- HASNA_EVENT_JSON: eventJson
13681
- };
13682
- return new Promise((resolve2) => {
13683
- const child = spawn(channel.command.command, channel.command.args ?? [], {
13684
- cwd: channel.command.cwd,
13685
- env,
13686
- stdio: ["pipe", "pipe", "pipe"]
13687
- });
13688
- let stdout = "";
13689
- let stderr = "";
13690
- const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
13691
- child.stdin.end(eventJson);
13692
- child.stdout.on("data", (chunk) => {
13693
- stdout += chunk.toString();
13694
- });
13695
- child.stderr.on("data", (chunk) => {
13696
- stderr += chunk.toString();
13697
- });
13698
- child.on("error", (error) => {
13699
- clearTimeout(timeout);
13700
- resolve2({
13701
- attempt: 1,
13702
- status: "failed",
13703
- startedAt,
13704
- completedAt: now(),
13705
- stdout: truncate(stdout),
13706
- stderr: truncate(stderr),
13707
- error: error.message
13708
- });
13709
- });
13710
- child.on("close", (code, signal) => {
13711
- clearTimeout(timeout);
13712
- const success = code === 0;
13713
- resolve2({
13714
- attempt: 1,
13715
- status: success ? "success" : "failed",
13716
- startedAt,
13717
- completedAt: now(),
13718
- stdout: truncate(stdout),
13719
- stderr: truncate(stderr),
13720
- error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
13721
- });
13722
- });
13723
- });
13724
- }
13725
- async function dispatchChannel2(event, channel, options = {}) {
13726
- if (channel.transport === "webhook")
13727
- return dispatchWebhook2(event, channel, options);
13728
- if (channel.transport === "command")
13729
- return dispatchCommand2(event, channel);
13730
- return {
13731
- attempt: 1,
13732
- status: "skipped",
13733
- startedAt: now(),
13734
- completedAt: now(),
13735
- error: `Unsupported transport: ${channel.transport}`
13736
- };
13737
- }
13738
- function createDeliveryResult(event, channel, attempts) {
13739
- const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
13740
- return {
13741
- id: randomUUID(),
13742
- eventId: event.id,
13743
- channelId: channel.id,
13744
- transport: channel.transport,
13745
- status,
13746
- attempts,
13747
- createdAt: attempts[0]?.startedAt ?? now(),
13748
- completedAt: attempts.at(-1)?.completedAt ?? now()
13749
- };
13750
- }
13751
- function createEvent(input) {
13752
- return {
13753
- id: input.id ?? randomUUID2(),
13754
- source: input.source,
13755
- type: input.type,
13756
- time: normalizeTime(input.time),
13757
- subject: input.subject,
13758
- severity: input.severity ?? "info",
13759
- data: input.data ?? {},
13760
- message: input.message,
13761
- dedupeKey: input.dedupeKey,
13762
- schemaVersion: input.schemaVersion ?? "1.0",
13763
- metadata: input.metadata ?? {}
13764
- };
13765
- }
13766
-
13767
- class EventsClient {
13768
- store;
13769
- redactors;
13770
- transportOptions;
13771
- constructor(options = {}) {
13772
- this.store = options.store ?? new JsonEventsStore(options.dataDir);
13773
- this.redactors = options.redactors ?? [];
13774
- this.transportOptions = { fetchImpl: options.fetchImpl };
13775
- }
13776
- async addChannel(input) {
13777
- const timestamp = new Date().toISOString();
13778
- return this.store.addChannel({
13779
- ...input,
13780
- createdAt: input.createdAt ?? timestamp,
13781
- updatedAt: input.updatedAt ?? timestamp
13782
- });
13783
- }
13784
- async listChannels() {
13785
- return this.store.listChannels();
13786
- }
13787
- async removeChannel(id) {
13788
- return this.store.removeChannel(id);
13789
- }
13790
- async emit(input, options = {}) {
13791
- const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
13792
- if (options.dedupe !== false) {
13793
- const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
13794
- if (existing) {
13795
- return { event: existing, deliveries: [], deduped: true };
13796
- }
13797
- }
13798
- await this.store.appendEvent(event);
13799
- const deliveries = options.deliver === false ? [] : await this.deliver(event);
13800
- return { event, deliveries, deduped: false };
13801
- }
13802
- async listEvents() {
13803
- return this.store.listEvents();
13804
- }
13805
- async listDeliveries() {
13806
- return this.store.listDeliveries();
13807
- }
13808
- async deliver(event) {
13809
- const channels = await this.store.listChannels();
13810
- const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
13811
- const deliveries = [];
13812
- for (const channel of selected) {
13813
- const eventForChannel = await this.applyRedaction(event, channel);
13814
- const result = await this.deliverWithRetry(eventForChannel, channel);
13815
- await this.store.appendDelivery(result);
13816
- deliveries.push(result);
13817
- }
13818
- return deliveries;
13819
- }
13820
- async testChannel(id, input = {}) {
13821
- const channel = await this.store.getChannel(id);
13822
- if (!channel)
13823
- throw new Error(`Channel not found: ${id}`);
13824
- const event = createEvent({
13825
- source: input.source ?? "hasna.events",
13826
- type: input.type ?? "events.test",
13827
- subject: input.subject ?? id,
13828
- severity: input.severity ?? "info",
13829
- data: input.data ?? { test: true },
13830
- message: input.message ?? "Hasna events test delivery",
13831
- dedupeKey: input.dedupeKey,
13832
- schemaVersion: input.schemaVersion,
13833
- metadata: input.metadata,
13834
- time: input.time,
13835
- id: input.id
13836
- });
13837
- const eventForChannel = await this.applyRedaction(event, channel);
13838
- const result = await this.deliverWithRetry(eventForChannel, channel);
13839
- await this.store.appendDelivery(result);
13840
- return result;
13841
- }
13842
- async replay(options = {}) {
13843
- const events = (await this.store.listEvents()).filter((event) => {
13844
- if (options.eventId && event.id !== options.eventId)
13845
- return false;
13846
- if (options.source && event.source !== options.source)
13847
- return false;
13848
- if (options.type && event.type !== options.type)
13849
- return false;
13850
- return true;
13851
- });
13852
- if (options.dryRun)
13853
- return { events, deliveries: [] };
13854
- const deliveries = [];
13855
- for (const event of events) {
13856
- deliveries.push(...await this.deliver(event));
13857
- }
13858
- return { events, deliveries };
13859
- }
13860
- async applyRedaction(event, channel) {
13861
- let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
13862
- for (const redactor of this.redactors) {
13863
- next = await redactor(next, channel);
13864
- }
13865
- return next;
13866
- }
13867
- async deliverWithRetry(event, channel) {
13868
- const policy = normalizeRetryPolicy(channel.retry);
13869
- const attempts = [];
13870
- for (let index = 0;index < policy.maxAttempts; index += 1) {
13871
- const attempt = await dispatchChannel2(event, channel, this.transportOptions);
13872
- attempt.attempt = index + 1;
13873
- if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
13874
- attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
13875
- }
13876
- attempts.push(attempt);
13877
- if (attempt.status !== "failed")
13878
- break;
13879
- if (attempt.nextBackoffMs)
13880
- await Bun.sleep(attempt.nextBackoffMs);
13881
- }
13882
- return createDeliveryResult(event, channel, attempts);
13883
- }
13884
- }
13885
- function redactPaths(event, paths, replacement = "[REDACTED]") {
13886
- if (paths.length === 0)
13887
- return event;
13888
- const copy = structuredClone(event);
13889
- for (const path of paths) {
13890
- setPath(copy, path, replacement);
13891
- }
13892
- return copy;
13893
- }
13894
- function sanitizeChannelForOutput(channel) {
13895
- const copy = structuredClone(channel);
13896
- if (copy.webhook?.secret)
13897
- copy.webhook.secret = "[REDACTED]";
13898
- if (copy.command?.env) {
13899
- copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
13900
- }
13901
- return copy;
13902
- }
13903
- function sanitizeChannelsForOutput(channels) {
13904
- return channels.map(sanitizeChannelForOutput);
13905
- }
13906
- function redactSensitiveKeys(event, replacement = "[REDACTED]") {
13907
- return redactValue(event, replacement);
13908
- }
13909
- function shouldRedactKey(key) {
13910
- return /secret|token|password|api[_-]?key|authorization/i.test(key);
13911
- }
13912
- function redactValue(value, replacement) {
13913
- if (Array.isArray(value))
13914
- return value.map((item) => redactValue(item, replacement));
13915
- if (!value || typeof value !== "object")
13916
- return value;
13917
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [
13918
- key,
13919
- shouldRedactKey(key) ? replacement : redactValue(item, replacement)
13920
- ]));
13921
- }
13922
- function setPath(input, path, replacement) {
13923
- const parts = path.split(".");
13924
- let cursor = input;
13925
- for (const part of parts.slice(0, -1)) {
13926
- const next = cursor[part];
13927
- if (!next || typeof next !== "object")
13928
- return;
13929
- cursor = next;
13930
- }
13931
- const last = parts.at(-1);
13932
- if (last && last in cursor)
13933
- cursor[last] = replacement;
13934
- }
13935
- function normalizeTime(value) {
13936
- if (!value)
13937
- return new Date().toISOString();
13938
- return value instanceof Date ? value.toISOString() : value;
13939
- }
13940
- function normalizeRetryPolicy(policy) {
13941
- return {
13942
- maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
13943
- backoffMs: Math.max(0, policy?.backoffMs ?? 250),
13944
- multiplier: Math.max(1, policy?.multiplier ?? 2)
13945
- };
13946
- }
13947
-
13948
- // src/commands/runtime.ts
13443
+ import { EventsClient } from "@hasna/events";
13949
13444
  function probeTmuxPane(target, tmuxCommand = process.env["HASNA_MACHINES_TMUX_BIN"] || "tmux") {
13950
13445
  const checkedAt = new Date().toISOString();
13951
13446
  const result = spawnSync4(tmuxCommand, ["display-message", "-p", "-t", target, "#{pane_id}"], {
@@ -14030,6 +13525,9 @@ async function emitTmuxEvent(client, type, probe, lastPresent, deliver) {
14030
13525
  };
14031
13526
  return client.emit(input, { deliver });
14032
13527
  }
13528
+ // src/commands/serve.ts
13529
+ import { EventsClient as EventsClient2, sanitizeChannelsForOutput } from "@hasna/events";
13530
+
14033
13531
  // src/commands/status.ts
14034
13532
  function getStatus() {
14035
13533
  const manifest = readManifest();
@@ -14255,7 +13753,7 @@ function jsonError(message, status = 400) {
14255
13753
  }
14256
13754
  function startDashboardServer(options = {}) {
14257
13755
  const info = getServeInfo(options);
14258
- const events = new EventsClient;
13756
+ const events = new EventsClient2;
14259
13757
  return Bun.serve({
14260
13758
  hostname: info.host,
14261
13759
  port: info.port,
@@ -14409,7 +13907,7 @@ function runSelfTest() {
14409
13907
  };
14410
13908
  }
14411
13909
  // src/commands/setup.ts
14412
- import { homedir as homedir5 } from "os";
13910
+ import { homedir as homedir4 } from "os";
14413
13911
  function quote3(value) {
14414
13912
  return `'${value.replace(/'/g, `'\\''`)}'`;
14415
13913
  }
@@ -14476,10 +13974,13 @@ function buildSetupPlan(machineId) {
14476
13974
  const manifest = readManifest();
14477
13975
  const currentMachineId = getLocalMachineId();
14478
13976
  const selected = machineId ? manifest.machines.find((machine) => machine.id === machineId) : manifest.machines.find((machine) => machine.id === currentMachineId);
13977
+ if (machineId && !selected) {
13978
+ throw new Error(`Machine not found in manifest: ${machineId}`);
13979
+ }
14479
13980
  const target = selected || {
14480
13981
  id: currentMachineId,
14481
13982
  platform: "linux",
14482
- workspacePath: `${homedir5()}/workspace`
13983
+ workspacePath: `${homedir4()}/workspace`
14483
13984
  };
14484
13985
  const steps = [...buildBaseSteps(target), ...buildPackageSteps(target)];
14485
13986
  return {
@@ -14523,7 +14024,8 @@ function runSetup(machineId, options = {}, runner = runMachineCommand) {
14523
14024
  return summary;
14524
14025
  }
14525
14026
  // src/commands/screen.ts
14526
- 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";
14527
14029
  function shellQuote7(value) {
14528
14030
  return `'${value.replace(/'/g, "'\\''")}'`;
14529
14031
  }
@@ -14547,7 +14049,8 @@ function splitTarget(target) {
14547
14049
  return [target.slice(0, at), target.slice(at + 1)];
14548
14050
  }
14549
14051
  function defaultScreenPasswordSecretKey(machineId) {
14550
- 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`;
14551
14054
  }
14552
14055
  function resolveScreenTarget(machineId, options = {}) {
14553
14056
  const resolved = resolveMachineRoute(machineId, options);
@@ -14644,8 +14147,8 @@ function buildScreenEnableCommand(machineId, options = {}) {
14644
14147
  };
14645
14148
  }
14646
14149
  // src/commands/sync.ts
14647
- import { existsSync as existsSync8, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
14648
- 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";
14649
14152
  function quote4(value) {
14650
14153
  return `'${value.replace(/'/g, `'\\''`)}'`;
14651
14154
  }
@@ -14697,8 +14200,8 @@ function detectFileActions(machine) {
14697
14200
  throw new Error(`Remote file sync planning is not supported for ${machine.id}; refusing to inspect or apply local paths as remote state.`);
14698
14201
  }
14699
14202
  return (machine.files || []).map((file, index) => {
14700
- const sourceExists = existsSync8(file.source);
14701
- const targetExists = existsSync8(file.target);
14203
+ const sourceExists = existsSync7(file.source);
14204
+ const targetExists = existsSync7(file.target);
14702
14205
  let status = "missing";
14703
14206
  if (sourceExists && targetExists) {
14704
14207
  if (file.mode === "symlink") {
@@ -14723,10 +14226,13 @@ function buildSyncPlan(machineId, runner = runMachineCommand) {
14723
14226
  const manifest = readManifest();
14724
14227
  const currentMachineId = getLocalMachineId();
14725
14228
  const selected = machineId ? manifest.machines.find((machine) => machine.id === machineId) : manifest.machines.find((machine) => machine.id === currentMachineId);
14229
+ if (machineId && !selected) {
14230
+ throw new Error(`Machine not found in manifest: ${machineId}`);
14231
+ }
14726
14232
  const target = selected || {
14727
14233
  id: currentMachineId,
14728
14234
  platform: "linux",
14729
- workspacePath: `${homedir6()}/workspace`
14235
+ workspacePath: `${homedir5()}/workspace`
14730
14236
  };
14731
14237
  const actions = [
14732
14238
  ...detectPackageActions(target, runner),
@@ -14946,6 +14452,9 @@ function repairWorkspaceManifestMappings(options) {
14946
14452
  warnings
14947
14453
  };
14948
14454
  }
14455
+ // src/mcp/server.ts
14456
+ import { EventsClient as EventsClient3, sanitizeChannelForOutput, sanitizeChannelsForOutput as sanitizeChannelsForOutput2 } from "@hasna/events";
14457
+
14949
14458
  // node_modules/zod/v4/core/core.js
14950
14459
  var NEVER2 = Object.freeze({
14951
14460
  status: "aborted"
@@ -23918,7 +23427,7 @@ function buildServer(version2 = getPackageVersion()) {
23918
23427
  }
23919
23428
  function createMcpServer(version2) {
23920
23429
  const server = new McpServer({ name: "machines", version: version2 });
23921
- const events = new EventsClient;
23430
+ const events = new EventsClient3;
23922
23431
  server.tool("machines_status", "Return local machine fleet status paths and machine identity.", {}, async () => ({
23923
23432
  content: [{ type: "text", text: JSON.stringify(getStatus(), null, 2) }]
23924
23433
  }));
@@ -24072,7 +23581,7 @@ function createMcpServer(version2) {
24072
23581
  }));
24073
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) }] }));
24074
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) }] }));
24075
- 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.", {
24076
23585
  channel_id: exports_external.string().describe("Channel identifier"),
24077
23586
  url: exports_external.string().url().describe("Webhook URL"),
24078
23587
  event_type: exports_external.string().optional().describe("Optional event type filter, e.g. machines.*"),
@@ -24080,26 +23589,26 @@ function createMcpServer(version2) {
24080
23589
  secret: exports_external.string().optional().describe("Optional HMAC secret"),
24081
23590
  enabled: exports_external.boolean().optional().describe("Whether the channel is enabled")
24082
23591
  }, async ({ channel_id, url, event_type, source, secret, enabled }) => {
24083
- const now2 = new Date().toISOString();
23592
+ const now = new Date().toISOString();
24084
23593
  const channel = await events.addChannel({
24085
23594
  id: channel_id,
24086
23595
  enabled: enabled ?? true,
24087
23596
  transport: "webhook",
24088
23597
  filters: event_type || source ? [{ type: event_type, source }] : undefined,
24089
23598
  webhook: { url, secret },
24090
- createdAt: now2,
24091
- updatedAt: now2
23599
+ createdAt: now,
23600
+ updatedAt: now
24092
23601
  });
24093
23602
  return { content: [{ type: "text", text: JSON.stringify(sanitizeChannelForOutput(channel), null, 2) }] };
24094
23603
  });
24095
- server.tool("machines_webhooks_list", "List shared Hasna event webhook channels.", {}, async () => ({
24096
- 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) }]
24097
23606
  }));
24098
- 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 }) => ({
24099
23608
  content: [{ type: "text", text: JSON.stringify(await events.testChannel(channel_id, { source: "machines", type: event_type ?? "events.test", message }), null, 2) }]
24100
23609
  }));
24101
- 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) }] }));
24102
- 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.", {
24103
23612
  event_type: exports_external.string().describe("Event type"),
24104
23613
  subject: exports_external.string().optional().describe("Event subject"),
24105
23614
  severity: exports_external.enum(["debug", "info", "notice", "warning", "error", "critical"]).optional().describe("Event severity"),
@@ -24120,10 +23629,10 @@ function createMcpServer(version2) {
24120
23629
  dedupeKey: dedupe_key
24121
23630
  }, { deliver: deliver !== false }), null, 2) }]
24122
23631
  }));
24123
- server.tool("machines_events_list", "List shared Hasna events.", {}, async () => ({
23632
+ server.tool("machines_events_list", "List shared events.", {}, async () => ({
24124
23633
  content: [{ type: "text", text: JSON.stringify(await events.listEvents(), null, 2) }]
24125
23634
  }));
24126
- 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 }) => ({
24127
23636
  content: [{ type: "text", text: JSON.stringify(await events.replay({ eventId: event_id, source, type: event_type, dryRun: dry_run }), null, 2) }]
24128
23637
  }));
24129
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) }] }));
@@ -24254,6 +23763,7 @@ export {
24254
23763
  STORAGE_TABLES,
24255
23764
  STORAGE_MODE_ENV,
24256
23765
  STORAGE_DATABASE_ENV,
23766
+ SCREEN_SECRET_NAMESPACE_ENV,
24257
23767
  MACHINE_MCP_TOOL_NAMES,
24258
23768
  MACHINES_STORAGE_TABLES,
24259
23769
  MACHINES_STORAGE_MODE_FALLBACK_ENV,