hbeam 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -99,10 +99,38 @@ hbeam whoami
99
99
  a1b2c3d4e5f6...
100
100
  ```
101
101
 
102
+ ### Serve a single file
103
+
104
+ Serve one file over an encrypted hbeam session:
105
+
106
+ ```bash
107
+ hbeam serve ./report.pdf
108
+ ```
109
+
110
+ This announces a one-time passphrase by default. To serve from your persistent identity instead:
111
+
112
+ ```bash
113
+ hbeam serve ./report.pdf --listen
114
+ ```
115
+
116
+ On the receiving side, connect normally (`hbeam <passphrase>` or `hbeam connect <name>`). hbeam detects the incoming file header and prompts where to save it. Use `-o` to skip the prompt:
117
+
118
+ ```bash
119
+ hbeam <passphrase> -o ./downloads/report.pdf
120
+ hbeam connect workserver -o ./downloads/report.pdf
121
+ ```
122
+
123
+ If stdout is piped, hbeam writes raw file bytes to stdout so shell redirection works:
124
+
125
+ ```bash
126
+ hbeam <passphrase> > report.pdf
127
+ ```
128
+
102
129
  ### Options
103
130
 
104
131
  ```
105
132
  -l, --listen Listen using passphrase or identity
133
+ -o, --output Save incoming file to a specific path
106
134
  -h, --help Show help
107
135
  -v, --version Show version
