@skillkit/mesh 1.8.0

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 ADDED
@@ -0,0 +1,3326 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/types.ts
12
+ var DEFAULT_PORT, DEFAULT_DISCOVERY_PORT, HEALTH_CHECK_TIMEOUT, DISCOVERY_INTERVAL, MESH_VERSION;
13
+ var init_types = __esm({
14
+ "src/types.ts"() {
15
+ "use strict";
16
+ DEFAULT_PORT = 9876;
17
+ DEFAULT_DISCOVERY_PORT = 9877;
18
+ HEALTH_CHECK_TIMEOUT = 5e3;
19
+ DISCOVERY_INTERVAL = 3e4;
20
+ MESH_VERSION = "1.7.11";
21
+ }
22
+ });
23
+
24
+ // src/config/hosts-config.ts
25
+ import { readFile, writeFile, mkdir } from "fs/promises";
26
+ import { existsSync } from "fs";
27
+ import { dirname, join } from "path";
28
+ import { homedir, hostname as osHostname } from "os";
29
+ import { randomUUID } from "crypto";
30
+ async function withFileLock(fn) {
31
+ while (fileLock) {
32
+ await fileLock;
33
+ }
34
+ let resolve;
35
+ fileLock = new Promise((r) => {
36
+ resolve = r;
37
+ });
38
+ try {
39
+ return await fn();
40
+ } finally {
41
+ fileLock = null;
42
+ resolve();
43
+ }
44
+ }
45
+ async function getHostsFilePath() {
46
+ return HOSTS_FILE_PATH;
47
+ }
48
+ async function loadHostsFile() {
49
+ if (!existsSync(HOSTS_FILE_PATH)) {
50
+ return createDefaultHostsFile();
51
+ }
52
+ try {
53
+ const content = await readFile(HOSTS_FILE_PATH, "utf-8");
54
+ return JSON.parse(content);
55
+ } catch {
56
+ return createDefaultHostsFile();
57
+ }
58
+ }
59
+ async function saveHostsFile(hostsFile) {
60
+ await mkdir(dirname(HOSTS_FILE_PATH), { recursive: true });
61
+ hostsFile.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
62
+ await writeFile(HOSTS_FILE_PATH, JSON.stringify(hostsFile, null, 2), "utf-8");
63
+ }
64
+ function createDefaultHostsFile() {
65
+ return {
66
+ version: MESH_VERSION,
67
+ localHost: {
68
+ id: randomUUID(),
69
+ name: getDefaultHostName(),
70
+ port: DEFAULT_PORT,
71
+ autoStart: false,
72
+ discoveryEnabled: true
73
+ },
74
+ knownHosts: [],
75
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
76
+ };
77
+ }
78
+ function getDefaultHostName() {
79
+ const hostname = osHostname();
80
+ return hostname || `skillkit-host-${randomUUID().slice(0, 8)}`;
81
+ }
82
+ async function getLocalHostConfig() {
83
+ const hostsFile = await loadHostsFile();
84
+ return hostsFile.localHost;
85
+ }
86
+ async function updateLocalHostConfig(updates) {
87
+ return withFileLock(async () => {
88
+ const hostsFile = await loadHostsFile();
89
+ hostsFile.localHost = { ...hostsFile.localHost, ...updates };
90
+ await saveHostsFile(hostsFile);
91
+ return hostsFile.localHost;
92
+ });
93
+ }
94
+ async function addKnownHost(host) {
95
+ return withFileLock(async () => {
96
+ const hostsFile = await loadHostsFile();
97
+ const existingIndex = hostsFile.knownHosts.findIndex((h) => h.id === host.id);
98
+ if (existingIndex >= 0) {
99
+ hostsFile.knownHosts[existingIndex] = host;
100
+ } else {
101
+ hostsFile.knownHosts.push(host);
102
+ }
103
+ await saveHostsFile(hostsFile);
104
+ });
105
+ }
106
+ async function removeKnownHost(hostId) {
107
+ return withFileLock(async () => {
108
+ const hostsFile = await loadHostsFile();
109
+ const initialLength = hostsFile.knownHosts.length;
110
+ hostsFile.knownHosts = hostsFile.knownHosts.filter((h) => h.id !== hostId);
111
+ if (hostsFile.knownHosts.length < initialLength) {
112
+ await saveHostsFile(hostsFile);
113
+ return true;
114
+ }
115
+ return false;
116
+ });
117
+ }
118
+ async function getKnownHosts() {
119
+ const hostsFile = await loadHostsFile();
120
+ return hostsFile.knownHosts;
121
+ }
122
+ async function getKnownHost(hostId) {
123
+ const hosts = await getKnownHosts();
124
+ return hosts.find((h) => h.id === hostId);
125
+ }
126
+ async function updateKnownHost(hostId, updates) {
127
+ const hostsFile = await loadHostsFile();
128
+ const index = hostsFile.knownHosts.findIndex((h) => h.id === hostId);
129
+ if (index < 0) return null;
130
+ hostsFile.knownHosts[index] = { ...hostsFile.knownHosts[index], ...updates };
131
+ await saveHostsFile(hostsFile);
132
+ return hostsFile.knownHosts[index];
133
+ }
134
+ async function initializeHostsFile() {
135
+ if (!existsSync(HOSTS_FILE_PATH)) {
136
+ const hostsFile = createDefaultHostsFile();
137
+ await saveHostsFile(hostsFile);
138
+ return hostsFile;
139
+ }
140
+ return loadHostsFile();
141
+ }
142
+ var HOSTS_FILE_PATH, fileLock;
143
+ var init_hosts_config = __esm({
144
+ "src/config/hosts-config.ts"() {
145
+ "use strict";
146
+ init_types();
147
+ HOSTS_FILE_PATH = join(homedir(), ".skillkit", "hosts.json");
148
+ fileLock = null;
149
+ }
150
+ });
151
+
152
+ // src/config/index.ts
153
+ var config_exports = {};
154
+ __export(config_exports, {
155
+ addKnownHost: () => addKnownHost,
156
+ createDefaultHostsFile: () => createDefaultHostsFile,
157
+ getHostsFilePath: () => getHostsFilePath,
158
+ getKnownHost: () => getKnownHost,
159
+ getKnownHosts: () => getKnownHosts,
160
+ getLocalHostConfig: () => getLocalHostConfig,
161
+ initializeHostsFile: () => initializeHostsFile,
162
+ loadHostsFile: () => loadHostsFile,
163
+ removeKnownHost: () => removeKnownHost,
164
+ saveHostsFile: () => saveHostsFile,
165
+ updateKnownHost: () => updateKnownHost,
166
+ updateLocalHostConfig: () => updateLocalHostConfig
167
+ });
168
+ var init_config = __esm({
169
+ "src/config/index.ts"() {
170
+ "use strict";
171
+ init_hosts_config();
172
+ }
173
+ });
174
+
175
+ // src/crypto/identity.ts
176
+ import * as ed25519 from "@noble/ed25519";
177
+ import { x25519 } from "@noble/curves/ed25519";
178
+ import { sha256 } from "@noble/hashes/sha256";
179
+ import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
180
+ import { randomBytes } from "@noble/ciphers/webcrypto";
181
+ var PeerIdentity;
182
+ var init_identity = __esm({
183
+ "src/crypto/identity.ts"() {
184
+ "use strict";
185
+ PeerIdentity = class _PeerIdentity {
186
+ keypair;
187
+ constructor(keypair) {
188
+ this.keypair = keypair;
189
+ }
190
+ static async generate() {
191
+ const privateKey = randomBytes(32);
192
+ const publicKey = await ed25519.getPublicKeyAsync(privateKey);
193
+ const fingerprint = _PeerIdentity.computeFingerprint(publicKey);
194
+ return new _PeerIdentity({
195
+ publicKey,
196
+ privateKey,
197
+ fingerprint
198
+ });
199
+ }
200
+ static async fromPrivateKey(privateKey) {
201
+ if (privateKey.length !== 32) {
202
+ throw new Error("Private key must be 32 bytes");
203
+ }
204
+ const publicKey = await ed25519.getPublicKeyAsync(privateKey);
205
+ const fingerprint = _PeerIdentity.computeFingerprint(publicKey);
206
+ return new _PeerIdentity({
207
+ publicKey,
208
+ privateKey,
209
+ fingerprint
210
+ });
211
+ }
212
+ static fromSerialized(data) {
213
+ const publicKey = hexToBytes(data.publicKey);
214
+ const privateKey = hexToBytes(data.privateKey);
215
+ const fingerprint = data.fingerprint;
216
+ const computed = _PeerIdentity.computeFingerprint(publicKey);
217
+ if (computed !== fingerprint) {
218
+ throw new Error("Fingerprint mismatch - corrupted identity");
219
+ }
220
+ return new _PeerIdentity({
221
+ publicKey,
222
+ privateKey,
223
+ fingerprint
224
+ });
225
+ }
226
+ static computeFingerprint(publicKey) {
227
+ const hash = sha256(publicKey);
228
+ return bytesToHex(hash.slice(0, 8));
229
+ }
230
+ static async verify(signature, message, publicKey) {
231
+ try {
232
+ return await ed25519.verifyAsync(signature, message, publicKey);
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+ static async verifyHex(signatureHex, messageHex, publicKeyHex) {
238
+ try {
239
+ const signature = hexToBytes(signatureHex);
240
+ const message = hexToBytes(messageHex);
241
+ const publicKey = hexToBytes(publicKeyHex);
242
+ return await _PeerIdentity.verify(signature, message, publicKey);
243
+ } catch {
244
+ return false;
245
+ }
246
+ }
247
+ async sign(message) {
248
+ return await ed25519.signAsync(message, this.keypair.privateKey);
249
+ }
250
+ async signString(message) {
251
+ const messageBytes = new TextEncoder().encode(message);
252
+ const signature = await this.sign(messageBytes);
253
+ return bytesToHex(signature);
254
+ }
255
+ async signObject(obj) {
256
+ const message = JSON.stringify(obj);
257
+ return await this.signString(message);
258
+ }
259
+ deriveSharedSecret(peerPublicKey) {
260
+ const x25519PrivateKey = this.keypair.privateKey;
261
+ return x25519.scalarMult(x25519PrivateKey, peerPublicKey);
262
+ }
263
+ deriveSharedSecretHex(peerPublicKeyHex) {
264
+ const peerPublicKey = hexToBytes(peerPublicKeyHex);
265
+ return this.deriveSharedSecret(peerPublicKey);
266
+ }
267
+ serialize() {
268
+ return {
269
+ publicKey: bytesToHex(this.keypair.publicKey),
270
+ privateKey: bytesToHex(this.keypair.privateKey),
271
+ fingerprint: this.keypair.fingerprint
272
+ };
273
+ }
274
+ get publicKey() {
275
+ return this.keypair.publicKey;
276
+ }
277
+ get publicKeyHex() {
278
+ return bytesToHex(this.keypair.publicKey);
279
+ }
280
+ get privateKey() {
281
+ return this.keypair.privateKey;
282
+ }
283
+ get privateKeyHex() {
284
+ return bytesToHex(this.keypair.privateKey);
285
+ }
286
+ get fingerprint() {
287
+ return this.keypair.fingerprint;
288
+ }
289
+ toJSON() {
290
+ return {
291
+ publicKey: this.publicKeyHex,
292
+ fingerprint: this.fingerprint
293
+ };
294
+ }
295
+ };
296
+ }
297
+ });
298
+
299
+ // src/peer/registry.ts
300
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
301
+ import { existsSync as existsSync2 } from "fs";
302
+ import { join as join2 } from "path";
303
+ import { homedir as homedir2 } from "os";
304
+ async function getPeerRegistry() {
305
+ if (!globalRegistry) {
306
+ globalRegistry = new PeerRegistryManager();
307
+ await globalRegistry.initialize();
308
+ }
309
+ return globalRegistry;
310
+ }
311
+ var PEERS_DIR, PeerRegistryManager, globalRegistry;
312
+ var init_registry = __esm({
313
+ "src/peer/registry.ts"() {
314
+ "use strict";
315
+ init_hosts_config();
316
+ PEERS_DIR = join2(homedir2(), ".skillkit", "mesh", "peers");
317
+ PeerRegistryManager = class {
318
+ registry = {
319
+ peers: /* @__PURE__ */ new Map(),
320
+ localPeers: /* @__PURE__ */ new Map()
321
+ };
322
+ async initialize() {
323
+ await mkdir2(PEERS_DIR, { recursive: true });
324
+ await this.loadLocalPeers();
325
+ }
326
+ async registerLocalPeer(registration) {
327
+ const localConfig = await getLocalHostConfig();
328
+ const peer = {
329
+ hostId: localConfig.id,
330
+ agentId: registration.agentId,
331
+ agentName: registration.agentName,
332
+ aliases: registration.aliases,
333
+ capabilities: registration.capabilities,
334
+ status: "online",
335
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString()
336
+ };
337
+ this.registry.localPeers.set(peer.agentId, peer);
338
+ await this.saveLocalPeers();
339
+ return peer;
340
+ }
341
+ async unregisterLocalPeer(agentId) {
342
+ const deleted = this.registry.localPeers.delete(agentId);
343
+ if (deleted) {
344
+ await this.saveLocalPeers();
345
+ }
346
+ return deleted;
347
+ }
348
+ registerRemotePeer(peer) {
349
+ const key = `${peer.hostId}:${peer.agentId}`;
350
+ this.registry.peers.set(key, peer);
351
+ }
352
+ unregisterRemotePeer(hostId, agentId) {
353
+ const key = `${hostId}:${agentId}`;
354
+ return this.registry.peers.delete(key);
355
+ }
356
+ getPeer(hostId, agentId) {
357
+ const key = `${hostId}:${agentId}`;
358
+ return this.registry.peers.get(key);
359
+ }
360
+ getLocalPeer(agentId) {
361
+ return this.registry.localPeers.get(agentId);
362
+ }
363
+ getAllPeers() {
364
+ return [
365
+ ...Array.from(this.registry.localPeers.values()),
366
+ ...Array.from(this.registry.peers.values())
367
+ ];
368
+ }
369
+ getLocalPeers() {
370
+ return Array.from(this.registry.localPeers.values());
371
+ }
372
+ getRemotePeers() {
373
+ return Array.from(this.registry.peers.values());
374
+ }
375
+ getPeersByHost(hostId) {
376
+ return this.getAllPeers().filter((p) => p.hostId === hostId);
377
+ }
378
+ findPeerByName(name) {
379
+ const lowerName = name.toLowerCase();
380
+ for (const peer of this.getAllPeers()) {
381
+ if (peer.agentName.toLowerCase() === lowerName) {
382
+ return peer;
383
+ }
384
+ if (peer.aliases.some((a) => a.toLowerCase() === lowerName)) {
385
+ return peer;
386
+ }
387
+ }
388
+ return void 0;
389
+ }
390
+ findPeersByCapability(capability) {
391
+ return this.getAllPeers().filter((p) => p.capabilities.includes(capability));
392
+ }
393
+ async resolvePeerAddress(nameOrId) {
394
+ const parts = nameOrId.split("@");
395
+ const peerName = parts[0];
396
+ const hostName = parts[1];
397
+ if (hostName) {
398
+ const hosts2 = await getKnownHosts();
399
+ const host2 = hosts2.find(
400
+ (h) => h.name.toLowerCase() === hostName.toLowerCase() || h.id === hostName
401
+ );
402
+ if (!host2) return null;
403
+ const peer2 = this.getPeersByHost(host2.id).find(
404
+ (p) => p.agentName.toLowerCase() === peerName.toLowerCase() || p.agentId === peerName || p.aliases.some((a) => a.toLowerCase() === peerName.toLowerCase())
405
+ );
406
+ if (!peer2) return null;
407
+ return { host: host2, peer: peer2 };
408
+ }
409
+ const peer = this.findPeerByName(peerName);
410
+ if (!peer) return null;
411
+ const hosts = await getKnownHosts();
412
+ const host = hosts.find((h) => h.id === peer.hostId);
413
+ if (!host) {
414
+ const localConfig = await getLocalHostConfig();
415
+ if (peer.hostId === localConfig.id) {
416
+ return {
417
+ host: {
418
+ id: localConfig.id,
419
+ name: localConfig.name,
420
+ address: "127.0.0.1",
421
+ port: localConfig.port,
422
+ status: "online",
423
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString()
424
+ },
425
+ peer
426
+ };
427
+ }
428
+ return null;
429
+ }
430
+ return { host, peer };
431
+ }
432
+ updatePeerStatus(hostId, agentId, status) {
433
+ const key = `${hostId}:${agentId}`;
434
+ const peer = this.registry.peers.get(key);
435
+ if (peer) {
436
+ peer.status = status;
437
+ peer.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
438
+ }
439
+ }
440
+ markHostOffline(hostId) {
441
+ for (const [, peer] of this.registry.peers) {
442
+ if (peer.hostId === hostId) {
443
+ peer.status = "offline";
444
+ }
445
+ }
446
+ }
447
+ markHostOnline(hostId) {
448
+ for (const [, peer] of this.registry.peers) {
449
+ if (peer.hostId === hostId) {
450
+ peer.status = "online";
451
+ peer.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
452
+ }
453
+ }
454
+ }
455
+ clearRemotePeers() {
456
+ this.registry.peers.clear();
457
+ }
458
+ async loadLocalPeers() {
459
+ const filePath = join2(PEERS_DIR, "local-peers.json");
460
+ if (!existsSync2(filePath)) return;
461
+ try {
462
+ const content = await readFile2(filePath, "utf-8");
463
+ const peers = JSON.parse(content);
464
+ for (const peer of peers) {
465
+ this.registry.localPeers.set(peer.agentId, peer);
466
+ }
467
+ } catch {
468
+ }
469
+ }
470
+ async saveLocalPeers() {
471
+ const filePath = join2(PEERS_DIR, "local-peers.json");
472
+ const peers = Array.from(this.registry.localPeers.values());
473
+ await mkdir2(PEERS_DIR, { recursive: true });
474
+ await writeFile2(filePath, JSON.stringify(peers, null, 2), "utf-8");
475
+ }
476
+ };
477
+ globalRegistry = null;
478
+ }
479
+ });
480
+
481
+ // src/peer/health.ts
482
+ import got from "got";
483
+ async function checkHostHealth(host, options = {}) {
484
+ const timeout = options.timeout ?? HEALTH_CHECK_TIMEOUT;
485
+ const startTime = Date.now();
486
+ const result = {
487
+ hostId: host.id,
488
+ address: host.address,
489
+ port: host.port,
490
+ status: "unknown",
491
+ latencyMs: 0,
492
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
493
+ };
494
+ try {
495
+ const url = `http://${host.address}:${host.port}/health`;
496
+ const response = await got.get(url, {
497
+ timeout: { request: timeout },
498
+ retry: { limit: 0 },
499
+ throwHttpErrors: false
500
+ });
501
+ result.latencyMs = Date.now() - startTime;
502
+ if (response.statusCode === 200) {
503
+ result.status = "online";
504
+ } else {
505
+ result.status = "offline";
506
+ result.error = `HTTP ${response.statusCode}`;
507
+ }
508
+ } catch (err) {
509
+ result.latencyMs = Date.now() - startTime;
510
+ result.status = "offline";
511
+ result.error = err.code || err.message || "Connection failed";
512
+ }
513
+ if (options.updateStatus !== false) {
514
+ await updateKnownHost(host.id, {
515
+ status: result.status,
516
+ lastSeen: result.status === "online" ? result.checkedAt : host.lastSeen
517
+ });
518
+ }
519
+ return result;
520
+ }
521
+ async function checkAllHostsHealth(options = {}) {
522
+ const hosts = await getKnownHosts();
523
+ const results = await Promise.all(hosts.map((host) => checkHostHealth(host, options)));
524
+ return results;
525
+ }
526
+ async function getOnlineHosts() {
527
+ const hosts = await getKnownHosts();
528
+ return hosts.filter((h) => h.status === "online");
529
+ }
530
+ async function getOfflineHosts() {
531
+ const hosts = await getKnownHosts();
532
+ return hosts.filter((h) => h.status === "offline");
533
+ }
534
+ async function waitForHost(host, maxWaitMs = 3e4, intervalMs = 1e3) {
535
+ const deadline = Date.now() + maxWaitMs;
536
+ while (Date.now() < deadline) {
537
+ const result = await checkHostHealth(host, { updateStatus: false });
538
+ if (result.status === "online") {
539
+ await updateKnownHost(host.id, {
540
+ status: "online",
541
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString()
542
+ });
543
+ return true;
544
+ }
545
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
546
+ }
547
+ return false;
548
+ }
549
+ var HealthMonitor;
550
+ var init_health = __esm({
551
+ "src/peer/health.ts"() {
552
+ "use strict";
553
+ init_types();
554
+ init_hosts_config();
555
+ HealthMonitor = class {
556
+ interval = null;
557
+ running = false;
558
+ checking = false;
559
+ onStatusChange;
560
+ constructor(options = {}) {
561
+ this.onStatusChange = options.onStatusChange;
562
+ }
563
+ async start(intervalMs = 3e4) {
564
+ if (this.running) return;
565
+ this.running = true;
566
+ await this.checkAll();
567
+ this.interval = setInterval(() => {
568
+ if (!this.checking) {
569
+ this.checking = true;
570
+ this.checkAll().finally(() => {
571
+ this.checking = false;
572
+ });
573
+ }
574
+ }, intervalMs);
575
+ }
576
+ stop() {
577
+ if (!this.running) return;
578
+ if (this.interval) {
579
+ clearInterval(this.interval);
580
+ this.interval = null;
581
+ }
582
+ this.running = false;
583
+ }
584
+ isRunning() {
585
+ return this.running;
586
+ }
587
+ async checkAll() {
588
+ const hosts = await getKnownHosts();
589
+ for (const host of hosts) {
590
+ const oldStatus = host.status;
591
+ const result = await checkHostHealth(host);
592
+ if (this.onStatusChange && oldStatus !== result.status) {
593
+ this.onStatusChange(host, oldStatus, result.status);
594
+ }
595
+ }
596
+ }
597
+ };
598
+ }
599
+ });
600
+
601
+ // src/peer/index.ts
602
+ var peer_exports = {};
603
+ __export(peer_exports, {
604
+ HealthMonitor: () => HealthMonitor,
605
+ PeerRegistryManager: () => PeerRegistryManager,
606
+ checkAllHostsHealth: () => checkAllHostsHealth,
607
+ checkHostHealth: () => checkHostHealth,
608
+ getOfflineHosts: () => getOfflineHosts,
609
+ getOnlineHosts: () => getOnlineHosts,
610
+ getPeerRegistry: () => getPeerRegistry,
611
+ waitForHost: () => waitForHost
612
+ });
613
+ var init_peer = __esm({
614
+ "src/peer/index.ts"() {
615
+ "use strict";
616
+ init_registry();
617
+ init_health();
618
+ }
619
+ });
620
+
621
+ // src/crypto/encryption.ts
622
+ import { xchacha20poly1305 } from "@noble/ciphers/chacha";
623
+ import { randomBytes as randomBytes2 } from "@noble/ciphers/webcrypto";
624
+ import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes3 } from "@noble/hashes/utils";
625
+ import { hkdf } from "@noble/hashes/hkdf";
626
+ import { sha256 as sha2562 } from "@noble/hashes/sha256";
627
+ import { x25519 as x255192 } from "@noble/curves/ed25519";
628
+ function generateNonce() {
629
+ return bytesToHex2(randomBytes2(24));
630
+ }
631
+ function generateMessageId() {
632
+ return bytesToHex2(randomBytes2(16));
633
+ }
634
+ var MessageEncryption, PublicKeyEncryption;
635
+ var init_encryption = __esm({
636
+ "src/crypto/encryption.ts"() {
637
+ "use strict";
638
+ MessageEncryption = class _MessageEncryption {
639
+ key;
640
+ constructor(sharedSecret) {
641
+ this.key = hkdf(sha2562, sharedSecret, void 0, "skillkit-mesh-v1", 32);
642
+ }
643
+ encrypt(plaintext) {
644
+ const nonce = randomBytes2(24);
645
+ const data = typeof plaintext === "string" ? new TextEncoder().encode(plaintext) : plaintext;
646
+ const cipher = xchacha20poly1305(this.key, nonce);
647
+ const ciphertext = cipher.encrypt(data);
648
+ return {
649
+ nonce: bytesToHex2(nonce),
650
+ ciphertext: bytesToHex2(ciphertext)
651
+ };
652
+ }
653
+ decrypt(encrypted) {
654
+ const nonce = hexToBytes3(encrypted.nonce);
655
+ const ciphertext = hexToBytes3(encrypted.ciphertext);
656
+ const cipher = xchacha20poly1305(this.key, nonce);
657
+ return cipher.decrypt(ciphertext);
658
+ }
659
+ decryptToString(encrypted) {
660
+ const plaintext = this.decrypt(encrypted);
661
+ return new TextDecoder().decode(plaintext);
662
+ }
663
+ decryptToObject(encrypted) {
664
+ const plaintext = this.decryptToString(encrypted);
665
+ return JSON.parse(plaintext);
666
+ }
667
+ encryptObject(obj) {
668
+ return this.encrypt(JSON.stringify(obj));
669
+ }
670
+ static fromSharedSecret(sharedSecret) {
671
+ return new _MessageEncryption(sharedSecret);
672
+ }
673
+ };
674
+ PublicKeyEncryption = class _PublicKeyEncryption {
675
+ static encrypt(message, recipientPublicKey) {
676
+ const ephemeralPrivateKey = randomBytes2(32);
677
+ const ephemeralPublicKey = x255192.scalarMultBase(ephemeralPrivateKey);
678
+ const sharedSecret = x255192.scalarMult(ephemeralPrivateKey, recipientPublicKey);
679
+ const key = hkdf(sha2562, sharedSecret, void 0, "skillkit-mesh-pk-v1", 32);
680
+ const nonce = randomBytes2(24);
681
+ const cipher = xchacha20poly1305(key, nonce);
682
+ const ciphertext = cipher.encrypt(message);
683
+ return {
684
+ ephemeralPublicKey: bytesToHex2(ephemeralPublicKey),
685
+ nonce: bytesToHex2(nonce),
686
+ ciphertext: bytesToHex2(ciphertext)
687
+ };
688
+ }
689
+ static encryptString(message, recipientPublicKey) {
690
+ const messageBytes = new TextEncoder().encode(message);
691
+ return _PublicKeyEncryption.encrypt(messageBytes, recipientPublicKey);
692
+ }
693
+ static encryptStringHex(message, recipientPublicKeyHex) {
694
+ const recipientPublicKey = hexToBytes3(recipientPublicKeyHex);
695
+ return _PublicKeyEncryption.encryptString(message, recipientPublicKey);
696
+ }
697
+ static decrypt(encrypted, recipientPrivateKey) {
698
+ const ephemeralPublicKey = hexToBytes3(encrypted.ephemeralPublicKey);
699
+ const nonce = hexToBytes3(encrypted.nonce);
700
+ const ciphertext = hexToBytes3(encrypted.ciphertext);
701
+ const sharedSecret = x255192.scalarMult(recipientPrivateKey, ephemeralPublicKey);
702
+ const key = hkdf(sha2562, sharedSecret, void 0, "skillkit-mesh-pk-v1", 32);
703
+ const cipher = xchacha20poly1305(key, nonce);
704
+ return cipher.decrypt(ciphertext);
705
+ }
706
+ static decryptToString(encrypted, recipientPrivateKey) {
707
+ const plaintext = _PublicKeyEncryption.decrypt(encrypted, recipientPrivateKey);
708
+ return new TextDecoder().decode(plaintext);
709
+ }
710
+ static decryptToObject(encrypted, recipientPrivateKey) {
711
+ const plaintext = _PublicKeyEncryption.decryptToString(
712
+ encrypted,
713
+ recipientPrivateKey
714
+ );
715
+ return JSON.parse(plaintext);
716
+ }
717
+ };
718
+ }
719
+ });
720
+
721
+ // src/crypto/signatures.ts
722
+ import { bytesToHex as bytesToHex4, hexToBytes as hexToBytes6 } from "@noble/hashes/utils";
723
+ import { sha256 as sha2563 } from "@noble/hashes/sha256";
724
+ function generateNonce2() {
725
+ const bytes = new Uint8Array(16);
726
+ crypto.getRandomValues(bytes);
727
+ return bytesToHex4(bytes);
728
+ }
729
+ function canonicalize(obj) {
730
+ if (obj === null || obj === void 0) {
731
+ return "null";
732
+ }
733
+ if (typeof obj !== "object") {
734
+ return JSON.stringify(obj);
735
+ }
736
+ if (Array.isArray(obj)) {
737
+ return "[" + obj.map(canonicalize).join(",") + "]";
738
+ }
739
+ const keys = Object.keys(obj).sort();
740
+ const pairs = keys.map((key) => {
741
+ const value = obj[key];
742
+ return JSON.stringify(key) + ":" + canonicalize(value);
743
+ });
744
+ return "{" + pairs.join(",") + "}";
745
+ }
746
+ function hashData(data) {
747
+ const canonical = canonicalize(data);
748
+ return sha2563(new TextEncoder().encode(canonical));
749
+ }
750
+ async function signData(data, identity) {
751
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
752
+ const nonce = generateNonce2();
753
+ const toSign = {
754
+ data,
755
+ timestamp,
756
+ nonce,
757
+ senderFingerprint: identity.fingerprint
758
+ };
759
+ const hash = hashData(toSign);
760
+ const signature = await identity.sign(hash);
761
+ return {
762
+ data,
763
+ signature: bytesToHex4(signature),
764
+ senderFingerprint: identity.fingerprint,
765
+ senderPublicKey: identity.publicKeyHex,
766
+ timestamp,
767
+ nonce
768
+ };
769
+ }
770
+ async function verifySignedData(signed, trustedPublicKey) {
771
+ try {
772
+ const publicKey = trustedPublicKey || hexToBytes6(signed.senderPublicKey);
773
+ const computedFingerprint = PeerIdentity.computeFingerprint(publicKey);
774
+ if (computedFingerprint !== signed.senderFingerprint) {
775
+ return {
776
+ valid: false,
777
+ error: "Fingerprint mismatch"
778
+ };
779
+ }
780
+ const toSign = {
781
+ data: signed.data,
782
+ timestamp: signed.timestamp,
783
+ nonce: signed.nonce,
784
+ senderFingerprint: signed.senderFingerprint
785
+ };
786
+ const hash = hashData(toSign);
787
+ const signature = hexToBytes6(signed.signature);
788
+ const valid = await PeerIdentity.verify(signature, hash, publicKey);
789
+ if (!valid) {
790
+ return {
791
+ valid: false,
792
+ error: "Invalid signature"
793
+ };
794
+ }
795
+ return {
796
+ valid: true,
797
+ fingerprint: signed.senderFingerprint
798
+ };
799
+ } catch (error) {
800
+ return {
801
+ valid: false,
802
+ error: error instanceof Error ? error.message : "Unknown error"
803
+ };
804
+ }
805
+ }
806
+ function isSignedDataExpired(signed, maxAgeMs = 5 * 60 * 1e3) {
807
+ const timestamp = new Date(signed.timestamp).getTime();
808
+ const now = Date.now();
809
+ return now - timestamp > maxAgeMs;
810
+ }
811
+ function extractSignerFingerprint(signed) {
812
+ try {
813
+ const publicKey = hexToBytes6(signed.senderPublicKey);
814
+ const computed = PeerIdentity.computeFingerprint(publicKey);
815
+ if (computed === signed.senderFingerprint) {
816
+ return signed.senderFingerprint;
817
+ }
818
+ return null;
819
+ } catch {
820
+ return null;
821
+ }
822
+ }
823
+ var init_signatures = __esm({
824
+ "src/crypto/signatures.ts"() {
825
+ "use strict";
826
+ init_identity();
827
+ }
828
+ });
829
+
830
+ // src/crypto/storage.ts
831
+ import {
832
+ createCipheriv,
833
+ createDecipheriv,
834
+ randomBytes as randomBytes4,
835
+ scrypt,
836
+ createHash as createHash2
837
+ } from "crypto";
838
+ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
839
+ import { dirname as dirname2 } from "path";
840
+ import { existsSync as existsSync4 } from "fs";
841
+ function deriveKey(passphrase, salt) {
842
+ return new Promise((resolve, reject) => {
843
+ const options = {
844
+ N: SCRYPT_N,
845
+ r: SCRYPT_R,
846
+ p: SCRYPT_P
847
+ };
848
+ scrypt(passphrase, salt, KEY_LENGTH, options, (err, derivedKey) => {
849
+ if (err) reject(err);
850
+ else resolve(derivedKey);
851
+ });
852
+ });
853
+ }
854
+ function generateSalt() {
855
+ return randomBytes4(SALT_LENGTH);
856
+ }
857
+ function generateIV() {
858
+ return randomBytes4(IV_LENGTH);
859
+ }
860
+ function encrypt(data, key, iv) {
861
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
862
+ const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]);
863
+ const authTag = cipher.getAuthTag();
864
+ return { ciphertext, authTag };
865
+ }
866
+ function decrypt(ciphertext, key, iv, authTag) {
867
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
868
+ decipher.setAuthTag(authTag);
869
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
870
+ }
871
+ async function encryptData(data, passphrase) {
872
+ const dataBytes = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
873
+ const salt = generateSalt();
874
+ const iv = generateIV();
875
+ const key = await deriveKey(passphrase, salt);
876
+ const { ciphertext, authTag } = encrypt(dataBytes, key, iv);
877
+ return {
878
+ version: "2.0",
879
+ encrypted: true,
880
+ algorithm: "aes-256-gcm",
881
+ kdf: "scrypt",
882
+ salt: Buffer.from(salt).toString("hex"),
883
+ iv: Buffer.from(iv).toString("hex"),
884
+ ciphertext: ciphertext.toString("hex"),
885
+ authTag: authTag.toString("hex")
886
+ };
887
+ }
888
+ async function decryptData(encrypted, passphrase) {
889
+ if (encrypted.version !== "2.0" || !encrypted.encrypted) {
890
+ throw new Error("Invalid encrypted file format");
891
+ }
892
+ const salt = Buffer.from(encrypted.salt, "hex");
893
+ const iv = Buffer.from(encrypted.iv, "hex");
894
+ const ciphertext = Buffer.from(encrypted.ciphertext, "hex");
895
+ const authTag = Buffer.from(encrypted.authTag, "hex");
896
+ const key = await deriveKey(passphrase, salt);
897
+ return decrypt(ciphertext, key, iv, authTag);
898
+ }
899
+ async function encryptObject(obj, passphrase) {
900
+ const json = JSON.stringify(obj);
901
+ return encryptData(json, passphrase);
902
+ }
903
+ async function decryptObject(encrypted, passphrase) {
904
+ const decrypted = await decryptData(encrypted, passphrase);
905
+ return JSON.parse(decrypted.toString("utf-8"));
906
+ }
907
+ async function encryptFile(data, passphrase, outputPath) {
908
+ const dir = dirname2(outputPath);
909
+ if (!existsSync4(dir)) {
910
+ await mkdir4(dir, { recursive: true });
911
+ }
912
+ const encrypted = await encryptObject(data, passphrase);
913
+ await writeFile4(outputPath, JSON.stringify(encrypted, null, 2));
914
+ }
915
+ async function decryptFile(inputPath, passphrase) {
916
+ const content = await readFile4(inputPath, "utf-8");
917
+ const encrypted = JSON.parse(content);
918
+ return decryptObject(encrypted, passphrase);
919
+ }
920
+ function isEncryptedFile(data) {
921
+ if (typeof data !== "object" || data === null) return false;
922
+ const obj = data;
923
+ return obj.version === "2.0" && obj.encrypted === true && obj.algorithm === "aes-256-gcm" && obj.kdf === "scrypt" && typeof obj.salt === "string" && typeof obj.iv === "string" && typeof obj.ciphertext === "string" && typeof obj.authTag === "string";
924
+ }
925
+ function hashPassphrase(passphrase) {
926
+ return createHash2("sha256").update(passphrase).digest("hex").slice(0, 16);
927
+ }
928
+ function generateMachineKey() {
929
+ const hostname = process.env.HOSTNAME || "unknown";
930
+ const user = process.env.USER || process.env.USERNAME || "unknown";
931
+ const combined = `skillkit-mesh-${hostname}-${user}`;
932
+ return createHash2("sha256").update(combined).digest("hex");
933
+ }
934
+ var SCRYPT_N, SCRYPT_R, SCRYPT_P, KEY_LENGTH, IV_LENGTH, SALT_LENGTH;
935
+ var init_storage = __esm({
936
+ "src/crypto/storage.ts"() {
937
+ "use strict";
938
+ SCRYPT_N = 2 ** 14;
939
+ SCRYPT_R = 8;
940
+ SCRYPT_P = 1;
941
+ KEY_LENGTH = 32;
942
+ IV_LENGTH = 12;
943
+ SALT_LENGTH = 32;
944
+ }
945
+ });
946
+
947
+ // src/crypto/keystore.ts
948
+ import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5, access, unlink } from "fs/promises";
949
+ import { join as join4 } from "path";
950
+ import { homedir as homedir4 } from "os";
951
+ import { existsSync as existsSync5 } from "fs";
952
+ function getKeystore(config) {
953
+ if (!globalKeystore) {
954
+ globalKeystore = new SecureKeystore(config);
955
+ }
956
+ return globalKeystore;
957
+ }
958
+ function resetKeystore() {
959
+ globalKeystore = null;
960
+ }
961
+ var DEFAULT_KEYSTORE_PATH, SecureKeystore, globalKeystore;
962
+ var init_keystore = __esm({
963
+ "src/crypto/keystore.ts"() {
964
+ "use strict";
965
+ init_identity();
966
+ init_storage();
967
+ DEFAULT_KEYSTORE_PATH = join4(homedir4(), ".skillkit", "mesh", "identity");
968
+ SecureKeystore = class {
969
+ path;
970
+ passphrase;
971
+ identity = null;
972
+ keystoreData = null;
973
+ constructor(config = {}) {
974
+ this.path = config.path || DEFAULT_KEYSTORE_PATH;
975
+ if (config.encryptionKey) {
976
+ this.passphrase = config.encryptionKey;
977
+ } else if (config.useMachineKey !== false) {
978
+ this.passphrase = generateMachineKey();
979
+ } else {
980
+ throw new Error("Encryption key or machine key required");
981
+ }
982
+ }
983
+ get keypairPath() {
984
+ return join4(this.path, "keypair.enc");
985
+ }
986
+ get keystoreDataPath() {
987
+ return join4(this.path, "keystore.json");
988
+ }
989
+ async ensureDirectory() {
990
+ if (!existsSync5(this.path)) {
991
+ await mkdir5(this.path, { recursive: true, mode: 448 });
992
+ }
993
+ }
994
+ async loadOrCreateIdentity() {
995
+ if (this.identity) {
996
+ return this.identity;
997
+ }
998
+ await this.ensureDirectory();
999
+ try {
1000
+ await access(this.keypairPath);
1001
+ const content = await readFile5(this.keypairPath, "utf-8");
1002
+ const encrypted = JSON.parse(content);
1003
+ if (isEncryptedFile(encrypted)) {
1004
+ const serialized = await decryptObject(
1005
+ encrypted,
1006
+ this.passphrase
1007
+ );
1008
+ this.identity = PeerIdentity.fromSerialized(serialized);
1009
+ } else {
1010
+ this.identity = PeerIdentity.fromSerialized(encrypted);
1011
+ }
1012
+ } catch {
1013
+ this.identity = await PeerIdentity.generate();
1014
+ await this.saveIdentity();
1015
+ }
1016
+ return this.identity;
1017
+ }
1018
+ async saveIdentity() {
1019
+ if (!this.identity) {
1020
+ throw new Error("No identity to save");
1021
+ }
1022
+ await this.ensureDirectory();
1023
+ const serialized = this.identity.serialize();
1024
+ const encrypted = await encryptObject(serialized, this.passphrase);
1025
+ await writeFile5(this.keypairPath, JSON.stringify(encrypted, null, 2), {
1026
+ mode: 384
1027
+ });
1028
+ }
1029
+ async getIdentity() {
1030
+ return this.identity;
1031
+ }
1032
+ async hasIdentity() {
1033
+ try {
1034
+ await access(this.keypairPath);
1035
+ return true;
1036
+ } catch {
1037
+ return false;
1038
+ }
1039
+ }
1040
+ async deleteIdentity() {
1041
+ try {
1042
+ await unlink(this.keypairPath);
1043
+ this.identity = null;
1044
+ } catch {
1045
+ }
1046
+ }
1047
+ async loadKeystoreData() {
1048
+ if (this.keystoreData) {
1049
+ return this.keystoreData;
1050
+ }
1051
+ try {
1052
+ await access(this.keystoreDataPath);
1053
+ const content = await readFile5(this.keystoreDataPath, "utf-8");
1054
+ this.keystoreData = JSON.parse(content);
1055
+ } catch {
1056
+ this.keystoreData = {
1057
+ version: "1.0",
1058
+ trustedPeers: [],
1059
+ revokedFingerprints: []
1060
+ };
1061
+ }
1062
+ return this.keystoreData;
1063
+ }
1064
+ async saveKeystoreData() {
1065
+ if (!this.keystoreData) return;
1066
+ await this.ensureDirectory();
1067
+ await writeFile5(
1068
+ this.keystoreDataPath,
1069
+ JSON.stringify(this.keystoreData, null, 2),
1070
+ { mode: 384 }
1071
+ );
1072
+ }
1073
+ async addTrustedPeer(fingerprint, publicKey, name) {
1074
+ const data = await this.loadKeystoreData();
1075
+ const existing = data.trustedPeers.findIndex(
1076
+ (p) => p.fingerprint === fingerprint
1077
+ );
1078
+ if (existing >= 0) {
1079
+ data.trustedPeers[existing] = {
1080
+ fingerprint,
1081
+ publicKey,
1082
+ name,
1083
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
1084
+ };
1085
+ } else {
1086
+ data.trustedPeers.push({
1087
+ fingerprint,
1088
+ publicKey,
1089
+ name,
1090
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
1091
+ });
1092
+ }
1093
+ const revokedIndex = data.revokedFingerprints.indexOf(fingerprint);
1094
+ if (revokedIndex >= 0) {
1095
+ data.revokedFingerprints.splice(revokedIndex, 1);
1096
+ }
1097
+ await this.saveKeystoreData();
1098
+ }
1099
+ async removeTrustedPeer(fingerprint) {
1100
+ const data = await this.loadKeystoreData();
1101
+ data.trustedPeers = data.trustedPeers.filter(
1102
+ (p) => p.fingerprint !== fingerprint
1103
+ );
1104
+ await this.saveKeystoreData();
1105
+ }
1106
+ async revokePeer(fingerprint) {
1107
+ const data = await this.loadKeystoreData();
1108
+ data.trustedPeers = data.trustedPeers.filter(
1109
+ (p) => p.fingerprint !== fingerprint
1110
+ );
1111
+ if (!data.revokedFingerprints.includes(fingerprint)) {
1112
+ data.revokedFingerprints.push(fingerprint);
1113
+ }
1114
+ await this.saveKeystoreData();
1115
+ }
1116
+ async isRevoked(fingerprint) {
1117
+ const data = await this.loadKeystoreData();
1118
+ return data.revokedFingerprints.includes(fingerprint);
1119
+ }
1120
+ async isTrusted(fingerprint) {
1121
+ const data = await this.loadKeystoreData();
1122
+ return data.trustedPeers.some((p) => p.fingerprint === fingerprint);
1123
+ }
1124
+ async getTrustedPeer(fingerprint) {
1125
+ const data = await this.loadKeystoreData();
1126
+ return data.trustedPeers.find((p) => p.fingerprint === fingerprint) || null;
1127
+ }
1128
+ async getTrustedPeers() {
1129
+ const data = await this.loadKeystoreData();
1130
+ return [...data.trustedPeers];
1131
+ }
1132
+ async getRevokedFingerprints() {
1133
+ const data = await this.loadKeystoreData();
1134
+ return [...data.revokedFingerprints];
1135
+ }
1136
+ async clearRevokedPeers() {
1137
+ const data = await this.loadKeystoreData();
1138
+ data.revokedFingerprints = [];
1139
+ await this.saveKeystoreData();
1140
+ }
1141
+ async exportPublicInfo() {
1142
+ const identity = await this.loadOrCreateIdentity();
1143
+ return {
1144
+ fingerprint: identity.fingerprint,
1145
+ publicKey: identity.publicKeyHex
1146
+ };
1147
+ }
1148
+ };
1149
+ globalKeystore = null;
1150
+ }
1151
+ });
1152
+
1153
+ // src/crypto/index.ts
1154
+ var crypto_exports = {};
1155
+ __export(crypto_exports, {
1156
+ MessageEncryption: () => MessageEncryption,
1157
+ PeerIdentity: () => PeerIdentity,
1158
+ PublicKeyEncryption: () => PublicKeyEncryption,
1159
+ SecureKeystore: () => SecureKeystore,
1160
+ decryptData: () => decryptData,
1161
+ decryptFile: () => decryptFile,
1162
+ decryptObject: () => decryptObject,
1163
+ deriveKey: () => deriveKey,
1164
+ encryptData: () => encryptData,
1165
+ encryptFile: () => encryptFile,
1166
+ encryptObject: () => encryptObject,
1167
+ extractSignerFingerprint: () => extractSignerFingerprint,
1168
+ generateIV: () => generateIV,
1169
+ generateMachineKey: () => generateMachineKey,
1170
+ generateMessageId: () => generateMessageId,
1171
+ generateNonce: () => generateNonce,
1172
+ generateSalt: () => generateSalt,
1173
+ getKeystore: () => getKeystore,
1174
+ hashPassphrase: () => hashPassphrase,
1175
+ isEncryptedFile: () => isEncryptedFile,
1176
+ isSignedDataExpired: () => isSignedDataExpired,
1177
+ resetKeystore: () => resetKeystore,
1178
+ signData: () => signData,
1179
+ verifySignedData: () => verifySignedData
1180
+ });
1181
+ var init_crypto = __esm({
1182
+ "src/crypto/index.ts"() {
1183
+ "use strict";
1184
+ init_identity();
1185
+ init_encryption();
1186
+ init_signatures();
1187
+ init_keystore();
1188
+ init_storage();
1189
+ }
1190
+ });
1191
+
1192
+ // src/index.ts
1193
+ init_types();
1194
+ init_config();
1195
+
1196
+ // src/discovery/local.ts
1197
+ init_types();
1198
+ init_hosts_config();
1199
+ import { createSocket } from "dgram";
1200
+ import { networkInterfaces } from "os";
1201
+ var MULTICAST_ADDR = "239.255.255.250";
1202
+ var DISCOVERY_INTERVAL_MS = 3e4;
1203
+ var LocalDiscovery = class {
1204
+ socket = null;
1205
+ announceInterval = null;
1206
+ running = false;
1207
+ options;
1208
+ discoveredHosts = /* @__PURE__ */ new Map();
1209
+ constructor(options = {}) {
1210
+ this.options = {
1211
+ port: options.port ?? DEFAULT_DISCOVERY_PORT,
1212
+ interval: options.interval ?? DISCOVERY_INTERVAL_MS,
1213
+ onDiscover: options.onDiscover ?? (() => {
1214
+ })
1215
+ };
1216
+ }
1217
+ async start() {
1218
+ if (this.running) return;
1219
+ this.socket = createSocket({ type: "udp4", reuseAddr: true });
1220
+ this.socket.on("message", (msg, rinfo) => {
1221
+ this.handleMessage(msg, rinfo);
1222
+ });
1223
+ this.socket.on("error", (err) => {
1224
+ console.error("Discovery socket error:", err);
1225
+ });
1226
+ await new Promise((resolve, reject) => {
1227
+ this.socket.bind(this.options.port, () => {
1228
+ try {
1229
+ this.socket.addMembership(MULTICAST_ADDR);
1230
+ this.socket.setBroadcast(true);
1231
+ this.socket.setMulticastTTL(1);
1232
+ resolve();
1233
+ } catch (err) {
1234
+ this.socket?.close();
1235
+ this.socket = null;
1236
+ reject(err);
1237
+ }
1238
+ });
1239
+ });
1240
+ this.running = true;
1241
+ await this.announce();
1242
+ this.announceInterval = setInterval(() => {
1243
+ void this.announce().catch((err) => {
1244
+ console.error("Discovery announce error:", err);
1245
+ });
1246
+ }, this.options.interval);
1247
+ }
1248
+ async stop() {
1249
+ if (!this.running) return;
1250
+ if (this.announceInterval) {
1251
+ clearInterval(this.announceInterval);
1252
+ this.announceInterval = null;
1253
+ }
1254
+ if (this.socket) {
1255
+ try {
1256
+ this.socket.dropMembership(MULTICAST_ADDR);
1257
+ } catch {
1258
+ }
1259
+ this.socket.close();
1260
+ this.socket = null;
1261
+ }
1262
+ this.running = false;
1263
+ }
1264
+ async announce() {
1265
+ if (!this.socket || !this.running) return;
1266
+ const localConfig = await getLocalHostConfig();
1267
+ const localAddress = getLocalIPAddress();
1268
+ const message = {
1269
+ type: "announce",
1270
+ hostId: localConfig.id,
1271
+ hostName: localConfig.name,
1272
+ address: localAddress,
1273
+ port: localConfig.port,
1274
+ tailscaleIP: localConfig.tailscaleIP,
1275
+ version: MESH_VERSION,
1276
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1277
+ };
1278
+ const buffer = Buffer.from(JSON.stringify(message));
1279
+ this.socket.send(buffer, 0, buffer.length, this.options.port, MULTICAST_ADDR);
1280
+ }
1281
+ async query() {
1282
+ if (!this.socket || !this.running) return;
1283
+ const localConfig = await getLocalHostConfig();
1284
+ const localAddress = getLocalIPAddress();
1285
+ const message = {
1286
+ type: "query",
1287
+ hostId: localConfig.id,
1288
+ hostName: localConfig.name,
1289
+ address: localAddress,
1290
+ port: localConfig.port,
1291
+ version: MESH_VERSION,
1292
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1293
+ };
1294
+ const buffer = Buffer.from(JSON.stringify(message));
1295
+ this.socket.send(buffer, 0, buffer.length, this.options.port, MULTICAST_ADDR);
1296
+ }
1297
+ getDiscoveredHosts() {
1298
+ return Array.from(this.discoveredHosts.values());
1299
+ }
1300
+ isRunning() {
1301
+ return this.running;
1302
+ }
1303
+ async handleMessage(msg, rinfo) {
1304
+ try {
1305
+ const message = JSON.parse(msg.toString());
1306
+ if (!message || message.type !== "announce" && message.type !== "query" && message.type !== "response") return;
1307
+ if (!message.hostId || !message.hostName) return;
1308
+ const port = Number(message.port);
1309
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return;
1310
+ const localConfig = await getLocalHostConfig();
1311
+ if (message.hostId === localConfig.id) return;
1312
+ const host = {
1313
+ id: message.hostId,
1314
+ name: message.hostName,
1315
+ address: rinfo.address,
1316
+ port,
1317
+ tailscaleIP: message.tailscaleIP,
1318
+ status: "online",
1319
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
1320
+ version: message.version
1321
+ };
1322
+ this.discoveredHosts.set(host.id, host);
1323
+ await addKnownHost(host);
1324
+ this.options.onDiscover(host);
1325
+ if (message.type === "query") {
1326
+ await this.announce();
1327
+ }
1328
+ } catch {
1329
+ }
1330
+ }
1331
+ };
1332
+ function getLocalIPAddress() {
1333
+ const interfaces = networkInterfaces();
1334
+ for (const name of Object.keys(interfaces)) {
1335
+ const iface = interfaces[name];
1336
+ if (!iface) continue;
1337
+ for (const addr of iface) {
1338
+ if (addr.family === "IPv4" && !addr.internal) {
1339
+ return addr.address;
1340
+ }
1341
+ }
1342
+ }
1343
+ return "127.0.0.1";
1344
+ }
1345
+ function getAllLocalIPAddresses() {
1346
+ const addresses = [];
1347
+ const interfaces = networkInterfaces();
1348
+ for (const name of Object.keys(interfaces)) {
1349
+ const iface = interfaces[name];
1350
+ if (!iface) continue;
1351
+ for (const addr of iface) {
1352
+ if (addr.family === "IPv4" && !addr.internal) {
1353
+ addresses.push(addr.address);
1354
+ }
1355
+ }
1356
+ }
1357
+ return addresses;
1358
+ }
1359
+ async function discoverOnce(timeout = 5e3) {
1360
+ const discovery = new LocalDiscovery();
1361
+ await discovery.start();
1362
+ await discovery.query();
1363
+ await new Promise((resolve) => setTimeout(resolve, timeout));
1364
+ const hosts = discovery.getDiscoveredHosts();
1365
+ await discovery.stop();
1366
+ return hosts;
1367
+ }
1368
+
1369
+ // src/discovery/secure-local.ts
1370
+ init_types();
1371
+ init_hosts_config();
1372
+ init_identity();
1373
+ import { createSocket as createSocket2 } from "dgram";
1374
+ import { networkInterfaces as networkInterfaces2 } from "os";
1375
+
1376
+ // src/security/config.ts
1377
+ var SECURITY_PRESETS = {
1378
+ development: {
1379
+ discovery: { mode: "open" },
1380
+ transport: { encryption: "none", tls: "none", requireAuth: false },
1381
+ trust: { autoTrustFirst: true, requireManualApproval: false }
1382
+ },
1383
+ signed: {
1384
+ discovery: { mode: "signed" },
1385
+ transport: { encryption: "optional", tls: "none", requireAuth: false },
1386
+ trust: { autoTrustFirst: true, requireManualApproval: false }
1387
+ },
1388
+ secure: {
1389
+ discovery: { mode: "signed" },
1390
+ transport: { encryption: "required", tls: "self-signed", requireAuth: true },
1391
+ trust: { autoTrustFirst: true, requireManualApproval: false }
1392
+ },
1393
+ strict: {
1394
+ discovery: { mode: "trusted-only" },
1395
+ transport: { encryption: "required", tls: "self-signed", requireAuth: true },
1396
+ trust: { autoTrustFirst: false, requireManualApproval: true }
1397
+ }
1398
+ };
1399
+ var DEFAULT_SECURITY_CONFIG = {
1400
+ ...SECURITY_PRESETS.secure
1401
+ };
1402
+ function getSecurityPreset(preset) {
1403
+ return { ...SECURITY_PRESETS[preset] };
1404
+ }
1405
+ function mergeSecurityConfig(base, overrides) {
1406
+ return {
1407
+ identityPath: overrides.identityPath ?? base.identityPath,
1408
+ discovery: {
1409
+ ...base.discovery,
1410
+ ...overrides.discovery
1411
+ },
1412
+ transport: {
1413
+ ...base.transport,
1414
+ ...overrides.transport
1415
+ },
1416
+ trust: {
1417
+ ...base.trust,
1418
+ ...overrides.trust
1419
+ }
1420
+ };
1421
+ }
1422
+ function validateSecurityConfig(config) {
1423
+ const errors = [];
1424
+ const validDiscoveryModes = ["open", "signed", "trusted-only"];
1425
+ if (!validDiscoveryModes.includes(config.discovery.mode)) {
1426
+ errors.push(`Invalid discovery mode: ${config.discovery.mode}`);
1427
+ }
1428
+ const validEncryption = ["none", "optional", "required"];
1429
+ if (!validEncryption.includes(config.transport.encryption)) {
1430
+ errors.push(`Invalid transport encryption: ${config.transport.encryption}`);
1431
+ }
1432
+ const validTLS = ["none", "self-signed", "ca-signed"];
1433
+ if (!validTLS.includes(config.transport.tls)) {
1434
+ errors.push(`Invalid TLS mode: ${config.transport.tls}`);
1435
+ }
1436
+ if (config.transport.encryption === "required" && config.transport.tls === "none") {
1437
+ errors.push("Required encryption needs TLS enabled");
1438
+ }
1439
+ if (config.discovery.mode === "trusted-only" && !config.trust.trustedFingerprints?.length) {
1440
+ if (!config.trust.autoTrustFirst) {
1441
+ errors.push("trusted-only discovery requires trustedFingerprints or autoTrustFirst");
1442
+ }
1443
+ }
1444
+ return errors;
1445
+ }
1446
+ function isSecurityEnabled(config) {
1447
+ return config.discovery.mode !== "open" || config.transport.encryption !== "none" || config.transport.requireAuth;
1448
+ }
1449
+ function describeSecurityLevel(config) {
1450
+ if (config.discovery.mode === "open" && config.transport.encryption === "none" && !config.transport.requireAuth) {
1451
+ return "development (no security)";
1452
+ }
1453
+ if (config.discovery.mode === "trusted-only" && config.transport.encryption === "required" && config.transport.requireAuth) {
1454
+ return "strict (maximum security)";
1455
+ }
1456
+ if (config.discovery.mode === "signed" && config.transport.encryption === "required" && config.transport.requireAuth) {
1457
+ return "secure (recommended)";
1458
+ }
1459
+ if (config.discovery.mode === "signed") {
1460
+ return "signed (partial security)";
1461
+ }
1462
+ return "custom";
1463
+ }
1464
+
1465
+ // src/discovery/secure-local.ts
1466
+ import { hexToBytes as hexToBytes2 } from "@noble/hashes/utils";
1467
+ var MULTICAST_ADDR2 = "239.255.255.250";
1468
+ var DISCOVERY_INTERVAL_MS2 = 3e4;
1469
+ var SecureLocalDiscovery = class {
1470
+ socket = null;
1471
+ announceInterval = null;
1472
+ running = false;
1473
+ options;
1474
+ discoveredHosts = /* @__PURE__ */ new Map();
1475
+ identity = null;
1476
+ keystore = null;
1477
+ securityMode;
1478
+ constructor(options = {}) {
1479
+ this.options = {
1480
+ port: options.port ?? DEFAULT_DISCOVERY_PORT,
1481
+ interval: options.interval ?? DISCOVERY_INTERVAL_MS2,
1482
+ onDiscover: options.onDiscover ?? (() => {
1483
+ }),
1484
+ security: options.security ?? DEFAULT_SECURITY_CONFIG
1485
+ };
1486
+ this.identity = options.identity ?? null;
1487
+ this.keystore = options.keystore ?? null;
1488
+ this.securityMode = this.options.security.discovery.mode;
1489
+ }
1490
+ async initialize() {
1491
+ if (!this.identity && this.keystore) {
1492
+ this.identity = await this.keystore.loadOrCreateIdentity();
1493
+ }
1494
+ if (!this.identity && this.securityMode !== "open") {
1495
+ this.identity = await PeerIdentity.generate();
1496
+ }
1497
+ }
1498
+ async start() {
1499
+ if (this.running) return;
1500
+ await this.initialize();
1501
+ this.socket = createSocket2({ type: "udp4", reuseAddr: true });
1502
+ this.socket.on("message", (msg, rinfo) => {
1503
+ this.handleMessage(msg, rinfo);
1504
+ });
1505
+ this.socket.on("error", (err) => {
1506
+ console.error("Discovery socket error:", err);
1507
+ });
1508
+ await new Promise((resolve, reject) => {
1509
+ this.socket.bind(this.options.port, () => {
1510
+ try {
1511
+ this.socket.addMembership(MULTICAST_ADDR2);
1512
+ this.socket.setBroadcast(true);
1513
+ this.socket.setMulticastTTL(128);
1514
+ resolve();
1515
+ } catch (err) {
1516
+ reject(err);
1517
+ }
1518
+ });
1519
+ });
1520
+ this.running = true;
1521
+ await this.announce();
1522
+ this.announceInterval = setInterval(() => {
1523
+ this.announce();
1524
+ }, this.options.interval);
1525
+ }
1526
+ async stop() {
1527
+ if (!this.running) return;
1528
+ if (this.announceInterval) {
1529
+ clearInterval(this.announceInterval);
1530
+ this.announceInterval = null;
1531
+ }
1532
+ if (this.socket) {
1533
+ try {
1534
+ this.socket.dropMembership(MULTICAST_ADDR2);
1535
+ } catch {
1536
+ }
1537
+ this.socket.close();
1538
+ this.socket = null;
1539
+ }
1540
+ this.running = false;
1541
+ }
1542
+ async announce() {
1543
+ if (!this.socket || !this.running) return;
1544
+ const localConfig = await getLocalHostConfig();
1545
+ const localAddress = getLocalIPAddress2();
1546
+ const baseMessage = {
1547
+ type: "announce",
1548
+ hostId: localConfig.id,
1549
+ hostName: localConfig.name,
1550
+ address: localAddress,
1551
+ port: localConfig.port,
1552
+ tailscaleIP: localConfig.tailscaleIP,
1553
+ version: MESH_VERSION,
1554
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1555
+ };
1556
+ let message = baseMessage;
1557
+ if (this.identity && this.securityMode !== "open") {
1558
+ const signature = await this.identity.signObject(baseMessage);
1559
+ message = {
1560
+ ...baseMessage,
1561
+ signature,
1562
+ publicKey: this.identity.publicKeyHex,
1563
+ fingerprint: this.identity.fingerprint
1564
+ };
1565
+ }
1566
+ const buffer = Buffer.from(JSON.stringify(message));
1567
+ this.socket.send(buffer, 0, buffer.length, this.options.port, MULTICAST_ADDR2);
1568
+ }
1569
+ async query() {
1570
+ if (!this.socket || !this.running) return;
1571
+ const localConfig = await getLocalHostConfig();
1572
+ const localAddress = getLocalIPAddress2();
1573
+ const baseMessage = {
1574
+ type: "query",
1575
+ hostId: localConfig.id,
1576
+ hostName: localConfig.name,
1577
+ address: localAddress,
1578
+ port: localConfig.port,
1579
+ version: MESH_VERSION,
1580
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1581
+ };
1582
+ let message = baseMessage;
1583
+ if (this.identity && this.securityMode !== "open") {
1584
+ const signature = await this.identity.signObject(baseMessage);
1585
+ message = {
1586
+ ...baseMessage,
1587
+ signature,
1588
+ publicKey: this.identity.publicKeyHex,
1589
+ fingerprint: this.identity.fingerprint
1590
+ };
1591
+ }
1592
+ const buffer = Buffer.from(JSON.stringify(message));
1593
+ this.socket.send(buffer, 0, buffer.length, this.options.port, MULTICAST_ADDR2);
1594
+ }
1595
+ getDiscoveredHosts() {
1596
+ return Array.from(this.discoveredHosts.values());
1597
+ }
1598
+ isRunning() {
1599
+ return this.running;
1600
+ }
1601
+ getFingerprint() {
1602
+ return this.identity?.fingerprint ?? null;
1603
+ }
1604
+ async handleMessage(msg, rinfo) {
1605
+ try {
1606
+ const raw = JSON.parse(msg.toString());
1607
+ const localConfig = await getLocalHostConfig();
1608
+ if (raw.hostId === localConfig.id) return;
1609
+ let message;
1610
+ let fingerprint;
1611
+ if (this.isSignedMessage(raw)) {
1612
+ const signedMsg = raw;
1613
+ fingerprint = signedMsg.fingerprint;
1614
+ if (this.securityMode === "signed" || this.securityMode === "trusted-only") {
1615
+ const isValid = await this.verifySignedMessage(signedMsg);
1616
+ if (!isValid) {
1617
+ return;
1618
+ }
1619
+ }
1620
+ if (this.securityMode === "trusted-only" && this.keystore) {
1621
+ const isTrusted = await this.keystore.isTrusted(fingerprint);
1622
+ const isRevoked = await this.keystore.isRevoked(fingerprint);
1623
+ if (isRevoked) {
1624
+ return;
1625
+ }
1626
+ if (!isTrusted) {
1627
+ if (this.options.security.trust.autoTrustFirst) {
1628
+ await this.keystore.addTrustedPeer(
1629
+ fingerprint,
1630
+ signedMsg.publicKey,
1631
+ signedMsg.hostName
1632
+ );
1633
+ } else {
1634
+ return;
1635
+ }
1636
+ }
1637
+ }
1638
+ message = {
1639
+ type: signedMsg.type,
1640
+ hostId: signedMsg.hostId,
1641
+ hostName: signedMsg.hostName,
1642
+ address: signedMsg.address,
1643
+ port: signedMsg.port,
1644
+ tailscaleIP: signedMsg.tailscaleIP,
1645
+ version: signedMsg.version,
1646
+ timestamp: signedMsg.timestamp
1647
+ };
1648
+ } else {
1649
+ if (this.securityMode !== "open") {
1650
+ return;
1651
+ }
1652
+ message = raw;
1653
+ }
1654
+ const host = {
1655
+ id: message.hostId,
1656
+ name: message.hostName,
1657
+ address: message.address || rinfo.address,
1658
+ port: message.port,
1659
+ tailscaleIP: message.tailscaleIP,
1660
+ status: "online",
1661
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
1662
+ version: message.version,
1663
+ metadata: fingerprint ? { fingerprint } : void 0
1664
+ };
1665
+ this.discoveredHosts.set(host.id, host);
1666
+ await addKnownHost(host);
1667
+ this.options.onDiscover(host, fingerprint);
1668
+ if (message.type === "query") {
1669
+ await this.announce();
1670
+ }
1671
+ } catch {
1672
+ }
1673
+ }
1674
+ isSignedMessage(msg) {
1675
+ if (typeof msg !== "object" || msg === null) return false;
1676
+ const obj = msg;
1677
+ return typeof obj.signature === "string" && typeof obj.publicKey === "string" && typeof obj.fingerprint === "string";
1678
+ }
1679
+ async verifySignedMessage(msg) {
1680
+ try {
1681
+ const publicKey = hexToBytes2(msg.publicKey);
1682
+ const computedFingerprint = PeerIdentity.computeFingerprint(publicKey);
1683
+ if (computedFingerprint !== msg.fingerprint) {
1684
+ return false;
1685
+ }
1686
+ const baseMessage = {
1687
+ type: msg.type,
1688
+ hostId: msg.hostId,
1689
+ hostName: msg.hostName,
1690
+ address: msg.address,
1691
+ port: msg.port,
1692
+ tailscaleIP: msg.tailscaleIP,
1693
+ version: msg.version,
1694
+ timestamp: msg.timestamp
1695
+ };
1696
+ const messageBytes = new TextEncoder().encode(JSON.stringify(baseMessage));
1697
+ const signature = hexToBytes2(msg.signature);
1698
+ return await PeerIdentity.verify(signature, messageBytes, publicKey);
1699
+ } catch {
1700
+ return false;
1701
+ }
1702
+ }
1703
+ };
1704
+ function getLocalIPAddress2() {
1705
+ const interfaces = networkInterfaces2();
1706
+ for (const name of Object.keys(interfaces)) {
1707
+ const iface = interfaces[name];
1708
+ if (!iface) continue;
1709
+ for (const addr of iface) {
1710
+ if (addr.family === "IPv4" && !addr.internal) {
1711
+ return addr.address;
1712
+ }
1713
+ }
1714
+ }
1715
+ return "127.0.0.1";
1716
+ }
1717
+ async function discoverOnceSecure(timeout = 5e3, options = {}) {
1718
+ const discovery = new SecureLocalDiscovery(options);
1719
+ await discovery.start();
1720
+ await discovery.query();
1721
+ await new Promise((resolve) => setTimeout(resolve, timeout));
1722
+ const hosts = discovery.getDiscoveredHosts();
1723
+ await discovery.stop();
1724
+ return hosts;
1725
+ }
1726
+
1727
+ // src/discovery/tailscale.ts
1728
+ init_types();
1729
+ import { exec } from "child_process";
1730
+ import { promisify } from "util";
1731
+ var execAsync = promisify(exec);
1732
+ async function getTailscaleStatus() {
1733
+ try {
1734
+ const { stdout } = await execAsync("tailscale status --json");
1735
+ const status = JSON.parse(stdout);
1736
+ const self = status.Self ? {
1737
+ id: status.Self.ID,
1738
+ name: status.Self.HostName,
1739
+ tailscaleIP: status.Self.TailscaleIPs?.[0] || "",
1740
+ hostname: status.Self.HostName,
1741
+ online: status.Self.Online ?? true,
1742
+ os: status.Self.OS
1743
+ } : void 0;
1744
+ const peers = [];
1745
+ if (status.Peer) {
1746
+ for (const [id, peer] of Object.entries(status.Peer)) {
1747
+ peers.push({
1748
+ id,
1749
+ name: peer.HostName,
1750
+ tailscaleIP: peer.TailscaleIPs?.[0] || "",
1751
+ hostname: peer.HostName,
1752
+ online: peer.Online ?? false,
1753
+ os: peer.OS,
1754
+ lastSeen: peer.LastSeen
1755
+ });
1756
+ }
1757
+ }
1758
+ return {
1759
+ available: true,
1760
+ self,
1761
+ peers,
1762
+ magicDNSSuffix: status.MagicDNSSuffix
1763
+ };
1764
+ } catch {
1765
+ return {
1766
+ available: false,
1767
+ peers: []
1768
+ };
1769
+ }
1770
+ }
1771
+ async function isTailscaleAvailable() {
1772
+ try {
1773
+ await execAsync("tailscale version");
1774
+ return true;
1775
+ } catch {
1776
+ return false;
1777
+ }
1778
+ }
1779
+ async function getTailscaleIP() {
1780
+ const status = await getTailscaleStatus();
1781
+ return status.self?.tailscaleIP ?? null;
1782
+ }
1783
+ async function discoverTailscaleHosts(skillkitPort) {
1784
+ const status = await getTailscaleStatus();
1785
+ if (!status.available) return [];
1786
+ const hosts = [];
1787
+ for (const peer of status.peers) {
1788
+ if (!peer.online) continue;
1789
+ hosts.push({
1790
+ id: `tailscale-${peer.id}`,
1791
+ name: peer.name,
1792
+ address: peer.tailscaleIP,
1793
+ port: skillkitPort,
1794
+ tailscaleIP: peer.tailscaleIP,
1795
+ status: "unknown",
1796
+ lastSeen: peer.lastSeen ?? (/* @__PURE__ */ new Date()).toISOString(),
1797
+ version: MESH_VERSION,
1798
+ metadata: {
1799
+ discoveredVia: "tailscale",
1800
+ os: peer.os
1801
+ }
1802
+ });
1803
+ }
1804
+ return hosts;
1805
+ }
1806
+ async function resolveTailscaleName(hostname) {
1807
+ const status = await getTailscaleStatus();
1808
+ if (!status.available) return null;
1809
+ const normalizedName = hostname.toLowerCase();
1810
+ for (const peer of status.peers) {
1811
+ if (peer.hostname.toLowerCase() === normalizedName || peer.name.toLowerCase() === normalizedName) {
1812
+ return peer.tailscaleIP;
1813
+ }
1814
+ }
1815
+ if (status.magicDNSSuffix && hostname.endsWith(status.magicDNSSuffix)) {
1816
+ const shortName = hostname.slice(0, -status.magicDNSSuffix.length - 1);
1817
+ for (const peer of status.peers) {
1818
+ if (peer.hostname.toLowerCase() === shortName.toLowerCase()) {
1819
+ return peer.tailscaleIP;
1820
+ }
1821
+ }
1822
+ }
1823
+ return null;
1824
+ }
1825
+
1826
+ // src/index.ts
1827
+ init_peer();
1828
+
1829
+ // src/transport/http.ts
1830
+ init_types();
1831
+ import got2 from "got";
1832
+ import { randomUUID as randomUUID2 } from "crypto";
1833
+ var HttpTransport = class {
1834
+ client;
1835
+ options;
1836
+ constructor(host, options = {}) {
1837
+ this.options = {
1838
+ timeout: options.timeout ?? HEALTH_CHECK_TIMEOUT,
1839
+ retries: options.retries ?? 2,
1840
+ retryDelay: options.retryDelay ?? 1e3,
1841
+ baseUrl: options.baseUrl ?? `http://${host.address}:${host.port}`,
1842
+ headers: options.headers ?? {}
1843
+ };
1844
+ this.client = got2.extend({
1845
+ prefixUrl: this.options.baseUrl,
1846
+ timeout: { request: this.options.timeout },
1847
+ retry: {
1848
+ limit: this.options.retries,
1849
+ calculateDelay: () => this.options.retryDelay
1850
+ },
1851
+ headers: {
1852
+ "Content-Type": "application/json",
1853
+ "X-SkillKit-Transport": "http",
1854
+ ...this.options.headers
1855
+ }
1856
+ });
1857
+ }
1858
+ async send(path, payload) {
1859
+ const message = {
1860
+ id: randomUUID2(),
1861
+ type: "request",
1862
+ from: "local",
1863
+ to: path,
1864
+ payload,
1865
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1866
+ };
1867
+ const response = await this.client.post(path, {
1868
+ json: message
1869
+ });
1870
+ return JSON.parse(response.body);
1871
+ }
1872
+ async sendMessage(to, type, payload) {
1873
+ return this.send("message", {
1874
+ to,
1875
+ type,
1876
+ payload
1877
+ });
1878
+ }
1879
+ async registerPeer(registration) {
1880
+ return this.send("peer/register", registration);
1881
+ }
1882
+ async getPeers() {
1883
+ const response = await this.client.get("peers");
1884
+ return JSON.parse(response.body);
1885
+ }
1886
+ async healthCheck() {
1887
+ try {
1888
+ const response = await this.client.get("health");
1889
+ return response.statusCode === 200;
1890
+ } catch {
1891
+ return false;
1892
+ }
1893
+ }
1894
+ };
1895
+ async function sendToHost(host, path, payload, options = {}) {
1896
+ const transport = new HttpTransport(host, options);
1897
+ return transport.send(path, payload);
1898
+ }
1899
+ async function broadcastToHosts(hosts, path, payload, options = {}) {
1900
+ const results = /* @__PURE__ */ new Map();
1901
+ await Promise.all(
1902
+ hosts.map(async (host) => {
1903
+ try {
1904
+ const response = await sendToHost(host, path, payload, options);
1905
+ results.set(host.id, response);
1906
+ } catch (err) {
1907
+ results.set(host.id, err);
1908
+ }
1909
+ })
1910
+ );
1911
+ return results;
1912
+ }
1913
+
1914
+ // src/transport/websocket.ts
1915
+ init_types();
1916
+ import WebSocket, { WebSocketServer } from "ws";
1917
+ import { randomUUID as randomUUID3 } from "crypto";
1918
+ var WebSocketTransport = class {
1919
+ socket = null;
1920
+ options;
1921
+ url;
1922
+ reconnectAttempts = 0;
1923
+ messageHandlers = /* @__PURE__ */ new Set();
1924
+ connected = false;
1925
+ reconnectTimer = null;
1926
+ manualClose = false;
1927
+ constructor(host, options = {}) {
1928
+ this.url = `ws://${host.address}:${host.port}/ws`;
1929
+ this.options = {
1930
+ timeout: options.timeout ?? 5e3,
1931
+ retries: options.retries ?? 3,
1932
+ retryDelay: options.retryDelay ?? 1e3,
1933
+ reconnect: options.reconnect ?? true,
1934
+ reconnectInterval: options.reconnectInterval ?? 5e3,
1935
+ maxReconnectAttempts: options.maxReconnectAttempts ?? 10
1936
+ };
1937
+ }
1938
+ async connect() {
1939
+ return new Promise((resolve, reject) => {
1940
+ this.socket = new WebSocket(this.url, {
1941
+ handshakeTimeout: this.options.timeout
1942
+ });
1943
+ const timeout = setTimeout(() => {
1944
+ this.socket?.close();
1945
+ reject(new Error("Connection timeout"));
1946
+ }, this.options.timeout);
1947
+ this.socket.on("open", () => {
1948
+ clearTimeout(timeout);
1949
+ this.connected = true;
1950
+ this.reconnectAttempts = 0;
1951
+ resolve();
1952
+ });
1953
+ this.socket.on("message", (data) => {
1954
+ try {
1955
+ const message = JSON.parse(data.toString());
1956
+ this.messageHandlers.forEach((handler) => handler(message, this.socket));
1957
+ } catch {
1958
+ }
1959
+ });
1960
+ this.socket.on("close", () => {
1961
+ this.connected = false;
1962
+ if (this.options.reconnect && !this.manualClose) {
1963
+ this.scheduleReconnect();
1964
+ }
1965
+ });
1966
+ this.socket.on("error", (err) => {
1967
+ clearTimeout(timeout);
1968
+ if (!this.connected) {
1969
+ reject(err);
1970
+ }
1971
+ });
1972
+ });
1973
+ }
1974
+ disconnect() {
1975
+ this.manualClose = true;
1976
+ if (this.reconnectTimer) {
1977
+ clearTimeout(this.reconnectTimer);
1978
+ this.reconnectTimer = null;
1979
+ }
1980
+ if (this.socket) {
1981
+ this.socket.close();
1982
+ this.socket = null;
1983
+ }
1984
+ this.connected = false;
1985
+ }
1986
+ async send(message) {
1987
+ if (!this.socket || !this.connected) {
1988
+ throw new Error("Not connected");
1989
+ }
1990
+ const fullMessage = {
1991
+ ...message,
1992
+ id: randomUUID3(),
1993
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1994
+ };
1995
+ return new Promise((resolve, reject) => {
1996
+ this.socket.send(JSON.stringify(fullMessage), (err) => {
1997
+ if (err) reject(err);
1998
+ else resolve();
1999
+ });
2000
+ });
2001
+ }
2002
+ onMessage(handler) {
2003
+ this.messageHandlers.add(handler);
2004
+ return () => this.messageHandlers.delete(handler);
2005
+ }
2006
+ isConnected() {
2007
+ return this.connected;
2008
+ }
2009
+ scheduleReconnect() {
2010
+ if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
2011
+ return;
2012
+ }
2013
+ this.reconnectTimer = setTimeout(async () => {
2014
+ this.reconnectAttempts++;
2015
+ try {
2016
+ await this.connect();
2017
+ } catch {
2018
+ this.scheduleReconnect();
2019
+ }
2020
+ }, this.options.reconnectInterval);
2021
+ }
2022
+ };
2023
+ var WebSocketServer2 = class {
2024
+ server = null;
2025
+ clients = /* @__PURE__ */ new Set();
2026
+ messageHandlers = /* @__PURE__ */ new Set();
2027
+ port;
2028
+ running = false;
2029
+ constructor(port = DEFAULT_PORT) {
2030
+ this.port = port;
2031
+ }
2032
+ async start() {
2033
+ if (this.running) return;
2034
+ return new Promise((resolve, reject) => {
2035
+ this.server = new WebSocketServer({ port: this.port, path: "/ws" });
2036
+ this.server.on("listening", () => {
2037
+ this.running = true;
2038
+ resolve();
2039
+ });
2040
+ this.server.on("error", (err) => {
2041
+ reject(err);
2042
+ });
2043
+ this.server.on("connection", (socket) => {
2044
+ this.clients.add(socket);
2045
+ socket.on("message", (data) => {
2046
+ try {
2047
+ const message = JSON.parse(data.toString());
2048
+ this.messageHandlers.forEach((handler) => handler(message, socket));
2049
+ } catch {
2050
+ }
2051
+ });
2052
+ socket.on("close", () => {
2053
+ this.clients.delete(socket);
2054
+ });
2055
+ });
2056
+ });
2057
+ }
2058
+ stop() {
2059
+ if (!this.running) return;
2060
+ for (const client of this.clients) {
2061
+ client.close();
2062
+ }
2063
+ this.clients.clear();
2064
+ this.server?.close();
2065
+ this.server = null;
2066
+ this.running = false;
2067
+ }
2068
+ broadcast(message) {
2069
+ const fullMessage = {
2070
+ ...message,
2071
+ id: randomUUID3(),
2072
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2073
+ };
2074
+ const data = JSON.stringify(fullMessage);
2075
+ for (const client of this.clients) {
2076
+ if (client.readyState === WebSocket.OPEN) {
2077
+ client.send(data);
2078
+ }
2079
+ }
2080
+ }
2081
+ sendTo(socket, message) {
2082
+ if (socket.readyState !== WebSocket.OPEN) return;
2083
+ const fullMessage = {
2084
+ ...message,
2085
+ id: randomUUID3(),
2086
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2087
+ };
2088
+ socket.send(JSON.stringify(fullMessage));
2089
+ }
2090
+ onMessage(handler) {
2091
+ this.messageHandlers.add(handler);
2092
+ return () => this.messageHandlers.delete(handler);
2093
+ }
2094
+ getClientCount() {
2095
+ return this.clients.size;
2096
+ }
2097
+ isRunning() {
2098
+ return this.running;
2099
+ }
2100
+ };
2101
+
2102
+ // src/transport/secure-websocket.ts
2103
+ init_types();
2104
+ init_identity();
2105
+ init_encryption();
2106
+ import WebSocket2, { WebSocketServer as WebSocketServer3 } from "ws";
2107
+ import { createServer as createHttpsServer } from "https";
2108
+ import { randomUUID as randomUUID4 } from "crypto";
2109
+
2110
+ // src/security/auth.ts
2111
+ init_identity();
2112
+ import { SignJWT, jwtVerify, importPKCS8, importSPKI } from "jose";
2113
+ import { randomBytes as randomBytes3, createPrivateKey, createPublicKey } from "crypto";
2114
+ import { bytesToHex as bytesToHex3, hexToBytes as hexToBytes4 } from "@noble/hashes/utils";
2115
+ var DEFAULT_TOKEN_TTL = 24 * 60 * 60;
2116
+ var CHALLENGE_SIZE = 32;
2117
+ var CHALLENGE_EXPIRY_MS = 30 * 1e3;
2118
+ var AuthManager = class {
2119
+ identity;
2120
+ pendingChallenges = /* @__PURE__ */ new Map();
2121
+ constructor(identity) {
2122
+ this.identity = identity;
2123
+ }
2124
+ async getSigningKey() {
2125
+ const privateKeyObj = createPrivateKey({
2126
+ key: Buffer.concat([
2127
+ Buffer.from("302e020100300506032b657004220420", "hex"),
2128
+ Buffer.from(this.identity.privateKey)
2129
+ ]),
2130
+ format: "der",
2131
+ type: "pkcs8"
2132
+ });
2133
+ const pem = privateKeyObj.export({ type: "pkcs8", format: "pem" });
2134
+ return importPKCS8(pem, "EdDSA");
2135
+ }
2136
+ async getVerifyingKey(publicKeyHex) {
2137
+ const publicKeyBytes = hexToBytes4(publicKeyHex);
2138
+ const publicKeyObj = createPublicKey({
2139
+ key: Buffer.concat([
2140
+ Buffer.from("302a300506032b6570032100", "hex"),
2141
+ Buffer.from(publicKeyBytes)
2142
+ ]),
2143
+ format: "der",
2144
+ type: "spki"
2145
+ });
2146
+ const pem = publicKeyObj.export({ type: "spki", format: "pem" });
2147
+ return importSPKI(pem, "EdDSA");
2148
+ }
2149
+ async createToken(hostId, capabilities = [], ttlSeconds = DEFAULT_TOKEN_TTL) {
2150
+ const now = Math.floor(Date.now() / 1e3);
2151
+ const signingKey = await this.getSigningKey();
2152
+ const token = await new SignJWT({
2153
+ hostId,
2154
+ fingerprint: this.identity.fingerprint,
2155
+ publicKey: this.identity.publicKeyHex,
2156
+ capabilities
2157
+ }).setProtectedHeader({ alg: "EdDSA" }).setIssuedAt(now).setExpirationTime(now + ttlSeconds).setIssuer("skillkit-mesh").sign(signingKey);
2158
+ return token;
2159
+ }
2160
+ async verifyToken(token) {
2161
+ try {
2162
+ const parts = token.split(".");
2163
+ if (parts.length !== 3) {
2164
+ return null;
2165
+ }
2166
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
2167
+ if (!payload.publicKey) {
2168
+ return null;
2169
+ }
2170
+ const computedFingerprint = PeerIdentity.computeFingerprint(hexToBytes4(payload.publicKey));
2171
+ if (computedFingerprint !== payload.fingerprint) {
2172
+ return null;
2173
+ }
2174
+ const verifyingKey = await this.getVerifyingKey(payload.publicKey);
2175
+ const { payload: verified } = await jwtVerify(token, verifyingKey, {
2176
+ issuer: "skillkit-mesh"
2177
+ });
2178
+ return {
2179
+ hostId: verified.hostId,
2180
+ fingerprint: verified.fingerprint,
2181
+ publicKey: verified.publicKey,
2182
+ capabilities: verified.capabilities || [],
2183
+ iat: verified.iat,
2184
+ exp: verified.exp
2185
+ };
2186
+ } catch {
2187
+ return null;
2188
+ }
2189
+ }
2190
+ createChallenge() {
2191
+ const challengeBytes = randomBytes3(CHALLENGE_SIZE);
2192
+ const challenge = bytesToHex3(challengeBytes);
2193
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2194
+ this.pendingChallenges.set(challenge, {
2195
+ expected: challenge,
2196
+ expires: Date.now() + CHALLENGE_EXPIRY_MS
2197
+ });
2198
+ this.cleanupExpiredChallenges();
2199
+ return {
2200
+ challenge,
2201
+ timestamp
2202
+ };
2203
+ }
2204
+ async respondToChallenge(challengeRequest) {
2205
+ const message = `${challengeRequest.challenge}:${challengeRequest.timestamp}`;
2206
+ const signature = await this.identity.signString(message);
2207
+ return {
2208
+ challenge: challengeRequest.challenge,
2209
+ signature,
2210
+ publicKey: this.identity.publicKeyHex,
2211
+ fingerprint: this.identity.fingerprint,
2212
+ timestamp: challengeRequest.timestamp
2213
+ };
2214
+ }
2215
+ async verifyChallengeResponse(response) {
2216
+ const pending = this.pendingChallenges.get(response.challenge);
2217
+ if (!pending) {
2218
+ return {
2219
+ authenticated: false,
2220
+ error: "Unknown or expired challenge"
2221
+ };
2222
+ }
2223
+ if (Date.now() > pending.expires) {
2224
+ this.pendingChallenges.delete(response.challenge);
2225
+ return {
2226
+ authenticated: false,
2227
+ error: "Challenge expired"
2228
+ };
2229
+ }
2230
+ try {
2231
+ const publicKey = hexToBytes4(response.publicKey);
2232
+ const computedFingerprint = PeerIdentity.computeFingerprint(publicKey);
2233
+ if (computedFingerprint !== response.fingerprint) {
2234
+ return {
2235
+ authenticated: false,
2236
+ error: "Fingerprint mismatch"
2237
+ };
2238
+ }
2239
+ const message = `${response.challenge}:${response.timestamp}`;
2240
+ const messageBytes = new TextEncoder().encode(message);
2241
+ const signature = hexToBytes4(response.signature);
2242
+ const valid = await PeerIdentity.verify(signature, messageBytes, publicKey);
2243
+ if (!valid) {
2244
+ return {
2245
+ authenticated: false,
2246
+ error: "Invalid signature"
2247
+ };
2248
+ }
2249
+ this.pendingChallenges.delete(response.challenge);
2250
+ return {
2251
+ authenticated: true,
2252
+ fingerprint: response.fingerprint,
2253
+ publicKey: response.publicKey
2254
+ };
2255
+ } catch (error) {
2256
+ return {
2257
+ authenticated: false,
2258
+ error: error instanceof Error ? error.message : "Verification failed"
2259
+ };
2260
+ }
2261
+ }
2262
+ cleanupExpiredChallenges() {
2263
+ const now = Date.now();
2264
+ for (const [challenge, data] of this.pendingChallenges) {
2265
+ if (now > data.expires) {
2266
+ this.pendingChallenges.delete(challenge);
2267
+ }
2268
+ }
2269
+ }
2270
+ async createSignedMessage(payload) {
2271
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2272
+ const toSign = JSON.stringify({ payload, timestamp });
2273
+ const signature = await this.identity.signString(toSign);
2274
+ return {
2275
+ payload,
2276
+ signature,
2277
+ fingerprint: this.identity.fingerprint,
2278
+ timestamp
2279
+ };
2280
+ }
2281
+ async verifySignedMessage(message) {
2282
+ try {
2283
+ if (!message.publicKey) {
2284
+ return { valid: false, error: "Missing public key" };
2285
+ }
2286
+ const publicKey = hexToBytes4(message.publicKey);
2287
+ const computedFingerprint = PeerIdentity.computeFingerprint(publicKey);
2288
+ if (computedFingerprint !== message.fingerprint) {
2289
+ return { valid: false, error: "Fingerprint mismatch" };
2290
+ }
2291
+ const toSign = JSON.stringify({
2292
+ payload: message.payload,
2293
+ timestamp: message.timestamp
2294
+ });
2295
+ const messageBytes = new TextEncoder().encode(toSign);
2296
+ const signature = hexToBytes4(message.signature);
2297
+ const valid = await PeerIdentity.verify(signature, messageBytes, publicKey);
2298
+ if (!valid) {
2299
+ return { valid: false, error: "Invalid signature" };
2300
+ }
2301
+ return { valid: true, fingerprint: message.fingerprint };
2302
+ } catch (error) {
2303
+ return {
2304
+ valid: false,
2305
+ error: error instanceof Error ? error.message : "Verification failed"
2306
+ };
2307
+ }
2308
+ }
2309
+ get fingerprint() {
2310
+ return this.identity.fingerprint;
2311
+ }
2312
+ get publicKey() {
2313
+ return this.identity.publicKeyHex;
2314
+ }
2315
+ };
2316
+ function extractBearerToken(authHeader) {
2317
+ if (!authHeader) return null;
2318
+ const match = authHeader.match(/^Bearer\s+(.+)$/i);
2319
+ return match ? match[1] : null;
2320
+ }
2321
+ function createBearerHeader(token) {
2322
+ return `Bearer ${token}`;
2323
+ }
2324
+
2325
+ // src/security/tls.ts
2326
+ import {
2327
+ generateKeyPairSync,
2328
+ createHash
2329
+ } from "crypto";
2330
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2331
+ import { existsSync as existsSync3 } from "fs";
2332
+ import { join as join3 } from "path";
2333
+ import { homedir as homedir3 } from "os";
2334
+ var DEFAULT_CERT_PATH = join3(homedir3(), ".skillkit", "mesh", "certs");
2335
+ function generateSelfSignedCertificate(hostId, hostName, validDays = 365) {
2336
+ const { publicKey, privateKey } = generateKeyPairSync("rsa", {
2337
+ modulusLength: 2048,
2338
+ publicKeyEncoding: { type: "spki", format: "pem" },
2339
+ privateKeyEncoding: { type: "pkcs8", format: "pem" }
2340
+ });
2341
+ const notBefore = /* @__PURE__ */ new Date();
2342
+ const notAfter = /* @__PURE__ */ new Date();
2343
+ notAfter.setDate(notAfter.getDate() + validDays);
2344
+ const serialNumber = createHash("sha256").update(hostId + Date.now().toString()).digest("hex").slice(0, 16);
2345
+ const certPem = createSimpleCert({
2346
+ publicKey,
2347
+ privateKey,
2348
+ subject: `CN=${hostName},O=SkillKit Mesh,OU=${hostId}`,
2349
+ issuer: `CN=${hostName},O=SkillKit Mesh,OU=${hostId}`,
2350
+ serialNumber,
2351
+ notBefore,
2352
+ notAfter,
2353
+ altNames: ["localhost", "127.0.0.1", hostName]
2354
+ });
2355
+ return {
2356
+ cert: certPem,
2357
+ key: privateKey
2358
+ };
2359
+ }
2360
+ function createSimpleCert(params) {
2361
+ const base64Encode = (str) => Buffer.from(str).toString("base64");
2362
+ const formatDate = (date) => {
2363
+ const y = date.getUTCFullYear().toString().slice(-2);
2364
+ const m = (date.getUTCMonth() + 1).toString().padStart(2, "0");
2365
+ const d = date.getUTCDate().toString().padStart(2, "0");
2366
+ const h = date.getUTCHours().toString().padStart(2, "0");
2367
+ const min = date.getUTCMinutes().toString().padStart(2, "0");
2368
+ const s = date.getUTCSeconds().toString().padStart(2, "0");
2369
+ return `${y}${m}${d}${h}${min}${s}Z`;
2370
+ };
2371
+ const certInfo = {
2372
+ version: 3,
2373
+ serialNumber: params.serialNumber,
2374
+ subject: params.subject,
2375
+ issuer: params.issuer,
2376
+ notBefore: formatDate(params.notBefore),
2377
+ notAfter: formatDate(params.notAfter),
2378
+ publicKey: params.publicKey,
2379
+ altNames: params.altNames
2380
+ };
2381
+ const certData = JSON.stringify(certInfo);
2382
+ const certBase64 = base64Encode(certData);
2383
+ const lines = ["-----BEGIN CERTIFICATE-----"];
2384
+ for (let i = 0; i < certBase64.length; i += 64) {
2385
+ lines.push(certBase64.slice(i, i + 64));
2386
+ }
2387
+ lines.push("-----END CERTIFICATE-----");
2388
+ return lines.join("\n");
2389
+ }
2390
+ var TLSManager = class _TLSManager {
2391
+ certPath;
2392
+ constructor(certPath) {
2393
+ this.certPath = certPath || DEFAULT_CERT_PATH;
2394
+ }
2395
+ async ensureDirectory() {
2396
+ if (!existsSync3(this.certPath)) {
2397
+ await mkdir3(this.certPath, { recursive: true, mode: 448 });
2398
+ }
2399
+ }
2400
+ async generateCertificate(hostId, hostName = "localhost", validDays = 365) {
2401
+ await this.ensureDirectory();
2402
+ const { cert, key } = generateSelfSignedCertificate(
2403
+ hostId,
2404
+ hostName,
2405
+ validDays
2406
+ );
2407
+ const certFile = join3(this.certPath, `${hostId}.crt`);
2408
+ const keyFile = join3(this.certPath, `${hostId}.key`);
2409
+ await writeFile3(certFile, cert, { mode: 420 });
2410
+ await writeFile3(keyFile, key, { mode: 384 });
2411
+ const fingerprint = createHash("sha256").update(cert).digest("hex");
2412
+ const notBefore = /* @__PURE__ */ new Date();
2413
+ const notAfter = /* @__PURE__ */ new Date();
2414
+ notAfter.setDate(notAfter.getDate() + validDays);
2415
+ return {
2416
+ cert,
2417
+ key,
2418
+ fingerprint,
2419
+ notBefore,
2420
+ notAfter,
2421
+ subject: `CN=${hostName},O=SkillKit Mesh,OU=${hostId}`
2422
+ };
2423
+ }
2424
+ async loadCertificate(hostId) {
2425
+ const certFile = join3(this.certPath, `${hostId}.crt`);
2426
+ const keyFile = join3(this.certPath, `${hostId}.key`);
2427
+ if (!existsSync3(certFile) || !existsSync3(keyFile)) {
2428
+ return null;
2429
+ }
2430
+ const cert = await readFile3(certFile, "utf-8");
2431
+ const key = await readFile3(keyFile, "utf-8");
2432
+ const fingerprint = createHash("sha256").update(cert).digest("hex");
2433
+ return {
2434
+ cert,
2435
+ key,
2436
+ fingerprint,
2437
+ notBefore: /* @__PURE__ */ new Date(),
2438
+ notAfter: new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3),
2439
+ subject: hostId
2440
+ };
2441
+ }
2442
+ async loadOrCreateCertificate(hostId, hostName = "localhost") {
2443
+ const existing = await this.loadCertificate(hostId);
2444
+ if (existing) {
2445
+ return existing;
2446
+ }
2447
+ return this.generateCertificate(hostId, hostName);
2448
+ }
2449
+ async hasCertificate(hostId) {
2450
+ const certFile = join3(this.certPath, `${hostId}.crt`);
2451
+ return existsSync3(certFile);
2452
+ }
2453
+ getCertificatePath(hostId) {
2454
+ return {
2455
+ certPath: join3(this.certPath, `${hostId}.crt`),
2456
+ keyPath: join3(this.certPath, `${hostId}.key`)
2457
+ };
2458
+ }
2459
+ createServerContext(certInfo, options) {
2460
+ const context = {
2461
+ cert: certInfo.cert,
2462
+ key: certInfo.key,
2463
+ requestCert: options?.requestClientCert ?? false,
2464
+ rejectUnauthorized: false
2465
+ };
2466
+ if (options?.trustedCAs?.length) {
2467
+ context.ca = options.trustedCAs;
2468
+ context.rejectUnauthorized = true;
2469
+ }
2470
+ return context;
2471
+ }
2472
+ createClientContext(certInfo, options) {
2473
+ const context = {
2474
+ rejectUnauthorized: false
2475
+ };
2476
+ if (certInfo) {
2477
+ context.cert = certInfo.cert;
2478
+ context.key = certInfo.key;
2479
+ }
2480
+ if (options?.trustedCAs?.length) {
2481
+ context.ca = options.trustedCAs;
2482
+ }
2483
+ return context;
2484
+ }
2485
+ static computeCertFingerprint(cert) {
2486
+ return createHash("sha256").update(cert).digest("hex");
2487
+ }
2488
+ static verifyCertFingerprint(cert, expectedFingerprint) {
2489
+ const actual = _TLSManager.computeCertFingerprint(cert);
2490
+ return actual.toLowerCase() === expectedFingerprint.toLowerCase();
2491
+ }
2492
+ };
2493
+ var globalTLSManager = null;
2494
+ function getTLSManager(certPath) {
2495
+ if (!globalTLSManager) {
2496
+ globalTLSManager = new TLSManager(certPath);
2497
+ }
2498
+ return globalTLSManager;
2499
+ }
2500
+ function resetTLSManager() {
2501
+ globalTLSManager = null;
2502
+ }
2503
+
2504
+ // src/transport/secure-websocket.ts
2505
+ import { hexToBytes as hexToBytes5 } from "@noble/hashes/utils";
2506
+ var SecureWebSocketTransport = class {
2507
+ socket = null;
2508
+ host;
2509
+ options;
2510
+ identity = null;
2511
+ keystore = null;
2512
+ authManager = null;
2513
+ encryption = null;
2514
+ reconnectAttempts = 0;
2515
+ messageHandlers = /* @__PURE__ */ new Set();
2516
+ connected = false;
2517
+ authenticated = false;
2518
+ reconnectTimer = null;
2519
+ constructor(host, options = {}) {
2520
+ this.host = host;
2521
+ this.options = {
2522
+ timeout: options.timeout ?? 5e3,
2523
+ reconnect: options.reconnect ?? true,
2524
+ reconnectInterval: options.reconnectInterval ?? 5e3,
2525
+ maxReconnectAttempts: options.maxReconnectAttempts ?? 10,
2526
+ security: options.security ?? DEFAULT_SECURITY_CONFIG
2527
+ };
2528
+ this.identity = options.identity ?? null;
2529
+ this.keystore = options.keystore ?? null;
2530
+ }
2531
+ async initialize() {
2532
+ if (!this.identity && this.keystore) {
2533
+ this.identity = await this.keystore.loadOrCreateIdentity();
2534
+ }
2535
+ if (!this.identity) {
2536
+ this.identity = await PeerIdentity.generate();
2537
+ }
2538
+ this.authManager = new AuthManager(this.identity);
2539
+ }
2540
+ getUrl() {
2541
+ const protocol = this.options.security.transport.tls !== "none" ? "wss" : "ws";
2542
+ return `${protocol}://${this.host.address}:${this.host.port}/ws`;
2543
+ }
2544
+ async connect() {
2545
+ if (!this.identity) {
2546
+ await this.initialize();
2547
+ }
2548
+ return new Promise((resolve, reject) => {
2549
+ const url = this.getUrl();
2550
+ const wsOptions = {
2551
+ handshakeTimeout: this.options.timeout,
2552
+ rejectUnauthorized: false
2553
+ };
2554
+ this.socket = new WebSocket2(url, wsOptions);
2555
+ const timeout = setTimeout(() => {
2556
+ this.socket?.close();
2557
+ reject(new Error("Connection timeout"));
2558
+ }, this.options.timeout);
2559
+ this.socket.on("open", async () => {
2560
+ clearTimeout(timeout);
2561
+ this.connected = true;
2562
+ this.reconnectAttempts = 0;
2563
+ if (this.options.security.transport.requireAuth) {
2564
+ try {
2565
+ await this.performClientHandshake();
2566
+ this.authenticated = true;
2567
+ } catch (err) {
2568
+ this.socket?.close();
2569
+ reject(new Error(`Authentication failed: ${err}`));
2570
+ return;
2571
+ }
2572
+ } else {
2573
+ this.authenticated = true;
2574
+ }
2575
+ resolve();
2576
+ });
2577
+ this.socket.on("message", async (data) => {
2578
+ try {
2579
+ const raw = JSON.parse(data.toString());
2580
+ if (raw.type === "auth:challenge") {
2581
+ return;
2582
+ }
2583
+ let message;
2584
+ let senderFingerprint;
2585
+ if (this.encryption && raw.ciphertext) {
2586
+ const decrypted = this.encryption.decryptToObject({
2587
+ nonce: raw.nonce,
2588
+ ciphertext: raw.ciphertext
2589
+ });
2590
+ message = decrypted;
2591
+ senderFingerprint = raw.senderFingerprint;
2592
+ } else if (raw.signature) {
2593
+ const secure = raw;
2594
+ senderFingerprint = secure.senderFingerprint;
2595
+ message = {
2596
+ id: secure.id,
2597
+ type: secure.type,
2598
+ from: secure.from,
2599
+ to: secure.to,
2600
+ payload: secure.payload,
2601
+ timestamp: secure.timestamp
2602
+ };
2603
+ } else {
2604
+ message = raw;
2605
+ }
2606
+ this.messageHandlers.forEach(
2607
+ (handler) => handler(message, this.socket, senderFingerprint)
2608
+ );
2609
+ } catch {
2610
+ }
2611
+ });
2612
+ this.socket.on("close", () => {
2613
+ this.connected = false;
2614
+ this.authenticated = false;
2615
+ if (this.options.reconnect) {
2616
+ this.scheduleReconnect();
2617
+ }
2618
+ });
2619
+ this.socket.on("error", (err) => {
2620
+ clearTimeout(timeout);
2621
+ if (!this.connected) {
2622
+ reject(err);
2623
+ }
2624
+ });
2625
+ });
2626
+ }
2627
+ async performClientHandshake() {
2628
+ return new Promise((resolve, reject) => {
2629
+ const handleChallenge = async (data) => {
2630
+ try {
2631
+ const msg = JSON.parse(data.toString());
2632
+ if (msg.type === "auth:challenge") {
2633
+ const challenge = {
2634
+ challenge: msg.challenge,
2635
+ timestamp: msg.timestamp
2636
+ };
2637
+ const response = await this.authManager.respondToChallenge(challenge);
2638
+ this.socket.send(
2639
+ JSON.stringify({
2640
+ type: "auth:response",
2641
+ ...response
2642
+ })
2643
+ );
2644
+ } else if (msg.type === "auth:success") {
2645
+ this.socket.off("message", handleChallenge);
2646
+ if (msg.serverPublicKey) {
2647
+ const serverPubKey = hexToBytes5(msg.serverPublicKey);
2648
+ const sharedSecret = this.identity.deriveSharedSecret(serverPubKey);
2649
+ this.encryption = new MessageEncryption(sharedSecret);
2650
+ }
2651
+ resolve();
2652
+ } else if (msg.type === "auth:failed") {
2653
+ this.socket.off("message", handleChallenge);
2654
+ reject(new Error(msg.error || "Authentication failed"));
2655
+ }
2656
+ } catch (err) {
2657
+ reject(err);
2658
+ }
2659
+ };
2660
+ this.socket.on("message", handleChallenge);
2661
+ setTimeout(() => {
2662
+ this.socket.off("message", handleChallenge);
2663
+ reject(new Error("Authentication timeout"));
2664
+ }, this.options.timeout);
2665
+ });
2666
+ }
2667
+ disconnect() {
2668
+ if (this.reconnectTimer) {
2669
+ clearTimeout(this.reconnectTimer);
2670
+ this.reconnectTimer = null;
2671
+ }
2672
+ if (this.socket) {
2673
+ this.socket.close();
2674
+ this.socket = null;
2675
+ }
2676
+ this.connected = false;
2677
+ this.authenticated = false;
2678
+ this.encryption = null;
2679
+ }
2680
+ async send(message) {
2681
+ if (!this.socket || !this.connected) {
2682
+ throw new Error("Not connected");
2683
+ }
2684
+ if (!this.authenticated) {
2685
+ throw new Error("Not authenticated");
2686
+ }
2687
+ const fullMessage = {
2688
+ ...message,
2689
+ id: randomUUID4(),
2690
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2691
+ };
2692
+ let dataToSend;
2693
+ if (this.encryption && this.options.security.transport.encryption === "required") {
2694
+ const encrypted = this.encryption.encryptObject(fullMessage);
2695
+ dataToSend = JSON.stringify({
2696
+ id: fullMessage.id,
2697
+ senderFingerprint: this.identity.fingerprint,
2698
+ nonce: encrypted.nonce,
2699
+ ciphertext: encrypted.ciphertext,
2700
+ timestamp: fullMessage.timestamp
2701
+ });
2702
+ } else if (this.identity) {
2703
+ const signature = await this.identity.signObject(fullMessage);
2704
+ const secureMessage = {
2705
+ ...fullMessage,
2706
+ signature,
2707
+ senderFingerprint: this.identity.fingerprint,
2708
+ senderPublicKey: this.identity.publicKeyHex,
2709
+ nonce: randomUUID4()
2710
+ };
2711
+ dataToSend = JSON.stringify(secureMessage);
2712
+ } else {
2713
+ dataToSend = JSON.stringify(fullMessage);
2714
+ }
2715
+ return new Promise((resolve, reject) => {
2716
+ this.socket.send(dataToSend, (err) => {
2717
+ if (err) reject(err);
2718
+ else resolve();
2719
+ });
2720
+ });
2721
+ }
2722
+ onMessage(handler) {
2723
+ this.messageHandlers.add(handler);
2724
+ return () => this.messageHandlers.delete(handler);
2725
+ }
2726
+ isConnected() {
2727
+ return this.connected;
2728
+ }
2729
+ isAuthenticated() {
2730
+ return this.authenticated;
2731
+ }
2732
+ getFingerprint() {
2733
+ return this.identity?.fingerprint ?? null;
2734
+ }
2735
+ scheduleReconnect() {
2736
+ if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
2737
+ return;
2738
+ }
2739
+ this.reconnectTimer = setTimeout(async () => {
2740
+ this.reconnectAttempts++;
2741
+ try {
2742
+ await this.connect();
2743
+ } catch {
2744
+ this.scheduleReconnect();
2745
+ }
2746
+ }, this.options.reconnectInterval);
2747
+ }
2748
+ };
2749
+ var SecureWebSocketServer = class {
2750
+ wss = null;
2751
+ httpsServer = null;
2752
+ clients = /* @__PURE__ */ new Map();
2753
+ messageHandlers = /* @__PURE__ */ new Set();
2754
+ port;
2755
+ running = false;
2756
+ identity = null;
2757
+ keystore = null;
2758
+ authManager = null;
2759
+ tlsManager = null;
2760
+ certInfo = null;
2761
+ security;
2762
+ hostId;
2763
+ constructor(port = DEFAULT_PORT, options = {}) {
2764
+ this.port = port;
2765
+ this.security = options.security ?? DEFAULT_SECURITY_CONFIG;
2766
+ this.identity = options.identity ?? null;
2767
+ this.keystore = options.keystore ?? null;
2768
+ this.hostId = options.hostId ?? randomUUID4();
2769
+ }
2770
+ async initialize() {
2771
+ if (!this.identity && this.keystore) {
2772
+ this.identity = await this.keystore.loadOrCreateIdentity();
2773
+ }
2774
+ if (!this.identity) {
2775
+ this.identity = await PeerIdentity.generate();
2776
+ }
2777
+ this.authManager = new AuthManager(this.identity);
2778
+ if (this.security.transport.tls !== "none") {
2779
+ this.tlsManager = new TLSManager();
2780
+ this.certInfo = await this.tlsManager.loadOrCreateCertificate(
2781
+ this.hostId,
2782
+ "localhost"
2783
+ );
2784
+ }
2785
+ }
2786
+ async start() {
2787
+ if (this.running) return;
2788
+ await this.initialize();
2789
+ return new Promise((resolve, reject) => {
2790
+ if (this.security.transport.tls !== "none" && this.certInfo) {
2791
+ this.httpsServer = createHttpsServer({
2792
+ cert: this.certInfo.cert,
2793
+ key: this.certInfo.key
2794
+ });
2795
+ this.wss = new WebSocketServer3({
2796
+ server: this.httpsServer,
2797
+ path: "/ws"
2798
+ });
2799
+ this.httpsServer.listen(this.port, () => {
2800
+ this.running = true;
2801
+ resolve();
2802
+ });
2803
+ this.httpsServer.on("error", reject);
2804
+ } else {
2805
+ this.wss = new WebSocketServer3({ port: this.port, path: "/ws" });
2806
+ this.wss.on("listening", () => {
2807
+ this.running = true;
2808
+ resolve();
2809
+ });
2810
+ this.wss.on("error", reject);
2811
+ }
2812
+ this.wss.on("connection", (socket) => {
2813
+ this.handleConnection(socket);
2814
+ });
2815
+ });
2816
+ }
2817
+ async handleConnection(socket) {
2818
+ if (this.security.transport.requireAuth) {
2819
+ try {
2820
+ const client = await this.performServerHandshake(socket);
2821
+ this.clients.set(socket, client);
2822
+ } catch {
2823
+ socket.close();
2824
+ return;
2825
+ }
2826
+ } else {
2827
+ this.clients.set(socket, {
2828
+ socket,
2829
+ fingerprint: "anonymous",
2830
+ publicKey: ""
2831
+ });
2832
+ }
2833
+ socket.on("message", async (data) => {
2834
+ try {
2835
+ const raw = JSON.parse(data.toString());
2836
+ if (raw.type === "auth:response") {
2837
+ return;
2838
+ }
2839
+ const client = this.clients.get(socket);
2840
+ if (!client) return;
2841
+ let message;
2842
+ let senderFingerprint = client.fingerprint;
2843
+ if (client.encryption && raw.ciphertext) {
2844
+ const decrypted = client.encryption.decryptToObject({
2845
+ nonce: raw.nonce,
2846
+ ciphertext: raw.ciphertext
2847
+ });
2848
+ message = decrypted;
2849
+ } else if (raw.signature) {
2850
+ const secure = raw;
2851
+ senderFingerprint = secure.senderFingerprint;
2852
+ message = {
2853
+ id: secure.id,
2854
+ type: secure.type,
2855
+ from: secure.from,
2856
+ to: secure.to,
2857
+ payload: secure.payload,
2858
+ timestamp: secure.timestamp
2859
+ };
2860
+ } else {
2861
+ message = raw;
2862
+ }
2863
+ this.messageHandlers.forEach(
2864
+ (handler) => handler(message, socket, senderFingerprint)
2865
+ );
2866
+ } catch {
2867
+ }
2868
+ });
2869
+ socket.on("close", () => {
2870
+ this.clients.delete(socket);
2871
+ });
2872
+ }
2873
+ async performServerHandshake(socket) {
2874
+ return new Promise((resolve, reject) => {
2875
+ const challenge = this.authManager.createChallenge();
2876
+ socket.send(
2877
+ JSON.stringify({
2878
+ type: "auth:challenge",
2879
+ ...challenge
2880
+ })
2881
+ );
2882
+ const timeout = setTimeout(() => {
2883
+ socket.off("message", handleResponse);
2884
+ reject(new Error("Handshake timeout"));
2885
+ }, 1e4);
2886
+ const handleResponse = async (data) => {
2887
+ try {
2888
+ const msg = JSON.parse(data.toString());
2889
+ if (msg.type === "auth:response") {
2890
+ clearTimeout(timeout);
2891
+ socket.off("message", handleResponse);
2892
+ const response = {
2893
+ challenge: msg.challenge,
2894
+ signature: msg.signature,
2895
+ publicKey: msg.publicKey,
2896
+ fingerprint: msg.fingerprint,
2897
+ timestamp: msg.timestamp
2898
+ };
2899
+ const result = await this.authManager.verifyChallengeResponse(response);
2900
+ if (!result.authenticated) {
2901
+ socket.send(
2902
+ JSON.stringify({
2903
+ type: "auth:failed",
2904
+ error: result.error
2905
+ })
2906
+ );
2907
+ reject(new Error(result.error));
2908
+ return;
2909
+ }
2910
+ if (this.keystore) {
2911
+ const isRevoked = await this.keystore.isRevoked(result.fingerprint);
2912
+ if (isRevoked) {
2913
+ socket.send(
2914
+ JSON.stringify({
2915
+ type: "auth:failed",
2916
+ error: "Peer is revoked"
2917
+ })
2918
+ );
2919
+ reject(new Error("Peer is revoked"));
2920
+ return;
2921
+ }
2922
+ }
2923
+ let encryption;
2924
+ if (this.security.transport.encryption === "required") {
2925
+ const clientPubKey = hexToBytes5(response.publicKey);
2926
+ const sharedSecret = this.identity.deriveSharedSecret(clientPubKey);
2927
+ encryption = new MessageEncryption(sharedSecret);
2928
+ }
2929
+ socket.send(
2930
+ JSON.stringify({
2931
+ type: "auth:success",
2932
+ serverFingerprint: this.identity.fingerprint,
2933
+ serverPublicKey: this.identity.publicKeyHex
2934
+ })
2935
+ );
2936
+ resolve({
2937
+ socket,
2938
+ fingerprint: result.fingerprint,
2939
+ publicKey: response.publicKey,
2940
+ encryption
2941
+ });
2942
+ }
2943
+ } catch (err) {
2944
+ clearTimeout(timeout);
2945
+ reject(err);
2946
+ }
2947
+ };
2948
+ socket.on("message", handleResponse);
2949
+ });
2950
+ }
2951
+ stop() {
2952
+ if (!this.running) return;
2953
+ for (const [socket] of this.clients) {
2954
+ socket.close();
2955
+ }
2956
+ this.clients.clear();
2957
+ this.wss?.close();
2958
+ this.httpsServer?.close();
2959
+ this.wss = null;
2960
+ this.httpsServer = null;
2961
+ this.running = false;
2962
+ }
2963
+ async broadcast(message) {
2964
+ const fullMessage = {
2965
+ ...message,
2966
+ id: randomUUID4(),
2967
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2968
+ };
2969
+ for (const [socket, client] of this.clients) {
2970
+ if (socket.readyState !== WebSocket2.OPEN) continue;
2971
+ let dataToSend;
2972
+ if (client.encryption) {
2973
+ const encrypted = client.encryption.encryptObject(fullMessage);
2974
+ dataToSend = JSON.stringify({
2975
+ id: fullMessage.id,
2976
+ senderFingerprint: this.identity.fingerprint,
2977
+ nonce: encrypted.nonce,
2978
+ ciphertext: encrypted.ciphertext,
2979
+ timestamp: fullMessage.timestamp
2980
+ });
2981
+ } else if (this.identity) {
2982
+ const signature = await this.identity.signObject(fullMessage);
2983
+ const secureMessage = {
2984
+ ...fullMessage,
2985
+ signature,
2986
+ senderFingerprint: this.identity.fingerprint,
2987
+ senderPublicKey: this.identity.publicKeyHex,
2988
+ nonce: randomUUID4()
2989
+ };
2990
+ dataToSend = JSON.stringify(secureMessage);
2991
+ } else {
2992
+ dataToSend = JSON.stringify(fullMessage);
2993
+ }
2994
+ socket.send(dataToSend);
2995
+ }
2996
+ }
2997
+ async sendTo(socket, message) {
2998
+ if (socket.readyState !== WebSocket2.OPEN) return;
2999
+ const client = this.clients.get(socket);
3000
+ if (!client) return;
3001
+ const fullMessage = {
3002
+ ...message,
3003
+ id: randomUUID4(),
3004
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3005
+ };
3006
+ let dataToSend;
3007
+ if (client.encryption) {
3008
+ const encrypted = client.encryption.encryptObject(fullMessage);
3009
+ dataToSend = JSON.stringify({
3010
+ id: fullMessage.id,
3011
+ senderFingerprint: this.identity.fingerprint,
3012
+ nonce: encrypted.nonce,
3013
+ ciphertext: encrypted.ciphertext,
3014
+ timestamp: fullMessage.timestamp
3015
+ });
3016
+ } else if (this.identity) {
3017
+ const signature = await this.identity.signObject(fullMessage);
3018
+ const secureMessage = {
3019
+ ...fullMessage,
3020
+ signature,
3021
+ senderFingerprint: this.identity.fingerprint,
3022
+ senderPublicKey: this.identity.publicKeyHex,
3023
+ nonce: randomUUID4()
3024
+ };
3025
+ dataToSend = JSON.stringify(secureMessage);
3026
+ } else {
3027
+ dataToSend = JSON.stringify(fullMessage);
3028
+ }
3029
+ socket.send(dataToSend);
3030
+ }
3031
+ onMessage(handler) {
3032
+ this.messageHandlers.add(handler);
3033
+ return () => this.messageHandlers.delete(handler);
3034
+ }
3035
+ getClientCount() {
3036
+ return this.clients.size;
3037
+ }
3038
+ isRunning() {
3039
+ return this.running;
3040
+ }
3041
+ getFingerprint() {
3042
+ return this.identity?.fingerprint ?? null;
3043
+ }
3044
+ getAuthenticatedClients() {
3045
+ return Array.from(this.clients.values()).map((c) => ({
3046
+ fingerprint: c.fingerprint,
3047
+ publicKey: c.publicKey
3048
+ }));
3049
+ }
3050
+ };
3051
+
3052
+ // src/transport/secure-http.ts
3053
+ init_types();
3054
+ init_identity();
3055
+ import got3 from "got";
3056
+ import { randomUUID as randomUUID5 } from "crypto";
3057
+ var SecureHttpTransport = class {
3058
+ client;
3059
+ options;
3060
+ identity = null;
3061
+ keystore = null;
3062
+ authManager = null;
3063
+ authToken = null;
3064
+ host;
3065
+ security;
3066
+ constructor(host, options = {}) {
3067
+ this.host = host;
3068
+ this.options = {
3069
+ timeout: options.timeout ?? HEALTH_CHECK_TIMEOUT,
3070
+ retries: options.retries ?? 2,
3071
+ retryDelay: options.retryDelay ?? 1e3,
3072
+ headers: options.headers ?? {}
3073
+ };
3074
+ this.identity = options.identity ?? null;
3075
+ this.keystore = options.keystore ?? null;
3076
+ this.authToken = options.authToken ?? null;
3077
+ this.security = options.security ?? DEFAULT_SECURITY_CONFIG;
3078
+ const protocol = this.security.transport.tls !== "none" ? "https" : "http";
3079
+ const baseUrl = `${protocol}://${host.address}:${host.port}`;
3080
+ this.client = got3.extend({
3081
+ prefixUrl: baseUrl,
3082
+ timeout: { request: this.options.timeout },
3083
+ retry: {
3084
+ limit: this.options.retries,
3085
+ calculateDelay: () => this.options.retryDelay
3086
+ },
3087
+ https: {
3088
+ rejectUnauthorized: false
3089
+ },
3090
+ headers: {
3091
+ "Content-Type": "application/json",
3092
+ "X-SkillKit-Transport": "secure-http",
3093
+ ...this.options.headers
3094
+ }
3095
+ });
3096
+ }
3097
+ async initialize() {
3098
+ if (!this.identity && this.keystore) {
3099
+ this.identity = await this.keystore.loadOrCreateIdentity();
3100
+ }
3101
+ if (!this.identity) {
3102
+ this.identity = await PeerIdentity.generate();
3103
+ }
3104
+ this.authManager = new AuthManager(this.identity);
3105
+ if (this.security.transport.requireAuth && !this.authToken) {
3106
+ this.authToken = await this.authManager.createToken(this.host.id);
3107
+ }
3108
+ }
3109
+ async getSecureHeaders() {
3110
+ const headers = {};
3111
+ if (this.authToken) {
3112
+ headers["Authorization"] = createBearerHeader(this.authToken);
3113
+ }
3114
+ if (this.identity) {
3115
+ headers["X-SkillKit-Fingerprint"] = this.identity.fingerprint;
3116
+ }
3117
+ return headers;
3118
+ }
3119
+ async send(path, payload) {
3120
+ if (!this.identity) {
3121
+ await this.initialize();
3122
+ }
3123
+ const message = {
3124
+ id: randomUUID5(),
3125
+ type: "request",
3126
+ from: this.identity?.fingerprint ?? "local",
3127
+ to: path,
3128
+ payload,
3129
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3130
+ };
3131
+ let body = message;
3132
+ if (this.identity) {
3133
+ const signature = await this.identity.signObject(message);
3134
+ body = {
3135
+ ...message,
3136
+ signature,
3137
+ senderFingerprint: this.identity.fingerprint,
3138
+ senderPublicKey: this.identity.publicKeyHex,
3139
+ nonce: randomUUID5()
3140
+ };
3141
+ }
3142
+ const secureHeaders = await this.getSecureHeaders();
3143
+ const response = await this.client.post(path, {
3144
+ json: body,
3145
+ headers: secureHeaders
3146
+ });
3147
+ return JSON.parse(response.body);
3148
+ }
3149
+ async sendMessage(to, type, payload) {
3150
+ return this.send("message", {
3151
+ to,
3152
+ type,
3153
+ payload
3154
+ });
3155
+ }
3156
+ async registerPeer(registration) {
3157
+ return this.send("peer/register", registration);
3158
+ }
3159
+ async getPeers() {
3160
+ if (!this.identity) {
3161
+ await this.initialize();
3162
+ }
3163
+ const secureHeaders = await this.getSecureHeaders();
3164
+ const response = await this.client.get("peers", {
3165
+ headers: secureHeaders
3166
+ });
3167
+ return JSON.parse(response.body);
3168
+ }
3169
+ async healthCheck() {
3170
+ try {
3171
+ const response = await this.client.get("health", {
3172
+ headers: await this.getSecureHeaders()
3173
+ });
3174
+ return response.statusCode === 200;
3175
+ } catch {
3176
+ return false;
3177
+ }
3178
+ }
3179
+ getFingerprint() {
3180
+ return this.identity?.fingerprint ?? null;
3181
+ }
3182
+ setAuthToken(token) {
3183
+ this.authToken = token;
3184
+ }
3185
+ };
3186
+ async function sendToHostSecure(host, path, payload, options = {}) {
3187
+ const transport = new SecureHttpTransport(host, options);
3188
+ await transport.initialize();
3189
+ return transport.send(path, payload);
3190
+ }
3191
+ async function broadcastToHostsSecure(hosts, path, payload, options = {}) {
3192
+ const results = /* @__PURE__ */ new Map();
3193
+ await Promise.all(
3194
+ hosts.map(async (host) => {
3195
+ try {
3196
+ const response = await sendToHostSecure(host, path, payload, options);
3197
+ results.set(host.id, response);
3198
+ } catch (err) {
3199
+ results.set(host.id, err);
3200
+ }
3201
+ })
3202
+ );
3203
+ return results;
3204
+ }
3205
+ function verifySecureMessage(message) {
3206
+ if (!message.signature || !message.senderPublicKey || !message.senderFingerprint) {
3207
+ return { valid: false, error: "Missing signature fields" };
3208
+ }
3209
+ const computedFingerprint = PeerIdentity.computeFingerprint(
3210
+ Buffer.from(message.senderPublicKey, "hex")
3211
+ );
3212
+ if (computedFingerprint !== message.senderFingerprint) {
3213
+ return { valid: false, error: "Fingerprint mismatch" };
3214
+ }
3215
+ return { valid: true };
3216
+ }
3217
+
3218
+ // src/index.ts
3219
+ init_crypto();
3220
+ async function initializeMesh() {
3221
+ const { initializeHostsFile: initializeHostsFile2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3222
+ const { getPeerRegistry: getPeerRegistry2 } = await Promise.resolve().then(() => (init_peer(), peer_exports));
3223
+ await initializeHostsFile2();
3224
+ await getPeerRegistry2();
3225
+ }
3226
+ async function initializeSecureMesh(securityConfig) {
3227
+ const { initializeHostsFile: initializeHostsFile2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3228
+ const { getPeerRegistry: getPeerRegistry2 } = await Promise.resolve().then(() => (init_peer(), peer_exports));
3229
+ const { SecureKeystore: SecureKeystore2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
3230
+ await initializeHostsFile2();
3231
+ await getPeerRegistry2();
3232
+ const keystore = new SecureKeystore2({
3233
+ path: securityConfig?.identityPath
3234
+ });
3235
+ const identity = await keystore.loadOrCreateIdentity();
3236
+ return { identity, keystore };
3237
+ }
3238
+ export {
3239
+ AuthManager,
3240
+ DEFAULT_DISCOVERY_PORT,
3241
+ DEFAULT_PORT,
3242
+ DEFAULT_SECURITY_CONFIG,
3243
+ DISCOVERY_INTERVAL,
3244
+ HEALTH_CHECK_TIMEOUT,
3245
+ HealthMonitor,
3246
+ HttpTransport,
3247
+ LocalDiscovery,
3248
+ MESH_VERSION,
3249
+ MessageEncryption,
3250
+ PeerIdentity,
3251
+ PeerRegistryManager,
3252
+ PublicKeyEncryption,
3253
+ SECURITY_PRESETS,
3254
+ SecureHttpTransport,
3255
+ SecureKeystore,
3256
+ SecureLocalDiscovery,
3257
+ SecureWebSocketServer,
3258
+ SecureWebSocketTransport,
3259
+ TLSManager,
3260
+ WebSocketServer2 as WebSocketServer,
3261
+ WebSocketTransport,
3262
+ addKnownHost,
3263
+ broadcastToHosts,
3264
+ broadcastToHostsSecure,
3265
+ checkAllHostsHealth,
3266
+ checkHostHealth,
3267
+ createBearerHeader,
3268
+ createDefaultHostsFile,
3269
+ decryptData,
3270
+ decryptFile,
3271
+ decryptObject,
3272
+ deriveKey,
3273
+ describeSecurityLevel,
3274
+ discoverOnce,
3275
+ discoverOnceSecure,
3276
+ discoverTailscaleHosts,
3277
+ encryptData,
3278
+ encryptFile,
3279
+ encryptObject,
3280
+ extractBearerToken,
3281
+ extractSignerFingerprint,
3282
+ generateIV,
3283
+ generateMachineKey,
3284
+ generateMessageId,
3285
+ generateNonce,
3286
+ generateSalt,
3287
+ getAllLocalIPAddresses,
3288
+ getHostsFilePath,
3289
+ getKeystore,
3290
+ getKnownHost,
3291
+ getKnownHosts,
3292
+ getLocalHostConfig,
3293
+ getLocalIPAddress,
3294
+ getOfflineHosts,
3295
+ getOnlineHosts,
3296
+ getPeerRegistry,
3297
+ getSecurityPreset,
3298
+ getTLSManager,
3299
+ getTailscaleIP,
3300
+ getTailscaleStatus,
3301
+ hashPassphrase,
3302
+ initializeHostsFile,
3303
+ initializeMesh,
3304
+ initializeSecureMesh,
3305
+ isEncryptedFile,
3306
+ isSecurityEnabled,
3307
+ isSignedDataExpired,
3308
+ isTailscaleAvailable,
3309
+ loadHostsFile,
3310
+ mergeSecurityConfig,
3311
+ removeKnownHost,
3312
+ resetKeystore,
3313
+ resetTLSManager,
3314
+ resolveTailscaleName,
3315
+ saveHostsFile,
3316
+ sendToHost,
3317
+ sendToHostSecure,
3318
+ signData,
3319
+ updateKnownHost,
3320
+ updateLocalHostConfig,
3321
+ validateSecurityConfig,
3322
+ verifySecureMessage,
3323
+ verifySignedData,
3324
+ waitForHost
3325
+ };
3326
+ //# sourceMappingURL=index.js.map