hatchee 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +75 -31
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -11521,6 +11521,24 @@ class Daemon {
|
|
|
11521
11521
|
openPairingWindow(code, ttlMs = 5 * 60000) {
|
|
11522
11522
|
this.pairing = { code, expiresAt: Date.now() + ttlMs };
|
|
11523
11523
|
}
|
|
11524
|
+
pairingPayload(forceCode) {
|
|
11525
|
+
const code = forceCode || newPairingCode();
|
|
11526
|
+
this.openPairingWindow(code);
|
|
11527
|
+
const secret = b64.decode(this.cfg.secretKey);
|
|
11528
|
+
const pk = b64.encode(publicKey(secret));
|
|
11529
|
+
const room = b64url(publicKey(secret));
|
|
11530
|
+
const lan = lanIPv4();
|
|
11531
|
+
const payload = {
|
|
11532
|
+
v: 1,
|
|
11533
|
+
...lan ? { ws: `ws://${lan}:${this.cfg.wsPort}` } : {},
|
|
11534
|
+
code,
|
|
11535
|
+
host: this.cfg.host,
|
|
11536
|
+
pk,
|
|
11537
|
+
relay: this.cfg.relayUrl,
|
|
11538
|
+
room
|
|
11539
|
+
};
|
|
11540
|
+
return { payload, code, lan, room, relay: this.cfg.relayUrl, wsPort: this.cfg.wsPort };
|
|
11541
|
+
}
|
|
11524
11542
|
sealFor(p, msg) {
|
|
11525
11543
|
return p.key ? encode({ t: "enc", b: seal(p.key, encode(msg)) }) : encode(msg);
|
|
11526
11544
|
}
|
|
@@ -11923,7 +11941,18 @@ class Daemon {
|
|
|
11923
11941
|
sock.on("error", () => self.phones.delete(conn));
|
|
11924
11942
|
});
|
|
11925
11943
|
const hooks = createServer((req, res) => {
|
|
11926
|
-
|
|
11944
|
+
const url = req.url ?? "";
|
|
11945
|
+
if (req.method === "POST" && url === "/pair") {
|
|
11946
|
+
try {
|
|
11947
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
11948
|
+
res.end(JSON.stringify(self.pairingPayload()));
|
|
11949
|
+
} catch (e) {
|
|
11950
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
11951
|
+
res.end(JSON.stringify({ ok: false, error: String(e) }));
|
|
11952
|
+
}
|
|
11953
|
+
return;
|
|
11954
|
+
}
|
|
11955
|
+
if (req.method !== "POST" || url !== "/hook") {
|
|
11927
11956
|
res.writeHead(404).end("not found");
|
|
11928
11957
|
return;
|
|
11929
11958
|
}
|
|
@@ -11951,6 +11980,9 @@ class Daemon {
|
|
|
11951
11980
|
function decision(d, reason) {
|
|
11952
11981
|
return { body: { decision: d, reason } };
|
|
11953
11982
|
}
|
|
11983
|
+
function b64url(bytes) {
|
|
11984
|
+
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
11985
|
+
}
|
|
11954
11986
|
function toolSummary(toolName, input) {
|
|
11955
11987
|
switch (toolName) {
|
|
11956
11988
|
case "Bash":
|
|
@@ -11984,7 +12016,7 @@ class RelayClient {
|
|
|
11984
12016
|
this.secretKeyB64 = secretKeyB64;
|
|
11985
12017
|
}
|
|
11986
12018
|
get room() {
|
|
11987
|
-
return
|
|
12019
|
+
return b64url2(publicKey(b64.decode(this.secretKeyB64)));
|
|
11988
12020
|
}
|
|
11989
12021
|
url() {
|
|
11990
12022
|
return `${this.relayBase.replace(/\/$/, "")}/c/${this.room}?role=daemon`;
|
|
@@ -12065,7 +12097,7 @@ class RelayClient {
|
|
|
12065
12097
|
}
|
|
12066
12098
|
}
|
|
12067
12099
|
}
|
|
12068
|
-
function
|
|
12100
|
+
function b64url2(bytes) {
|
|
12069
12101
|
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
12070
12102
|
}
|
|
12071
12103
|
|
|
@@ -12329,7 +12361,7 @@ function uninstallService() {
|
|
|
12329
12361
|
}
|
|
12330
12362
|
|
|
12331
12363
|
// src/cli.ts
|
|
12332
|
-
var VERSION2 = "0.1.
|
|
12364
|
+
var VERSION2 = "0.1.3";
|
|
12333
12365
|
var cmd = process.argv[2] ?? "help";
|
|
12334
12366
|
switch (cmd) {
|
|
12335
12367
|
case "up": {
|
|
@@ -12360,39 +12392,19 @@ switch (cmd) {
|
|
|
12360
12392
|
}
|
|
12361
12393
|
case "pair": {
|
|
12362
12394
|
const cfg = loadConfig();
|
|
12395
|
+
const remote = await remotePair(cfg.hookPort);
|
|
12396
|
+
if (remote) {
|
|
12397
|
+
printPairing(remote, true);
|
|
12398
|
+
break;
|
|
12399
|
+
}
|
|
12363
12400
|
const daemon = new Daemon(cfg);
|
|
12364
12401
|
if (!safeListen(daemon))
|
|
12365
12402
|
break;
|
|
12366
12403
|
const relay = new RelayClient(daemon, cfg.relayUrl, cfg.secretKey);
|
|
12367
12404
|
relay.start();
|
|
12368
12405
|
installHooks(hatcheeBin());
|
|
12369
|
-
const
|
|
12370
|
-
|
|
12371
|
-
const myPub = b64.encode(publicKey(b64.decode(cfg.secretKey)));
|
|
12372
|
-
const lan = lanIPv4();
|
|
12373
|
-
const payload = JSON.stringify({
|
|
12374
|
-
v: 1,
|
|
12375
|
-
...lan ? { ws: `ws://${lan}:${cfg.wsPort}` } : {},
|
|
12376
|
-
code,
|
|
12377
|
-
host: cfg.host,
|
|
12378
|
-
pk: myPub,
|
|
12379
|
-
relay: cfg.relayUrl,
|
|
12380
|
-
room: relay.room
|
|
12381
|
-
});
|
|
12382
|
-
banner();
|
|
12383
|
-
import_qrcode_terminal.default.generate(payload, { small: true }, (qr) => {
|
|
12384
|
-
console.log(qr);
|
|
12385
|
-
console.log(` scan with the Hatchee iOS app \u2014 or enter manually:`);
|
|
12386
|
-
if (lan)
|
|
12387
|
-
console.log(` LAN address ${lan}:${cfg.wsPort}`);
|
|
12388
|
-
else
|
|
12389
|
-
console.log(` LAN address no LAN address (VPN?) \u2014 phone will use the relay`);
|
|
12390
|
-
console.log(` relay ${cfg.relayUrl}/c/${relay.room}?role=phone`);
|
|
12391
|
-
console.log(` code ${code} (valid 5 minutes, single use)
|
|
12392
|
-
`);
|
|
12393
|
-
console.log(` keeping the daemon running after pairing\u2026 (^C to stop)`);
|
|
12394
|
-
tips();
|
|
12395
|
-
});
|
|
12406
|
+
const info = daemon.pairingPayload(process.env.DROVER_PAIR_CODE);
|
|
12407
|
+
printPairing(info, false);
|
|
12396
12408
|
break;
|
|
12397
12409
|
}
|
|
12398
12410
|
case "hook":
|
|
@@ -12459,6 +12471,38 @@ function banner() {
|
|
|
12459
12471
|
\uD83D\uDC23 hatchee ${VERSION2} \u2014 your coding agents, alive on your lock screen
|
|
12460
12472
|
`);
|
|
12461
12473
|
}
|
|
12474
|
+
async function remotePair(hookPort) {
|
|
12475
|
+
try {
|
|
12476
|
+
const r = await fetch(`http://127.0.0.1:${hookPort}/pair`, { method: "POST", signal: AbortSignal.timeout(2500) });
|
|
12477
|
+
if (!r.ok)
|
|
12478
|
+
return null;
|
|
12479
|
+
const info = await r.json();
|
|
12480
|
+
return info?.payload ? info : null;
|
|
12481
|
+
} catch {
|
|
12482
|
+
return null;
|
|
12483
|
+
}
|
|
12484
|
+
}
|
|
12485
|
+
function printPairing(info, viaService) {
|
|
12486
|
+
banner();
|
|
12487
|
+
import_qrcode_terminal.default.generate(JSON.stringify(info.payload), { small: true }, (qr) => {
|
|
12488
|
+
console.log(qr);
|
|
12489
|
+
console.log(` scan with the Hatchee iOS app \u2014 or enter manually:`);
|
|
12490
|
+
if (info.lan)
|
|
12491
|
+
console.log(` LAN address ${info.lan}:${info.wsPort}`);
|
|
12492
|
+
else
|
|
12493
|
+
console.log(` LAN address no LAN address (VPN?) \u2014 phone will use the relay`);
|
|
12494
|
+
console.log(` relay ${info.relay}/c/${info.room}?role=phone`);
|
|
12495
|
+
console.log(` code ${info.code} (valid 5 minutes, single use)
|
|
12496
|
+
`);
|
|
12497
|
+
if (viaService)
|
|
12498
|
+
console.log(` paired against the Hatchee already running here \u2014 it keeps running.
|
|
12499
|
+
`);
|
|
12500
|
+
else {
|
|
12501
|
+
console.log(` keeping the daemon running after pairing\u2026 (^C to stop)`);
|
|
12502
|
+
tips();
|
|
12503
|
+
}
|
|
12504
|
+
});
|
|
12505
|
+
}
|
|
12462
12506
|
function tips() {
|
|
12463
12507
|
console.log(`
|
|
12464
12508
|
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
package/package.json
CHANGED