hbeam 0.1.6 → 0.1.7-alpha.1
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 +28 -0
- package/dist/cli.mjs +347 -83
- package/dist/cli.mjs.map +1 -1
- package/dist/package-CS3N9FMX.mjs +6 -0
- package/dist/package-CS3N9FMX.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/package-BifKRD9f.mjs +0 -6
- package/dist/package-BifKRD9f.mjs.map +0 -1
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,17 +2,19 @@
|
|
|
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 {
|
|
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";
|
|
17
|
+
import { Readable } from "node:stream";
|
|
16
18
|
|
|
17
19
|
//#region src/lib/clipboard.ts
|
|
18
20
|
const EXIT_SUCCESS$3 = 0;
|
|
@@ -253,9 +255,60 @@ function createSpinner(frames, intervalMs) {
|
|
|
253
255
|
};
|
|
254
256
|
}
|
|
255
257
|
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/lib/file-protocol.ts
|
|
260
|
+
const FILE_TYPE = "file";
|
|
261
|
+
const HEADER_PREFIX = `{"type":"${FILE_TYPE}"`;
|
|
262
|
+
const NEWLINE = "\n";
|
|
263
|
+
const BYTES_PER_KIB = 1024;
|
|
264
|
+
const UNIT_PRECISION = 1;
|
|
265
|
+
const FIRST_INDEX$1 = 0;
|
|
266
|
+
const MIN_SIZE = 0;
|
|
267
|
+
const LAST_INDEX_OFFSET = 1;
|
|
268
|
+
const UNIT_LABELS = [
|
|
269
|
+
"B",
|
|
270
|
+
"KB",
|
|
271
|
+
"MB",
|
|
272
|
+
"GB",
|
|
273
|
+
"TB"
|
|
274
|
+
];
|
|
275
|
+
function encodeHeader(header) {
|
|
276
|
+
return Buffer.from(`${JSON.stringify(header)}${NEWLINE}`, "utf8");
|
|
277
|
+
}
|
|
278
|
+
function isFileHeader(chunk) {
|
|
279
|
+
const prefix = HEADER_PREFIX.slice(FIRST_INDEX$1, Math.min(chunk.length, HEADER_PREFIX.length));
|
|
280
|
+
return chunk.toString("utf8", FIRST_INDEX$1, prefix.length) === prefix;
|
|
281
|
+
}
|
|
282
|
+
function parseFileHeader(line) {
|
|
283
|
+
const parsed = JSON.parse(line.toString("utf8"));
|
|
284
|
+
if (parsed.type !== FILE_TYPE) throw new Error("Invalid file header type");
|
|
285
|
+
if (!parsed.name || typeof parsed.name !== "string") throw new Error("Invalid file header name");
|
|
286
|
+
if (typeof parsed.size !== "number" || !Number.isSafeInteger(parsed.size) || parsed.size < MIN_SIZE) throw new Error("Invalid file header size");
|
|
287
|
+
return {
|
|
288
|
+
name: parsed.name,
|
|
289
|
+
size: parsed.size,
|
|
290
|
+
type: FILE_TYPE
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function findHeaderLineEnd(chunk) {
|
|
294
|
+
return chunk.indexOf(NEWLINE);
|
|
295
|
+
}
|
|
296
|
+
function formatFileSize(bytes) {
|
|
297
|
+
if (!Number.isFinite(bytes) || bytes < MIN_SIZE) return `0 ${UNIT_LABELS[FIRST_INDEX$1]}`;
|
|
298
|
+
let size = bytes;
|
|
299
|
+
let unitIndex = FIRST_INDEX$1;
|
|
300
|
+
const lastUnitIndex = UNIT_LABELS.length - LAST_INDEX_OFFSET;
|
|
301
|
+
while (size >= BYTES_PER_KIB && unitIndex < lastUnitIndex) {
|
|
302
|
+
size /= BYTES_PER_KIB;
|
|
303
|
+
unitIndex++;
|
|
304
|
+
}
|
|
305
|
+
if (unitIndex === FIRST_INDEX$1) return `${Math.round(size)} ${UNIT_LABELS[unitIndex]}`;
|
|
306
|
+
return `${size.toFixed(UNIT_PRECISION)} ${UNIT_LABELS[unitIndex]}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
256
309
|
//#endregion
|
|
257
310
|
//#region src/lib/lifecycle.ts
|
|
258
|
-
const EXIT_FAILURE$
|
|
311
|
+
const EXIT_FAILURE$3 = 1;
|
|
259
312
|
const SHUTDOWN_TIMEOUT_MS = 2e3;
|
|
260
313
|
/**
|
|
261
314
|
* Create a lifecycle controller that manages SIGINT handling and graceful shutdown.
|
|
@@ -272,7 +325,7 @@ function createLifecycle(beam, spinner) {
|
|
|
272
325
|
log(dim$1("SHUTTING DOWN"));
|
|
273
326
|
blank();
|
|
274
327
|
const timeout = globalThis.setTimeout(() => {
|
|
275
|
-
process.exit(EXIT_FAILURE$
|
|
328
|
+
process.exit(EXIT_FAILURE$3);
|
|
276
329
|
}, SHUTDOWN_TIMEOUT_MS);
|
|
277
330
|
beam.destroy();
|
|
278
331
|
beam.on("close", () => {
|
|
@@ -291,6 +344,71 @@ function createLifecycle(beam, spinner) {
|
|
|
291
344
|
};
|
|
292
345
|
}
|
|
293
346
|
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/lib/prompt.ts
|
|
349
|
+
const YES = "y";
|
|
350
|
+
const NO = "n";
|
|
351
|
+
const CTRL_C = "";
|
|
352
|
+
const ENTER = "\r";
|
|
353
|
+
const FIRST_CHAR_INDEX = 0;
|
|
354
|
+
const EMPTY_INPUT = "";
|
|
355
|
+
function firstChar(data) {
|
|
356
|
+
return data.toString("utf8").toLowerCase().charAt(FIRST_CHAR_INDEX);
|
|
357
|
+
}
|
|
358
|
+
/** Minimal single-keypress confirm prompt with `y/N` default. */
|
|
359
|
+
async function confirm(message) {
|
|
360
|
+
process.stderr.write(`${INDENT}${message} ${dim$1("(y/N)")} `);
|
|
361
|
+
const stdin = process.stdin;
|
|
362
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
363
|
+
process.stderr.write("\n");
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
const originalRaw = stdin.isRaw;
|
|
367
|
+
return await new Promise((resolve) => {
|
|
368
|
+
function cleanup(answer) {
|
|
369
|
+
stdin.setRawMode(Boolean(originalRaw));
|
|
370
|
+
stdin.pause();
|
|
371
|
+
stdin.removeListener("data", onData);
|
|
372
|
+
process.stderr.write(`${answer ? YES : NO}\n`);
|
|
373
|
+
resolve(answer);
|
|
374
|
+
}
|
|
375
|
+
function onData(data) {
|
|
376
|
+
const key = firstChar(data);
|
|
377
|
+
if (key === CTRL_C) {
|
|
378
|
+
process.stderr.write("\n");
|
|
379
|
+
process.kill(process.pid, "SIGINT");
|
|
380
|
+
cleanup(false);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (key === YES) {
|
|
384
|
+
cleanup(true);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (key === NO || key === "" || key === ENTER) cleanup(false);
|
|
388
|
+
}
|
|
389
|
+
stdin.setRawMode(true);
|
|
390
|
+
stdin.resume();
|
|
391
|
+
stdin.on("data", onData);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/** Minimal line-input prompt with an editable pre-filled default value. */
|
|
395
|
+
async function input(message, placeholder) {
|
|
396
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) return placeholder;
|
|
397
|
+
const rl = createInterface({
|
|
398
|
+
input: process.stdin,
|
|
399
|
+
output: process.stderr,
|
|
400
|
+
terminal: true
|
|
401
|
+
});
|
|
402
|
+
try {
|
|
403
|
+
const answerPromise = rl.question(`${INDENT}${message} `);
|
|
404
|
+
rl.write(placeholder);
|
|
405
|
+
const trimmed = (await answerPromise).trim();
|
|
406
|
+
return trimmed === EMPTY_INPUT ? placeholder : trimmed;
|
|
407
|
+
} finally {
|
|
408
|
+
rl.close();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
294
412
|
//#endregion
|
|
295
413
|
//#region src/lib/pulse.ts
|
|
296
414
|
const INTERVAL_MS = 125;
|
|
@@ -346,6 +464,9 @@ function createPulseFrames(label) {
|
|
|
346
464
|
|
|
347
465
|
//#endregion
|
|
348
466
|
//#region src/lib/session.ts
|
|
467
|
+
const FIRST_INDEX = 0;
|
|
468
|
+
const NEXT_INDEX = 1;
|
|
469
|
+
const MIN_BUFFERED_CHUNKS = 0;
|
|
349
470
|
/** Run the standard hbeam CLI session UI and stdin/stdout piping. */
|
|
350
471
|
function runBeamSession(beam, options) {
|
|
351
472
|
const { frames, intervalMs } = createPulseFrames("HBEAM");
|
|
@@ -387,24 +508,106 @@ function runBeamSession(beam, options) {
|
|
|
387
508
|
});
|
|
388
509
|
beam.on("end", () => beam.end());
|
|
389
510
|
let receivedData = false;
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
511
|
+
let mode = "unknown";
|
|
512
|
+
let bufferedChunks = [];
|
|
513
|
+
let fileStream = void 0;
|
|
514
|
+
let filePath = void 0;
|
|
515
|
+
let initializingFileMode = false;
|
|
516
|
+
let queuedChunks = [];
|
|
517
|
+
function writeTextChunk(chunk) {
|
|
518
|
+
if (!receivedData) {
|
|
519
|
+
receivedData = true;
|
|
520
|
+
write(SEPARATOR);
|
|
521
|
+
blank();
|
|
522
|
+
}
|
|
523
|
+
const lines = chunk.toString().replace(/^(?!$)/gm, INDENT);
|
|
524
|
+
process.stdout.write(lines);
|
|
525
|
+
}
|
|
526
|
+
async function resolveOutputPath(fileName) {
|
|
527
|
+
if (options.outputPath) return resolve(options.outputPath);
|
|
528
|
+
if (!process.stdout.isTTY) return;
|
|
529
|
+
const suggestedPath = resolve(process.cwd(), fileName);
|
|
530
|
+
if (!await confirm("Save incoming file?")) return "";
|
|
531
|
+
return await input("Save to:", suggestedPath);
|
|
532
|
+
}
|
|
533
|
+
async function startFileMode(initialChunk) {
|
|
534
|
+
const lineEnd = findHeaderLineEnd(initialChunk);
|
|
535
|
+
if (lineEnd < FIRST_INDEX) return;
|
|
536
|
+
const header = parseFileHeader(initialChunk.subarray(FIRST_INDEX, lineEnd));
|
|
537
|
+
const remainder = initialChunk.subarray(lineEnd + NEXT_INDEX);
|
|
538
|
+
log(dim$1(`INCOMING FILE ${header.name} (${formatFileSize(header.size)})`));
|
|
539
|
+
const outputPath = await resolveOutputPath(header.name);
|
|
540
|
+
if (outputPath === "") {
|
|
541
|
+
log(dim$1("RECEIVE CANCELLED"));
|
|
542
|
+
blank();
|
|
543
|
+
lifecycle.shutdown();
|
|
544
|
+
return;
|
|
405
545
|
}
|
|
546
|
+
if (outputPath === void 0) {
|
|
547
|
+
mode = "file-stdout";
|
|
548
|
+
if (remainder.length > MIN_BUFFERED_CHUNKS) process.stdout.write(remainder);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
mode = "file";
|
|
552
|
+
filePath = outputPath;
|
|
553
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
554
|
+
fileStream = createWriteStream(outputPath);
|
|
555
|
+
fileStream.on("error", (error) => beam.destroy(error));
|
|
556
|
+
if (remainder.length > MIN_BUFFERED_CHUNKS) fileStream.write(remainder);
|
|
557
|
+
}
|
|
558
|
+
function handleChunk(chunk) {
|
|
559
|
+
if (initializingFileMode) {
|
|
560
|
+
queuedChunks.push(chunk);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (mode === "text") {
|
|
564
|
+
writeTextChunk(chunk);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (mode === "file") {
|
|
568
|
+
fileStream?.write(chunk);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (mode === "file-stdout") {
|
|
572
|
+
process.stdout.write(chunk);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
bufferedChunks.push(chunk);
|
|
576
|
+
const pending = Buffer.concat(bufferedChunks);
|
|
577
|
+
if (isFileHeader(pending)) {
|
|
578
|
+
if (findHeaderLineEnd(pending) < FIRST_INDEX) return;
|
|
579
|
+
bufferedChunks = [];
|
|
580
|
+
initializingFileMode = true;
|
|
581
|
+
startFileMode(pending).finally(() => {
|
|
582
|
+
initializingFileMode = false;
|
|
583
|
+
for (const queuedChunk of queuedChunks) handleChunk(queuedChunk);
|
|
584
|
+
queuedChunks = [];
|
|
585
|
+
});
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
mode = "text";
|
|
589
|
+
bufferedChunks = [];
|
|
590
|
+
writeTextChunk(pending);
|
|
591
|
+
}
|
|
592
|
+
beam.on("data", (chunk) => {
|
|
593
|
+
handleChunk(chunk);
|
|
594
|
+
});
|
|
595
|
+
beam.on("end", () => {
|
|
596
|
+
if (mode === "unknown" && bufferedChunks.length > MIN_BUFFERED_CHUNKS) {
|
|
597
|
+
mode = "text";
|
|
598
|
+
writeTextChunk(Buffer.concat(bufferedChunks));
|
|
599
|
+
bufferedChunks = [];
|
|
600
|
+
}
|
|
601
|
+
if (mode === "text" && receivedData) {
|
|
602
|
+
blank();
|
|
603
|
+
write(SEPARATOR);
|
|
604
|
+
}
|
|
605
|
+
if (mode === "file") fileStream?.end(() => {
|
|
606
|
+
log(dim$1(`SAVED ${filePath ?? ""}`));
|
|
607
|
+
blank();
|
|
608
|
+
});
|
|
406
609
|
});
|
|
407
|
-
process.stdin.pipe(beam)
|
|
610
|
+
process.stdin.pipe(beam);
|
|
408
611
|
if (typeof process.stdin.unref === "function") process.stdin.unref();
|
|
409
612
|
}
|
|
410
613
|
|
|
@@ -698,31 +901,31 @@ async function getPeer(name) {
|
|
|
698
901
|
|
|
699
902
|
//#endregion
|
|
700
903
|
//#region src/commands/connect.ts
|
|
701
|
-
const EXIT_FAILURE$
|
|
904
|
+
const EXIT_FAILURE$2 = 1;
|
|
702
905
|
const PUBLIC_KEY_BYTES = 32;
|
|
703
906
|
/** Execute `hbeam connect <name>`. Exits on error; stays alive for the session. */
|
|
704
|
-
async function runConnectCommand(argv) {
|
|
907
|
+
async function runConnectCommand(argv, options = {}) {
|
|
705
908
|
const [name] = argv;
|
|
706
909
|
if (!name) {
|
|
707
910
|
blank();
|
|
708
911
|
logError("Missing peer name.");
|
|
709
912
|
write(dim$1("Usage: hbeam connect <name>"));
|
|
710
913
|
blank();
|
|
711
|
-
process.exit(EXIT_FAILURE$
|
|
914
|
+
process.exit(EXIT_FAILURE$2);
|
|
712
915
|
}
|
|
713
916
|
const peer = await getPeer(name).catch(() => void 0);
|
|
714
917
|
if (!peer) {
|
|
715
918
|
blank();
|
|
716
919
|
logError(`Unknown peer: ${name}`);
|
|
717
920
|
blank();
|
|
718
|
-
process.exit(EXIT_FAILURE$
|
|
921
|
+
process.exit(EXIT_FAILURE$2);
|
|
719
922
|
}
|
|
720
923
|
const remotePublicKey = Buffer.from(peer.publicKey, "hex");
|
|
721
924
|
if (remotePublicKey.length !== PUBLIC_KEY_BYTES) {
|
|
722
925
|
blank();
|
|
723
926
|
logError(`Invalid public key for peer: ${name}`);
|
|
724
927
|
blank();
|
|
725
|
-
process.exit(EXIT_FAILURE$
|
|
928
|
+
process.exit(EXIT_FAILURE$2);
|
|
726
929
|
}
|
|
727
930
|
const identity = await loadOrCreateIdentityWithMeta();
|
|
728
931
|
if (identity.created) {
|
|
@@ -735,61 +938,15 @@ async function runConnectCommand(argv) {
|
|
|
735
938
|
remotePublicKey
|
|
736
939
|
}), {
|
|
737
940
|
mode: "connect",
|
|
941
|
+
outputPath: options.outputPath,
|
|
738
942
|
value: name
|
|
739
943
|
});
|
|
740
944
|
}
|
|
741
945
|
|
|
742
|
-
//#endregion
|
|
743
|
-
//#region src/lib/prompt.ts
|
|
744
|
-
const YES = "y";
|
|
745
|
-
const NO = "n";
|
|
746
|
-
const CTRL_C = "";
|
|
747
|
-
const ENTER = "\r";
|
|
748
|
-
const FIRST_CHAR_INDEX = 0;
|
|
749
|
-
function firstChar(data) {
|
|
750
|
-
return data.toString("utf8").toLowerCase().charAt(FIRST_CHAR_INDEX);
|
|
751
|
-
}
|
|
752
|
-
/** Minimal single-keypress confirm prompt with `y/N` default. */
|
|
753
|
-
async function confirm(message) {
|
|
754
|
-
process.stderr.write(`${INDENT}${message} ${dim$1("(y/N)")} `);
|
|
755
|
-
const stdin = process.stdin;
|
|
756
|
-
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
757
|
-
process.stderr.write("\n");
|
|
758
|
-
return false;
|
|
759
|
-
}
|
|
760
|
-
const originalRaw = stdin.isRaw;
|
|
761
|
-
return await new Promise((resolve) => {
|
|
762
|
-
function cleanup(answer) {
|
|
763
|
-
stdin.setRawMode(Boolean(originalRaw));
|
|
764
|
-
stdin.pause();
|
|
765
|
-
stdin.removeListener("data", onData);
|
|
766
|
-
process.stderr.write(`${answer ? YES : NO}\n`);
|
|
767
|
-
resolve(answer);
|
|
768
|
-
}
|
|
769
|
-
function onData(data) {
|
|
770
|
-
const key = firstChar(data);
|
|
771
|
-
if (key === CTRL_C) {
|
|
772
|
-
process.stderr.write("\n");
|
|
773
|
-
process.kill(process.pid, "SIGINT");
|
|
774
|
-
cleanup(false);
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
if (key === YES) {
|
|
778
|
-
cleanup(true);
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
if (key === NO || key === "" || key === ENTER) cleanup(false);
|
|
782
|
-
}
|
|
783
|
-
stdin.setRawMode(true);
|
|
784
|
-
stdin.resume();
|
|
785
|
-
stdin.on("data", onData);
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
|
|
789
946
|
//#endregion
|
|
790
947
|
//#region src/commands/peers.ts
|
|
791
948
|
const EXIT_SUCCESS$2 = 0;
|
|
792
|
-
const EXIT_FAILURE = 1;
|
|
949
|
+
const EXIT_FAILURE$1 = 1;
|
|
793
950
|
const START_INDEX = 0;
|
|
794
951
|
const PUBLIC_KEY_PREFIX_LENGTH = 8;
|
|
795
952
|
const SECONDS_PER_MINUTE = 60;
|
|
@@ -825,7 +982,7 @@ async function handleAdd(name, publicKey) {
|
|
|
825
982
|
blank();
|
|
826
983
|
usage();
|
|
827
984
|
blank();
|
|
828
|
-
return EXIT_FAILURE;
|
|
985
|
+
return EXIT_FAILURE$1;
|
|
829
986
|
}
|
|
830
987
|
try {
|
|
831
988
|
await addPeer(name, publicKey);
|
|
@@ -833,7 +990,7 @@ async function handleAdd(name, publicKey) {
|
|
|
833
990
|
blank();
|
|
834
991
|
logError(error.message);
|
|
835
992
|
blank();
|
|
836
|
-
return EXIT_FAILURE;
|
|
993
|
+
return EXIT_FAILURE$1;
|
|
837
994
|
}
|
|
838
995
|
blank();
|
|
839
996
|
log(bold$1("SAVED"));
|
|
@@ -846,13 +1003,13 @@ async function handleRemove(name) {
|
|
|
846
1003
|
blank();
|
|
847
1004
|
usage();
|
|
848
1005
|
blank();
|
|
849
|
-
return EXIT_FAILURE;
|
|
1006
|
+
return EXIT_FAILURE$1;
|
|
850
1007
|
}
|
|
851
1008
|
if (!await getPeer(name).catch(() => void 0)) {
|
|
852
1009
|
blank();
|
|
853
1010
|
logError(`Unknown peer: ${name}`);
|
|
854
1011
|
blank();
|
|
855
|
-
return EXIT_FAILURE;
|
|
1012
|
+
return EXIT_FAILURE$1;
|
|
856
1013
|
}
|
|
857
1014
|
blank();
|
|
858
1015
|
if (!await confirm(`REMOVE ${name}?`)) {
|
|
@@ -888,7 +1045,102 @@ async function runPeersCommand(argv) {
|
|
|
888
1045
|
blank();
|
|
889
1046
|
usage();
|
|
890
1047
|
blank();
|
|
891
|
-
return EXIT_FAILURE;
|
|
1048
|
+
return EXIT_FAILURE$1;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
//#endregion
|
|
1052
|
+
//#region src/commands/serve.ts
|
|
1053
|
+
const EXIT_FAILURE = 1;
|
|
1054
|
+
const MIN_FILE_SIZE = 0;
|
|
1055
|
+
function showUsageError(message) {
|
|
1056
|
+
blank();
|
|
1057
|
+
logError(message);
|
|
1058
|
+
write(dim$1("Usage: hbeam serve <file> [--listen]"));
|
|
1059
|
+
blank();
|
|
1060
|
+
process.exit(EXIT_FAILURE);
|
|
1061
|
+
}
|
|
1062
|
+
function createSessionSpinner() {
|
|
1063
|
+
const { frames, intervalMs } = createPulseFrames("HBEAM");
|
|
1064
|
+
return createSpinner(frames, intervalMs);
|
|
1065
|
+
}
|
|
1066
|
+
async function resolveServeIdentity(listen) {
|
|
1067
|
+
if (!listen) return { announceLabel: "PASSPHRASE" };
|
|
1068
|
+
const identity = await loadOrCreateIdentityWithMeta();
|
|
1069
|
+
if (identity.created) {
|
|
1070
|
+
blank();
|
|
1071
|
+
log(dim$1("IDENTITY CREATED"));
|
|
1072
|
+
write(cyan(identity.keyPair.publicKey.toString("hex")));
|
|
1073
|
+
}
|
|
1074
|
+
return {
|
|
1075
|
+
announceLabel: "PUBLIC KEY",
|
|
1076
|
+
keyPair: identity.keyPair
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
/** Execute `hbeam serve <file>` to transfer one file to the first peer. */
|
|
1080
|
+
async function runServeCommand(argv, options = {}) {
|
|
1081
|
+
const [targetFile] = argv;
|
|
1082
|
+
if (!targetFile) showUsageError("Missing file path.");
|
|
1083
|
+
const filePath = resolve(targetFile);
|
|
1084
|
+
const fileName = basename(filePath);
|
|
1085
|
+
const fileStat = await stat(filePath).catch(() => void 0);
|
|
1086
|
+
if (!fileStat || !fileStat.isFile()) showUsageError(`Not a readable file: ${targetFile}`);
|
|
1087
|
+
if (fileStat.size < MIN_FILE_SIZE) showUsageError(`Invalid file size: ${targetFile}`);
|
|
1088
|
+
const identity = await resolveServeIdentity(options.listen);
|
|
1089
|
+
const beam = identity.keyPair ? new Beam({
|
|
1090
|
+
announce: true,
|
|
1091
|
+
keyPair: identity.keyPair
|
|
1092
|
+
}) : new Beam(void 0, { announce: true });
|
|
1093
|
+
const spinner = createSessionSpinner();
|
|
1094
|
+
const lifecycle = createLifecycle(beam, spinner);
|
|
1095
|
+
blank();
|
|
1096
|
+
spinner.start();
|
|
1097
|
+
spinner.blank();
|
|
1098
|
+
spinner.write(dim$1(identity.announceLabel));
|
|
1099
|
+
spinner.write(cyan(beam.key));
|
|
1100
|
+
spinner.write(dim$1(`FILE ${fileName} (${formatFileSize(fileStat.size)})`));
|
|
1101
|
+
copyToClipboard(beam.key);
|
|
1102
|
+
beam.on("remote-address", ({ host, port }) => {
|
|
1103
|
+
if (lifecycle.done()) return;
|
|
1104
|
+
if (host) {
|
|
1105
|
+
spinner.write(dim$1(`ONLINE ${gray(`[${host}:${port}]`)}`));
|
|
1106
|
+
spinner.blank();
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
beam.on("connected", () => {
|
|
1110
|
+
if (lifecycle.done()) return;
|
|
1111
|
+
spinner.stop();
|
|
1112
|
+
log(bold$1("PIPE ACTIVE"));
|
|
1113
|
+
write(gray("SENDING FILE"));
|
|
1114
|
+
blank();
|
|
1115
|
+
const header = encodeHeader({
|
|
1116
|
+
name: fileName,
|
|
1117
|
+
size: fileStat.size,
|
|
1118
|
+
type: "file"
|
|
1119
|
+
});
|
|
1120
|
+
if (beam.write(header) === false) {
|
|
1121
|
+
beam.once("drain", () => {
|
|
1122
|
+
createReadStream(filePath).pipe(beam);
|
|
1123
|
+
});
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
createReadStream(filePath).pipe(beam);
|
|
1127
|
+
});
|
|
1128
|
+
beam.on("error", (error) => {
|
|
1129
|
+
spinner.stop();
|
|
1130
|
+
const isPeerNotFound = error.message.includes("PEER_NOT_FOUND");
|
|
1131
|
+
if (isPeerNotFound) log(red$1(dim$1("PEER NOT FOUND")));
|
|
1132
|
+
else if (error.message.includes("connection reset by peer")) log(dim$1("PEER DISCONNECTED"));
|
|
1133
|
+
else logError(error.message);
|
|
1134
|
+
blank();
|
|
1135
|
+
if (!isPeerNotFound) lifecycle.shutdown();
|
|
1136
|
+
});
|
|
1137
|
+
beam.on("end", () => beam.end());
|
|
1138
|
+
beam.on("finish", () => {
|
|
1139
|
+
log(dim$1("FILE SENT"));
|
|
1140
|
+
blank();
|
|
1141
|
+
beam.destroy();
|
|
1142
|
+
});
|
|
1143
|
+
Readable.from([]).pipe(beam, { end: false });
|
|
892
1144
|
}
|
|
893
1145
|
|
|
894
1146
|
//#endregion
|
|
@@ -916,13 +1168,15 @@ const argv = mri(process.argv.slice(ARGV_OFFSET), {
|
|
|
916
1168
|
alias: {
|
|
917
1169
|
h: "help",
|
|
918
1170
|
l: "listen",
|
|
1171
|
+
o: "output",
|
|
919
1172
|
v: "version"
|
|
920
1173
|
},
|
|
921
1174
|
boolean: [
|
|
922
1175
|
"help",
|
|
923
1176
|
"listen",
|
|
924
1177
|
"version"
|
|
925
|
-
]
|
|
1178
|
+
],
|
|
1179
|
+
string: ["output"]
|
|
926
1180
|
});
|
|
927
1181
|
if (argv.help) {
|
|
928
1182
|
writeBlock([
|
|
@@ -932,10 +1186,12 @@ if (argv.help) {
|
|
|
932
1186
|
` hbeam ${dim$1("[passphrase]")} ${dim$1("[options]")}`,
|
|
933
1187
|
` hbeam connect ${dim$1("<name>")}`,
|
|
934
1188
|
` hbeam peers ${dim$1("<add|rm|ls> ...")}`,
|
|
1189
|
+
` hbeam serve ${dim$1("<file>")} ${dim$1("[--listen]")}`,
|
|
935
1190
|
` hbeam whoami`,
|
|
936
1191
|
"",
|
|
937
1192
|
`${bold$1("Options:")}`,
|
|
938
1193
|
` ${dim$1("-l, --listen")} Listen using passphrase or identity`,
|
|
1194
|
+
` ${dim$1("-o, --output")} Save incoming file to a specific path`,
|
|
939
1195
|
` ${dim$1("-h, --help")} Show this help`,
|
|
940
1196
|
` ${dim$1("-v, --version")} Show version`,
|
|
941
1197
|
"",
|
|
@@ -954,19 +1210,26 @@ if (argv.help) {
|
|
|
954
1210
|
"",
|
|
955
1211
|
` ${dim$1("# Save and connect to peers by name")}`,
|
|
956
1212
|
" hbeam peers add workserver <public-key>",
|
|
957
|
-
" hbeam connect workserver"
|
|
1213
|
+
" hbeam connect workserver",
|
|
1214
|
+
"",
|
|
1215
|
+
` ${dim$1("# Serve a single file")}`,
|
|
1216
|
+
" hbeam serve ./report.pdf"
|
|
958
1217
|
]);
|
|
959
1218
|
process.exit(EXIT_SUCCESS);
|
|
960
1219
|
}
|
|
961
1220
|
if (argv.version) {
|
|
962
|
-
write((await import("./package-
|
|
1221
|
+
write((await import("./package-CS3N9FMX.mjs")).version ?? "0.0.0", NO_INDENT);
|
|
963
1222
|
process.exit(EXIT_SUCCESS);
|
|
964
1223
|
}
|
|
965
1224
|
const [firstArg, ...restArgs] = argv._;
|
|
966
1225
|
let ranSubcommand = false;
|
|
967
1226
|
if (firstArg === "peers") process.exit(await runPeersCommand(restArgs));
|
|
968
1227
|
if (firstArg === "connect") {
|
|
969
|
-
await runConnectCommand(restArgs);
|
|
1228
|
+
await runConnectCommand(restArgs, { outputPath: argv.output });
|
|
1229
|
+
ranSubcommand = true;
|
|
1230
|
+
}
|
|
1231
|
+
if (firstArg === "serve") {
|
|
1232
|
+
await runServeCommand(restArgs, { listen: argv.listen });
|
|
970
1233
|
ranSubcommand = true;
|
|
971
1234
|
}
|
|
972
1235
|
if (firstArg === "whoami") process.exit(await runWhoamiCommand());
|
|
@@ -994,6 +1257,7 @@ if (!ranSubcommand) {
|
|
|
994
1257
|
announceLabel: "PASSPHRASE",
|
|
995
1258
|
copyValue: copyToClipboard,
|
|
996
1259
|
mode: beam.announce ? "announce" : "connect",
|
|
1260
|
+
outputPath: argv.output,
|
|
997
1261
|
value: beam.announce ? beam.key : passphrase ?? "unknown"
|
|
998
1262
|
});
|
|
999
1263
|
}
|
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_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_FAILURE = 1\nconst PUBLIC_KEY_BYTES = 32\n\n/** Execute `hbeam connect <name>`. Exits on error; stays alive for the session. */\nexport async function runConnectCommand(argv: string[]): 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\tvalue: name,\n\t})\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[]\nlet ranSubcommand = false\n\nif (firstArg === 'peers') {\n\tprocess.exit(await runPeersCommand(restArgs))\n}\nif (firstArg === 'connect') {\n\tawait runConnectCommand(restArgs)\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\tvalue: beam.announce ? beam.key : (passphrase ?? 'unknown'),\n\t\t})\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'\nimport { Readable } from 'node:stream'\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\t// Trigger Beam._open without piping stdin; this starts announcing immediately.\n\tReadable.from([]).pipe(beam, { end: false })\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":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-CS3N9FMX.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"package-BifKRD9f.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
|