108
136
  ```
package/dist/cli.mjs CHANGED
@@ -2,20 +2,21 @@
2
2
  import mri from "mri";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import DHT from "hyperdht";
5
- import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
5
+ import { chmod, mkdir, readFile, stat, writeFile } from "node:fs/promises";
6
6
  import { homedir } from "node:os";
7
- import { dirname, join } from "node:path";
7
+ import { basename, dirname, join, resolve } from "node:path";
8
8
  import * as b4a from "b4a";
9
9
  import b32 from "hi-base32";
10
10
  import sodium from "sodium-universal";
11
11
  import { bold, bold as bold$1, cyan, dim, dim as dim$1, gray, red, red as red$1 } from "colorette";
12
- import { Transform } from "node:stream";
12
+ import { createReadStream, createWriteStream } from "node:fs";
13
+ import { createInterface } from "node:readline/promises";
13
14
  import queueTick from "queue-tick";
14
15
  import { Duplex } from "streamx";
15
16
  import { createHash } from "node:crypto";
16
17
 
17
18
  //#region src/lib/clipboard.ts
18
- const EXIT_SUCCESS$4 = 0;
19
+ const EXIT_SUCCESS$3 = 0;
19
20
  const EMPTY_ARGS = [];
20
21
  const DARWIN_COMMANDS = [{
21
22
  args: EMPTY_ARGS,
@@ -53,7 +54,7 @@ function tryClipboardCommand(text, item) {
53
54
  "ignore"
54
55
  ]
55
56
  });
56
- return !result.error && result.status === EXIT_SUCCESS$4;
57
+ return !result.error && result.status === EXIT_SUCCESS$3;
57
58
  }
58
59
  /** Copy text to the system clipboard using common platform commands. */
59
60
  function copyToClipboard(text) {
@@ -253,9 +254,60 @@ function createSpinner(frames, intervalMs) {
253
254
  };
254
255
  }
255
256
 
257
+ //#endregion
258
+ //#region src/lib/file-protocol.ts
259
+ const FILE_TYPE = "file";
260
+ const HEADER_PREFIX = `{"type":"${FILE_TYPE}"`;
261
+ const NEWLINE = "\n";
262
+ const BYTES_PER_KIB = 1024;
263
+ const UNIT_PRECISION = 1;
264
+ const FIRST_INDEX$1 = 0;
265
+ const MIN_SIZE = 0;
266
+ const LAST_INDEX_OFFSET = 1;
267
+ const UNIT_LABELS = [
268
+ "B",
269
+ "KB",
270
+ "MB",
271
+ "GB",
272
+ "TB"
273
+ ];
274
+ function encodeHeader(header) {
275
+ return Buffer.from(`${JSON.stringify(header)}${NEWLINE}`, "utf8");
276
+ }
277
+ function isFileHeader(chunk) {
278
+ const prefix = HEADER_PREFIX.slice(FIRST_INDEX$1, Math.min(chunk.length, HEADER_PREFIX.length));
279
+ return chunk.toString("utf8", FIRST_INDEX$1, prefix.length) === prefix;
280
+ }
281
+ function parseFileHeader(line) {
282
+ const parsed = JSON.parse(line.toString("utf8"));
283
+ if (parsed.type !== FILE_TYPE) throw new Error("Invalid file header type");
284
+ if (!parsed.name || typeof parsed.name !== "string") throw new Error("Invalid file header name");
285
+ if (typeof parsed.size !== "number" || !Number.isSafeInteger(parsed.size) || parsed.size < MIN_SIZE) throw new Error("Invalid file header size");
286
+ return {
287
+ name: parsed.name,
288
+ size: parsed.size,
289
+ type: FILE_TYPE
290
+ };
291
+ }
292
+ function findHeaderLineEnd(chunk) {
293
+ return chunk.indexOf(NEWLINE);
294
+ }
295
+ function formatFileSize(bytes) {
296
+ if (!Number.isFinite(bytes) || bytes < MIN_SIZE) return `0 ${UNIT_LABELS[FIRST_INDEX$1]}`;
297
+ let size = bytes;
298
+ let unitIndex = FIRST_INDEX$1;
299
+ const lastUnitIndex = UNIT_LABELS.length - LAST_INDEX_OFFSET;
300
+ while (size >= BYTES_PER_KIB && unitIndex < lastUnitIndex) {
301
+ size /= BYTES_PER_KIB;
302
+ unitIndex++;
303
+ }
304
+ if (unitIndex === FIRST_INDEX$1) return `${Math.round(size)} ${UNIT_LABELS[unitIndex]}`;
305
+ return `${size.toFixed(UNIT_PRECISION)} ${UNIT_LABELS[unitIndex]}`;
306
+ }
307
+
256
308
  //#endregion
257
309
  //#region src/lib/lifecycle.ts
258
- const EXIT_FAILURE$2 = 1;
310
+ const EXIT_FAILURE$3 = 1;
259
311
  const SHUTDOWN_TIMEOUT_MS = 2e3;
260
312
  /**
261
313
  * Create a lifecycle controller that manages SIGINT handling and graceful shutdown.
@@ -272,7 +324,7 @@ function createLifecycle(beam, spinner) {
272
324
  log(dim$1("SHUTTING DOWN"));
273
325
  blank();
274
326
  const timeout = globalThis.setTimeout(() => {
275
- process.exit(EXIT_FAILURE$2);
327
+ process.exit(EXIT_FAILURE$3);
276
328
  }, SHUTDOWN_TIMEOUT_MS);
277
329
  beam.destroy();
278
330
  beam.on("close", () => {
@@ -291,6 +343,71 @@ function createLifecycle(beam, spinner) {
291
343
  };
292
344
  }
293
345
 
346
+ //#endregion
347
+ //#region src/lib/prompt.ts
348
+ const YES = "y";
349
+ const NO = "n";
350
+ const CTRL_C = "";
351
+ const ENTER = "\r";
352
+ const FIRST_CHAR_INDEX = 0;
353
+ const EMPTY_INPUT = "";
354
+ function firstChar(data) {
355
+ return data.toString("utf8").toLowerCase().charAt(FIRST_CHAR_INDEX);
356
+ }
357
+ /** Minimal single-keypress confirm prompt with `y/N` default. */
358
+ async function confirm(message) {
359
+ process.stderr.write(`${INDENT}${message} ${dim$1("(y/N)")} `);
360
+ const stdin = process.stdin;
361
+ if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
362
+ process.stderr.write("\n");
363
+ return false;
364
+ }
365
+ const originalRaw = stdin.isRaw;
366
+ return await new Promise((resolve) => {
367
+ function cleanup(answer) {
368
+ stdin.setRawMode(Boolean(originalRaw));
369
+ stdin.pause();
370
+ stdin.removeListener("data", onData);
371
+ process.stderr.write(`${answer ? YES : NO}\n`);
372
+ resolve(answer);
373
+ }
374
+ function onData(data) {
375
+ const key = firstChar(data);
376
+ if (key === CTRL_C) {
377
+ process.stderr.write("\n");
378
+ process.kill(process.pid, "SIGINT");
379
+ cleanup(false);
380
+ return;
381
+ }
382
+ if (key === YES) {
383
+ cleanup(true);
384
+ return;
385
+ }
386
+ if (key === NO || key === "" || key === ENTER) cleanup(false);
387
+ }
388
+ stdin.setRawMode(true);
389
+ stdin.resume();
390
+ stdin.on("data", onData);
391
+ });
392
+ }
393
+ /** Minimal line-input prompt with an editable pre-filled default value. */
394
+ async function input(message, placeholder) {
395
+ if (!process.stdin.isTTY || !process.stderr.isTTY) return placeholder;
396
+ const rl = createInterface({
397
+ input: process.stdin,
398
+ output: process.stderr,
399
+ terminal: true
400
+ });
401
+ try {
402
+ const answerPromise = rl.question(`${INDENT}${message} `);
403
+ rl.write(placeholder);
404
+ const trimmed = (await answerPromise).trim();
405
+ return trimmed === EMPTY_INPUT ? placeholder : trimmed;
406
+ } finally {
407
+ rl.close();
408
+ }
409
+ }
410
+
294
411
  //#endregion
295
412
  //#region src/lib/pulse.ts
296
413
  const INTERVAL_MS = 125;
@@ -346,6 +463,9 @@ function createPulseFrames(label) {
346
463
 
347
464
  //#endregion
348
465
  //#region src/lib/session.ts
466
+ const FIRST_INDEX = 0;
467
+ const NEXT_INDEX = 1;
468
+ const MIN_BUFFERED_CHUNKS = 0;
349
469
  /** Run the standard hbeam CLI session UI and stdin/stdout piping. */
350
470
  function runBeamSession(beam, options) {
351
471
  const { frames, intervalMs } = createPulseFrames("HBEAM");
@@ -387,24 +507,106 @@ function runBeamSession(beam, options) {
387
507
  });
388
508
  beam.on("end", () => beam.end());
389
509
  let receivedData = false;
390
- const indent = new Transform({
391
- flush(cb) {
392
- if (receivedData) {
393
- blank();
394
- write(SEPARATOR);
395
- }
396
- cb(void 0, "\n");
397
- },
398
- transform(chunk, _encoding, cb) {
399
- if (!receivedData) {
400
- receivedData = true;
401
- write(SEPARATOR);
402
- blank();
403
- }
404
- cb(void 0, chunk.toString().replace(/^(?!$)/gm, INDENT));
510
+ let mode = "unknown";
511
+ let bufferedChunks = [];
512
+ let fileStream = void 0;
513
+ let filePath = void 0;
514
+ let initializingFileMode = false;
515
+ let queuedChunks = [];
516
+ function writeTextChunk(chunk) {
517
+ if (!receivedData) {
518
+ receivedData = true;
519
+ write(SEPARATOR);
520
+ blank();
521
+ }
522
+ const lines = chunk.toString().replace(/^(?!$)/gm, INDENT);
523
+ process.stdout.write(lines);
524
+ }
525
+ async function resolveOutputPath(fileName) {
526
+ if (options.outputPath) return resolve(options.outputPath);
527
+ if (!process.stdout.isTTY) return;
528
+ const suggestedPath = resolve(process.cwd(), fileName);
529
+ if (!await confirm("Save incoming file?")) return "";
530
+ return await input("Save to:", suggestedPath);
531
+ }
532
+ async function startFileMode(initialChunk) {
533
+ const lineEnd = findHeaderLineEnd(initialChunk);
534
+ if (lineEnd < FIRST_INDEX) return;
535
+ const header = parseFileHeader(initialChunk.subarray(FIRST_INDEX, lineEnd));
536
+ const remainder = initialChunk.subarray(lineEnd + NEXT_INDEX);
537
+ log(dim$1(`INCOMING FILE ${header.name} (${formatFileSize(header.size)})`));
538
+ const outputPath = await resolveOutputPath(header.name);
539
+ if (outputPath === "") {
540
+ log(dim$1("RECEIVE CANCELLED"));
541
+ blank();
542
+ lifecycle.shutdown();
543
+ return;
544
+ }
545
+ if (outputPath === void 0) {
546
+ mode = "file-stdout";
547
+ if (remainder.length > MIN_BUFFERED_CHUNKS) process.stdout.write(remainder);
548
+ return;
549
+ }
550
+ mode = "file";
551
+ filePath = outputPath;
552
+ await mkdir(dirname(outputPath), { recursive: true });
553
+ fileStream = createWriteStream(outputPath);
554
+ fileStream.on("error", (error) => beam.destroy(error));
555
+ if (remainder.length > MIN_BUFFERED_CHUNKS) fileStream.write(remainder);
556
+ }
557
+ function handleChunk(chunk) {
558
+ if (initializingFileMode) {
559
+ queuedChunks.push(chunk);
560
+ return;
561
+ }
562
+ if (mode === "text") {
563
+ writeTextChunk(chunk);
564
+ return;
565
+ }
566
+ if (mode === "file") {
567
+ fileStream?.write(chunk);
568
+ return;
569
+ }
570
+ if (mode === "file-stdout") {
571
+ process.stdout.write(chunk);
572
+ return;
573
+ }
574
+ bufferedChunks.push(chunk);
575
+ const pending = Buffer.concat(bufferedChunks);
576
+ if (isFileHeader(pending)) {
577
+ if (findHeaderLineEnd(pending) < FIRST_INDEX) return;
578
+ bufferedChunks = [];
579
+ initializingFileMode = true;
580
+ startFileMode(pending).finally(() => {
581
+ initializingFileMode = false;
582
+ for (const queuedChunk of queuedChunks) handleChunk(queuedChunk);
583
+ queuedChunks = [];
584
+ });
585
+ return;
586
+ }
587
+ mode = "text";
588
+ bufferedChunks = [];
589
+ writeTextChunk(pending);
590
+ }
591
+ beam.on("data", (chunk) => {
592
+ handleChunk(chunk);
593
+ });
594
+ beam.on("end", () => {
595
+ if (mode === "unknown" && bufferedChunks.length > MIN_BUFFERED_CHUNKS) {
596
+ mode = "text";
597
+ writeTextChunk(Buffer.concat(bufferedChunks));
598
+ bufferedChunks = [];
405
599
  }
600
+ if (mode === "text" && receivedData) {
601
+ blank();
602
+ write(SEPARATOR);
603
+ }
604
+ if (mode === "file") fileStream?.end(() => {
605
+ log(dim$1(`SAVED ${filePath ?? ""}`));
606
+ blank();
607
+ });
406
608
  });
407
- process.stdin.pipe(beam).pipe(indent).pipe(process.stdout);
609
+ process.stdin.pipe(beam);
408
610
  if (typeof process.stdin.unref === "function") process.stdin.unref();
409
611
  }
410
612
 
@@ -698,32 +900,31 @@ async function getPeer(name) {
698
900
 
699
901
  //#endregion
700
902
  //#region src/commands/connect.ts
701
- const EXIT_SUCCESS$3 = 0;
702
- const EXIT_FAILURE$1 = 1;
903
+ const EXIT_FAILURE$2 = 1;
703
904
  const PUBLIC_KEY_BYTES = 32;
704
- /** Execute `hbeam connect <name>`. */
705
- async function runConnectCommand(argv) {
905
+ /** Execute `hbeam connect <name>`. Exits on error; stays alive for the session. */
906
+ async function runConnectCommand(argv, options = {}) {
706
907
  const [name] = argv;
707
908
  if (!name) {
708
909
  blank();
709
910
  logError("Missing peer name.");
710
911
  write(dim$1("Usage: hbeam connect <name>"));
711
912
  blank();
712
- return EXIT_FAILURE$1;
913
+ process.exit(EXIT_FAILURE$2);
713
914
  }
714
915
  const peer = await getPeer(name).catch(() => void 0);
715
916
  if (!peer) {
716
917
  blank();
717
918
  logError(`Unknown peer: ${name}`);
718
919
  blank();
719
- return EXIT_FAILURE$1;
920
+ process.exit(EXIT_FAILURE$2);
720
921
  }
721
922
  const remotePublicKey = Buffer.from(peer.publicKey, "hex");
722
923
  if (remotePublicKey.length !== PUBLIC_KEY_BYTES) {
723
924
  blank();
724
925
  logError(`Invalid public key for peer: ${name}`);
725
926
  blank();
726
- return EXIT_FAILURE$1;
927
+ process.exit(EXIT_FAILURE$2);
727
928
  }
728
929
  const identity = await loadOrCreateIdentityWithMeta();
729
930
  if (identity.created) {
@@ -736,62 +937,15 @@ async function runConnectCommand(argv) {
736
937
  remotePublicKey
737
938
  }), {
738
939
  mode: "connect",
940
+ outputPath: options.outputPath,
739
941
  value: name
740
942
  });
741
- return EXIT_SUCCESS$3;
742
- }
743
-
744
- //#endregion
745
- //#region src/lib/prompt.ts
746
- const YES = "y";
747
- const NO = "n";
748
- const CTRL_C = "";
749
- const ENTER = "\r";
750
- const FIRST_CHAR_INDEX = 0;
751
- function firstChar(data) {
752
- return data.toString("utf8").toLowerCase().charAt(FIRST_CHAR_INDEX);
753
- }
754
- /** Minimal single-keypress confirm prompt with `y/N` default. */
755
- async function confirm(message) {
756
- process.stderr.write(`${INDENT}${message} ${dim$1("(y/N)")} `);
757
- const stdin = process.stdin;
758
- if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
759
- process.stderr.write("\n");
760
- return false;
761
- }
762
- const originalRaw = stdin.isRaw;
763
- return await new Promise((resolve) => {
764
- function cleanup(answer) {
765
- stdin.setRawMode(Boolean(originalRaw));
766
- stdin.pause();
767
- stdin.removeListener("data", onData);
768
- process.stderr.write(`${answer ? YES : NO}\n`);
769
- resolve(answer);
770
- }
771
- function onData(data) {
772
- const key = firstChar(data);
773
- if (key === CTRL_C) {
774
- process.stderr.write("\n");
775
- process.kill(process.pid, "SIGINT");
776
- cleanup(false);
777
- return;
778
- }
779
- if (key === YES) {
780
- cleanup(true);
781
- return;
782
- }
783
- if (key === NO || key === "" || key === ENTER) cleanup(false);
784
- }
785
- stdin.setRawMode(true);
786
- stdin.resume();
787
- stdin.on("data", onData);
788
- });
789
943
  }
790
944
 
791
945
  //#endregion
792
946
  //#region src/commands/peers.ts
793
947
  const EXIT_SUCCESS$2 = 0;
794
- const EXIT_FAILURE = 1;
948
+ const EXIT_FAILURE$1 = 1;
795
949
  const START_INDEX = 0;
796
950
  const PUBLIC_KEY_PREFIX_LENGTH = 8;
797
951
  const SECONDS_PER_MINUTE = 60;
@@ -827,7 +981,7 @@ async function handleAdd(name, publicKey) {
827
981
  blank();
828
982
  usage();
829
983
  blank();
830
- return EXIT_FAILURE;
984
+ return EXIT_FAILURE$1;
831
985
  }
832
986
  try {
833
987
  await addPeer(name, publicKey);
@@ -835,7 +989,7 @@ async function handleAdd(name, publicKey) {
835
989
  blank();
836
990
  logError(error.message);
837
991
  blank();
838
- return EXIT_FAILURE;
992
+ return EXIT_FAILURE$1;
839
993
  }
840
994
  blank();
841
995
  log(bold$1("SAVED"));
@@ -848,13 +1002,13 @@ async function handleRemove(name) {
848
1002
  blank();
849
1003
  usage();
850
1004
  blank();
851
- return EXIT_FAILURE;
1005
+ return EXIT_FAILURE$1;
852
1006
  }
853
1007
  if (!await getPeer(name).catch(() => void 0)) {
854
1008
  blank();
855
1009
  logError(`Unknown peer: ${name}`);
856
1010
  blank();
857
- return EXIT_FAILURE;
1011
+ return EXIT_FAILURE$1;
858
1012
  }
859
1013
  blank();
860
1014
  if (!await confirm(`REMOVE ${name}?`)) {
@@ -890,7 +1044,101 @@ async function runPeersCommand(argv) {
890
1044
  blank();
891
1045
  usage();
892
1046
  blank();
893
- return EXIT_FAILURE;
1047
+ return EXIT_FAILURE$1;
1048
+ }
1049
+
1050
+ //#endregion
1051
+ //#region src/commands/serve.ts
1052
+ const EXIT_FAILURE = 1;
1053
+ const MIN_FILE_SIZE = 0;
1054
+ function showUsageError(message) {
1055
+ blank();
1056
+ logError(message);
1057
+ write(dim$1("Usage: hbeam serve <file> [--listen]"));
1058
+ blank();
1059
+ process.exit(EXIT_FAILURE);
1060
+ }
1061
+ function createSessionSpinner() {
1062
+ const { frames, intervalMs } = createPulseFrames("HBEAM");
1063
+ return createSpinner(frames, intervalMs);
1064
+ }
1065
+ async function resolveServeIdentity(listen) {
1066
+ if (!listen) return { announceLabel: "PASSPHRASE" };
1067
+ const identity = await loadOrCreateIdentityWithMeta();
1068
+ if (identity.created) {
1069
+ blank();
1070
+ log(dim$1("IDENTITY CREATED"));
1071
+ write(cyan(identity.keyPair.publicKey.toString("hex")));
1072
+ }
1073
+ return {
1074
+ announceLabel: "PUBLIC KEY",
1075
+ keyPair: identity.keyPair
1076
+ };
1077
+ }
1078
+ /** Execute `hbeam serve <file>` to transfer one file to the first peer. */
1079
+ async function runServeCommand(argv, options = {}) {
1080
+ const [targetFile] = argv;
1081
+ if (!targetFile) showUsageError("Missing file path.");
1082
+ const filePath = resolve(targetFile);
1083
+ const fileName = basename(filePath);
1084
+ const fileStat = await stat(filePath).catch(() => void 0);
1085
+ if (!fileStat || !fileStat.isFile()) showUsageError(`Not a readable file: ${targetFile}`);
1086
+ if (fileStat.size < MIN_FILE_SIZE) showUsageError(`Invalid file size: ${targetFile}`);
1087
+ const identity = await resolveServeIdentity(options.listen);
1088
+ const beam = identity.keyPair ? new Beam({
1089
+ announce: true,
1090
+ keyPair: identity.keyPair
1091
+ }) : new Beam(void 0, { announce: true });
1092
+ const spinner = createSessionSpinner();
1093
+ const lifecycle = createLifecycle(beam, spinner);
1094
+ blank();
1095
+ spinner.start();
1096
+ spinner.blank();
1097
+ spinner.write(dim$1(identity.announceLabel));
1098
+ spinner.write(cyan(beam.key));
1099
+ spinner.write(dim$1(`FILE ${fileName} (${formatFileSize(fileStat.size)})`));
1100
+ copyToClipboard(beam.key);
1101
+ beam.on("remote-address", ({ host, port }) => {
1102
+ if (lifecycle.done()) return;
1103
+ if (host) {
1104
+ spinner.write(dim$1(`ONLINE ${gray(`[${host}:${port}]`)}`));
1105
+ spinner.blank();
1106
+ }
1107
+ });
1108
+ beam.on("connected", () => {
1109
+ if (lifecycle.done()) return;
1110
+ spinner.stop();
1111
+ log(bold$1("PIPE ACTIVE"));
1112
+ write(gray("SENDING FILE"));
1113
+ blank();
1114
+ const header = encodeHeader({
1115
+ name: fileName,
1116
+ size: fileStat.size,
1117
+ type: "file"
1118
+ });
1119
+ if (beam.write(header) === false) {
1120
+ beam.once("drain", () => {
1121
+ createReadStream(filePath).pipe(beam);
1122
+ });
1123
+ return;
1124
+ }
1125
+ createReadStream(filePath).pipe(beam);
1126
+ });
1127
+ beam.on("error", (error) => {
1128
+ spinner.stop();
1129
+ const isPeerNotFound = error.message.includes("PEER_NOT_FOUND");
1130
+ if (isPeerNotFound) log(red$1(dim$1("PEER NOT FOUND")));
1131
+ else if (error.message.includes("connection reset by peer")) log(dim$1("PEER DISCONNECTED"));
1132
+ else logError(error.message);
1133
+ blank();
1134
+ if (!isPeerNotFound) lifecycle.shutdown();
1135
+ });
1136
+ beam.on("end", () => beam.end());
1137
+ beam.on("finish", () => {
1138
+ log(dim$1("FILE SENT"));
1139
+ blank();
1140
+ beam.destroy();
1141
+ });
894
1142
  }
895
1143
 
896
1144
  //#endregion
@@ -918,13 +1166,15 @@ const argv = mri(process.argv.slice(ARGV_OFFSET), {
918
1166
  alias: {
919
1167
  h: "help",
920
1168
  l: "listen",
1169
+ o: "output",
921
1170
  v: "version"
922
1171
  },
923
1172
  boolean: [
924
1173
  "help",
925
1174
  "listen",
926
1175
  "version"
927
- ]
1176
+ ],
1177
+ string: ["output"]
928
1178
  });
929
1179
  if (argv.help) {
930
1180
  writeBlock([
@@ -934,10 +1184,12 @@ if (argv.help) {
934
1184
  ` hbeam ${dim$1("[passphrase]")} ${dim$1("[options]")}`,
935
1185
  ` hbeam connect ${dim$1("<name>")}`,
936
1186
  ` hbeam peers ${dim$1("<add|rm|ls> ...")}`,
1187
+ ` hbeam serve ${dim$1("<file>")} ${dim$1("[--listen]")}`,
937
1188
  ` hbeam whoami`,
938
1189
  "",
939
1190
  `${bold$1("Options:")}`,
940
1191
  ` ${dim$1("-l, --listen")} Listen using passphrase or identity`,
1192
+ ` ${dim$1("-o, --output")} Save incoming file to a specific path`,
941
1193
  ` ${dim$1("-h, --help")} Show this help`,
942
1194
  ` ${dim$1("-v, --version")} Show version`,
943
1195
  "",
@@ -956,43 +1208,57 @@ if (argv.help) {
956
1208
  "",
957
1209
  ` ${dim$1("# Save and connect to peers by name")}`,
958
1210
  " hbeam peers add workserver <public-key>",
959
- " hbeam connect workserver"
1211
+ " hbeam connect workserver",
1212
+ "",
1213
+ ` ${dim$1("# Serve a single file")}`,
1214
+ " hbeam serve ./report.pdf"
960
1215
  ]);
961
1216
  process.exit(EXIT_SUCCESS);
962
1217
  }
963
1218
  if (argv.version) {
964
- write((await import("./package-Bwpu9EFI.mjs")).version ?? "0.0.0", NO_INDENT);
1219
+ write((await import("./package-BnMY6oRB.mjs")).version ?? "0.0.0", NO_INDENT);
965
1220
  process.exit(EXIT_SUCCESS);
966
1221
  }
967
1222
  const [firstArg, ...restArgs] = argv._;
1223
+ let ranSubcommand = false;
968
1224
  if (firstArg === "peers") process.exit(await runPeersCommand(restArgs));
969
- if (firstArg === "connect") process.exit(await runConnectCommand(restArgs));
1225
+ if (firstArg === "connect") {
1226
+ await runConnectCommand(restArgs, { outputPath: argv.output });
1227
+ ranSubcommand = true;
1228
+ }
1229
+ if (firstArg === "serve") {
1230
+ await runServeCommand(restArgs, { listen: argv.listen });
1231
+ ranSubcommand = true;
1232
+ }
970
1233
  if (firstArg === "whoami") process.exit(await runWhoamiCommand());
971
- const passphrase = firstArg;
972
- if (argv.listen && !passphrase) {
973
- const identity = await loadOrCreateIdentityWithMeta();
974
- if (identity.created) {
975
- log(dim$1("IDENTITY CREATED"));
976
- write(cyan(identity.keyPair.publicKey.toString("hex")));
1234
+ if (!ranSubcommand) {
1235
+ const passphrase = firstArg;
1236
+ if (argv.listen && !passphrase) {
1237
+ const identity = await loadOrCreateIdentityWithMeta();
1238
+ if (identity.created) {
1239
+ log(dim$1("IDENTITY CREATED"));
1240
+ write(cyan(identity.keyPair.publicKey.toString("hex")));
1241
+ }
1242
+ const beam = new Beam({
1243
+ announce: true,
1244
+ keyPair: identity.keyPair
1245
+ });
1246
+ runBeamSession(beam, {
1247
+ announceLabel: "PUBLIC KEY",
1248
+ copyValue: copyToClipboard,
1249
+ mode: "announce",
1250
+ value: beam.key
1251
+ });
1252
+ } else {
1253
+ const beam = new Beam(passphrase, argv.listen ? { announce: true } : void 0);
1254
+ runBeamSession(beam, {
1255
+ announceLabel: "PASSPHRASE",
1256
+ copyValue: copyToClipboard,
1257
+ mode: beam.announce ? "announce" : "connect",
1258
+ outputPath: argv.output,
1259
+ value: beam.announce ? beam.key : passphrase ?? "unknown"
1260
+ });
977
1261
  }
978
- const beam = new Beam({
979
- announce: true,
980
- keyPair: identity.keyPair
981
- });
982
- runBeamSession(beam, {
983
- announceLabel: "PUBLIC KEY",
984
- copyValue: copyToClipboard,
985
- mode: "announce",
986
- value: beam.key
987
- });
988
- } else {
989
- const beam = new Beam(passphrase, argv.listen ? { announce: true } : void 0);
990
- runBeamSession(beam, {
991
- announceLabel: "PASSPHRASE",
992
- copyValue: copyToClipboard,
993
- mode: beam.announce ? "announce" : "connect",
994
- value: beam.announce ? beam.key : passphrase ?? "unknown"
995
- });
996
1262
  }
997
1263
 
998
1264
  //#endregion
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["EXIT_SUCCESS","MODULUS_EVEN","REMAINDER_ZERO","PUBLIC_KEY_BYTES","KEY_SEED_BYTES","isHex","EXIT_FAILURE","dim","dim","bold","red","KEY_SEED_BYTES","PUBLIC_KEY_BYTES","EXIT_SUCCESS","EXIT_FAILURE","dim","dim","EXIT_SUCCESS","dim","bold","EXIT_SUCCESS","dim","bold","bold","dim"],"sources":["../src/lib/clipboard.ts","../src/lib/config.ts","../src/lib/encoding.ts","../src/lib/identity.ts","../src/lib/log.ts","../src/lib/lifecycle.ts","../src/lib/pulse.ts","../src/lib/session.ts","../src/lib/dht.ts","../src/beam.ts","../src/lib/addressbook.ts","../src/commands/connect.ts","../src/lib/prompt.ts","../src/commands/peers.ts","../src/commands/whoami.ts","../src/cli.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process'\n\ntype ClipboardCommand = Readonly<{\n\targs: readonly string[]\n\tcommand: string\n}>\n\nconst EXIT_SUCCESS = 0\nconst EMPTY_ARGS: readonly string[] = []\n\nconst DARWIN_COMMANDS: readonly ClipboardCommand[] = [{ args: EMPTY_ARGS, command: 'pbcopy' }]\nconst WINDOWS_COMMANDS: readonly ClipboardCommand[] = [{ args: EMPTY_ARGS, command: 'clip' }]\n\nconst LINUX_COMMANDS: readonly ClipboardCommand[] = [\n\t{ args: EMPTY_ARGS, command: 'wl-copy' },\n\t{ args: ['-selection', 'clipboard'], command: 'xclip' },\n\t{ args: ['--clipboard', '--input'], command: 'xsel' },\n]\n\nfunction getClipboardCommands(): readonly ClipboardCommand[] {\n\tif (process.platform === 'darwin') {\n\t\treturn DARWIN_COMMANDS\n\t}\n\tif (process.platform === 'win32') {\n\t\treturn WINDOWS_COMMANDS\n\t}\n\treturn LINUX_COMMANDS\n}\n\nfunction tryClipboardCommand(text: string, item: ClipboardCommand): boolean {\n\tconst result = spawnSync(item.command, item.args, {\n\t\tinput: text,\n\t\tstdio: ['pipe', 'ignore', 'ignore'],\n\t})\n\treturn !result.error && result.status === EXIT_SUCCESS\n}\n\n/** Copy text to the system clipboard using common platform commands. */\nexport function copyToClipboard(text: string): boolean {\n\tfor (const item of getClipboardCommands()) {\n\t\tif (tryClipboardCommand(text, item)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n","import { mkdir, readFile, writeFile, chmod } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nconst CONFIG_ROOT_DIR = '.config'\nconst APP_CONFIG_DIR = 'hbeam'\nconst CONFIG_DIR_ENV = 'HBEAM_CONFIG_DIR'\n\nconst DIR_MODE = 0o700\nconst FILE_MODE_SECURE = 0o600\n\n/** Absolute path to hbeam config directory. */\nexport function getConfigDir(): string {\n\treturn process.env[CONFIG_DIR_ENV] ?? join(homedir(), CONFIG_ROOT_DIR, APP_CONFIG_DIR)\n}\n\nfunction resolveConfigPath(filename: string): string {\n\treturn join(getConfigDir(), filename)\n}\n\n/** Ensure `~/.config/hbeam` exists with private directory permissions. */\nexport async function ensureConfigDir(): Promise<void> {\n\tawait mkdir(getConfigDir(), { mode: DIR_MODE, recursive: true })\n}\n\n/** Read and parse a JSON file from the hbeam config directory. */\nexport async function readJsonFile<T>(filename: string): Promise<T | undefined> {\n\tconst path = resolveConfigPath(filename)\n\ttry {\n\t\tconst raw = await readFile(path, 'utf8')\n\t\treturn JSON.parse(raw) as T\n\t} catch (error) {\n\t\tconst err = error as NodeJS.ErrnoException\n\t\tif (err.code === 'ENOENT') {\n\t\t\treturn undefined\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/**\n * Write a JSON file into the hbeam config directory.\n *\n * Set `secure` for files containing private key material.\n */\nexport async function writeJsonFile(\n\tfilename: string,\n\tdata: unknown,\n\toptions?: { secure?: boolean },\n): Promise<void> {\n\tconst path = resolveConfigPath(filename)\n\tawait ensureConfigDir()\n\tawait mkdir(dirname(path), { mode: DIR_MODE, recursive: true })\n\tawait writeFile(path, `${JSON.stringify(data, null, '\\t')}\\n`, 'utf8')\n\tif (options?.secure) {\n\t\tawait chmod(path, FILE_MODE_SECURE)\n\t}\n}\n","import * as b4a from 'b4a'\nimport b32 from 'hi-base32'\nimport sodium from 'sodium-universal'\n\n/** Encode a buffer as a lowercase base32 string without padding. */\nexport function toBase32(buf: Buffer): string {\n\treturn b32.encode(buf).replace(/=/g, '').toLowerCase()\n}\n\n/** Decode a base32 string back into a raw Buffer. */\nexport function fromBase32(str: string): Buffer {\n\treturn b4a.from(b32.decode.asBytes(str.toUpperCase()))\n}\n\n/** Generate cryptographically secure random bytes. */\nexport function randomBytes(length: number): Buffer {\n\tconst buffer = b4a.alloc(length)\n\tsodium.randombytes_buf(buffer)\n\treturn buffer\n}\n","import DHT from 'hyperdht'\n\nimport { readJsonFile, writeJsonFile } from './config.ts'\nimport { randomBytes } from './encoding.ts'\n\nimport type { Identity, KeyPair } from '../types.ts'\n\nconst IDENTITY_FILE = 'identity.json'\n\nconst MODULUS_EVEN = 2\nconst REMAINDER_ZERO = 0\nconst PUBLIC_KEY_BYTES = 32\nconst KEY_SEED_BYTES = 32\nconst SECRET_KEY_BYTES = 64\n\nfunction asHex(buffer: Buffer): string {\n\treturn buffer.toString('hex')\n}\n\nfunction fromHex(hex: string): Buffer {\n\treturn Buffer.from(hex, 'hex')\n}\n\nfunction isHex(value: string): boolean {\n\treturn /^[0-9a-f]+$/i.test(value) && value.length % MODULUS_EVEN === REMAINDER_ZERO\n}\n\nfunction parseIdentity(value: Identity): KeyPair {\n\tif (!isHex(value.publicKey) || !isHex(value.secretKey)) {\n\t\tthrow new Error('Invalid identity file: keys must be hex-encoded')\n\t}\n\n\tconst publicKey = fromHex(value.publicKey)\n\tconst secretKey = fromHex(value.secretKey)\n\n\tif (publicKey.length !== PUBLIC_KEY_BYTES || secretKey.length !== SECRET_KEY_BYTES) {\n\t\tthrow new Error('Invalid identity file: unexpected key lengths')\n\t}\n\n\treturn { publicKey, secretKey }\n}\n\nfunction serializeIdentity(keyPair: KeyPair): Identity {\n\treturn {\n\t\tpublicKey: asHex(keyPair.publicKey),\n\t\tsecretKey: asHex(keyPair.secretKey),\n\t}\n}\n\nfunction createIdentity(): KeyPair {\n\treturn DHT.keyPair(randomBytes(KEY_SEED_BYTES))\n}\n\n/** Load a persisted identity if present; otherwise create and persist one. */\nexport async function loadOrCreateIdentityWithMeta(): Promise<{\n\tcreated: boolean\n\tkeyPair: KeyPair\n}> {\n\tconst existing = await readJsonFile<Identity>(IDENTITY_FILE)\n\tif (existing) {\n\t\treturn { created: false, keyPair: parseIdentity(existing) }\n\t}\n\n\tconst keyPair = createIdentity()\n\tawait writeJsonFile(IDENTITY_FILE, serializeIdentity(keyPair), { secure: true })\n\treturn { created: true, keyPair }\n}\n\n/** Load or create the local hbeam identity keypair. */\nexport async function loadOrCreateIdentity(): Promise<KeyPair> {\n\tconst { keyPair } = await loadOrCreateIdentityWithMeta()\n\treturn keyPair\n}\n\n/** Return the public key hex string for the local identity. */\nexport async function getPublicKeyHex(): Promise<string> {\n\tconst keyPair = await loadOrCreateIdentity()\n\treturn asHex(keyPair.publicKey)\n}\n","import { dim, red, yellow } from 'colorette'\n\nexport { bold, cyan, dim, gray, green, italic, red, yellow } from 'colorette'\n\nconst SEPARATOR_WIDTH = 36\nconst CLEAR_LINE = '\\r\\u001B[2K'\nconst NO_OFFSET = 0\n\nexport const INDENT = ' '\nexport const SEPARATOR = dim('╌'.repeat(SEPARATOR_WIDTH))\n\n/** Write a line to stderr at the standard indent level. */\nexport function write(message: string, indent: string = INDENT): void {\n\tprocess.stderr.write(`${indent}${message}\\n`)\n}\n\n/** Write a blank line to stderr. */\nexport function blank(): void {\n\tprocess.stderr.write('\\n')\n}\n\n/** Write a pre-formatted block (multiple lines) to stderr. */\nexport function writeBlock(lines: string[]): void {\n\tfor (const line of lines) {\n\t\tprocess.stderr.write(`${INDENT}${line}\\n`)\n\t}\n}\n\n/** Write a status message to stderr at the standard indent level. */\nexport function log(message: string): void {\n\twrite(message)\n}\n\n/** Write an error message to stderr at the standard indent level. */\nexport function logError(message: string): void {\n\tprocess.stderr.write(`${INDENT}${red('ERROR')} ${message}\\n`)\n}\n\n/** Write a warning/notice message to stderr with a yellow prefix. */\nexport function logWarn(message: string): void {\n\tprocess.stderr.write(`${yellow('!')} ${message}\\n`)\n}\n\n/** Clear the current line (wipe terminal-echoed ^C, etc.). Falls back to a newline on non-TTY. */\nexport function clearLine(): void {\n\tif (process.stderr.isTTY) {\n\t\tprocess.stderr.write(CLEAR_LINE)\n\t} else {\n\t\tprocess.stderr.write('\\n')\n\t}\n}\n\n// -- Spinner ----------------------------------------------------------------\n\n/** ANSI escape: move cursor up N lines. */\nfunction cursorUp(n: number): string {\n\treturn `\\u001B[${n}A`\n}\n\n/** ANSI escape: move cursor down N lines. */\nfunction cursorDown(n: number): string {\n\treturn `\\u001B[${n}B`\n}\n\n/** Handle for a line that animates in-place while content prints below. */\nexport interface Spinner {\n\t/** Write a blank line below the spinner and track the cursor offset. */\n\tblank(): void\n\t/** Render the first frame and begin the animation loop. */\n\tstart(): void\n\t/** Stop the animation loop. */\n\tstop(): void\n\t/** Write an indented line below the spinner and track the cursor offset. */\n\twrite(message: string): void\n}\n\n/** Animate a single line in-place while content continues to print below it. */\nexport function createSpinner(frames: readonly string[], intervalMs: number): Spinner {\n\tlet offset = NO_OFFSET\n\tlet frameIndex = NO_OFFSET\n\tlet timer: ReturnType<typeof globalThis.setInterval> | undefined = undefined\n\n\tfunction render(): void {\n\t\tif (offset > NO_OFFSET) {\n\t\t\tprocess.stderr.write(cursorUp(offset))\n\t\t}\n\t\tprocess.stderr.write(`${CLEAR_LINE}${INDENT}${frames[frameIndex]}`)\n\t\tif (offset > NO_OFFSET) {\n\t\t\tprocess.stderr.write(`${cursorDown(offset)}\\r`)\n\t\t}\n\t\tframeIndex++\n\t\tif (frameIndex >= frames.length) {\n\t\t\tframeIndex = NO_OFFSET\n\t\t}\n\t}\n\n\treturn {\n\t\tblank(): void {\n\t\t\tblank()\n\t\t\toffset++\n\t\t},\n\t\tstart(): void {\n\t\t\trender()\n\t\t\tprocess.stderr.write('\\n')\n\t\t\toffset++\n\t\t\ttimer = globalThis.setInterval(render, intervalMs)\n\t\t},\n\t\tstop(): void {\n\t\t\tif (timer) {\n\t\t\t\tglobalThis.clearInterval(timer)\n\t\t\t\ttimer = undefined\n\t\t\t}\n\t\t},\n\t\twrite(message: string): void {\n\t\t\twrite(message)\n\t\t\toffset++\n\t\t},\n\t}\n}\n","import { blank, clearLine, dim, log } from './log.ts'\n\nimport type { Beam } from '../beam.ts'\n\nconst EXIT_FAILURE = 1\nconst SHUTDOWN_TIMEOUT_MS = 2000\n\n/** Controller for graceful shutdown of a beam session. */\nexport interface Lifecycle {\n\t/** Returns true if shutdown is in progress (use as an early-return guard). */\n\tdone(): boolean\n\t/** Tear down the beam, stop the spinner, and exit after a grace period. */\n\tshutdown(): void\n}\n\n/**\n * Create a lifecycle controller that manages SIGINT handling and graceful shutdown.\n *\n * Registers a one-shot SIGINT handler on creation. All shutdown state is\n * encapsulated — callers just check `done()` and call `shutdown()`.\n */\nexport function createLifecycle(beam: Beam, spinner?: { stop(): void }): Lifecycle {\n\tlet isShuttingDown = false\n\n\tfunction shutdown(): void {\n\t\tif (isShuttingDown) {\n\t\t\treturn\n\t\t}\n\t\tisShuttingDown = true\n\t\tspinner?.stop()\n\n\t\tlog(dim('SHUTTING DOWN'))\n\t\tblank()\n\n\t\tconst timeout = globalThis.setTimeout(() => {\n\t\t\tprocess.exit(EXIT_FAILURE)\n\t\t}, SHUTDOWN_TIMEOUT_MS)\n\n\t\tbeam.destroy()\n\t\tbeam.on('close', () => {\n\t\t\tglobalThis.clearTimeout(timeout)\n\t\t})\n\t}\n\n\tprocess.once('SIGINT', () => {\n\t\tclearLine()\n\t\tshutdown()\n\t})\n\n\treturn {\n\t\tdone(): boolean {\n\t\t\treturn isShuttingDown\n\t\t},\n\t\tshutdown,\n\t}\n}\n","import { bold, dim } from 'colorette'\n\nconst INTERVAL_MS = 125\nconst PEAK_A = 5\nconst PEAK_B = 15\nconst PEAK_WRAP = 25\nconst DIST_PEAK = 0\nconst DIST_NEAR = 1\n\nconst RAW_FRAMES: readonly string[] = [\n\t' ',\n\t'· ',\n\t'·· ',\n\t'··· ',\n\t'···· ',\n\t'·····',\n\t' ····',\n\t' ···',\n\t' ··',\n\t' ·',\n\t' ',\n\t' ·',\n\t' ··',\n\t' ···',\n\t' ····',\n\t'·····',\n\t'···· ',\n\t'··· ',\n\t'·· ',\n\t'· ',\n\t' ',\n]\n\n/**\n * Generate the styled spinner frames for the HBEAM pulse animation.\n *\n * Each frame is rendered with a brightness gradient: bold at peak,\n * normal near peak, and dim everywhere else.\n *\n * @param label - The text label to prefix each frame (e.g. \"HBEAM\").\n * @returns An object with the styled `frames` array and `intervalMs` timing.\n */\nexport function createPulseFrames(label: string): { frames: string[]; intervalMs: number } {\n\tconst frames = RAW_FRAMES.map((s, i) => {\n\t\tconst distanceToPeak = Math.min(\n\t\t\tMath.abs(i - PEAK_A),\n\t\t\tMath.abs(i - PEAK_B),\n\t\t\tMath.abs(i - PEAK_WRAP),\n\t\t)\n\t\tlet glyph = dim(s)\n\t\tif (distanceToPeak === DIST_PEAK) {\n\t\t\tglyph = bold(s)\n\t\t} else if (distanceToPeak === DIST_NEAR) {\n\t\t\tglyph = s\n\t\t}\n\t\treturn `${bold(label)} ${glyph}`\n\t})\n\n\treturn { frames, intervalMs: INTERVAL_MS }\n}\n","import { Transform } from 'node:stream'\n\nimport { createLifecycle } from './lifecycle.ts'\nimport {\n\tblank,\n\tbold,\n\tcreateSpinner,\n\tcyan,\n\tdim,\n\tgray,\n\tINDENT,\n\tlog,\n\tlogError,\n\tred,\n\tSEPARATOR,\n\twrite,\n} from './log.ts'\nimport { createPulseFrames } from './pulse.ts'\n\nimport type { Beam } from '../beam.ts'\nimport type { ConnectionInfo } from '../types.ts'\n\ntype SessionMode = 'announce' | 'connect'\n\nexport interface SessionOptions {\n\tannounceLabel?: string\n\tcopyValue?: (text: string) => void\n\tmode: SessionMode\n\tvalue: string\n}\n\n/** Run the standard hbeam CLI session UI and stdin/stdout piping. */\nexport function runBeamSession(beam: Beam, options: SessionOptions): void {\n\tconst { frames, intervalMs } = createPulseFrames('HBEAM')\n\tconst spinner = createSpinner(frames, intervalMs)\n\tconst lifecycle = createLifecycle(beam, spinner)\n\n\tblank()\n\tspinner.start()\n\tspinner.blank()\n\n\tif (options.mode === 'announce') {\n\t\tspinner.write(dim(options.announceLabel ?? 'PUBLIC KEY'))\n\t\tspinner.write(cyan(options.value))\n\t\toptions.copyValue?.(options.value)\n\t} else {\n\t\tspinner.write(dim('CONNECTING'))\n\t\tspinner.write(cyan(options.value))\n\t}\n\n\tbeam.on('remote-address', ({ host, port }: ConnectionInfo) => {\n\t\tif (lifecycle.done()) {\n\t\t\treturn\n\t\t}\n\t\tif (host) {\n\t\t\tspinner.write(dim(`ONLINE ${gray(`[${host}:${port}]`)}`))\n\t\t\tspinner.blank()\n\t\t}\n\t})\n\n\tbeam.on('connected', () => {\n\t\tif (lifecycle.done()) {\n\t\t\treturn\n\t\t}\n\t\tspinner.stop()\n\t\tlog(bold('PIPE ACTIVE'))\n\t\twrite(gray('CTRL+C TO TERMINATE'))\n\t\tblank()\n\t})\n\n\tbeam.on('error', (error: Error) => {\n\t\tspinner.stop()\n\t\tconst isPeerNotFound = error.message.includes('PEER_NOT_FOUND')\n\t\tif (isPeerNotFound) {\n\t\t\tlog(red(dim('PEER NOT FOUND')))\n\t\t} else if (error.message.includes('connection reset by peer')) {\n\t\t\tlog(dim('PEER DISCONNECTED'))\n\t\t} else {\n\t\t\tlogError(error.message)\n\t\t}\n\t\tblank()\n\t\tif (!isPeerNotFound) {\n\t\t\tlifecycle.shutdown()\n\t\t}\n\t})\n\n\tbeam.on('end', () => beam.end())\n\n\tlet receivedData = false\n\n\tconst indent = new Transform({\n\t\tflush(cb) {\n\t\t\tif (receivedData) {\n\t\t\t\tblank()\n\t\t\t\twrite(SEPARATOR)\n\t\t\t}\n\t\t\tcb(undefined, '\\n')\n\t\t},\n\t\ttransform(chunk: Buffer, _encoding, cb) {\n\t\t\tif (!receivedData) {\n\t\t\t\treceivedData = true\n\t\t\t\twrite(SEPARATOR)\n\t\t\t\tblank()\n\t\t\t}\n\t\t\tconst lines = chunk.toString().replace(/^(?!$)/gm, INDENT)\n\t\t\tcb(undefined, lines)\n\t\t},\n\t})\n\n\tprocess.stdin.pipe(beam).pipe(indent).pipe(process.stdout)\n\n\tif (typeof process.stdin.unref === 'function') {\n\t\tprocess.stdin.unref()\n\t}\n}\n","import { createHash } from 'node:crypto'\n\nimport * as b4a from 'b4a'\nimport DHT from 'hyperdht'\n\nimport { fromBase32 } from './encoding.ts'\n\nimport type { EncryptedSocket, HyperDHTNode, KeyPair } from '../types.ts'\n\nconst KEY_SEED_BYTES = 32\n\n/**\n * Normalize an arbitrary decoded seed into exactly 32 bytes.\n *\n * HyperDHT's `keyPair()` behavior for non-32-byte seeds can differ across JS\n * runtimes (e.g. Node vs Bun). By hashing to 32 bytes we ensure both sides\n * derive the same keypair for a given passphrase.\n */\nfunction normalizeSeed(seed: Buffer): Buffer {\n\tif (seed.length === KEY_SEED_BYTES) {\n\t\treturn seed\n\t}\n\tconst digest = createHash('sha256').update(seed).digest()\n\treturn b4a.from(digest)\n}\n\n/** Derive a Noise keypair from a base32-encoded passphrase. */\nexport function deriveKeyPair(passphrase: string): KeyPair {\n\tconst seed = fromBase32(passphrase)\n\treturn DHT.keyPair(normalizeSeed(seed))\n}\n\n/** Create an ephemeral HyperDHT node that is destroyed with the beam. */\nexport function createNode(): HyperDHTNode {\n\treturn new DHT({ ephemeral: true }) as unknown as HyperDHTNode\n}\n\n/** Wait for an encrypted socket to complete its Noise handshake. */\nexport function awaitOpen(socket: EncryptedSocket): Promise<void> {\n\treturn new Promise<void>((resolve, reject) => {\n\t\tsocket.once('open', resolve)\n\t\tsocket.once('close', reject)\n\t\tsocket.once('error', reject)\n\t})\n}\n\n/** Create a firewall that rejects any connection not matching the keypair. */\nexport function createFirewall(keyPair: KeyPair): (remotePublicKey: Buffer) => boolean {\n\treturn (remotePublicKey: Buffer) => !b4a.equals(remotePublicKey, keyPair.publicKey)\n}\n","import queueTick from 'queue-tick'\nimport { Duplex } from 'streamx'\n\nimport { awaitOpen, createFirewall, createNode, deriveKeyPair } from './lib/dht.ts'\nimport { randomBytes, toBase32 } from './lib/encoding.ts'\n\nimport type {\n\tBeamOptions,\n\tConnectionInfo,\n\tEncryptedSocket,\n\tHyperDHTNode,\n\tHyperDHTServer,\n\tKeyPair,\n\tStreamCallback,\n} from './types.ts'\n\n/** Number of random bytes used to generate a passphrase seed. */\nconst KEY_SEED_BYTES = 32\n\n/**\n * A 1-to-1 end-to-end encrypted duplex stream powered by HyperDHT.\n *\n * Creates an encrypted tunnel between two peers using a shared passphrase.\n * If no passphrase is provided, one is generated and the beam listens for\n * an incoming connection (server mode). When a passphrase is provided, the\n * beam connects to the listening peer (client mode).\n *\n * @example\n * ```ts\n * const server = new Beam()\n * console.log(server.key) // Share this with the other side\n *\n * const client = new Beam(server.key)\n * ```\n */\nexport class Beam extends Duplex {\n\t/** Base32-encoded passphrase for peer discovery and key derivation. */\n\treadonly key: string\n\n\t/** Whether this beam is announcing (server) or connecting (client). */\n\treadonly announce: boolean\n\n\tprivate node: HyperDHTNode | undefined\n\tprivate server: HyperDHTServer | undefined = undefined\n\tprivate inbound: EncryptedSocket | undefined = undefined\n\tprivate outbound: EncryptedSocket | undefined = undefined\n\tprivate readonly keyPairOverride: KeyPair | undefined\n\tprivate readonly remotePublicKeyOverride: Buffer | undefined\n\tprivate readonly openInboundFirewall: boolean\n\n\tprivate openCallback: StreamCallback | undefined = undefined\n\tprivate readCallback: StreamCallback | undefined = undefined\n\tprivate drainCallback: StreamCallback | undefined = undefined\n\n\tconstructor(keyOrOptions?: string | BeamOptions, options?: BeamOptions) {\n\t\tsuper()\n\n\t\tlet key: string | undefined = undefined\n\t\tlet opts: BeamOptions = {}\n\t\tconst passphraseWasProvided = typeof keyOrOptions === 'string'\n\n\t\tif (passphraseWasProvided) {\n\t\t\tkey = keyOrOptions\n\t\t\topts = options ?? {}\n\t\t} else {\n\t\t\topts = keyOrOptions ?? {}\n\t\t}\n\n\t\tlet shouldAnnounce = opts.announce ?? false\n\n\t\tif (!key && !opts.keyPair) {\n\t\t\tkey = toBase32(randomBytes(KEY_SEED_BYTES))\n\t\t\tshouldAnnounce = true\n\t\t} else if (!key && opts.keyPair) {\n\t\t\tkey = opts.keyPair.publicKey.toString('hex')\n\t\t}\n\t\tif (!key) {\n\t\t\tthrow new Error('Missing key material')\n\t\t}\n\n\t\tthis.key = key\n\t\tthis.announce = shouldAnnounce\n\t\tthis.node = (opts.dht as HyperDHTNode) ?? undefined\n\t\tthis.keyPairOverride = opts.keyPair\n\t\tthis.remotePublicKeyOverride = opts.remotePublicKey\n\t\tthis.openInboundFirewall = !passphraseWasProvided && opts.keyPair !== undefined\n\t}\n\n\t/** Whether a peer connection has been established. */\n\tget connected(): boolean {\n\t\treturn this.outbound !== undefined\n\t}\n\n\t// Streamx lifecycle\n\n\toverride async _open(cb: StreamCallback): Promise<void> {\n\t\tthis.openCallback = cb\n\t\tconst keyPair = this.keyPairOverride ?? deriveKeyPair(this.key)\n\t\tthis.node ??= createNode()\n\n\t\tif (this.announce) {\n\t\t\tawait this.listenAsServer(keyPair)\n\t\t} else {\n\t\t\tawait this.connectAsClient(keyPair)\n\t\t}\n\t}\n\n\toverride _read(cb: StreamCallback): void {\n\t\tthis.readCallback = cb\n\t\tthis.inbound?.resume()\n\t}\n\n\toverride _write(data: unknown, cb: StreamCallback): void {\n\t\tif (this.outbound!.write(data as Buffer) !== false) {\n\t\t\tcb()\n\t\t\treturn\n\t\t}\n\t\tthis.drainCallback = cb\n\t}\n\n\toverride _final(cb: StreamCallback): void {\n\t\tconst done = (): void => {\n\t\t\tthis.outbound!.removeListener('finish', done)\n\t\t\tthis.outbound!.removeListener('error', done)\n\t\t\tcb()\n\t\t}\n\t\tthis.outbound!.end()\n\t\tthis.outbound!.on('finish', done)\n\t\tthis.outbound!.on('error', done)\n\t}\n\n\toverride _predestroy(): void {\n\t\tthis.inbound?.destroy()\n\t\tthis.outbound?.destroy()\n\t\tconst error = new Error('Destroyed')\n\t\tthis.resolveOpen(error)\n\t\tthis.resolveRead(error)\n\t\tthis.resolveDrain(error)\n\t}\n\n\toverride async _destroy(cb: StreamCallback): Promise<void> {\n\t\tif (!this.node) {\n\t\t\tcb()\n\t\t\treturn\n\t\t}\n\t\tif (this.server) {\n\t\t\tawait this.server.close().catch(() => {})\n\t\t}\n\t\tawait this.node.destroy().catch(() => {})\n\t\tcb()\n\t}\n\n\t// Connection setup\n\n\tprivate async listenAsServer(keyPair: KeyPair): Promise<void> {\n\t\tconst serverOptions = this.openInboundFirewall\n\t\t\t? undefined\n\t\t\t: { firewall: createFirewall(keyPair) }\n\t\tthis.server = this.node!.createServer(serverOptions)\n\t\tthis.server.on('connection', (socket: EncryptedSocket) => this.handleConnection(socket))\n\n\t\ttry {\n\t\t\tawait this.server.listen(keyPair)\n\t\t} catch (error) {\n\t\t\tthis.resolveOpen(error as Error)\n\t\t\treturn\n\t\t}\n\t\tthis.emitRemoteAddress()\n\t}\n\n\tprivate async connectAsClient(keyPair: KeyPair): Promise<void> {\n\t\tconst remotePublicKey = this.remotePublicKeyOverride ?? keyPair.publicKey\n\t\tconst socket: EncryptedSocket = this.node!.connect(remotePublicKey, { keyPair })\n\n\t\ttry {\n\t\t\tawait awaitOpen(socket)\n\t\t} catch (error) {\n\t\t\tthis.resolveOpen(error as Error)\n\t\t\treturn\n\t\t}\n\t\tthis.emitRemoteAddress()\n\t\tthis.handleConnection(socket)\n\t}\n\n\tprivate handleConnection(socket: EncryptedSocket): void {\n\t\tsocket.on('data', (data: Buffer) => {\n\t\t\tif (!this.inbound) {\n\t\t\t\tthis.inbound = socket\n\t\t\t\tthis.inbound.on('error', (err: Error) => this.destroy(err))\n\t\t\t\tthis.inbound.on('end', () => this.pushEndOfStream())\n\t\t\t}\n\t\t\tif (socket !== this.inbound) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.pushData(data) === false) {\n\t\t\t\tsocket.pause()\n\t\t\t}\n\t\t})\n\n\t\tsocket.on('end', () => {\n\t\t\tif (this.inbound) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pushEndOfStream()\n\t\t})\n\n\t\tif (!this.outbound) {\n\t\t\tthis.outbound = socket\n\t\t\tthis.outbound.on('error', (err: Error) => this.destroy(err))\n\t\t\tthis.outbound.on('drain', () => this.resolveDrain())\n\t\t\tthis.emit('connected')\n\t\t\tthis.resolveOpen()\n\t\t}\n\t}\n\n\t// Helpers\n\n\tprivate pushData(data: Buffer | null): boolean {\n\t\tconst result = this.push(data)\n\t\tqueueTick(() => this.resolveRead())\n\t\treturn result\n\t}\n\n\tprivate pushEndOfStream(): void {\n\t\t// oxlint-disable-next-line unicorn/no-null\n\t\tthis.pushData(null)\n\t}\n\n\tprivate emitRemoteAddress(): void {\n\t\tthis.emit('remote-address', {\n\t\t\thost: this.node!.host,\n\t\t\tport: this.node!.port,\n\t\t} satisfies ConnectionInfo)\n\t}\n\n\tprivate resolveOpen(error?: Error): void {\n\t\tconst cb = this.openCallback\n\t\tif (cb) {\n\t\t\tthis.openCallback = undefined\n\t\t\tcb(error)\n\t\t}\n\t}\n\n\tprivate resolveRead(error?: Error): void {\n\t\tconst cb = this.readCallback\n\t\tif (cb) {\n\t\t\tthis.readCallback = undefined\n\t\t\tcb(error)\n\t\t}\n\t}\n\n\tprivate resolveDrain(error?: Error): void {\n\t\tconst cb = this.drainCallback\n\t\tif (cb) {\n\t\t\tthis.drainCallback = undefined\n\t\t\tcb(error)\n\t\t}\n\t}\n}\n","import { readJsonFile, writeJsonFile } from './config.ts'\n\nimport type { AddressBook, Peer } from '../types.ts'\n\nconst PEERS_FILE = 'peers.json'\nconst MODULUS_EVEN = 2\nconst PUBLIC_KEY_BYTES = 32\nconst REMAINDER_ZERO = 0\n\nfunction isHex(value: string): boolean {\n\treturn /^[0-9a-f]+$/i.test(value) && value.length % MODULUS_EVEN === REMAINDER_ZERO\n}\n\nfunction normalizePublicKeyHex(publicKeyHex: string): string {\n\tconst normalized = publicKeyHex.trim().toLowerCase()\n\tif (!isHex(normalized)) {\n\t\tthrow new Error('Public key must be a valid hex string')\n\t}\n\tconst key = Buffer.from(normalized, 'hex')\n\tif (key.length !== PUBLIC_KEY_BYTES) {\n\t\tthrow new Error('Public key must be 32 bytes (64 hex chars)')\n\t}\n\treturn normalized\n}\n\nfunction normalizePeerName(name: string): string {\n\tconst normalized = name.trim().toLowerCase()\n\tif (!/^[a-z0-9-]+$/.test(normalized)) {\n\t\tthrow new Error('Peer name must use only letters, numbers, and hyphens')\n\t}\n\treturn normalized\n}\n\nasync function readAddressBook(): Promise<AddressBook> {\n\treturn (await readJsonFile<AddressBook>(PEERS_FILE)) ?? {}\n}\n\nasync function writeAddressBook(addressBook: AddressBook): Promise<void> {\n\tawait writeJsonFile(PEERS_FILE, addressBook)\n}\n\n/** Add or update a named peer in the local address book. */\nexport async function addPeer(name: string, publicKeyHex: string): Promise<Peer> {\n\tconst normalizedName = normalizePeerName(name)\n\tconst normalizedPublicKey = normalizePublicKeyHex(publicKeyHex)\n\tconst addressBook = await readAddressBook()\n\n\tconst peer: Peer = {\n\t\taddedAt: new Date().toISOString(),\n\t\tpublicKey: normalizedPublicKey,\n\t}\n\taddressBook[normalizedName] = peer\n\n\tawait writeAddressBook(addressBook)\n\treturn peer\n}\n\n/** Remove a peer from the local address book. */\nexport async function removePeer(name: string): Promise<boolean> {\n\tconst normalizedName = normalizePeerName(name)\n\tconst addressBook = await readAddressBook()\n\tif (!addressBook[normalizedName]) {\n\t\treturn false\n\t}\n\tdelete addressBook[normalizedName]\n\tawait writeAddressBook(addressBook)\n\treturn true\n}\n\n/** Return all peers sorted by name. */\nexport async function listPeers(): Promise<({ name: string } & Peer)[]> {\n\tconst addressBook = await readAddressBook()\n\treturn Object.entries(addressBook)\n\t\t.map(([name, peer]) => ({ name, ...peer }))\n\t\t.toSorted((a, b) => a.name.localeCompare(b.name))\n}\n\n/** Lookup a single peer by name. */\nexport async function getPeer(name: string): Promise<Peer | undefined> {\n\tconst normalizedName = normalizePeerName(name)\n\tconst addressBook = await readAddressBook()\n\treturn addressBook[normalizedName]\n}\n","import { getPeer } from '@/lib/addressbook.ts'\nimport { loadOrCreateIdentityWithMeta } from '@/lib/identity.ts'\nimport { blank, cyan, dim, log, logError, write } from '@/lib/log.ts'\nimport { runBeamSession } from '@/lib/session.ts'\n\nimport { Beam } from '../beam.ts'\n\nconst EXIT_SUCCESS = 0\nconst EXIT_FAILURE = 1\nconst PUBLIC_KEY_BYTES = 32\n\n/** Execute `hbeam connect <name>`. */\nexport async function runConnectCommand(argv: string[]): Promise<number> {\n\tconst [name] = argv\n\tif (!name) {\n\t\tblank()\n\t\tlogError('Missing peer name.')\n\t\twrite(dim('Usage: hbeam connect <name>'))\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\tconst peer = await getPeer(name).catch(() => undefined)\n\tif (!peer) {\n\t\tblank()\n\t\tlogError(`Unknown peer: ${name}`)\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\tconst remotePublicKey = Buffer.from(peer.publicKey, 'hex')\n\tif (remotePublicKey.length !== PUBLIC_KEY_BYTES) {\n\t\tblank()\n\t\tlogError(`Invalid public key for peer: ${name}`)\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\tconst identity = await loadOrCreateIdentityWithMeta()\n\tif (identity.created) {\n\t\tblank()\n\t\tlog(dim('IDENTITY CREATED'))\n\t\twrite(cyan(identity.keyPair.publicKey.toString('hex')))\n\t}\n\n\tconst beam = new Beam({\n\t\tkeyPair: identity.keyPair,\n\t\tremotePublicKey,\n\t})\n\n\trunBeamSession(beam, {\n\t\tmode: 'connect',\n\t\tvalue: name,\n\t})\n\n\treturn EXIT_SUCCESS\n}\n","import { INDENT, dim } from './log.ts'\n\nconst YES = 'y'\nconst NO = 'n'\nconst CTRL_C = '\\u0003'\nconst ENTER = '\\r'\nconst FIRST_CHAR_INDEX = 0\n\nfunction firstChar(data: Buffer): string {\n\treturn data.toString('utf8').toLowerCase().charAt(FIRST_CHAR_INDEX)\n}\n\n/** Minimal single-keypress confirm prompt with `y/N` default. */\nexport async function confirm(message: string): Promise<boolean> {\n\tprocess.stderr.write(`${INDENT}${message} ${dim('(y/N)')} `)\n\n\tconst stdin = process.stdin\n\tif (!stdin.isTTY || typeof stdin.setRawMode !== 'function') {\n\t\tprocess.stderr.write('\\n')\n\t\treturn false\n\t}\n\n\tconst originalRaw = stdin.isRaw\n\n\treturn await new Promise<boolean>(resolve => {\n\t\tfunction cleanup(answer: boolean): void {\n\t\t\tstdin.setRawMode(Boolean(originalRaw))\n\t\t\tstdin.pause()\n\t\t\tstdin.removeListener('data', onData)\n\t\t\tprocess.stderr.write(`${answer ? YES : NO}\\n`)\n\t\t\tresolve(answer)\n\t\t}\n\n\t\tfunction onData(data: Buffer): void {\n\t\t\tconst key = firstChar(data)\n\t\t\tif (key === CTRL_C) {\n\t\t\t\tprocess.stderr.write('\\n')\n\t\t\t\tprocess.kill(process.pid, 'SIGINT')\n\t\t\t\tcleanup(false)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (key === YES) {\n\t\t\t\tcleanup(true)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (key === NO || key === '' || key === ENTER) {\n\t\t\t\tcleanup(false)\n\t\t\t}\n\t\t}\n\n\t\tstdin.setRawMode(true)\n\t\tstdin.resume()\n\t\tstdin.on('data', onData)\n\t})\n}\n","import { addPeer, getPeer, listPeers, removePeer } from '@/lib/addressbook.ts'\nimport { blank, bold, cyan, dim, log, logError, write } from '@/lib/log.ts'\nimport { confirm } from '@/lib/prompt.ts'\n\nconst EXIT_SUCCESS = 0\nconst EXIT_FAILURE = 1\nconst START_INDEX = 0\nconst PUBLIC_KEY_PREFIX_LENGTH = 8\nconst SECONDS_PER_MINUTE = 60\nconst MINUTES_PER_HOUR = 60\nconst HOURS_PER_DAY = 24\nconst DAYS_PER_WEEK = 7\nconst MILLISECONDS_PER_SECOND = 1000\nconst SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR\nconst SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY\nconst SECONDS_PER_WEEK = SECONDS_PER_DAY * DAYS_PER_WEEK\nconst EMPTY_PEERS = 0\n\nfunction formatAge(addedAt: string): string {\n\tconst then = Date.parse(addedAt)\n\n\tif (Number.isNaN(then)) {\n\t\treturn 'unknown'\n\t}\n\n\tconst seconds = Math.floor((Date.now() - then) / MILLISECONDS_PER_SECOND)\n\n\tif (seconds < SECONDS_PER_MINUTE) {\n\t\treturn 'just now'\n\t}\n\n\tif (seconds < SECONDS_PER_HOUR) {\n\t\treturn `${Math.floor(seconds / SECONDS_PER_MINUTE)}m ago`\n\t}\n\n\tif (seconds < SECONDS_PER_DAY) {\n\t\treturn `${Math.floor(seconds / SECONDS_PER_HOUR)}h ago`\n\t}\n\n\tif (seconds < SECONDS_PER_WEEK) {\n\t\treturn `${Math.floor(seconds / SECONDS_PER_DAY)}d ago`\n\t}\n\n\treturn `${Math.floor(seconds / SECONDS_PER_WEEK)}w ago`\n}\n\nfunction shortenKey(publicKey: string): string {\n\treturn `${publicKey.slice(START_INDEX, PUBLIC_KEY_PREFIX_LENGTH)}...`\n}\n\nfunction usage(): void {\n\tlogError('Invalid peers command.')\n\twrite(dim('Usage: hbeam peers add <name> <public-key>'))\n\twrite(dim(' hbeam peers rm <name>'))\n\twrite(dim(' hbeam peers ls'))\n}\n\nasync function handleAdd(name: string | undefined, publicKey: string | undefined): Promise<number> {\n\tif (!name || !publicKey) {\n\t\tblank()\n\t\tusage()\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\ttry {\n\t\tawait addPeer(name, publicKey)\n\t} catch (error) {\n\t\tblank()\n\t\tlogError((error as Error).message)\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\tblank()\n\tlog(bold('SAVED'))\n\twrite(cyan(name))\n\tblank()\n\n\treturn EXIT_SUCCESS\n}\n\nasync function handleRemove(name: string | undefined): Promise<number> {\n\tif (!name) {\n\t\tblank()\n\t\tusage()\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\tconst peer = await getPeer(name).catch(() => undefined)\n\n\tif (!peer) {\n\t\tblank()\n\t\tlogError(`Unknown peer: ${name}`)\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\tblank()\n\tconst approved = await confirm(`REMOVE ${name}?`)\n\n\tif (!approved) {\n\t\tlog(dim('CANCELLED'))\n\t\tblank()\n\t\treturn EXIT_SUCCESS\n\t}\n\n\tawait removePeer(name)\n\tlog(bold('REMOVED'))\n\tblank()\n\n\treturn EXIT_SUCCESS\n}\n\nasync function handleList(): Promise<number> {\n\tconst peers = await listPeers()\n\n\tblank()\n\tlog(bold('PEERS'))\n\n\tif (peers.length === EMPTY_PEERS) {\n\t\twrite(dim('No peers saved yet.'))\n\t\tblank()\n\t\treturn EXIT_SUCCESS\n\t}\n\n\tblank()\n\n\tfor (const peer of peers) {\n\t\twrite(`${peer.name} ${dim(shortenKey(peer.publicKey))} ${dim(formatAge(peer.addedAt))}`)\n\t}\n\n\tblank()\n\n\treturn EXIT_SUCCESS\n}\n\n/** Execute `hbeam peers` subcommands. */\nexport async function runPeersCommand(argv: string[]): Promise<number> {\n\tconst [action, name, publicKey] = argv\n\n\tif (action === 'add') {\n\t\treturn handleAdd(name, publicKey)\n\t}\n\n\tif (action === 'rm') {\n\t\treturn handleRemove(name)\n\t}\n\n\tif (action === 'ls') {\n\t\treturn handleList()\n\t}\n\n\tblank()\n\tusage()\n\tblank()\n\n\treturn EXIT_FAILURE\n}\n","import { copyToClipboard } from '@/lib/clipboard.ts'\nimport { loadOrCreateIdentityWithMeta } from '@/lib/identity.ts'\nimport { blank, bold, cyan, dim, log, write } from '@/lib/log.ts'\n\nconst EXIT_SUCCESS = 0\n\n/** Execute `hbeam whoami`. */\nexport async function runWhoamiCommand(): Promise<number> {\n\tconst identity = await loadOrCreateIdentityWithMeta()\n\tconst publicKey = identity.keyPair.publicKey.toString('hex')\n\n\tblank()\n\n\tif (identity.created) {\n\t\tlog(dim('IDENTITY CREATED'))\n\t}\n\n\tlog(bold('IDENTITY'))\n\twrite(cyan(publicKey))\n\tcopyToClipboard(publicKey)\n\n\tblank()\n\n\treturn EXIT_SUCCESS\n}\n","#!/usr/bin/env node\n\nimport mri from 'mri'\n\nimport { copyToClipboard } from '@/lib/clipboard.ts'\nimport { loadOrCreateIdentityWithMeta } from '@/lib/identity.ts'\nimport { bold, cyan, dim, log, write, writeBlock } from '@/lib/log.ts'\nimport { runBeamSession } from '@/lib/session.ts'\n\nimport { Beam } from './beam.ts'\nimport { runConnectCommand } from './commands/connect.ts'\nimport { runPeersCommand } from './commands/peers.ts'\nimport { runWhoamiCommand } from './commands/whoami.ts'\n\nimport type { BeamOptions } from './types.ts'\n\nconst ARGV_OFFSET = 2\nconst EXIT_SUCCESS = 0\n\nconst NO_INDENT = ''\n\nconst argv = mri(process.argv.slice(ARGV_OFFSET), {\n\talias: { h: 'help', l: 'listen', v: 'version' },\n\tboolean: ['help', 'listen', 'version'],\n})\n\nif (argv.help) {\n\twriteBlock([\n\t\t`${bold('hbeam')} — end-to-end encrypted pipe over HyperDHT`,\n\t\t'',\n\t\t`${bold('Usage:')}`,\n\t\t` hbeam ${dim('[passphrase]')} ${dim('[options]')}`,\n\t\t` hbeam connect ${dim('<name>')}`,\n\t\t` hbeam peers ${dim('<add|rm|ls> ...')}`,\n\t\t` hbeam whoami`,\n\t\t'',\n\t\t`${bold('Options:')}`,\n\t\t` ${dim('-l, --listen')} Listen using passphrase or identity`,\n\t\t` ${dim('-h, --help')} Show this help`,\n\t\t` ${dim('-v, --version')} Show version`,\n\t\t'',\n\t\t`${bold('Examples:')}`,\n\t\t` ${dim('# Start a new pipe (generates a passphrase)')}`,\n\t\t\" echo 'hello' | hbeam\",\n\t\t'',\n\t\t` ${dim('# Connect to an existing pipe')}`,\n\t\t' hbeam <passphrase>',\n\t\t'',\n\t\t` ${dim('# Listen with a specific passphrase')}`,\n\t\t\" echo 'hello again' | hbeam <passphrase> --listen\",\n\t\t'',\n\t\t` ${dim('# Listen on your persistent identity')}`,\n\t\t' hbeam --listen',\n\t\t'',\n\t\t` ${dim('# Save and connect to peers by name')}`,\n\t\t' hbeam peers add workserver <public-key>',\n\t\t' hbeam connect workserver',\n\t])\n\tprocess.exit(EXIT_SUCCESS)\n}\n\nif (argv.version) {\n\tconst pkg = (await import('../package.json')) as { version?: string }\n\twrite(pkg.version ?? '0.0.0', NO_INDENT)\n\tprocess.exit(EXIT_SUCCESS)\n}\n\nconst [firstArg, ...restArgs] = argv._ as string[]\n\nif (firstArg === 'peers') {\n\tprocess.exit(await runPeersCommand(restArgs))\n}\nif (firstArg === 'connect') {\n\tprocess.exit(await runConnectCommand(restArgs))\n}\nif (firstArg === 'whoami') {\n\tprocess.exit(await runWhoamiCommand())\n}\n\nconst passphrase = firstArg\n\nif (argv.listen && !passphrase) {\n\tconst identity = await loadOrCreateIdentityWithMeta()\n\tif (identity.created) {\n\t\tlog(dim('IDENTITY CREATED'))\n\t\twrite(cyan(identity.keyPair.publicKey.toString('hex')))\n\t}\n\n\tconst beam = new Beam({\n\t\tannounce: true,\n\t\tkeyPair: identity.keyPair,\n\t})\n\trunBeamSession(beam, {\n\t\tannounceLabel: 'PUBLIC KEY',\n\t\tcopyValue: copyToClipboard,\n\t\tmode: 'announce',\n\t\tvalue: beam.key,\n\t})\n} else {\n\tconst beamOptions: BeamOptions | undefined = argv.listen ? { announce: true } : undefined\n\tconst beam = new Beam(passphrase, beamOptions)\n\trunBeamSession(beam, {\n\t\tannounceLabel: 'PASSPHRASE',\n\t\tcopyValue: copyToClipboard,\n\t\tmode: beam.announce ? 'announce' : 'connect',\n\t\tvalue: beam.announce ? beam.key : (passphrase ?? 'unknown'),\n\t})\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["EXIT_SUCCESS","MODULUS_EVEN","REMAINDER_ZERO","PUBLIC_KEY_BYTES","KEY_SEED_BYTES","isHex","FIRST_INDEX","EXIT_FAILURE","dim","dim","dim","bold","red","KEY_SEED_BYTES","PUBLIC_KEY_BYTES","EXIT_FAILURE","dim","EXIT_SUCCESS","EXIT_FAILURE","dim","bold","dim","bold","red","EXIT_SUCCESS","dim","bold","bold","dim"],"sources":["../src/lib/clipboard.ts","../src/lib/config.ts","../src/lib/encoding.ts","../src/lib/identity.ts","../src/lib/log.ts","../src/lib/file-protocol.ts","../src/lib/lifecycle.ts","../src/lib/prompt.ts","../src/lib/pulse.ts","../src/lib/session.ts","../src/lib/dht.ts","../src/beam.ts","../src/lib/addressbook.ts","../src/commands/connect.ts","../src/commands/peers.ts","../src/commands/serve.ts","../src/commands/whoami.ts","../src/cli.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process'\n\ntype ClipboardCommand = Readonly<{\n\targs: readonly string[]\n\tcommand: string\n}>\n\nconst EXIT_SUCCESS = 0\nconst EMPTY_ARGS: readonly string[] = []\n\nconst DARWIN_COMMANDS: readonly ClipboardCommand[] = [{ args: EMPTY_ARGS, command: 'pbcopy' }]\nconst WINDOWS_COMMANDS: readonly ClipboardCommand[] = [{ args: EMPTY_ARGS, command: 'clip' }]\n\nconst LINUX_COMMANDS: readonly ClipboardCommand[] = [\n\t{ args: EMPTY_ARGS, command: 'wl-copy' },\n\t{ args: ['-selection', 'clipboard'], command: 'xclip' },\n\t{ args: ['--clipboard', '--input'], command: 'xsel' },\n]\n\nfunction getClipboardCommands(): readonly ClipboardCommand[] {\n\tif (process.platform === 'darwin') {\n\t\treturn DARWIN_COMMANDS\n\t}\n\tif (process.platform === 'win32') {\n\t\treturn WINDOWS_COMMANDS\n\t}\n\treturn LINUX_COMMANDS\n}\n\nfunction tryClipboardCommand(text: string, item: ClipboardCommand): boolean {\n\tconst result = spawnSync(item.command, item.args, {\n\t\tinput: text,\n\t\tstdio: ['pipe', 'ignore', 'ignore'],\n\t})\n\treturn !result.error && result.status === EXIT_SUCCESS\n}\n\n/** Copy text to the system clipboard using common platform commands. */\nexport function copyToClipboard(text: string): boolean {\n\tfor (const item of getClipboardCommands()) {\n\t\tif (tryClipboardCommand(text, item)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n","import { mkdir, readFile, writeFile, chmod } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nconst CONFIG_ROOT_DIR = '.config'\nconst APP_CONFIG_DIR = 'hbeam'\nconst CONFIG_DIR_ENV = 'HBEAM_CONFIG_DIR'\n\nconst DIR_MODE = 0o700\nconst FILE_MODE_SECURE = 0o600\n\n/** Absolute path to hbeam config directory. */\nexport function getConfigDir(): string {\n\treturn process.env[CONFIG_DIR_ENV] ?? join(homedir(), CONFIG_ROOT_DIR, APP_CONFIG_DIR)\n}\n\nfunction resolveConfigPath(filename: string): string {\n\treturn join(getConfigDir(), filename)\n}\n\n/** Ensure `~/.config/hbeam` exists with private directory permissions. */\nexport async function ensureConfigDir(): Promise<void> {\n\tawait mkdir(getConfigDir(), { mode: DIR_MODE, recursive: true })\n}\n\n/** Read and parse a JSON file from the hbeam config directory. */\nexport async function readJsonFile<T>(filename: string): Promise<T | undefined> {\n\tconst path = resolveConfigPath(filename)\n\ttry {\n\t\tconst raw = await readFile(path, 'utf8')\n\t\treturn JSON.parse(raw) as T\n\t} catch (error) {\n\t\tconst err = error as NodeJS.ErrnoException\n\t\tif (err.code === 'ENOENT') {\n\t\t\treturn undefined\n\t\t}\n\t\tthrow error\n\t}\n}\n\n/**\n * Write a JSON file into the hbeam config directory.\n *\n * Set `secure` for files containing private key material.\n */\nexport async function writeJsonFile(\n\tfilename: string,\n\tdata: unknown,\n\toptions?: { secure?: boolean },\n): Promise<void> {\n\tconst path = resolveConfigPath(filename)\n\tawait ensureConfigDir()\n\tawait mkdir(dirname(path), { mode: DIR_MODE, recursive: true })\n\tawait writeFile(path, `${JSON.stringify(data, null, '\\t')}\\n`, 'utf8')\n\tif (options?.secure) {\n\t\tawait chmod(path, FILE_MODE_SECURE)\n\t}\n}\n","import * as b4a from 'b4a'\nimport b32 from 'hi-base32'\nimport sodium from 'sodium-universal'\n\n/** Encode a buffer as a lowercase base32 string without padding. */\nexport function toBase32(buf: Buffer): string {\n\treturn b32.encode(buf).replace(/=/g, '').toLowerCase()\n}\n\n/** Decode a base32 string back into a raw Buffer. */\nexport function fromBase32(str: string): Buffer {\n\treturn b4a.from(b32.decode.asBytes(str.toUpperCase()))\n}\n\n/** Generate cryptographically secure random bytes. */\nexport function randomBytes(length: number): Buffer {\n\tconst buffer = b4a.alloc(length)\n\tsodium.randombytes_buf(buffer)\n\treturn buffer\n}\n","import DHT from 'hyperdht'\n\nimport { readJsonFile, writeJsonFile } from './config.ts'\nimport { randomBytes } from './encoding.ts'\n\nimport type { Identity, KeyPair } from '../types.ts'\n\nconst IDENTITY_FILE = 'identity.json'\n\nconst MODULUS_EVEN = 2\nconst REMAINDER_ZERO = 0\nconst PUBLIC_KEY_BYTES = 32\nconst KEY_SEED_BYTES = 32\nconst SECRET_KEY_BYTES = 64\n\nfunction asHex(buffer: Buffer): string {\n\treturn buffer.toString('hex')\n}\n\nfunction fromHex(hex: string): Buffer {\n\treturn Buffer.from(hex, 'hex')\n}\n\nfunction isHex(value: string): boolean {\n\treturn /^[0-9a-f]+$/i.test(value) && value.length % MODULUS_EVEN === REMAINDER_ZERO\n}\n\nfunction parseIdentity(value: Identity): KeyPair {\n\tif (!isHex(value.publicKey) || !isHex(value.secretKey)) {\n\t\tthrow new Error('Invalid identity file: keys must be hex-encoded')\n\t}\n\n\tconst publicKey = fromHex(value.publicKey)\n\tconst secretKey = fromHex(value.secretKey)\n\n\tif (publicKey.length !== PUBLIC_KEY_BYTES || secretKey.length !== SECRET_KEY_BYTES) {\n\t\tthrow new Error('Invalid identity file: unexpected key lengths')\n\t}\n\n\treturn { publicKey, secretKey }\n}\n\nfunction serializeIdentity(keyPair: KeyPair): Identity {\n\treturn {\n\t\tpublicKey: asHex(keyPair.publicKey),\n\t\tsecretKey: asHex(keyPair.secretKey),\n\t}\n}\n\nfunction createIdentity(): KeyPair {\n\treturn DHT.keyPair(randomBytes(KEY_SEED_BYTES))\n}\n\n/** Load a persisted identity if present; otherwise create and persist one. */\nexport async function loadOrCreateIdentityWithMeta(): Promise<{\n\tcreated: boolean\n\tkeyPair: KeyPair\n}> {\n\tconst existing = await readJsonFile<Identity>(IDENTITY_FILE)\n\tif (existing) {\n\t\treturn { created: false, keyPair: parseIdentity(existing) }\n\t}\n\n\tconst keyPair = createIdentity()\n\tawait writeJsonFile(IDENTITY_FILE, serializeIdentity(keyPair), { secure: true })\n\treturn { created: true, keyPair }\n}\n\n/** Load or create the local hbeam identity keypair. */\nexport async function loadOrCreateIdentity(): Promise<KeyPair> {\n\tconst { keyPair } = await loadOrCreateIdentityWithMeta()\n\treturn keyPair\n}\n\n/** Return the public key hex string for the local identity. */\nexport async function getPublicKeyHex(): Promise<string> {\n\tconst keyPair = await loadOrCreateIdentity()\n\treturn asHex(keyPair.publicKey)\n}\n","import { dim, red, yellow } from 'colorette'\n\nexport { bold, cyan, dim, gray, green, italic, red, yellow } from 'colorette'\n\nconst SEPARATOR_WIDTH = 36\nconst CLEAR_LINE = '\\r\\u001B[2K'\nconst NO_OFFSET = 0\n\nexport const INDENT = ' '\nexport const SEPARATOR = dim('╌'.repeat(SEPARATOR_WIDTH))\n\n/** Write a line to stderr at the standard indent level. */\nexport function write(message: string, indent: string = INDENT): void {\n\tprocess.stderr.write(`${indent}${message}\\n`)\n}\n\n/** Write a blank line to stderr. */\nexport function blank(): void {\n\tprocess.stderr.write('\\n')\n}\n\n/** Write a pre-formatted block (multiple lines) to stderr. */\nexport function writeBlock(lines: string[]): void {\n\tfor (const line of lines) {\n\t\tprocess.stderr.write(`${INDENT}${line}\\n`)\n\t}\n}\n\n/** Write a status message to stderr at the standard indent level. */\nexport function log(message: string): void {\n\twrite(message)\n}\n\n/** Write an error message to stderr at the standard indent level. */\nexport function logError(message: string): void {\n\tprocess.stderr.write(`${INDENT}${red('ERROR')} ${message}\\n`)\n}\n\n/** Write a warning/notice message to stderr with a yellow prefix. */\nexport function logWarn(message: string): void {\n\tprocess.stderr.write(`${yellow('!')} ${message}\\n`)\n}\n\n/** Clear the current line (wipe terminal-echoed ^C, etc.). Falls back to a newline on non-TTY. */\nexport function clearLine(): void {\n\tif (process.stderr.isTTY) {\n\t\tprocess.stderr.write(CLEAR_LINE)\n\t} else {\n\t\tprocess.stderr.write('\\n')\n\t}\n}\n\n// -- Spinner ----------------------------------------------------------------\n\n/** ANSI escape: move cursor up N lines. */\nfunction cursorUp(n: number): string {\n\treturn `\\u001B[${n}A`\n}\n\n/** ANSI escape: move cursor down N lines. */\nfunction cursorDown(n: number): string {\n\treturn `\\u001B[${n}B`\n}\n\n/** Handle for a line that animates in-place while content prints below. */\nexport interface Spinner {\n\t/** Write a blank line below the spinner and track the cursor offset. */\n\tblank(): void\n\t/** Render the first frame and begin the animation loop. */\n\tstart(): void\n\t/** Stop the animation loop. */\n\tstop(): void\n\t/** Write an indented line below the spinner and track the cursor offset. */\n\twrite(message: string): void\n}\n\n/** Animate a single line in-place while content continues to print below it. */\nexport function createSpinner(frames: readonly string[], intervalMs: number): Spinner {\n\tlet offset = NO_OFFSET\n\tlet frameIndex = NO_OFFSET\n\tlet timer: ReturnType<typeof globalThis.setInterval> | undefined = undefined\n\n\tfunction render(): void {\n\t\tif (offset > NO_OFFSET) {\n\t\t\tprocess.stderr.write(cursorUp(offset))\n\t\t}\n\t\tprocess.stderr.write(`${CLEAR_LINE}${INDENT}${frames[frameIndex]}`)\n\t\tif (offset > NO_OFFSET) {\n\t\t\tprocess.stderr.write(`${cursorDown(offset)}\\r`)\n\t\t}\n\t\tframeIndex++\n\t\tif (frameIndex >= frames.length) {\n\t\t\tframeIndex = NO_OFFSET\n\t\t}\n\t}\n\n\treturn {\n\t\tblank(): void {\n\t\t\tblank()\n\t\t\toffset++\n\t\t},\n\t\tstart(): void {\n\t\t\trender()\n\t\t\tprocess.stderr.write('\\n')\n\t\t\toffset++\n\t\t\ttimer = globalThis.setInterval(render, intervalMs)\n\t\t},\n\t\tstop(): void {\n\t\t\tif (timer) {\n\t\t\t\tglobalThis.clearInterval(timer)\n\t\t\t\ttimer = undefined\n\t\t\t}\n\t\t},\n\t\twrite(message: string): void {\n\t\t\twrite(message)\n\t\t\toffset++\n\t\t},\n\t}\n}\n","const FILE_TYPE = 'file'\nconst HEADER_PREFIX = `{\"type\":\"${FILE_TYPE}\"`\nconst NEWLINE = '\\n'\nconst BYTES_PER_KIB = 1024\nconst UNIT_PRECISION = 1\nconst FIRST_INDEX = 0\nconst MIN_SIZE = 0\nconst LAST_INDEX_OFFSET = 1\nconst UNIT_LABELS = ['B', 'KB', 'MB', 'GB', 'TB'] as const\n\nexport interface FileHeader {\n\tname: string\n\tsize: number\n\ttype: typeof FILE_TYPE\n}\n\nexport function encodeHeader(header: FileHeader): Buffer {\n\treturn Buffer.from(`${JSON.stringify(header)}${NEWLINE}`, 'utf8')\n}\n\nexport function isFileHeader(chunk: Buffer): boolean {\n\tconst prefix = HEADER_PREFIX.slice(FIRST_INDEX, Math.min(chunk.length, HEADER_PREFIX.length))\n\treturn chunk.toString('utf8', FIRST_INDEX, prefix.length) === prefix\n}\n\nexport function parseFileHeader(line: Buffer): FileHeader {\n\tconst parsed = JSON.parse(line.toString('utf8')) as Partial<FileHeader>\n\tif (parsed.type !== FILE_TYPE) {\n\t\tthrow new Error('Invalid file header type')\n\t}\n\tif (!parsed.name || typeof parsed.name !== 'string') {\n\t\tthrow new Error('Invalid file header name')\n\t}\n\tif (\n\t\ttypeof parsed.size !== 'number' ||\n\t\t!Number.isSafeInteger(parsed.size) ||\n\t\tparsed.size < MIN_SIZE\n\t) {\n\t\tthrow new Error('Invalid file header size')\n\t}\n\treturn { name: parsed.name, size: parsed.size, type: FILE_TYPE }\n}\n\nexport function findHeaderLineEnd(chunk: Buffer): number {\n\treturn chunk.indexOf(NEWLINE)\n}\n\nexport function formatFileSize(bytes: number): string {\n\tif (!Number.isFinite(bytes) || bytes < MIN_SIZE) {\n\t\treturn `0 ${UNIT_LABELS[FIRST_INDEX]}`\n\t}\n\n\tlet size = bytes\n\tlet unitIndex = FIRST_INDEX\n\tconst lastUnitIndex = UNIT_LABELS.length - LAST_INDEX_OFFSET\n\twhile (size >= BYTES_PER_KIB && unitIndex < lastUnitIndex) {\n\t\tsize /= BYTES_PER_KIB\n\t\tunitIndex++\n\t}\n\n\tif (unitIndex === FIRST_INDEX) {\n\t\treturn `${Math.round(size)} ${UNIT_LABELS[unitIndex]}`\n\t}\n\treturn `${size.toFixed(UNIT_PRECISION)} ${UNIT_LABELS[unitIndex]}`\n}\n","import { blank, clearLine, dim, log } from './log.ts'\n\nimport type { Beam } from '../beam.ts'\n\nconst EXIT_FAILURE = 1\nconst SHUTDOWN_TIMEOUT_MS = 2000\n\n/** Controller for graceful shutdown of a beam session. */\nexport interface Lifecycle {\n\t/** Returns true if shutdown is in progress (use as an early-return guard). */\n\tdone(): boolean\n\t/** Tear down the beam, stop the spinner, and exit after a grace period. */\n\tshutdown(): void\n}\n\n/**\n * Create a lifecycle controller that manages SIGINT handling and graceful shutdown.\n *\n * Registers a one-shot SIGINT handler on creation. All shutdown state is\n * encapsulated — callers just check `done()` and call `shutdown()`.\n */\nexport function createLifecycle(beam: Beam, spinner?: { stop(): void }): Lifecycle {\n\tlet isShuttingDown = false\n\n\tfunction shutdown(): void {\n\t\tif (isShuttingDown) {\n\t\t\treturn\n\t\t}\n\t\tisShuttingDown = true\n\t\tspinner?.stop()\n\n\t\tlog(dim('SHUTTING DOWN'))\n\t\tblank()\n\n\t\tconst timeout = globalThis.setTimeout(() => {\n\t\t\tprocess.exit(EXIT_FAILURE)\n\t\t}, SHUTDOWN_TIMEOUT_MS)\n\n\t\tbeam.destroy()\n\t\tbeam.on('close', () => {\n\t\t\tglobalThis.clearTimeout(timeout)\n\t\t})\n\t}\n\n\tprocess.once('SIGINT', () => {\n\t\tclearLine()\n\t\tshutdown()\n\t})\n\n\treturn {\n\t\tdone(): boolean {\n\t\t\treturn isShuttingDown\n\t\t},\n\t\tshutdown,\n\t}\n}\n","import { createInterface } from 'node:readline/promises'\n\nimport { INDENT, dim } from './log.ts'\n\nconst YES = 'y'\nconst NO = 'n'\nconst CTRL_C = '\\u0003'\nconst ENTER = '\\r'\nconst FIRST_CHAR_INDEX = 0\nconst EMPTY_INPUT = ''\n\nfunction firstChar(data: Buffer): string {\n\treturn data.toString('utf8').toLowerCase().charAt(FIRST_CHAR_INDEX)\n}\n\n/** Minimal single-keypress confirm prompt with `y/N` default. */\nexport async function confirm(message: string): Promise<boolean> {\n\tprocess.stderr.write(`${INDENT}${message} ${dim('(y/N)')} `)\n\n\tconst stdin = process.stdin\n\tif (!stdin.isTTY || typeof stdin.setRawMode !== 'function') {\n\t\tprocess.stderr.write('\\n')\n\t\treturn false\n\t}\n\n\tconst originalRaw = stdin.isRaw\n\n\treturn await new Promise<boolean>(resolve => {\n\t\tfunction cleanup(answer: boolean): void {\n\t\t\tstdin.setRawMode(Boolean(originalRaw))\n\t\t\tstdin.pause()\n\t\t\tstdin.removeListener('data', onData)\n\t\t\tprocess.stderr.write(`${answer ? YES : NO}\\n`)\n\t\t\tresolve(answer)\n\t\t}\n\n\t\tfunction onData(data: Buffer): void {\n\t\t\tconst key = firstChar(data)\n\t\t\tif (key === CTRL_C) {\n\t\t\t\tprocess.stderr.write('\\n')\n\t\t\t\tprocess.kill(process.pid, 'SIGINT')\n\t\t\t\tcleanup(false)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (key === YES) {\n\t\t\t\tcleanup(true)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (key === NO || key === '' || key === ENTER) {\n\t\t\t\tcleanup(false)\n\t\t\t}\n\t\t}\n\n\t\tstdin.setRawMode(true)\n\t\tstdin.resume()\n\t\tstdin.on('data', onData)\n\t})\n}\n\n/** Minimal line-input prompt with an editable pre-filled default value. */\nexport async function input(message: string, placeholder: string): Promise<string> {\n\tif (!process.stdin.isTTY || !process.stderr.isTTY) {\n\t\treturn placeholder\n\t}\n\n\tconst rl = createInterface({\n\t\tinput: process.stdin,\n\t\toutput: process.stderr,\n\t\tterminal: true,\n\t})\n\n\ttry {\n\t\tconst answerPromise = rl.question(`${INDENT}${message} `)\n\t\trl.write(placeholder)\n\t\tconst answer = await answerPromise\n\t\tconst trimmed = answer.trim()\n\t\treturn trimmed === EMPTY_INPUT ? placeholder : trimmed\n\t} finally {\n\t\trl.close()\n\t}\n}\n","import { bold, dim } from 'colorette'\n\nconst INTERVAL_MS = 125\nconst PEAK_A = 5\nconst PEAK_B = 15\nconst PEAK_WRAP = 25\nconst DIST_PEAK = 0\nconst DIST_NEAR = 1\n\nconst RAW_FRAMES: readonly string[] = [\n\t' ',\n\t'· ',\n\t'·· ',\n\t'··· ',\n\t'···· ',\n\t'·····',\n\t' ····',\n\t' ···',\n\t' ··',\n\t' ·',\n\t' ',\n\t' ·',\n\t' ··',\n\t' ···',\n\t' ····',\n\t'·····',\n\t'···· ',\n\t'··· ',\n\t'·· ',\n\t'· ',\n\t' ',\n]\n\n/**\n * Generate the styled spinner frames for the HBEAM pulse animation.\n *\n * Each frame is rendered with a brightness gradient: bold at peak,\n * normal near peak, and dim everywhere else.\n *\n * @param label - The text label to prefix each frame (e.g. \"HBEAM\").\n * @returns An object with the styled `frames` array and `intervalMs` timing.\n */\nexport function createPulseFrames(label: string): { frames: string[]; intervalMs: number } {\n\tconst frames = RAW_FRAMES.map((s, i) => {\n\t\tconst distanceToPeak = Math.min(\n\t\t\tMath.abs(i - PEAK_A),\n\t\t\tMath.abs(i - PEAK_B),\n\t\t\tMath.abs(i - PEAK_WRAP),\n\t\t)\n\t\tlet glyph = dim(s)\n\t\tif (distanceToPeak === DIST_PEAK) {\n\t\t\tglyph = bold(s)\n\t\t} else if (distanceToPeak === DIST_NEAR) {\n\t\t\tglyph = s\n\t\t}\n\t\treturn `${bold(label)} ${glyph}`\n\t})\n\n\treturn { frames, intervalMs: INTERVAL_MS }\n}\n","import { createWriteStream } from 'node:fs'\nimport { mkdir } from 'node:fs/promises'\nimport { dirname, resolve } from 'node:path'\n\nimport { findHeaderLineEnd, formatFileSize, isFileHeader, parseFileHeader } from './file-protocol.ts'\nimport { createLifecycle } from './lifecycle.ts'\nimport {\n\tblank,\n\tbold,\n\tcreateSpinner,\n\tcyan,\n\tdim,\n\tgray,\n\tINDENT,\n\tlog,\n\tlogError,\n\tred,\n\tSEPARATOR,\n\twrite,\n} from './log.ts'\nimport { confirm, input } from './prompt.ts'\nimport { createPulseFrames } from './pulse.ts'\n\nimport type { Beam } from '../beam.ts'\nimport type { ConnectionInfo } from '../types.ts'\n\ntype SessionMode = 'announce' | 'connect'\nconst FIRST_INDEX = 0\nconst NEXT_INDEX = 1\nconst MIN_BUFFERED_CHUNKS = 0\n\nexport interface SessionOptions {\n\tannounceLabel?: string\n\tcopyValue?: (text: string) => void\n\tmode: SessionMode\n\toutputPath?: string\n\tvalue: string\n}\n\n/** Run the standard hbeam CLI session UI and stdin/stdout piping. */\nexport function runBeamSession(beam: Beam, options: SessionOptions): void {\n\tconst { frames, intervalMs } = createPulseFrames('HBEAM')\n\tconst spinner = createSpinner(frames, intervalMs)\n\tconst lifecycle = createLifecycle(beam, spinner)\n\n\tblank()\n\tspinner.start()\n\tspinner.blank()\n\n\tif (options.mode === 'announce') {\n\t\tspinner.write(dim(options.announceLabel ?? 'PUBLIC KEY'))\n\t\tspinner.write(cyan(options.value))\n\t\toptions.copyValue?.(options.value)\n\t} else {\n\t\tspinner.write(dim('CONNECTING'))\n\t\tspinner.write(cyan(options.value))\n\t}\n\n\tbeam.on('remote-address', ({ host, port }: ConnectionInfo) => {\n\t\tif (lifecycle.done()) {\n\t\t\treturn\n\t\t}\n\t\tif (host) {\n\t\t\tspinner.write(dim(`ONLINE ${gray(`[${host}:${port}]`)}`))\n\t\t\tspinner.blank()\n\t\t}\n\t})\n\n\tbeam.on('connected', () => {\n\t\tif (lifecycle.done()) {\n\t\t\treturn\n\t\t}\n\t\tspinner.stop()\n\t\tlog(bold('PIPE ACTIVE'))\n\t\twrite(gray('CTRL+C TO TERMINATE'))\n\t\tblank()\n\t})\n\n\tbeam.on('error', (error: Error) => {\n\t\tspinner.stop()\n\t\tconst isPeerNotFound = error.message.includes('PEER_NOT_FOUND')\n\t\tif (isPeerNotFound) {\n\t\t\tlog(red(dim('PEER NOT FOUND')))\n\t\t} else if (error.message.includes('connection reset by peer')) {\n\t\t\tlog(dim('PEER DISCONNECTED'))\n\t\t} else {\n\t\t\tlogError(error.message)\n\t\t}\n\t\tblank()\n\t\tif (!isPeerNotFound) {\n\t\t\tlifecycle.shutdown()\n\t\t}\n\t})\n\n\tbeam.on('end', () => beam.end())\n\n\tlet receivedData = false\n\tlet mode: 'unknown' | 'text' | 'file' | 'file-stdout' = 'unknown'\n\tlet bufferedChunks: Buffer[] = []\n\tlet fileStream: ReturnType<typeof createWriteStream> | undefined = undefined\n\tlet filePath: string | undefined = undefined\n\tlet initializingFileMode = false\n\tlet queuedChunks: Buffer[] = []\n\n\tfunction writeTextChunk(chunk: Buffer): void {\n\t\tif (!receivedData) {\n\t\t\treceivedData = true\n\t\t\twrite(SEPARATOR)\n\t\t\tblank()\n\t\t}\n\t\tconst lines = chunk.toString().replace(/^(?!$)/gm, INDENT)\n\t\tprocess.stdout.write(lines)\n\t}\n\n\tasync function resolveOutputPath(fileName: string): Promise<string | undefined> {\n\t\tif (options.outputPath) {\n\t\t\treturn resolve(options.outputPath)\n\t\t}\n\t\tif (!process.stdout.isTTY) {\n\t\t\treturn undefined\n\t\t}\n\n\t\tconst suggestedPath = resolve(process.cwd(), fileName)\n\t\tconst shouldSave = await confirm('Save incoming file?')\n\t\tif (!shouldSave) {\n\t\t\treturn ''\n\t\t}\n\t\treturn await input('Save to:', suggestedPath)\n\t}\n\n\tasync function startFileMode(initialChunk: Buffer): Promise<void> {\n\t\tconst lineEnd = findHeaderLineEnd(initialChunk)\n\t\tif (lineEnd < FIRST_INDEX) {\n\t\t\treturn\n\t\t}\n\n\t\tconst header = parseFileHeader(initialChunk.subarray(FIRST_INDEX, lineEnd))\n\t\tconst remainder = initialChunk.subarray(lineEnd + NEXT_INDEX)\n\n\t\tlog(dim(`INCOMING FILE ${header.name} (${formatFileSize(header.size)})`))\n\n\t\tconst outputPath = await resolveOutputPath(header.name)\n\t\tif (outputPath === '') {\n\t\t\tlog(dim('RECEIVE CANCELLED'))\n\t\t\tblank()\n\t\t\tlifecycle.shutdown()\n\t\t\treturn\n\t\t}\n\n\t\tif (outputPath === undefined) {\n\t\t\tmode = 'file-stdout'\n\t\t\tif (remainder.length > MIN_BUFFERED_CHUNKS) {\n\t\t\t\tprocess.stdout.write(remainder)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tmode = 'file'\n\t\tfilePath = outputPath\n\t\tawait mkdir(dirname(outputPath), { recursive: true })\n\t\tfileStream = createWriteStream(outputPath)\n\t\tfileStream.on('error', error => beam.destroy(error))\n\t\tif (remainder.length > MIN_BUFFERED_CHUNKS) {\n\t\t\tfileStream.write(remainder)\n\t\t}\n\t}\n\n\tfunction handleChunk(chunk: Buffer): void {\n\t\tif (initializingFileMode) {\n\t\t\tqueuedChunks.push(chunk)\n\t\t\treturn\n\t\t}\n\n\t\tif (mode === 'text') {\n\t\t\twriteTextChunk(chunk)\n\t\t\treturn\n\t\t}\n\t\tif (mode === 'file') {\n\t\t\tfileStream?.write(chunk)\n\t\t\treturn\n\t\t}\n\t\tif (mode === 'file-stdout') {\n\t\t\tprocess.stdout.write(chunk)\n\t\t\treturn\n\t\t}\n\n\t\tbufferedChunks.push(chunk)\n\t\tconst pending = Buffer.concat(bufferedChunks)\n\t\tif (isFileHeader(pending)) {\n\t\t\tconst lineEnd = findHeaderLineEnd(pending)\n\t\t\tif (lineEnd < FIRST_INDEX) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbufferedChunks = []\n\t\t\tinitializingFileMode = true\n\t\t\tvoid startFileMode(pending).finally(() => {\n\t\t\t\tinitializingFileMode = false\n\t\t\t\tfor (const queuedChunk of queuedChunks) {\n\t\t\t\t\thandleChunk(queuedChunk)\n\t\t\t\t}\n\t\t\t\tqueuedChunks = []\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\tmode = 'text'\n\t\tbufferedChunks = []\n\t\twriteTextChunk(pending)\n\t}\n\n\tbeam.on('data', (chunk: Buffer) => {\n\t\thandleChunk(chunk)\n\t})\n\n\tbeam.on('end', () => {\n\t\tif (mode === 'unknown' && bufferedChunks.length > MIN_BUFFERED_CHUNKS) {\n\t\t\tmode = 'text'\n\t\t\twriteTextChunk(Buffer.concat(bufferedChunks))\n\t\t\tbufferedChunks = []\n\t\t}\n\t\tif (mode === 'text' && receivedData) {\n\t\t\tblank()\n\t\t\twrite(SEPARATOR)\n\t\t}\n\t\tif (mode === 'file') {\n\t\t\tfileStream?.end(() => {\n\t\t\t\tlog(dim(`SAVED ${filePath ?? ''}`))\n\t\t\t\tblank()\n\t\t\t})\n\t\t}\n\t})\n\n\tprocess.stdin.pipe(beam)\n\n\tif (typeof process.stdin.unref === 'function') {\n\t\tprocess.stdin.unref()\n\t}\n}\n","import { createHash } from 'node:crypto'\n\nimport * as b4a from 'b4a'\nimport DHT from 'hyperdht'\n\nimport { fromBase32 } from './encoding.ts'\n\nimport type { EncryptedSocket, HyperDHTNode, KeyPair } from '../types.ts'\n\nconst KEY_SEED_BYTES = 32\n\n/**\n * Normalize an arbitrary decoded seed into exactly 32 bytes.\n *\n * HyperDHT's `keyPair()` behavior for non-32-byte seeds can differ across JS\n * runtimes (e.g. Node vs Bun). By hashing to 32 bytes we ensure both sides\n * derive the same keypair for a given passphrase.\n */\nfunction normalizeSeed(seed: Buffer): Buffer {\n\tif (seed.length === KEY_SEED_BYTES) {\n\t\treturn seed\n\t}\n\tconst digest = createHash('sha256').update(seed).digest()\n\treturn b4a.from(digest)\n}\n\n/** Derive a Noise keypair from a base32-encoded passphrase. */\nexport function deriveKeyPair(passphrase: string): KeyPair {\n\tconst seed = fromBase32(passphrase)\n\treturn DHT.keyPair(normalizeSeed(seed))\n}\n\n/** Create an ephemeral HyperDHT node that is destroyed with the beam. */\nexport function createNode(): HyperDHTNode {\n\treturn new DHT({ ephemeral: true }) as unknown as HyperDHTNode\n}\n\n/** Wait for an encrypted socket to complete its Noise handshake. */\nexport function awaitOpen(socket: EncryptedSocket): Promise<void> {\n\treturn new Promise<void>((resolve, reject) => {\n\t\tsocket.once('open', resolve)\n\t\tsocket.once('close', reject)\n\t\tsocket.once('error', reject)\n\t})\n}\n\n/** Create a firewall that rejects any connection not matching the keypair. */\nexport function createFirewall(keyPair: KeyPair): (remotePublicKey: Buffer) => boolean {\n\treturn (remotePublicKey: Buffer) => !b4a.equals(remotePublicKey, keyPair.publicKey)\n}\n","import queueTick from 'queue-tick'\nimport { Duplex } from 'streamx'\n\nimport { awaitOpen, createFirewall, createNode, deriveKeyPair } from './lib/dht.ts'\nimport { randomBytes, toBase32 } from './lib/encoding.ts'\n\nimport type {\n\tBeamOptions,\n\tConnectionInfo,\n\tEncryptedSocket,\n\tHyperDHTNode,\n\tHyperDHTServer,\n\tKeyPair,\n\tStreamCallback,\n} from './types.ts'\n\n/** Number of random bytes used to generate a passphrase seed. */\nconst KEY_SEED_BYTES = 32\n\n/**\n * A 1-to-1 end-to-end encrypted duplex stream powered by HyperDHT.\n *\n * Creates an encrypted tunnel between two peers using a shared passphrase.\n * If no passphrase is provided, one is generated and the beam listens for\n * an incoming connection (server mode). When a passphrase is provided, the\n * beam connects to the listening peer (client mode).\n *\n * @example\n * ```ts\n * const server = new Beam()\n * console.log(server.key) // Share this with the other side\n *\n * const client = new Beam(server.key)\n * ```\n */\nexport class Beam extends Duplex {\n\t/** Base32-encoded passphrase for peer discovery and key derivation. */\n\treadonly key: string\n\n\t/** Whether this beam is announcing (server) or connecting (client). */\n\treadonly announce: boolean\n\n\tprivate node: HyperDHTNode | undefined\n\tprivate server: HyperDHTServer | undefined = undefined\n\tprivate inbound: EncryptedSocket | undefined = undefined\n\tprivate outbound: EncryptedSocket | undefined = undefined\n\tprivate readonly keyPairOverride: KeyPair | undefined\n\tprivate readonly remotePublicKeyOverride: Buffer | undefined\n\tprivate readonly openInboundFirewall: boolean\n\n\tprivate openCallback: StreamCallback | undefined = undefined\n\tprivate readCallback: StreamCallback | undefined = undefined\n\tprivate drainCallback: StreamCallback | undefined = undefined\n\n\tconstructor(keyOrOptions?: string | BeamOptions, options?: BeamOptions) {\n\t\tsuper()\n\n\t\tlet key: string | undefined = undefined\n\t\tlet opts: BeamOptions = {}\n\t\tconst passphraseWasProvided = typeof keyOrOptions === 'string'\n\n\t\tif (passphraseWasProvided) {\n\t\t\tkey = keyOrOptions\n\t\t\topts = options ?? {}\n\t\t} else {\n\t\t\topts = keyOrOptions ?? {}\n\t\t}\n\n\t\tlet shouldAnnounce = opts.announce ?? false\n\n\t\tif (!key && !opts.keyPair) {\n\t\t\tkey = toBase32(randomBytes(KEY_SEED_BYTES))\n\t\t\tshouldAnnounce = true\n\t\t} else if (!key && opts.keyPair) {\n\t\t\tkey = opts.keyPair.publicKey.toString('hex')\n\t\t}\n\t\tif (!key) {\n\t\t\tthrow new Error('Missing key material')\n\t\t}\n\n\t\tthis.key = key\n\t\tthis.announce = shouldAnnounce\n\t\tthis.node = (opts.dht as HyperDHTNode) ?? undefined\n\t\tthis.keyPairOverride = opts.keyPair\n\t\tthis.remotePublicKeyOverride = opts.remotePublicKey\n\t\tthis.openInboundFirewall = !passphraseWasProvided && opts.keyPair !== undefined\n\t}\n\n\t/** Whether a peer connection has been established. */\n\tget connected(): boolean {\n\t\treturn this.outbound !== undefined\n\t}\n\n\t// Streamx lifecycle\n\n\toverride async _open(cb: StreamCallback): Promise<void> {\n\t\tthis.openCallback = cb\n\t\tconst keyPair = this.keyPairOverride ?? deriveKeyPair(this.key)\n\t\tthis.node ??= createNode()\n\n\t\tif (this.announce) {\n\t\t\tawait this.listenAsServer(keyPair)\n\t\t} else {\n\t\t\tawait this.connectAsClient(keyPair)\n\t\t}\n\t}\n\n\toverride _read(cb: StreamCallback): void {\n\t\tthis.readCallback = cb\n\t\tthis.inbound?.resume()\n\t}\n\n\toverride _write(data: unknown, cb: StreamCallback): void {\n\t\tif (this.outbound!.write(data as Buffer) !== false) {\n\t\t\tcb()\n\t\t\treturn\n\t\t}\n\t\tthis.drainCallback = cb\n\t}\n\n\toverride _final(cb: StreamCallback): void {\n\t\tconst done = (): void => {\n\t\t\tthis.outbound!.removeListener('finish', done)\n\t\t\tthis.outbound!.removeListener('error', done)\n\t\t\tcb()\n\t\t}\n\t\tthis.outbound!.end()\n\t\tthis.outbound!.on('finish', done)\n\t\tthis.outbound!.on('error', done)\n\t}\n\n\toverride _predestroy(): void {\n\t\tthis.inbound?.destroy()\n\t\tthis.outbound?.destroy()\n\t\tconst error = new Error('Destroyed')\n\t\tthis.resolveOpen(error)\n\t\tthis.resolveRead(error)\n\t\tthis.resolveDrain(error)\n\t}\n\n\toverride async _destroy(cb: StreamCallback): Promise<void> {\n\t\tif (!this.node) {\n\t\t\tcb()\n\t\t\treturn\n\t\t}\n\t\tif (this.server) {\n\t\t\tawait this.server.close().catch(() => {})\n\t\t}\n\t\tawait this.node.destroy().catch(() => {})\n\t\tcb()\n\t}\n\n\t// Connection setup\n\n\tprivate async listenAsServer(keyPair: KeyPair): Promise<void> {\n\t\tconst serverOptions = this.openInboundFirewall\n\t\t\t? undefined\n\t\t\t: { firewall: createFirewall(keyPair) }\n\t\tthis.server = this.node!.createServer(serverOptions)\n\t\tthis.server.on('connection', (socket: EncryptedSocket) => this.handleConnection(socket))\n\n\t\ttry {\n\t\t\tawait this.server.listen(keyPair)\n\t\t} catch (error) {\n\t\t\tthis.resolveOpen(error as Error)\n\t\t\treturn\n\t\t}\n\t\tthis.emitRemoteAddress()\n\t}\n\n\tprivate async connectAsClient(keyPair: KeyPair): Promise<void> {\n\t\tconst remotePublicKey = this.remotePublicKeyOverride ?? keyPair.publicKey\n\t\tconst socket: EncryptedSocket = this.node!.connect(remotePublicKey, { keyPair })\n\n\t\ttry {\n\t\t\tawait awaitOpen(socket)\n\t\t} catch (error) {\n\t\t\tthis.resolveOpen(error as Error)\n\t\t\treturn\n\t\t}\n\t\tthis.emitRemoteAddress()\n\t\tthis.handleConnection(socket)\n\t}\n\n\tprivate handleConnection(socket: EncryptedSocket): void {\n\t\tsocket.on('data', (data: Buffer) => {\n\t\t\tif (!this.inbound) {\n\t\t\t\tthis.inbound = socket\n\t\t\t\tthis.inbound.on('error', (err: Error) => this.destroy(err))\n\t\t\t\tthis.inbound.on('end', () => this.pushEndOfStream())\n\t\t\t}\n\t\t\tif (socket !== this.inbound) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.pushData(data) === false) {\n\t\t\t\tsocket.pause()\n\t\t\t}\n\t\t})\n\n\t\tsocket.on('end', () => {\n\t\t\tif (this.inbound) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pushEndOfStream()\n\t\t})\n\n\t\tif (!this.outbound) {\n\t\t\tthis.outbound = socket\n\t\t\tthis.outbound.on('error', (err: Error) => this.destroy(err))\n\t\t\tthis.outbound.on('drain', () => this.resolveDrain())\n\t\t\tthis.emit('connected')\n\t\t\tthis.resolveOpen()\n\t\t}\n\t}\n\n\t// Helpers\n\n\tprivate pushData(data: Buffer | null): boolean {\n\t\tconst result = this.push(data)\n\t\tqueueTick(() => this.resolveRead())\n\t\treturn result\n\t}\n\n\tprivate pushEndOfStream(): void {\n\t\t// oxlint-disable-next-line unicorn/no-null\n\t\tthis.pushData(null)\n\t}\n\n\tprivate emitRemoteAddress(): void {\n\t\tthis.emit('remote-address', {\n\t\t\thost: this.node!.host,\n\t\t\tport: this.node!.port,\n\t\t} satisfies ConnectionInfo)\n\t}\n\n\tprivate resolveOpen(error?: Error): void {\n\t\tconst cb = this.openCallback\n\t\tif (cb) {\n\t\t\tthis.openCallback = undefined\n\t\t\tcb(error)\n\t\t}\n\t}\n\n\tprivate resolveRead(error?: Error): void {\n\t\tconst cb = this.readCallback\n\t\tif (cb) {\n\t\t\tthis.readCallback = undefined\n\t\t\tcb(error)\n\t\t}\n\t}\n\n\tprivate resolveDrain(error?: Error): void {\n\t\tconst cb = this.drainCallback\n\t\tif (cb) {\n\t\t\tthis.drainCallback = undefined\n\t\t\tcb(error)\n\t\t}\n\t}\n}\n","import { readJsonFile, writeJsonFile } from './config.ts'\n\nimport type { AddressBook, Peer } from '../types.ts'\n\nconst PEERS_FILE = 'peers.json'\nconst MODULUS_EVEN = 2\nconst PUBLIC_KEY_BYTES = 32\nconst REMAINDER_ZERO = 0\n\nfunction isHex(value: string): boolean {\n\treturn /^[0-9a-f]+$/i.test(value) && value.length % MODULUS_EVEN === REMAINDER_ZERO\n}\n\nfunction normalizePublicKeyHex(publicKeyHex: string): string {\n\tconst normalized = publicKeyHex.trim().toLowerCase()\n\tif (!isHex(normalized)) {\n\t\tthrow new Error('Public key must be a valid hex string')\n\t}\n\tconst key = Buffer.from(normalized, 'hex')\n\tif (key.length !== PUBLIC_KEY_BYTES) {\n\t\tthrow new Error('Public key must be 32 bytes (64 hex chars)')\n\t}\n\treturn normalized\n}\n\nfunction normalizePeerName(name: string): string {\n\tconst normalized = name.trim().toLowerCase()\n\tif (!/^[a-z0-9-]+$/.test(normalized)) {\n\t\tthrow new Error('Peer name must use only letters, numbers, and hyphens')\n\t}\n\treturn normalized\n}\n\nasync function readAddressBook(): Promise<AddressBook> {\n\treturn (await readJsonFile<AddressBook>(PEERS_FILE)) ?? {}\n}\n\nasync function writeAddressBook(addressBook: AddressBook): Promise<void> {\n\tawait writeJsonFile(PEERS_FILE, addressBook)\n}\n\n/** Add or update a named peer in the local address book. */\nexport async function addPeer(name: string, publicKeyHex: string): Promise<Peer> {\n\tconst normalizedName = normalizePeerName(name)\n\tconst normalizedPublicKey = normalizePublicKeyHex(publicKeyHex)\n\tconst addressBook = await readAddressBook()\n\n\tconst peer: Peer = {\n\t\taddedAt: new Date().toISOString(),\n\t\tpublicKey: normalizedPublicKey,\n\t}\n\taddressBook[normalizedName] = peer\n\n\tawait writeAddressBook(addressBook)\n\treturn peer\n}\n\n/** Remove a peer from the local address book. */\nexport async function removePeer(name: string): Promise<boolean> {\n\tconst normalizedName = normalizePeerName(name)\n\tconst addressBook = await readAddressBook()\n\tif (!addressBook[normalizedName]) {\n\t\treturn false\n\t}\n\tdelete addressBook[normalizedName]\n\tawait writeAddressBook(addressBook)\n\treturn true\n}\n\n/** Return all peers sorted by name. */\nexport async function listPeers(): Promise<({ name: string } & Peer)[]> {\n\tconst addressBook = await readAddressBook()\n\treturn Object.entries(addressBook)\n\t\t.map(([name, peer]) => ({ name, ...peer }))\n\t\t.toSorted((a, b) => a.name.localeCompare(b.name))\n}\n\n/** Lookup a single peer by name. */\nexport async function getPeer(name: string): Promise<Peer | undefined> {\n\tconst normalizedName = normalizePeerName(name)\n\tconst addressBook = await readAddressBook()\n\treturn addressBook[normalizedName]\n}\n","import { getPeer } from '@/lib/addressbook.ts'\nimport { loadOrCreateIdentityWithMeta } from '@/lib/identity.ts'\nimport { blank, cyan, dim, log, logError, write } from '@/lib/log.ts'\nimport { runBeamSession } from '@/lib/session.ts'\n\nimport { Beam } from '../beam.ts'\n\nconst EXIT_FAILURE = 1\nconst PUBLIC_KEY_BYTES = 32\n\ninterface ConnectCommandOptions {\n\toutputPath?: string\n}\n\n/** Execute `hbeam connect <name>`. Exits on error; stays alive for the session. */\nexport async function runConnectCommand(\n\targv: string[],\n\toptions: ConnectCommandOptions = {},\n): Promise<void> {\n\tconst [name] = argv\n\tif (!name) {\n\t\tblank()\n\t\tlogError('Missing peer name.')\n\t\twrite(dim('Usage: hbeam connect <name>'))\n\t\tblank()\n\t\tprocess.exit(EXIT_FAILURE)\n\t}\n\n\tconst peer = await getPeer(name).catch(() => undefined)\n\tif (!peer) {\n\t\tblank()\n\t\tlogError(`Unknown peer: ${name}`)\n\t\tblank()\n\t\tprocess.exit(EXIT_FAILURE)\n\t}\n\n\tconst remotePublicKey = Buffer.from(peer.publicKey, 'hex')\n\tif (remotePublicKey.length !== PUBLIC_KEY_BYTES) {\n\t\tblank()\n\t\tlogError(`Invalid public key for peer: ${name}`)\n\t\tblank()\n\t\tprocess.exit(EXIT_FAILURE)\n\t}\n\n\tconst identity = await loadOrCreateIdentityWithMeta()\n\tif (identity.created) {\n\t\tblank()\n\t\tlog(dim('IDENTITY CREATED'))\n\t\twrite(cyan(identity.keyPair.publicKey.toString('hex')))\n\t}\n\n\tconst beam = new Beam({\n\t\tkeyPair: identity.keyPair,\n\t\tremotePublicKey,\n\t})\n\n\trunBeamSession(beam, {\n\t\tmode: 'connect',\n\t\toutputPath: options.outputPath,\n\t\tvalue: name,\n\t})\n}\n","import { addPeer, getPeer, listPeers, removePeer } from '@/lib/addressbook.ts'\nimport { blank, bold, cyan, dim, log, logError, write } from '@/lib/log.ts'\nimport { confirm } from '@/lib/prompt.ts'\n\nconst EXIT_SUCCESS = 0\nconst EXIT_FAILURE = 1\nconst START_INDEX = 0\nconst PUBLIC_KEY_PREFIX_LENGTH = 8\nconst SECONDS_PER_MINUTE = 60\nconst MINUTES_PER_HOUR = 60\nconst HOURS_PER_DAY = 24\nconst DAYS_PER_WEEK = 7\nconst MILLISECONDS_PER_SECOND = 1000\nconst SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR\nconst SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY\nconst SECONDS_PER_WEEK = SECONDS_PER_DAY * DAYS_PER_WEEK\nconst EMPTY_PEERS = 0\n\nfunction formatAge(addedAt: string): string {\n\tconst then = Date.parse(addedAt)\n\n\tif (Number.isNaN(then)) {\n\t\treturn 'unknown'\n\t}\n\n\tconst seconds = Math.floor((Date.now() - then) / MILLISECONDS_PER_SECOND)\n\n\tif (seconds < SECONDS_PER_MINUTE) {\n\t\treturn 'just now'\n\t}\n\n\tif (seconds < SECONDS_PER_HOUR) {\n\t\treturn `${Math.floor(seconds / SECONDS_PER_MINUTE)}m ago`\n\t}\n\n\tif (seconds < SECONDS_PER_DAY) {\n\t\treturn `${Math.floor(seconds / SECONDS_PER_HOUR)}h ago`\n\t}\n\n\tif (seconds < SECONDS_PER_WEEK) {\n\t\treturn `${Math.floor(seconds / SECONDS_PER_DAY)}d ago`\n\t}\n\n\treturn `${Math.floor(seconds / SECONDS_PER_WEEK)}w ago`\n}\n\nfunction shortenKey(publicKey: string): string {\n\treturn `${publicKey.slice(START_INDEX, PUBLIC_KEY_PREFIX_LENGTH)}...`\n}\n\nfunction usage(): void {\n\tlogError('Invalid peers command.')\n\twrite(dim('Usage: hbeam peers add <name> <public-key>'))\n\twrite(dim(' hbeam peers rm <name>'))\n\twrite(dim(' hbeam peers ls'))\n}\n\nasync function handleAdd(name: string | undefined, publicKey: string | undefined): Promise<number> {\n\tif (!name || !publicKey) {\n\t\tblank()\n\t\tusage()\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\ttry {\n\t\tawait addPeer(name, publicKey)\n\t} catch (error) {\n\t\tblank()\n\t\tlogError((error as Error).message)\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\tblank()\n\tlog(bold('SAVED'))\n\twrite(cyan(name))\n\tblank()\n\n\treturn EXIT_SUCCESS\n}\n\nasync function handleRemove(name: string | undefined): Promise<number> {\n\tif (!name) {\n\t\tblank()\n\t\tusage()\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\tconst peer = await getPeer(name).catch(() => undefined)\n\n\tif (!peer) {\n\t\tblank()\n\t\tlogError(`Unknown peer: ${name}`)\n\t\tblank()\n\t\treturn EXIT_FAILURE\n\t}\n\n\tblank()\n\tconst approved = await confirm(`REMOVE ${name}?`)\n\n\tif (!approved) {\n\t\tlog(dim('CANCELLED'))\n\t\tblank()\n\t\treturn EXIT_SUCCESS\n\t}\n\n\tawait removePeer(name)\n\tlog(bold('REMOVED'))\n\tblank()\n\n\treturn EXIT_SUCCESS\n}\n\nasync function handleList(): Promise<number> {\n\tconst peers = await listPeers()\n\n\tblank()\n\tlog(bold('PEERS'))\n\n\tif (peers.length === EMPTY_PEERS) {\n\t\twrite(dim('No peers saved yet.'))\n\t\tblank()\n\t\treturn EXIT_SUCCESS\n\t}\n\n\tblank()\n\n\tfor (const peer of peers) {\n\t\twrite(`${peer.name} ${dim(shortenKey(peer.publicKey))} ${dim(formatAge(peer.addedAt))}`)\n\t}\n\n\tblank()\n\n\treturn EXIT_SUCCESS\n}\n\n/** Execute `hbeam peers` subcommands. */\nexport async function runPeersCommand(argv: string[]): Promise<number> {\n\tconst [action, name, publicKey] = argv\n\n\tif (action === 'add') {\n\t\treturn handleAdd(name, publicKey)\n\t}\n\n\tif (action === 'rm') {\n\t\treturn handleRemove(name)\n\t}\n\n\tif (action === 'ls') {\n\t\treturn handleList()\n\t}\n\n\tblank()\n\tusage()\n\tblank()\n\n\treturn EXIT_FAILURE\n}\n","import { createReadStream } from 'node:fs'\nimport { stat } from 'node:fs/promises'\nimport { basename, resolve } from 'node:path'\n\nimport { Beam } from '@/beam.ts'\nimport { copyToClipboard } from '@/lib/clipboard.ts'\nimport { encodeHeader, formatFileSize } from '@/lib/file-protocol.ts'\nimport { loadOrCreateIdentityWithMeta } from '@/lib/identity.ts'\nimport { createLifecycle } from '@/lib/lifecycle.ts'\nimport {\n\tblank,\n\tbold,\n\tcreateSpinner,\n\tcyan,\n\tdim,\n\tgray,\n\tlog,\n\tlogError,\n\tred,\n\ttype Spinner,\n\twrite,\n} from '@/lib/log.ts'\nimport { createPulseFrames } from '@/lib/pulse.ts'\n\nimport type { ConnectionInfo, KeyPair } from '@/types.ts'\n\nconst EXIT_FAILURE = 1\nconst MIN_FILE_SIZE = 0\n\ninterface ServeCommandOptions {\n\tlisten?: boolean\n}\n\nfunction showUsageError(message: string): never {\n\tblank()\n\tlogError(message)\n\twrite(dim('Usage: hbeam serve <file> [--listen]'))\n\tblank()\n\tprocess.exit(EXIT_FAILURE)\n}\n\nfunction createSessionSpinner(): Spinner {\n\tconst { frames, intervalMs } = createPulseFrames('HBEAM')\n\treturn createSpinner(frames, intervalMs)\n}\n\nasync function resolveServeIdentity(listen: boolean | undefined): Promise<{\n\tannounceLabel: string\n\tkeyPair?: KeyPair\n}> {\n\tif (!listen) {\n\t\treturn { announceLabel: 'PASSPHRASE' }\n\t}\n\tconst identity = await loadOrCreateIdentityWithMeta()\n\tif (identity.created) {\n\t\tblank()\n\t\tlog(dim('IDENTITY CREATED'))\n\t\twrite(cyan(identity.keyPair.publicKey.toString('hex')))\n\t}\n\treturn {\n\t\tannounceLabel: 'PUBLIC KEY',\n\t\tkeyPair: identity.keyPair,\n\t}\n}\n\n/** Execute `hbeam serve <file>` to transfer one file to the first peer. */\nexport async function runServeCommand(\n\targv: string[],\n\toptions: ServeCommandOptions = {},\n): Promise<void> {\n\tconst [targetFile] = argv\n\tif (!targetFile) {\n\t\tshowUsageError('Missing file path.')\n\t}\n\n\tconst filePath = resolve(targetFile)\n\tconst fileName = basename(filePath)\n\tconst fileStat = await stat(filePath).catch(() => undefined)\n\tif (!fileStat || !fileStat.isFile()) {\n\t\tshowUsageError(`Not a readable file: ${targetFile}`)\n\t}\n\tif (fileStat.size < MIN_FILE_SIZE) {\n\t\tshowUsageError(`Invalid file size: ${targetFile}`)\n\t}\n\n\tconst identity = await resolveServeIdentity(options.listen)\n\tconst beam = identity.keyPair\n\t\t? new Beam({\n\t\t\t\tannounce: true,\n\t\t\t\tkeyPair: identity.keyPair,\n\t\t\t})\n\t\t: new Beam(undefined, { announce: true })\n\n\tconst spinner = createSessionSpinner()\n\tconst lifecycle = createLifecycle(beam, spinner)\n\n\tblank()\n\tspinner.start()\n\tspinner.blank()\n\tspinner.write(dim(identity.announceLabel))\n\tspinner.write(cyan(beam.key))\n\tspinner.write(dim(`FILE ${fileName} (${formatFileSize(fileStat.size)})`))\n\tcopyToClipboard(beam.key)\n\n\tbeam.on('remote-address', ({ host, port }: ConnectionInfo) => {\n\t\tif (lifecycle.done()) {\n\t\t\treturn\n\t\t}\n\t\tif (host) {\n\t\t\tspinner.write(dim(`ONLINE ${gray(`[${host}:${port}]`)}`))\n\t\t\tspinner.blank()\n\t\t}\n\t})\n\n\tbeam.on('connected', () => {\n\t\tif (lifecycle.done()) {\n\t\t\treturn\n\t\t}\n\t\tspinner.stop()\n\t\tlog(bold('PIPE ACTIVE'))\n\t\twrite(gray('SENDING FILE'))\n\t\tblank()\n\n\t\tconst header = encodeHeader({ name: fileName, size: fileStat.size, type: 'file' })\n\t\tif (beam.write(header) === false) {\n\t\t\tbeam.once('drain', () => {\n\t\t\t\tcreateReadStream(filePath).pipe(beam)\n\t\t\t})\n\t\t\treturn\n\t\t}\n\t\tcreateReadStream(filePath).pipe(beam)\n\t})\n\n\tbeam.on('error', (error: Error) => {\n\t\tspinner.stop()\n\t\tconst isPeerNotFound = error.message.includes('PEER_NOT_FOUND')\n\t\tif (isPeerNotFound) {\n\t\t\tlog(red(dim('PEER NOT FOUND')))\n\t\t} else if (error.message.includes('connection reset by peer')) {\n\t\t\tlog(dim('PEER DISCONNECTED'))\n\t\t} else {\n\t\t\tlogError(error.message)\n\t\t}\n\t\tblank()\n\t\tif (!isPeerNotFound) {\n\t\t\tlifecycle.shutdown()\n\t\t}\n\t})\n\n\tbeam.on('end', () => beam.end())\n\tbeam.on('finish', () => {\n\t\tlog(dim('FILE SENT'))\n\t\tblank()\n\t\tbeam.destroy()\n\t})\n}\n","import { copyToClipboard } from '@/lib/clipboard.ts'\nimport { loadOrCreateIdentityWithMeta } from '@/lib/identity.ts'\nimport { blank, bold, cyan, dim, log, write } from '@/lib/log.ts'\n\nconst EXIT_SUCCESS = 0\n\n/** Execute `hbeam whoami`. */\nexport async function runWhoamiCommand(): Promise<number> {\n\tconst identity = await loadOrCreateIdentityWithMeta()\n\tconst publicKey = identity.keyPair.publicKey.toString('hex')\n\n\tblank()\n\n\tif (identity.created) {\n\t\tlog(dim('IDENTITY CREATED'))\n\t}\n\n\tlog(bold('IDENTITY'))\n\twrite(cyan(publicKey))\n\tcopyToClipboard(publicKey)\n\n\tblank()\n\n\treturn EXIT_SUCCESS\n}\n","#!/usr/bin/env node\n\nimport mri from 'mri'\n\nimport { copyToClipboard } from '@/lib/clipboard.ts'\nimport { loadOrCreateIdentityWithMeta } from '@/lib/identity.ts'\nimport { bold, cyan, dim, log, write, writeBlock } from '@/lib/log.ts'\nimport { runBeamSession } from '@/lib/session.ts'\n\nimport { Beam } from './beam.ts'\nimport { runConnectCommand } from './commands/connect.ts'\nimport { runPeersCommand } from './commands/peers.ts'\nimport { runServeCommand } from './commands/serve.ts'\nimport { runWhoamiCommand } from './commands/whoami.ts'\n\nimport type { BeamOptions } from './types.ts'\n\nconst ARGV_OFFSET = 2\nconst EXIT_SUCCESS = 0\n\nconst NO_INDENT = ''\n\nconst argv = mri(process.argv.slice(ARGV_OFFSET), {\n\talias: { h: 'help', l: 'listen', o: 'output', v: 'version' },\n\tboolean: ['help', 'listen', 'version'],\n\tstring: ['output'],\n})\n\nif (argv.help) {\n\twriteBlock([\n\t\t`${bold('hbeam')} — end-to-end encrypted pipe over HyperDHT`,\n\t\t'',\n\t\t`${bold('Usage:')}`,\n\t\t` hbeam ${dim('[passphrase]')} ${dim('[options]')}`,\n\t\t` hbeam connect ${dim('<name>')}`,\n\t\t` hbeam peers ${dim('<add|rm|ls> ...')}`,\n\t\t` hbeam serve ${dim('<file>')} ${dim('[--listen]')}`,\n\t\t` hbeam whoami`,\n\t\t'',\n\t\t`${bold('Options:')}`,\n\t\t` ${dim('-l, --listen')} Listen using passphrase or identity`,\n\t\t` ${dim('-o, --output')} Save incoming file to a specific path`,\n\t\t` ${dim('-h, --help')} Show this help`,\n\t\t` ${dim('-v, --version')} Show version`,\n\t\t'',\n\t\t`${bold('Examples:')}`,\n\t\t` ${dim('# Start a new pipe (generates a passphrase)')}`,\n\t\t\" echo 'hello' | hbeam\",\n\t\t'',\n\t\t` ${dim('# Connect to an existing pipe')}`,\n\t\t' hbeam <passphrase>',\n\t\t'',\n\t\t` ${dim('# Listen with a specific passphrase')}`,\n\t\t\" echo 'hello again' | hbeam <passphrase> --listen\",\n\t\t'',\n\t\t` ${dim('# Listen on your persistent identity')}`,\n\t\t' hbeam --listen',\n\t\t'',\n\t\t` ${dim('# Save and connect to peers by name')}`,\n\t\t' hbeam peers add workserver <public-key>',\n\t\t' hbeam connect workserver',\n\t\t'',\n\t\t` ${dim('# Serve a single file')}`,\n\t\t' hbeam serve ./report.pdf',\n\t])\n\tprocess.exit(EXIT_SUCCESS)\n}\n\nif (argv.version) {\n\tconst pkg = (await import('../package.json')) as { version?: string }\n\twrite(pkg.version ?? '0.0.0', NO_INDENT)\n\tprocess.exit(EXIT_SUCCESS)\n}\n\nconst [firstArg, ...restArgs] = argv._ as string[]\nlet ranSubcommand = false\n\nif (firstArg === 'peers') {\n\tprocess.exit(await runPeersCommand(restArgs))\n}\nif (firstArg === 'connect') {\n\tawait runConnectCommand(restArgs, { outputPath: argv.output })\n\tranSubcommand = true\n}\nif (firstArg === 'serve') {\n\tawait runServeCommand(restArgs, { listen: argv.listen })\n\tranSubcommand = true\n}\nif (firstArg === 'whoami') {\n\tprocess.exit(await runWhoamiCommand())\n}\n\nif (!ranSubcommand) {\n\tconst passphrase = firstArg\n\n\tif (argv.listen && !passphrase) {\n\t\tconst identity = await loadOrCreateIdentityWithMeta()\n\t\tif (identity.created) {\n\t\t\tlog(dim('IDENTITY CREATED'))\n\t\t\twrite(cyan(identity.keyPair.publicKey.toString('hex')))\n\t\t}\n\n\t\tconst beam = new Beam({\n\t\t\tannounce: true,\n\t\t\tkeyPair: identity.keyPair,\n\t\t})\n\t\trunBeamSession(beam, {\n\t\t\tannounceLabel: 'PUBLIC KEY',\n\t\t\tcopyValue: copyToClipboard,\n\t\t\tmode: 'announce',\n\t\t\tvalue: beam.key,\n\t\t})\n\t} else {\n\t\tconst beamOptions: BeamOptions | undefined = argv.listen ? { announce: true } : undefined\n\t\tconst beam = new Beam(passphrase, beamOptions)\n\t\trunBeamSession(beam, {\n\t\t\tannounceLabel: 'PASSPHRASE',\n\t\t\tcopyValue: copyToClipboard,\n\t\t\tmode: beam.announce ? 'announce' : 'connect',\n\t\t\toutputPath: argv.output,\n\t\t\tvalue: beam.announce ? beam.key : (passphrase ?? 'unknown'),\n\t\t})\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAOA,MAAMA,iBAAe;AACrB,MAAM,aAAgC,EAAE;AAExC,MAAM,kBAA+C,CAAC;CAAE,MAAM;CAAY,SAAS;CAAU,CAAC;AAC9F,MAAM,mBAAgD,CAAC;CAAE,MAAM;CAAY,SAAS;CAAQ,CAAC;AAE7F,MAAM,iBAA8C;CACnD;EAAE,MAAM;EAAY,SAAS;EAAW;CACxC;EAAE,MAAM,CAAC,cAAc,YAAY;EAAE,SAAS;EAAS;CACvD;EAAE,MAAM,CAAC,eAAe,UAAU;EAAE,SAAS;EAAQ;CACrD;AAED,SAAS,uBAAoD;AAC5D,KAAI,QAAQ,aAAa,SACxB,QAAO;AAER,KAAI,QAAQ,aAAa,QACxB,QAAO;AAER,QAAO;;AAGR,SAAS,oBAAoB,MAAc,MAAiC;CAC3E,MAAM,SAAS,UAAU,KAAK,SAAS,KAAK,MAAM;EACjD,OAAO;EACP,OAAO;GAAC;GAAQ;GAAU;GAAS;EACnC,CAAC;AACF,QAAO,CAAC,OAAO,SAAS,OAAO,WAAWA;;;AAI3C,SAAgB,gBAAgB,MAAuB;AACtD,MAAK,MAAM,QAAQ,sBAAsB,CACxC,KAAI,oBAAoB,MAAM,KAAK,CAClC,QAAO;AAGT,QAAO;;;;;ACxCR,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAEvB,MAAM,WAAW;AACjB,MAAM,mBAAmB;;AAGzB,SAAgB,eAAuB;AACtC,QAAO,QAAQ,IAAI,mBAAmB,KAAK,SAAS,EAAE,iBAAiB,eAAe;;AAGvF,SAAS,kBAAkB,UAA0B;AACpD,QAAO,KAAK,cAAc,EAAE,SAAS;;;AAItC,eAAsB,kBAAiC;AACtD,OAAM,MAAM,cAAc,EAAE;EAAE,MAAM;EAAU,WAAW;EAAM,CAAC;;;AAIjE,eAAsB,aAAgB,UAA0C;CAC/E,MAAM,OAAO,kBAAkB,SAAS;AACxC,KAAI;EACH,MAAM,MAAM,MAAM,SAAS,MAAM,OAAO;AACxC,SAAO,KAAK,MAAM,IAAI;UACd,OAAO;AAEf,MADY,MACJ,SAAS,SAChB;AAED,QAAM;;;;;;;;AASR,eAAsB,cACrB,UACA,MACA,SACgB;CAChB,MAAM,OAAO,kBAAkB,SAAS;AACxC,OAAM,iBAAiB;AACvB,OAAM,MAAM,QAAQ,KAAK,EAAE;EAAE,MAAM;EAAU,WAAW;EAAM,CAAC;AAC/D,OAAM,UAAU,MAAM,GAAG,KAAK,UAAU,MAAM,MAAM,IAAK,CAAC,KAAK,OAAO;AACtE,KAAI,SAAS,OACZ,OAAM,MAAM,MAAM,iBAAiB;;;;;;AClDrC,SAAgB,SAAS,KAAqB;AAC7C,QAAO,IAAI,OAAO,IAAI,CAAC,QAAQ,MAAM,GAAG,CAAC,aAAa;;;AAIvD,SAAgB,WAAW,KAAqB;AAC/C,QAAO,IAAI,KAAK,IAAI,OAAO,QAAQ,IAAI,aAAa,CAAC,CAAC;;;AAIvD,SAAgB,YAAY,QAAwB;CACnD,MAAM,SAAS,IAAI,MAAM,OAAO;AAChC,QAAO,gBAAgB,OAAO;AAC9B,QAAO;;;;;ACXR,MAAM,gBAAgB;AAEtB,MAAMC,iBAAe;AACrB,MAAMC,mBAAiB;AACvB,MAAMC,qBAAmB;AACzB,MAAMC,mBAAiB;AACvB,MAAM,mBAAmB;AAEzB,SAAS,MAAM,QAAwB;AACtC,QAAO,OAAO,SAAS,MAAM;;AAG9B,SAAS,QAAQ,KAAqB;AACrC,QAAO,OAAO,KAAK,KAAK,MAAM;;AAG/B,SAASC,QAAM,OAAwB;AACtC,QAAO,eAAe,KAAK,MAAM,IAAI,MAAM,SAASJ,mBAAiBC;;AAGtE,SAAS,cAAc,OAA0B;AAChD,KAAI,CAACG,QAAM,MAAM,UAAU,IAAI,CAACA,QAAM,MAAM,UAAU,CACrD,OAAM,IAAI,MAAM,kDAAkD;CAGnE,MAAM,YAAY,QAAQ,MAAM,UAAU;CAC1C,MAAM,YAAY,QAAQ,MAAM,UAAU;AAE1C,KAAI,UAAU,WAAWF,sBAAoB,UAAU,WAAW,iBACjE,OAAM,IAAI,MAAM,gDAAgD;AAGjE,QAAO;EAAE;EAAW;EAAW;;AAGhC,SAAS,kBAAkB,SAA4B;AACtD,QAAO;EACN,WAAW,MAAM,QAAQ,UAAU;EACnC,WAAW,MAAM,QAAQ,UAAU;EACnC;;AAGF,SAAS,iBAA0B;AAClC,QAAO,IAAI,QAAQ,YAAYC,iBAAe,CAAC;;;AAIhD,eAAsB,+BAGnB;CACF,MAAM,WAAW,MAAM,aAAuB,cAAc;AAC5D,KAAI,SACH,QAAO;EAAE,SAAS;EAAO,SAAS,cAAc,SAAS;EAAE;CAG5D,MAAM,UAAU,gBAAgB;AAChC,OAAM,cAAc,eAAe,kBAAkB,QAAQ,EAAE,EAAE,QAAQ,MAAM,CAAC;AAChF,QAAO;EAAE,SAAS;EAAM;EAAS;;;;;AC7DlC,MAAM,kBAAkB;AACxB,MAAM,aAAa;AACnB,MAAM,YAAY;AAElB,MAAa,SAAS;AACtB,MAAa,YAAY,IAAI,IAAI,OAAO,gBAAgB,CAAC;;AAGzD,SAAgB,MAAM,SAAiB,SAAiB,QAAc;AACrE,SAAQ,OAAO,MAAM,GAAG,SAAS,QAAQ,IAAI;;;AAI9C,SAAgB,QAAc;AAC7B,SAAQ,OAAO,MAAM,KAAK;;;AAI3B,SAAgB,WAAW,OAAuB;AACjD,MAAK,MAAM,QAAQ,MAClB,SAAQ,OAAO,MAAM,GAAG,SAAS,KAAK,IAAI;;;AAK5C,SAAgB,IAAI,SAAuB;AAC1C,OAAM,QAAQ;;;AAIf,SAAgB,SAAS,SAAuB;AAC/C,SAAQ,OAAO,MAAM,GAAG,SAAS,IAAI,QAAQ,CAAC,GAAG,QAAQ,IAAI;;;AAS9D,SAAgB,YAAkB;AACjC,KAAI,QAAQ,OAAO,MAClB,SAAQ,OAAO,MAAM,WAAW;KAEhC,SAAQ,OAAO,MAAM,KAAK;;;AAO5B,SAAS,SAAS,GAAmB;AACpC,QAAO,UAAU,EAAE;;;AAIpB,SAAS,WAAW,GAAmB;AACtC,QAAO,UAAU,EAAE;;;AAgBpB,SAAgB,cAAc,QAA2B,YAA6B;CACrF,IAAI,SAAS;CACb,IAAI,aAAa;CACjB,IAAI,QAA+D;CAEnE,SAAS,SAAe;AACvB,MAAI,SAAS,UACZ,SAAQ,OAAO,MAAM,SAAS,OAAO,CAAC;AAEvC,UAAQ,OAAO,MAAM,GAAG,aAAa,SAAS,OAAO,cAAc;AACnE,MAAI,SAAS,UACZ,SAAQ,OAAO,MAAM,GAAG,WAAW,OAAO,CAAC,IAAI;AAEhD;AACA,MAAI,cAAc,OAAO,OACxB,cAAa;;AAIf,QAAO;EACN,QAAc;AACb,UAAO;AACP;;EAED,QAAc;AACb,WAAQ;AACR,WAAQ,OAAO,MAAM,KAAK;AAC1B;AACA,WAAQ,WAAW,YAAY,QAAQ,WAAW;;EAEnD,OAAa;AACZ,OAAI,OAAO;AACV,eAAW,cAAc,MAAM;AAC/B,YAAQ;;;EAGV,MAAM,SAAuB;AAC5B,SAAM,QAAQ;AACd;;EAED;;;;;ACrHF,MAAM,YAAY;AAClB,MAAM,gBAAgB,YAAY,UAAU;AAC5C,MAAM,UAAU;AAChB,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAME,gBAAc;AACpB,MAAM,WAAW;AACjB,MAAM,oBAAoB;AAC1B,MAAM,cAAc;CAAC;CAAK;CAAM;CAAM;CAAM;CAAK;AAQjD,SAAgB,aAAa,QAA4B;AACxD,QAAO,OAAO,KAAK,GAAG,KAAK,UAAU,OAAO,GAAG,WAAW,OAAO;;AAGlE,SAAgB,aAAa,OAAwB;CACpD,MAAM,SAAS,cAAc,MAAMA,eAAa,KAAK,IAAI,MAAM,QAAQ,cAAc,OAAO,CAAC;AAC7F,QAAO,MAAM,SAAS,QAAQA,eAAa,OAAO,OAAO,KAAK;;AAG/D,SAAgB,gBAAgB,MAA0B;CACzD,MAAM,SAAS,KAAK,MAAM,KAAK,SAAS,OAAO,CAAC;AAChD,KAAI,OAAO,SAAS,UACnB,OAAM,IAAI,MAAM,2BAA2B;AAE5C,KAAI,CAAC,OAAO,QAAQ,OAAO,OAAO,SAAS,SAC1C,OAAM,IAAI,MAAM,2BAA2B;AAE5C,KACC,OAAO,OAAO,SAAS,YACvB,CAAC,OAAO,cAAc,OAAO,KAAK,IAClC,OAAO,OAAO,SAEd,OAAM,IAAI,MAAM,2BAA2B;AAE5C,QAAO;EAAE,MAAM,OAAO;EAAM,MAAM,OAAO;EAAM,MAAM;EAAW;;AAGjE,SAAgB,kBAAkB,OAAuB;AACxD,QAAO,MAAM,QAAQ,QAAQ;;AAG9B,SAAgB,eAAe,OAAuB;AACrD,KAAI,CAAC,OAAO,SAAS,MAAM,IAAI,QAAQ,SACtC,QAAO,KAAK,YAAYA;CAGzB,IAAI,OAAO;CACX,IAAI,YAAYA;CAChB,MAAM,gBAAgB,YAAY,SAAS;AAC3C,QAAO,QAAQ,iBAAiB,YAAY,eAAe;AAC1D,UAAQ;AACR;;AAGD,KAAI,cAAcA,cACjB,QAAO,GAAG,KAAK,MAAM,KAAK,CAAC,GAAG,YAAY;AAE3C,QAAO,GAAG,KAAK,QAAQ,eAAe,CAAC,GAAG,YAAY;;;;;AC3DvD,MAAMC,iBAAe;AACrB,MAAM,sBAAsB;;;;;;;AAgB5B,SAAgB,gBAAgB,MAAY,SAAuC;CAClF,IAAI,iBAAiB;CAErB,SAAS,WAAiB;AACzB,MAAI,eACH;AAED,mBAAiB;AACjB,WAAS,MAAM;AAEf,MAAIC,MAAI,gBAAgB,CAAC;AACzB,SAAO;EAEP,MAAM,UAAU,WAAW,iBAAiB;AAC3C,WAAQ,KAAKD,eAAa;KACxB,oBAAoB;AAEvB,OAAK,SAAS;AACd,OAAK,GAAG,eAAe;AACtB,cAAW,aAAa,QAAQ;IAC/B;;AAGH,SAAQ,KAAK,gBAAgB;AAC5B,aAAW;AACX,YAAU;GACT;AAEF,QAAO;EACN,OAAgB;AACf,UAAO;;EAER;EACA;;;;;AClDF,MAAM,MAAM;AACZ,MAAM,KAAK;AACX,MAAM,SAAS;AACf,MAAM,QAAQ;AACd,MAAM,mBAAmB;AACzB,MAAM,cAAc;AAEpB,SAAS,UAAU,MAAsB;AACxC,QAAO,KAAK,SAAS,OAAO,CAAC,aAAa,CAAC,OAAO,iBAAiB;;;AAIpE,eAAsB,QAAQ,SAAmC;AAChE,SAAQ,OAAO,MAAM,GAAG,SAAS,QAAQ,GAAGE,MAAI,QAAQ,CAAC,GAAG;CAE5D,MAAM,QAAQ,QAAQ;AACtB,KAAI,CAAC,MAAM,SAAS,OAAO,MAAM,eAAe,YAAY;AAC3D,UAAQ,OAAO,MAAM,KAAK;AAC1B,SAAO;;CAGR,MAAM,cAAc,MAAM;AAE1B,QAAO,MAAM,IAAI,SAAiB,YAAW;EAC5C,SAAS,QAAQ,QAAuB;AACvC,SAAM,WAAW,QAAQ,YAAY,CAAC;AACtC,SAAM,OAAO;AACb,SAAM,eAAe,QAAQ,OAAO;AACpC,WAAQ,OAAO,MAAM,GAAG,SAAS,MAAM,GAAG,IAAI;AAC9C,WAAQ,OAAO;;EAGhB,SAAS,OAAO,MAAoB;GACnC,MAAM,MAAM,UAAU,KAAK;AAC3B,OAAI,QAAQ,QAAQ;AACnB,YAAQ,OAAO,MAAM,KAAK;AAC1B,YAAQ,KAAK,QAAQ,KAAK,SAAS;AACnC,YAAQ,MAAM;AACd;;AAED,OAAI,QAAQ,KAAK;AAChB,YAAQ,KAAK;AACb;;AAED,OAAI,QAAQ,MAAM,QAAQ,MAAM,QAAQ,MACvC,SAAQ,MAAM;;AAIhB,QAAM,WAAW,KAAK;AACtB,QAAM,QAAQ;AACd,QAAM,GAAG,QAAQ,OAAO;GACvB;;;AAIH,eAAsB,MAAM,SAAiB,aAAsC;AAClF,KAAI,CAAC,QAAQ,MAAM,SAAS,CAAC,QAAQ,OAAO,MAC3C,QAAO;CAGR,MAAM,KAAK,gBAAgB;EAC1B,OAAO,QAAQ;EACf,QAAQ,QAAQ;EAChB,UAAU;EACV,CAAC;AAEF,KAAI;EACH,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,QAAQ,GAAG;AACzD,KAAG,MAAM,YAAY;EAErB,MAAM,WADS,MAAM,eACE,MAAM;AAC7B,SAAO,YAAY,cAAc,cAAc;WACtC;AACT,KAAG,OAAO;;;;;;AC5EZ,MAAM,cAAc;AACpB,MAAM,SAAS;AACf,MAAM,SAAS;AACf,MAAM,YAAY;AAClB,MAAM,YAAY;AAClB,MAAM,YAAY;AAElB,MAAM,aAAgC;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;;;;;;;;;AAWD,SAAgB,kBAAkB,OAAyD;AAgB1F,QAAO;EAAE,QAfM,WAAW,KAAK,GAAG,MAAM;GACvC,MAAM,iBAAiB,KAAK,IAC3B,KAAK,IAAI,IAAI,OAAO,EACpB,KAAK,IAAI,IAAI,OAAO,EACpB,KAAK,IAAI,IAAI,UAAU,CACvB;GACD,IAAI,QAAQ,IAAI,EAAE;AAClB,OAAI,mBAAmB,UACtB,SAAQ,KAAK,EAAE;YACL,mBAAmB,UAC7B,SAAQ;AAET,UAAO,GAAG,KAAK,MAAM,CAAC,GAAG;IACxB;EAEe,YAAY;EAAa;;;;;AC/B3C,MAAM,cAAc;AACpB,MAAM,aAAa;AACnB,MAAM,sBAAsB;;AAW5B,SAAgB,eAAe,MAAY,SAA+B;CACzE,MAAM,EAAE,QAAQ,eAAe,kBAAkB,QAAQ;CACzD,MAAM,UAAU,cAAc,QAAQ,WAAW;CACjD,MAAM,YAAY,gBAAgB,MAAM,QAAQ;AAEhD,QAAO;AACP,SAAQ,OAAO;AACf,SAAQ,OAAO;AAEf,KAAI,QAAQ,SAAS,YAAY;AAChC,UAAQ,MAAMC,MAAI,QAAQ,iBAAiB,aAAa,CAAC;AACzD,UAAQ,MAAM,KAAK,QAAQ,MAAM,CAAC;AAClC,UAAQ,YAAY,QAAQ,MAAM;QAC5B;AACN,UAAQ,MAAMA,MAAI,aAAa,CAAC;AAChC,UAAQ,MAAM,KAAK,QAAQ,MAAM,CAAC;;AAGnC,MAAK,GAAG,mBAAmB,EAAE,MAAM,WAA2B;AAC7D,MAAI,UAAU,MAAM,CACnB;AAED,MAAI,MAAM;AACT,WAAQ,MAAMA,MAAI,UAAU,KAAK,IAAI,KAAK,GAAG,KAAK,GAAG,GAAG,CAAC;AACzD,WAAQ,OAAO;;GAEf;AAEF,MAAK,GAAG,mBAAmB;AAC1B,MAAI,UAAU,MAAM,CACnB;AAED,UAAQ,MAAM;AACd,MAAIC,OAAK,cAAc,CAAC;AACxB,QAAM,KAAK,sBAAsB,CAAC;AAClC,SAAO;GACN;AAEF,MAAK,GAAG,UAAU,UAAiB;AAClC,UAAQ,MAAM;EACd,MAAM,iBAAiB,MAAM,QAAQ,SAAS,iBAAiB;AAC/D,MAAI,eACH,KAAIC,MAAIF,MAAI,iBAAiB,CAAC,CAAC;WACrB,MAAM,QAAQ,SAAS,2BAA2B,CAC5D,KAAIA,MAAI,oBAAoB,CAAC;MAE7B,UAAS,MAAM,QAAQ;AAExB,SAAO;AACP,MAAI,CAAC,eACJ,WAAU,UAAU;GAEpB;AAEF,MAAK,GAAG,aAAa,KAAK,KAAK,CAAC;CAEhC,IAAI,eAAe;CACnB,IAAI,OAAoD;CACxD,IAAI,iBAA2B,EAAE;CACjC,IAAI,aAA+D;CACnE,IAAI,WAA+B;CACnC,IAAI,uBAAuB;CAC3B,IAAI,eAAyB,EAAE;CAE/B,SAAS,eAAe,OAAqB;AAC5C,MAAI,CAAC,cAAc;AAClB,kBAAe;AACf,SAAM,UAAU;AAChB,UAAO;;EAER,MAAM,QAAQ,MAAM,UAAU,CAAC,QAAQ,YAAY,OAAO;AAC1D,UAAQ,OAAO,MAAM,MAAM;;CAG5B,eAAe,kBAAkB,UAA+C;AAC/E,MAAI,QAAQ,WACX,QAAO,QAAQ,QAAQ,WAAW;AAEnC,MAAI,CAAC,QAAQ,OAAO,MACnB;EAGD,MAAM,gBAAgB,QAAQ,QAAQ,KAAK,EAAE,SAAS;AAEtD,MAAI,CADe,MAAM,QAAQ,sBAAsB,CAEtD,QAAO;AAER,SAAO,MAAM,MAAM,YAAY,cAAc;;CAG9C,eAAe,cAAc,cAAqC;EACjE,MAAM,UAAU,kBAAkB,aAAa;AAC/C,MAAI,UAAU,YACb;EAGD,MAAM,SAAS,gBAAgB,aAAa,SAAS,aAAa,QAAQ,CAAC;EAC3E,MAAM,YAAY,aAAa,SAAS,UAAU,WAAW;AAE7D,MAAIA,MAAI,iBAAiB,OAAO,KAAK,IAAI,eAAe,OAAO,KAAK,CAAC,GAAG,CAAC;EAEzE,MAAM,aAAa,MAAM,kBAAkB,OAAO,KAAK;AACvD,MAAI,eAAe,IAAI;AACtB,OAAIA,MAAI,oBAAoB,CAAC;AAC7B,UAAO;AACP,aAAU,UAAU;AACpB;;AAGD,MAAI,eAAe,QAAW;AAC7B,UAAO;AACP,OAAI,UAAU,SAAS,oBACtB,SAAQ,OAAO,MAAM,UAAU;AAEhC;;AAGD,SAAO;AACP,aAAW;AACX,QAAM,MAAM,QAAQ,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;AACrD,eAAa,kBAAkB,WAAW;AAC1C,aAAW,GAAG,UAAS,UAAS,KAAK,QAAQ,MAAM,CAAC;AACpD,MAAI,UAAU,SAAS,oBACtB,YAAW,MAAM,UAAU;;CAI7B,SAAS,YAAY,OAAqB;AACzC,MAAI,sBAAsB;AACzB,gBAAa,KAAK,MAAM;AACxB;;AAGD,MAAI,SAAS,QAAQ;AACpB,kBAAe,MAAM;AACrB;;AAED,MAAI,SAAS,QAAQ;AACpB,eAAY,MAAM,MAAM;AACxB;;AAED,MAAI,SAAS,eAAe;AAC3B,WAAQ,OAAO,MAAM,MAAM;AAC3B;;AAGD,iBAAe,KAAK,MAAM;EAC1B,MAAM,UAAU,OAAO,OAAO,eAAe;AAC7C,MAAI,aAAa,QAAQ,EAAE;AAE1B,OADgB,kBAAkB,QAAQ,GAC5B,YACb;AAED,oBAAiB,EAAE;AACnB,0BAAuB;AACvB,GAAK,cAAc,QAAQ,CAAC,cAAc;AACzC,2BAAuB;AACvB,SAAK,MAAM,eAAe,aACzB,aAAY,YAAY;AAEzB,mBAAe,EAAE;KAChB;AACF;;AAGD,SAAO;AACP,mBAAiB,EAAE;AACnB,iBAAe,QAAQ;;AAGxB,MAAK,GAAG,SAAS,UAAkB;AAClC,cAAY,MAAM;GACjB;AAEF,MAAK,GAAG,aAAa;AACpB,MAAI,SAAS,aAAa,eAAe,SAAS,qBAAqB;AACtE,UAAO;AACP,kBAAe,OAAO,OAAO,eAAe,CAAC;AAC7C,oBAAiB,EAAE;;AAEpB,MAAI,SAAS,UAAU,cAAc;AACpC,UAAO;AACP,SAAM,UAAU;;AAEjB,MAAI,SAAS,OACZ,aAAY,UAAU;AACrB,OAAIA,MAAI,SAAS,YAAY,KAAK,CAAC;AACnC,UAAO;IACN;GAEF;AAEF,SAAQ,MAAM,KAAK,KAAK;AAExB,KAAI,OAAO,QAAQ,MAAM,UAAU,WAClC,SAAQ,MAAM,OAAO;;;;;AClOvB,MAAMG,mBAAiB;;;;;;;;AASvB,SAAS,cAAc,MAAsB;AAC5C,KAAI,KAAK,WAAWA,iBACnB,QAAO;CAER,MAAM,SAAS,WAAW,SAAS,CAAC,OAAO,KAAK,CAAC,QAAQ;AACzD,QAAO,IAAI,KAAK,OAAO;;;AAIxB,SAAgB,cAAc,YAA6B;CAC1D,MAAM,OAAO,WAAW,WAAW;AACnC,QAAO,IAAI,QAAQ,cAAc,KAAK,CAAC;;;AAIxC,SAAgB,aAA2B;AAC1C,QAAO,IAAI,IAAI,EAAE,WAAW,MAAM,CAAC;;;AAIpC,SAAgB,UAAU,QAAwC;AACjE,QAAO,IAAI,SAAe,SAAS,WAAW;AAC7C,SAAO,KAAK,QAAQ,QAAQ;AAC5B,SAAO,KAAK,SAAS,OAAO;AAC5B,SAAO,KAAK,SAAS,OAAO;GAC3B;;;AAIH,SAAgB,eAAe,SAAwD;AACtF,SAAQ,oBAA4B,CAAC,IAAI,OAAO,iBAAiB,QAAQ,UAAU;;;;;;AC/BpF,MAAM,iBAAiB;;;;;;;;;;;;;;;;;AAkBvB,IAAa,OAAb,cAA0B,OAAO;;CAEhC,AAAS;;CAGT,AAAS;CAET,AAAQ;CACR,AAAQ,SAAqC;CAC7C,AAAQ,UAAuC;CAC/C,AAAQ,WAAwC;CAChD,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,AAAQ,eAA2C;CACnD,AAAQ,eAA2C;CACnD,AAAQ,gBAA4C;CAEpD,YAAY,cAAqC,SAAuB;AACvE,SAAO;EAEP,IAAI,MAA0B;EAC9B,IAAI,OAAoB,EAAE;EAC1B,MAAM,wBAAwB,OAAO,iBAAiB;AAEtD,MAAI,uBAAuB;AAC1B,SAAM;AACN,UAAO,WAAW,EAAE;QAEpB,QAAO,gBAAgB,EAAE;EAG1B,IAAI,iBAAiB,KAAK,YAAY;AAEtC,MAAI,CAAC,OAAO,CAAC,KAAK,SAAS;AAC1B,SAAM,SAAS,YAAY,eAAe,CAAC;AAC3C,oBAAiB;aACP,CAAC,OAAO,KAAK,QACvB,OAAM,KAAK,QAAQ,UAAU,SAAS,MAAM;AAE7C,MAAI,CAAC,IACJ,OAAM,IAAI,MAAM,uBAAuB;AAGxC,OAAK,MAAM;AACX,OAAK,WAAW;AAChB,OAAK,OAAQ,KAAK,OAAwB;AAC1C,OAAK,kBAAkB,KAAK;AAC5B,OAAK,0BAA0B,KAAK;AACpC,OAAK,sBAAsB,CAAC,yBAAyB,KAAK,YAAY;;;CAIvE,IAAI,YAAqB;AACxB,SAAO,KAAK,aAAa;;CAK1B,MAAe,MAAM,IAAmC;AACvD,OAAK,eAAe;EACpB,MAAM,UAAU,KAAK,mBAAmB,cAAc,KAAK,IAAI;AAC/D,OAAK,SAAS,YAAY;AAE1B,MAAI,KAAK,SACR,OAAM,KAAK,eAAe,QAAQ;MAElC,OAAM,KAAK,gBAAgB,QAAQ;;CAIrC,AAAS,MAAM,IAA0B;AACxC,OAAK,eAAe;AACpB,OAAK,SAAS,QAAQ;;CAGvB,AAAS,OAAO,MAAe,IAA0B;AACxD,MAAI,KAAK,SAAU,MAAM,KAAe,KAAK,OAAO;AACnD,OAAI;AACJ;;AAED,OAAK,gBAAgB;;CAGtB,AAAS,OAAO,IAA0B;EACzC,MAAM,aAAmB;AACxB,QAAK,SAAU,eAAe,UAAU,KAAK;AAC7C,QAAK,SAAU,eAAe,SAAS,KAAK;AAC5C,OAAI;;AAEL,OAAK,SAAU,KAAK;AACpB,OAAK,SAAU,GAAG,UAAU,KAAK;AACjC,OAAK,SAAU,GAAG,SAAS,KAAK;;CAGjC,AAAS,cAAoB;AAC5B,OAAK,SAAS,SAAS;AACvB,OAAK,UAAU,SAAS;EACxB,MAAM,wBAAQ,IAAI,MAAM,YAAY;AACpC,OAAK,YAAY,MAAM;AACvB,OAAK,YAAY,MAAM;AACvB,OAAK,aAAa,MAAM;;CAGzB,MAAe,SAAS,IAAmC;AAC1D,MAAI,CAAC,KAAK,MAAM;AACf,OAAI;AACJ;;AAED,MAAI,KAAK,OACR,OAAM,KAAK,OAAO,OAAO,CAAC,YAAY,GAAG;AAE1C,QAAM,KAAK,KAAK,SAAS,CAAC,YAAY,GAAG;AACzC,MAAI;;CAKL,MAAc,eAAe,SAAiC;EAC7D,MAAM,gBAAgB,KAAK,sBACxB,SACA,EAAE,UAAU,eAAe,QAAQ,EAAE;AACxC,OAAK,SAAS,KAAK,KAAM,aAAa,cAAc;AACpD,OAAK,OAAO,GAAG,eAAe,WAA4B,KAAK,iBAAiB,OAAO,CAAC;AAExF,MAAI;AACH,SAAM,KAAK,OAAO,OAAO,QAAQ;WACzB,OAAO;AACf,QAAK,YAAY,MAAe;AAChC;;AAED,OAAK,mBAAmB;;CAGzB,MAAc,gBAAgB,SAAiC;EAC9D,MAAM,kBAAkB,KAAK,2BAA2B,QAAQ;EAChE,MAAM,SAA0B,KAAK,KAAM,QAAQ,iBAAiB,EAAE,SAAS,CAAC;AAEhF,MAAI;AACH,SAAM,UAAU,OAAO;WACf,OAAO;AACf,QAAK,YAAY,MAAe;AAChC;;AAED,OAAK,mBAAmB;AACxB,OAAK,iBAAiB,OAAO;;CAG9B,AAAQ,iBAAiB,QAA+B;AACvD,SAAO,GAAG,SAAS,SAAiB;AACnC,OAAI,CAAC,KAAK,SAAS;AAClB,SAAK,UAAU;AACf,SAAK,QAAQ,GAAG,UAAU,QAAe,KAAK,QAAQ,IAAI,CAAC;AAC3D,SAAK,QAAQ,GAAG,aAAa,KAAK,iBAAiB,CAAC;;AAErD,OAAI,WAAW,KAAK,QACnB;AAED,OAAI,KAAK,SAAS,KAAK,KAAK,MAC3B,QAAO,OAAO;IAEd;AAEF,SAAO,GAAG,aAAa;AACtB,OAAI,KAAK,QACR;AAED,QAAK,iBAAiB;IACrB;AAEF,MAAI,CAAC,KAAK,UAAU;AACnB,QAAK,WAAW;AAChB,QAAK,SAAS,GAAG,UAAU,QAAe,KAAK,QAAQ,IAAI,CAAC;AAC5D,QAAK,SAAS,GAAG,eAAe,KAAK,cAAc,CAAC;AACpD,QAAK,KAAK,YAAY;AACtB,QAAK,aAAa;;;CAMpB,AAAQ,SAAS,MAA8B;EAC9C,MAAM,SAAS,KAAK,KAAK,KAAK;AAC9B,kBAAgB,KAAK,aAAa,CAAC;AACnC,SAAO;;CAGR,AAAQ,kBAAwB;AAE/B,OAAK,SAAS,KAAK;;CAGpB,AAAQ,oBAA0B;AACjC,OAAK,KAAK,kBAAkB;GAC3B,MAAM,KAAK,KAAM;GACjB,MAAM,KAAK,KAAM;GACjB,CAA0B;;CAG5B,AAAQ,YAAY,OAAqB;EACxC,MAAM,KAAK,KAAK;AAChB,MAAI,IAAI;AACP,QAAK,eAAe;AACpB,MAAG,MAAM;;;CAIX,AAAQ,YAAY,OAAqB;EACxC,MAAM,KAAK,KAAK;AAChB,MAAI,IAAI;AACP,QAAK,eAAe;AACpB,MAAG,MAAM;;;CAIX,AAAQ,aAAa,OAAqB;EACzC,MAAM,KAAK,KAAK;AAChB,MAAI,IAAI;AACP,QAAK,gBAAgB;AACrB,MAAG,MAAM;;;;;;;AC3PZ,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAMC,qBAAmB;AACzB,MAAM,iBAAiB;AAEvB,SAAS,MAAM,OAAwB;AACtC,QAAO,eAAe,KAAK,MAAM,IAAI,MAAM,SAAS,iBAAiB;;AAGtE,SAAS,sBAAsB,cAA8B;CAC5D,MAAM,aAAa,aAAa,MAAM,CAAC,aAAa;AACpD,KAAI,CAAC,MAAM,WAAW,CACrB,OAAM,IAAI,MAAM,wCAAwC;AAGzD,KADY,OAAO,KAAK,YAAY,MAAM,CAClC,WAAWA,mBAClB,OAAM,IAAI,MAAM,6CAA6C;AAE9D,QAAO;;AAGR,SAAS,kBAAkB,MAAsB;CAChD,MAAM,aAAa,KAAK,MAAM,CAAC,aAAa;AAC5C,KAAI,CAAC,eAAe,KAAK,WAAW,CACnC,OAAM,IAAI,MAAM,wDAAwD;AAEzE,QAAO;;AAGR,eAAe,kBAAwC;AACtD,QAAQ,MAAM,aAA0B,WAAW,IAAK,EAAE;;AAG3D,eAAe,iBAAiB,aAAyC;AACxE,OAAM,cAAc,YAAY,YAAY;;;AAI7C,eAAsB,QAAQ,MAAc,cAAqC;CAChF,MAAM,iBAAiB,kBAAkB,KAAK;CAC9C,MAAM,sBAAsB,sBAAsB,aAAa;CAC/D,MAAM,cAAc,MAAM,iBAAiB;CAE3C,MAAM,OAAa;EAClB,0BAAS,IAAI,MAAM,EAAC,aAAa;EACjC,WAAW;EACX;AACD,aAAY,kBAAkB;AAE9B,OAAM,iBAAiB,YAAY;AACnC,QAAO;;;AAIR,eAAsB,WAAW,MAAgC;CAChE,MAAM,iBAAiB,kBAAkB,KAAK;CAC9C,MAAM,cAAc,MAAM,iBAAiB;AAC3C,KAAI,CAAC,YAAY,gBAChB,QAAO;AAER,QAAO,YAAY;AACnB,OAAM,iBAAiB,YAAY;AACnC,QAAO;;;AAIR,eAAsB,YAAkD;CACvE,MAAM,cAAc,MAAM,iBAAiB;AAC3C,QAAO,OAAO,QAAQ,YAAY,CAChC,KAAK,CAAC,MAAM,WAAW;EAAE;EAAM,GAAG;EAAM,EAAE,CAC1C,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC;;;AAInD,eAAsB,QAAQ,MAAyC;CACtE,MAAM,iBAAiB,kBAAkB,KAAK;AAE9C,SADoB,MAAM,iBAAiB,EACxB;;;;;AC1EpB,MAAMC,iBAAe;AACrB,MAAM,mBAAmB;;AAOzB,eAAsB,kBACrB,MACA,UAAiC,EAAE,EACnB;CAChB,MAAM,CAAC,QAAQ;AACf,KAAI,CAAC,MAAM;AACV,SAAO;AACP,WAAS,qBAAqB;AAC9B,QAAMC,MAAI,8BAA8B,CAAC;AACzC,SAAO;AACP,UAAQ,KAAKD,eAAa;;CAG3B,MAAM,OAAO,MAAM,QAAQ,KAAK,CAAC,YAAY,OAAU;AACvD,KAAI,CAAC,MAAM;AACV,SAAO;AACP,WAAS,iBAAiB,OAAO;AACjC,SAAO;AACP,UAAQ,KAAKA,eAAa;;CAG3B,MAAM,kBAAkB,OAAO,KAAK,KAAK,WAAW,MAAM;AAC1D,KAAI,gBAAgB,WAAW,kBAAkB;AAChD,SAAO;AACP,WAAS,gCAAgC,OAAO;AAChD,SAAO;AACP,UAAQ,KAAKA,eAAa;;CAG3B,MAAM,WAAW,MAAM,8BAA8B;AACrD,KAAI,SAAS,SAAS;AACrB,SAAO;AACP,MAAIC,MAAI,mBAAmB,CAAC;AAC5B,QAAM,KAAK,SAAS,QAAQ,UAAU,SAAS,MAAM,CAAC,CAAC;;AAQxD,gBALa,IAAI,KAAK;EACrB,SAAS,SAAS;EAClB;EACA,CAAC,EAEmB;EACpB,MAAM;EACN,YAAY,QAAQ;EACpB,OAAO;EACP,CAAC;;;;;ACxDH,MAAMC,iBAAe;AACrB,MAAMC,iBAAe;AACrB,MAAM,cAAc;AACpB,MAAM,2BAA2B;AACjC,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AACzB,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;AACtB,MAAM,0BAA0B;AAChC,MAAM,mBAAmB,qBAAqB;AAC9C,MAAM,kBAAkB,mBAAmB;AAC3C,MAAM,mBAAmB,kBAAkB;AAC3C,MAAM,cAAc;AAEpB,SAAS,UAAU,SAAyB;CAC3C,MAAM,OAAO,KAAK,MAAM,QAAQ;AAEhC,KAAI,OAAO,MAAM,KAAK,CACrB,QAAO;CAGR,MAAM,UAAU,KAAK,OAAO,KAAK,KAAK,GAAG,QAAQ,wBAAwB;AAEzE,KAAI,UAAU,mBACb,QAAO;AAGR,KAAI,UAAU,iBACb,QAAO,GAAG,KAAK,MAAM,UAAU,mBAAmB,CAAC;AAGpD,KAAI,UAAU,gBACb,QAAO,GAAG,KAAK,MAAM,UAAU,iBAAiB,CAAC;AAGlD,KAAI,UAAU,iBACb,QAAO,GAAG,KAAK,MAAM,UAAU,gBAAgB,CAAC;AAGjD,QAAO,GAAG,KAAK,MAAM,UAAU,iBAAiB,CAAC;;AAGlD,SAAS,WAAW,WAA2B;AAC9C,QAAO,GAAG,UAAU,MAAM,aAAa,yBAAyB,CAAC;;AAGlE,SAAS,QAAc;AACtB,UAAS,yBAAyB;AAClC,OAAMC,MAAI,6CAA6C,CAAC;AACxD,OAAMA,MAAI,+BAA+B,CAAC;AAC1C,OAAMA,MAAI,wBAAwB,CAAC;;AAGpC,eAAe,UAAU,MAA0B,WAAgD;AAClG,KAAI,CAAC,QAAQ,CAAC,WAAW;AACxB,SAAO;AACP,SAAO;AACP,SAAO;AACP,SAAOD;;AAGR,KAAI;AACH,QAAM,QAAQ,MAAM,UAAU;UACtB,OAAO;AACf,SAAO;AACP,WAAU,MAAgB,QAAQ;AAClC,SAAO;AACP,SAAOA;;AAGR,QAAO;AACP,KAAIE,OAAK,QAAQ,CAAC;AAClB,OAAM,KAAK,KAAK,CAAC;AACjB,QAAO;AAEP,QAAOH;;AAGR,eAAe,aAAa,MAA2C;AACtE,KAAI,CAAC,MAAM;AACV,SAAO;AACP,SAAO;AACP,SAAO;AACP,SAAOC;;AAKR,KAAI,CAFS,MAAM,QAAQ,KAAK,CAAC,YAAY,OAAU,EAE5C;AACV,SAAO;AACP,WAAS,iBAAiB,OAAO;AACjC,SAAO;AACP,SAAOA;;AAGR,QAAO;AAGP,KAAI,CAFa,MAAM,QAAQ,UAAU,KAAK,GAAG,EAElC;AACd,MAAIC,MAAI,YAAY,CAAC;AACrB,SAAO;AACP,SAAOF;;AAGR,OAAM,WAAW,KAAK;AACtB,KAAIG,OAAK,UAAU,CAAC;AACpB,QAAO;AAEP,QAAOH;;AAGR,eAAe,aAA8B;CAC5C,MAAM,QAAQ,MAAM,WAAW;AAE/B,QAAO;AACP,KAAIG,OAAK,QAAQ,CAAC;AAElB,KAAI,MAAM,WAAW,aAAa;AACjC,QAAMD,MAAI,sBAAsB,CAAC;AACjC,SAAO;AACP,SAAOF;;AAGR,QAAO;AAEP,MAAK,MAAM,QAAQ,MAClB,OAAM,GAAG,KAAK,KAAK,IAAIE,MAAI,WAAW,KAAK,UAAU,CAAC,CAAC,IAAIA,MAAI,UAAU,KAAK,QAAQ,CAAC,GAAG;AAG3F,QAAO;AAEP,QAAOF;;;AAIR,eAAsB,gBAAgB,MAAiC;CACtE,MAAM,CAAC,QAAQ,MAAM,aAAa;AAElC,KAAI,WAAW,MACd,QAAO,UAAU,MAAM,UAAU;AAGlC,KAAI,WAAW,KACd,QAAO,aAAa,KAAK;AAG1B,KAAI,WAAW,KACd,QAAO,YAAY;AAGpB,QAAO;AACP,QAAO;AACP,QAAO;AAEP,QAAOC;;;;;ACpIR,MAAM,eAAe;AACrB,MAAM,gBAAgB;AAMtB,SAAS,eAAe,SAAwB;AAC/C,QAAO;AACP,UAAS,QAAQ;AACjB,OAAMG,MAAI,uCAAuC,CAAC;AAClD,QAAO;AACP,SAAQ,KAAK,aAAa;;AAG3B,SAAS,uBAAgC;CACxC,MAAM,EAAE,QAAQ,eAAe,kBAAkB,QAAQ;AACzD,QAAO,cAAc,QAAQ,WAAW;;AAGzC,eAAe,qBAAqB,QAGjC;AACF,KAAI,CAAC,OACJ,QAAO,EAAE,eAAe,cAAc;CAEvC,MAAM,WAAW,MAAM,8BAA8B;AACrD,KAAI,SAAS,SAAS;AACrB,SAAO;AACP,MAAIA,MAAI,mBAAmB,CAAC;AAC5B,QAAM,KAAK,SAAS,QAAQ,UAAU,SAAS,MAAM,CAAC,CAAC;;AAExD,QAAO;EACN,eAAe;EACf,SAAS,SAAS;EAClB;;;AAIF,eAAsB,gBACrB,MACA,UAA+B,EAAE,EACjB;CAChB,MAAM,CAAC,cAAc;AACrB,KAAI,CAAC,WACJ,gBAAe,qBAAqB;CAGrC,MAAM,WAAW,QAAQ,WAAW;CACpC,MAAM,WAAW,SAAS,SAAS;CACnC,MAAM,WAAW,MAAM,KAAK,SAAS,CAAC,YAAY,OAAU;AAC5D,KAAI,CAAC,YAAY,CAAC,SAAS,QAAQ,CAClC,gBAAe,wBAAwB,aAAa;AAErD,KAAI,SAAS,OAAO,cACnB,gBAAe,sBAAsB,aAAa;CAGnD,MAAM,WAAW,MAAM,qBAAqB,QAAQ,OAAO;CAC3D,MAAM,OAAO,SAAS,UACnB,IAAI,KAAK;EACT,UAAU;EACV,SAAS,SAAS;EAClB,CAAC,GACD,IAAI,KAAK,QAAW,EAAE,UAAU,MAAM,CAAC;CAE1C,MAAM,UAAU,sBAAsB;CACtC,MAAM,YAAY,gBAAgB,MAAM,QAAQ;AAEhD,QAAO;AACP,SAAQ,OAAO;AACf,SAAQ,OAAO;AACf,SAAQ,MAAMA,MAAI,SAAS,cAAc,CAAC;AAC1C,SAAQ,MAAM,KAAK,KAAK,IAAI,CAAC;AAC7B,SAAQ,MAAMA,MAAI,QAAQ,SAAS,IAAI,eAAe,SAAS,KAAK,CAAC,GAAG,CAAC;AACzE,iBAAgB,KAAK,IAAI;AAEzB,MAAK,GAAG,mBAAmB,EAAE,MAAM,WAA2B;AAC7D,MAAI,UAAU,MAAM,CACnB;AAED,MAAI,MAAM;AACT,WAAQ,MAAMA,MAAI,UAAU,KAAK,IAAI,KAAK,GAAG,KAAK,GAAG,GAAG,CAAC;AACzD,WAAQ,OAAO;;GAEf;AAEF,MAAK,GAAG,mBAAmB;AAC1B,MAAI,UAAU,MAAM,CACnB;AAED,UAAQ,MAAM;AACd,MAAIC,OAAK,cAAc,CAAC;AACxB,QAAM,KAAK,eAAe,CAAC;AAC3B,SAAO;EAEP,MAAM,SAAS,aAAa;GAAE,MAAM;GAAU,MAAM,SAAS;GAAM,MAAM;GAAQ,CAAC;AAClF,MAAI,KAAK,MAAM,OAAO,KAAK,OAAO;AACjC,QAAK,KAAK,eAAe;AACxB,qBAAiB,SAAS,CAAC,KAAK,KAAK;KACpC;AACF;;AAED,mBAAiB,SAAS,CAAC,KAAK,KAAK;GACpC;AAEF,MAAK,GAAG,UAAU,UAAiB;AAClC,UAAQ,MAAM;EACd,MAAM,iBAAiB,MAAM,QAAQ,SAAS,iBAAiB;AAC/D,MAAI,eACH,KAAIC,MAAIF,MAAI,iBAAiB,CAAC,CAAC;WACrB,MAAM,QAAQ,SAAS,2BAA2B,CAC5D,KAAIA,MAAI,oBAAoB,CAAC;MAE7B,UAAS,MAAM,QAAQ;AAExB,SAAO;AACP,MAAI,CAAC,eACJ,WAAU,UAAU;GAEpB;AAEF,MAAK,GAAG,aAAa,KAAK,KAAK,CAAC;AAChC,MAAK,GAAG,gBAAgB;AACvB,MAAIA,MAAI,YAAY,CAAC;AACrB,SAAO;AACP,OAAK,SAAS;GACb;;;;;ACtJH,MAAMG,iBAAe;;AAGrB,eAAsB,mBAAoC;CACzD,MAAM,WAAW,MAAM,8BAA8B;CACrD,MAAM,YAAY,SAAS,QAAQ,UAAU,SAAS,MAAM;AAE5D,QAAO;AAEP,KAAI,SAAS,QACZ,KAAIC,MAAI,mBAAmB,CAAC;AAG7B,KAAIC,OAAK,WAAW,CAAC;AACrB,OAAM,KAAK,UAAU,CAAC;AACtB,iBAAgB,UAAU;AAE1B,QAAO;AAEP,QAAOF;;;;;ACNR,MAAM,cAAc;AACpB,MAAM,eAAe;AAErB,MAAM,YAAY;AAElB,MAAM,OAAO,IAAI,QAAQ,KAAK,MAAM,YAAY,EAAE;CACjD,OAAO;EAAE,GAAG;EAAQ,GAAG;EAAU,GAAG;EAAU,GAAG;EAAW;CAC5D,SAAS;EAAC;EAAQ;EAAU;EAAU;CACtC,QAAQ,CAAC,SAAS;CAClB,CAAC;AAEF,IAAI,KAAK,MAAM;AACd,YAAW;EACV,GAAGG,OAAK,QAAQ,CAAC;EACjB;EACA,GAAGA,OAAK,SAAS;EACjB,WAAWC,MAAI,eAAe,CAAC,GAAGA,MAAI,YAAY;EAClD,mBAAmBA,MAAI,SAAS;EAChC,iBAAiBA,MAAI,kBAAkB;EACvC,iBAAiBA,MAAI,SAAS,CAAC,GAAGA,MAAI,aAAa;EACnD;EACA;EACA,GAAGD,OAAK,WAAW;EACnB,KAAKC,MAAI,eAAe,CAAC;EACzB,KAAKA,MAAI,eAAe,CAAC;EACzB,KAAKA,MAAI,aAAa,CAAC;EACvB,KAAKA,MAAI,gBAAgB,CAAC;EAC1B;EACA,GAAGD,OAAK,YAAY;EACpB,KAAKC,MAAI,8CAA8C;EACvD;EACA;EACA,KAAKA,MAAI,gCAAgC;EACzC;EACA;EACA,KAAKA,MAAI,sCAAsC;EAC/C;EACA;EACA,KAAKA,MAAI,uCAAuC;EAChD;EACA;EACA,KAAKA,MAAI,sCAAsC;EAC/C;EACA;EACA;EACA,KAAKA,MAAI,wBAAwB;EACjC;EACA,CAAC;AACF,SAAQ,KAAK,aAAa;;AAG3B,IAAI,KAAK,SAAS;AAEjB,QADa,MAAM,OAAO,2BAChB,WAAW,SAAS,UAAU;AACxC,SAAQ,KAAK,aAAa;;AAG3B,MAAM,CAAC,UAAU,GAAG,YAAY,KAAK;AACrC,IAAI,gBAAgB;AAEpB,IAAI,aAAa,QAChB,SAAQ,KAAK,MAAM,gBAAgB,SAAS,CAAC;AAE9C,IAAI,aAAa,WAAW;AAC3B,OAAM,kBAAkB,UAAU,EAAE,YAAY,KAAK,QAAQ,CAAC;AAC9D,iBAAgB;;AAEjB,IAAI,aAAa,SAAS;AACzB,OAAM,gBAAgB,UAAU,EAAE,QAAQ,KAAK,QAAQ,CAAC;AACxD,iBAAgB;;AAEjB,IAAI,aAAa,SAChB,SAAQ,KAAK,MAAM,kBAAkB,CAAC;AAGvC,IAAI,CAAC,eAAe;CACnB,MAAM,aAAa;AAEnB,KAAI,KAAK,UAAU,CAAC,YAAY;EAC/B,MAAM,WAAW,MAAM,8BAA8B;AACrD,MAAI,SAAS,SAAS;AACrB,OAAIA,MAAI,mBAAmB,CAAC;AAC5B,SAAM,KAAK,SAAS,QAAQ,UAAU,SAAS,MAAM,CAAC,CAAC;;EAGxD,MAAM,OAAO,IAAI,KAAK;GACrB,UAAU;GACV,SAAS,SAAS;GAClB,CAAC;AACF,iBAAe,MAAM;GACpB,eAAe;GACf,WAAW;GACX,MAAM;GACN,OAAO,KAAK;GACZ,CAAC;QACI;EAEN,MAAM,OAAO,IAAI,KAAK,YADuB,KAAK,SAAS,EAAE,UAAU,MAAM,GAAG,OAClC;AAC9C,iBAAe,MAAM;GACpB,eAAe;GACf,WAAW;GACX,MAAM,KAAK,WAAW,aAAa;GACnC,YAAY,KAAK;GACjB,OAAO,KAAK,WAAW,KAAK,MAAO,cAAc;GACjD,CAAC"}
@@ -0,0 +1,6 @@
1
+ //#region package.json
2
+ var version = "0.1.7";
3
+
4
+ //#endregion
5
+ export { version };
6
+ //# sourceMappingURL=package-BnMY6oRB.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-BnMY6oRB.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hbeam",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "A 1-to-1 end-to-end encrypted pipe over HyperDHT.",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,6 +0,0 @@
1
- //#region package.json
2
- var version = "0.1.5";
3
-
4
- //#endregion
5
- export { version };
6
- //# sourceMappingURL=package-Bwpu9EFI.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"package-Bwpu9EFI.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}