agent-yes 1.190.0 → 1.191.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SUPPORTED_CLIS-P3I0D3Ka.js +8 -0
- package/dist/{SUPPORTED_CLIS-BB7Br0fS.js → SUPPORTED_CLIS-mUaQHk4M.js} +2 -2
- package/dist/{agentShare-BXfWzPJu.js → agentShare-Boa9P2dK.js} +2 -2
- package/dist/cli.js +4 -4
- package/dist/{expose-DUYH0tvh.js → expose-B3AGhVhM.js} +151 -68
- package/dist/index.js +2 -2
- package/dist/{notifyDaemon-APkkoCzC.js → notifyDaemon-hKT38cBs.js} +2 -2
- package/dist/{rustBinary-C8YZUZGT.js → rustBinary-Cu8q2m9s.js} +2 -2
- package/dist/{schedule-BYG6MGpH.js → schedule-Bb98iG9W.js} +4 -4
- package/dist/{serve-Df3wcUU-.js → serve-DVhbtjR4.js} +46 -10
- package/dist/{setup-C65eaHZ8.js → setup-Dj8Grg0K.js} +2 -2
- package/dist/{subcommands-Deaq1dOk.js → subcommands-CNCgJ48t.js} +7 -7
- package/dist/{subcommands-D6TbKFTF.js → subcommands-Uerol8Tz.js} +1 -1
- package/dist/{ts-CeGEhsC9.js → ts-BQh1-ljE.js} +2 -2
- package/dist/{versionChecker-DXwf5ip7.js → versionChecker-DhCi7ncH.js} +2 -2
- package/lab/ui/index.html +239 -3
- package/package.json +1 -1
- package/ts/expose.ts +204 -78
- package/ts/serve.ts +44 -0
- package/dist/SUPPORTED_CLIS-B729E086.js +0 -8
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./ts-BQh1-ljE.js";
|
|
2
|
+
import "./logger-CDIsZ-Pp.js";
|
|
3
|
+
import "./versionChecker-DhCi7ncH.js";
|
|
4
|
+
import "./pidStore-BIvsBQ8X.js";
|
|
5
|
+
import "./globalPidIndex-CoNr7tS8.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-mUaQHk4M.js";
|
|
7
|
+
|
|
8
|
+
export { SUPPORTED_CLIS };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-BQh1-ljE.js";
|
|
2
2
|
|
|
3
3
|
//#region ts/SUPPORTED_CLIS.ts
|
|
4
4
|
const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
5
5
|
|
|
6
6
|
//#endregion
|
|
7
7
|
export { SUPPORTED_CLIS as t };
|
|
8
|
-
//# sourceMappingURL=SUPPORTED_CLIS-
|
|
8
|
+
//# sourceMappingURL=SUPPORTED_CLIS-mUaQHk4M.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./logger-CDIsZ-Pp.js";
|
|
2
2
|
import "./globalPidIndex-CoNr7tS8.js";
|
|
3
3
|
import "./configShared-0MnIQ652.js";
|
|
4
|
-
import { j as resolveOne } from "./subcommands-
|
|
4
|
+
import { j as resolveOne } from "./subcommands-CNCgJ48t.js";
|
|
5
5
|
import "./e2e-BeKjLhmO.js";
|
|
6
6
|
import "./webrtcLink-BG0Xc4-W.js";
|
|
7
7
|
import "./remotes-CgT91bMo.js";
|
|
@@ -228,4 +228,4 @@ function transformEvent(rawEvent, agentId, forwarded) {
|
|
|
228
228
|
|
|
229
229
|
//#endregion
|
|
230
230
|
export { createScopedShare, listShares, revokeAllShares, revokeShare };
|
|
231
|
-
//# sourceMappingURL=agentShare-
|
|
231
|
+
//# sourceMappingURL=agentShare-Boa9P2dK.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { t as invokedCliName } from "./invokedCli-uqM2YYA7.js";
|
|
3
3
|
import { n as logger } from "./logger-CDIsZ-Pp.js";
|
|
4
|
-
import { i as versionString, n as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-
|
|
5
|
-
import { n as getRustBinary } from "./rustBinary-
|
|
4
|
+
import { i as versionString, n as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-DhCi7ncH.js";
|
|
5
|
+
import { n as getRustBinary } from "./rustBinary-Cu8q2m9s.js";
|
|
6
6
|
import { argv } from "process";
|
|
7
7
|
import { spawn } from "child_process";
|
|
8
8
|
import ms from "ms";
|
|
@@ -285,7 +285,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
285
285
|
const rawArg = process.argv[2];
|
|
286
286
|
const managerCommands = !invokedCliName(process.argv);
|
|
287
287
|
const isHelpFlag = rawArg === "-h" || rawArg === "--help";
|
|
288
|
-
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-
|
|
288
|
+
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-Uerol8Tz.js");
|
|
289
289
|
if (isHelpFlag && process.argv.length === 3) {
|
|
290
290
|
await cmdHelp(managerCommands);
|
|
291
291
|
process.exit(0);
|
|
@@ -327,7 +327,7 @@ if (config.useRust) {
|
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
if (rustBinary) {
|
|
330
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
330
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-P3I0D3Ka.js");
|
|
331
331
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
332
332
|
if (config.verbose) {
|
|
333
333
|
console.log(`[rust] Using binary: ${rustBinary}`);
|
|
@@ -327,7 +327,7 @@ function exposuresPath() {
|
|
|
327
327
|
mkdirSync(home, { recursive: true });
|
|
328
328
|
return path.join(home, "exposures.json");
|
|
329
329
|
}
|
|
330
|
-
/** Stable id+key per (relay, port): re-
|
|
330
|
+
/** Stable id+key per (relay, port): re-exposing a port keeps its URL. */
|
|
331
331
|
function loadOrCreateExposure(relayHost, port) {
|
|
332
332
|
const file = exposuresPath();
|
|
333
333
|
let all = {};
|
|
@@ -369,6 +369,139 @@ function wsTransport(ws) {
|
|
|
369
369
|
onClose: (cb) => ws.addEventListener("close", () => cb())
|
|
370
370
|
};
|
|
371
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Start (or fail) one exposure. Resolves once the relay has accepted the daemon
|
|
374
|
+
* and the tunnel is live; rejects if the relay refuses this exposure (bad key).
|
|
375
|
+
* Reconnects with backoff for the life of the handle.
|
|
376
|
+
*/
|
|
377
|
+
function startExposure(opts) {
|
|
378
|
+
const relay = opts.relay ?? DEFAULT_RELAY;
|
|
379
|
+
const port = opts.port;
|
|
380
|
+
const log = opts.log ?? (() => {});
|
|
381
|
+
const relayUrl = new URL(relay);
|
|
382
|
+
const rec = loadOrCreateExposure(relayUrl.host, port);
|
|
383
|
+
const tunnelUrl = `${relayUrl.protocol === "http:" ? "ws:" : "wss:"}//${relayUrl.host}/_ay/tunnel/${rec.id}`;
|
|
384
|
+
const publicHost = relayUrl.host === "agent-yes.com" ? `${rec.id}.agent-yes.com` : relayUrl.host;
|
|
385
|
+
const publicUrl = `https://${publicHost}/`;
|
|
386
|
+
let stopped = false;
|
|
387
|
+
let sock = null;
|
|
388
|
+
let ready = false;
|
|
389
|
+
let backoff = RECONNECT_MIN_MS;
|
|
390
|
+
const handle = {
|
|
391
|
+
id: rec.id,
|
|
392
|
+
port,
|
|
393
|
+
publicHost,
|
|
394
|
+
url: publicUrl,
|
|
395
|
+
relayHost: relayUrl.host,
|
|
396
|
+
createdAt: Date.now(),
|
|
397
|
+
mintClaim() {
|
|
398
|
+
const token = randomBytes(18).toString("base64url");
|
|
399
|
+
const hash = createHash("sha256").update(token).digest("hex");
|
|
400
|
+
if (sock && sock.readyState === WebSocket.OPEN) sock.send(JSON.stringify({
|
|
401
|
+
t: "claim",
|
|
402
|
+
claims: [hash]
|
|
403
|
+
}));
|
|
404
|
+
return `https://${publicHost}/_ay/claim?t=${token}`;
|
|
405
|
+
},
|
|
406
|
+
stop() {
|
|
407
|
+
stopped = true;
|
|
408
|
+
try {
|
|
409
|
+
sock?.close();
|
|
410
|
+
} catch {}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
return new Promise((resolve, reject) => {
|
|
414
|
+
const connect = () => {
|
|
415
|
+
if (stopped) return;
|
|
416
|
+
sock = new WebSocket(tunnelUrl);
|
|
417
|
+
sock.binaryType = "arraybuffer";
|
|
418
|
+
const ws = sock;
|
|
419
|
+
let ping = null;
|
|
420
|
+
ws.addEventListener("open", () => {
|
|
421
|
+
ws.send(JSON.stringify({
|
|
422
|
+
t: "hello",
|
|
423
|
+
key: rec.key,
|
|
424
|
+
port,
|
|
425
|
+
v: 1
|
|
426
|
+
}));
|
|
427
|
+
});
|
|
428
|
+
ws.addEventListener("message", (ev) => {
|
|
429
|
+
if (typeof ev.data !== "string") return;
|
|
430
|
+
let msg;
|
|
431
|
+
try {
|
|
432
|
+
msg = JSON.parse(ev.data);
|
|
433
|
+
} catch {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (msg.t === "ready") {
|
|
437
|
+
backoff = RECONNECT_MIN_MS;
|
|
438
|
+
new TunnelHost(wsTransport(ws), { port });
|
|
439
|
+
ping = setInterval(() => {
|
|
440
|
+
if (ws.readyState === WebSocket.OPEN) ws.send("ping");
|
|
441
|
+
}, PING_MS);
|
|
442
|
+
if (!ready) {
|
|
443
|
+
ready = true;
|
|
444
|
+
log(`sharing 127.0.0.1:${port} at ${publicUrl}`);
|
|
445
|
+
resolve(handle);
|
|
446
|
+
} else log(`reconnected`);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
ws.addEventListener("close", (ev) => {
|
|
450
|
+
if (ping) clearInterval(ping);
|
|
451
|
+
if (stopped) return;
|
|
452
|
+
if (ev.code === 1008) {
|
|
453
|
+
const err = /* @__PURE__ */ new Error(`relay refused exposure (${ev.reason || "forbidden"})`);
|
|
454
|
+
if (!ready) return reject(err);
|
|
455
|
+
log(err.message);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
log(`connection lost, retrying in ${Math.round(backoff / 1e3)}s…`);
|
|
459
|
+
setTimeout(connect, backoff);
|
|
460
|
+
backoff = Math.min(backoff * 2, RECONNECT_MAX_MS);
|
|
461
|
+
});
|
|
462
|
+
ws.addEventListener("error", () => {});
|
|
463
|
+
};
|
|
464
|
+
connect();
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
const active = /* @__PURE__ */ new Map();
|
|
468
|
+
/** In-flight starts, so concurrent POSTs for the same port share one dial. */
|
|
469
|
+
const starting = /* @__PURE__ */ new Map();
|
|
470
|
+
/** Start an exposure for `port` (or reuse a running one). Idempotent per port. */
|
|
471
|
+
async function ensureExposure(port, relay) {
|
|
472
|
+
const existing = active.get(port);
|
|
473
|
+
if (existing) return existing;
|
|
474
|
+
const inflight = starting.get(port);
|
|
475
|
+
if (inflight) return inflight;
|
|
476
|
+
const p = startExposure({
|
|
477
|
+
port,
|
|
478
|
+
relay
|
|
479
|
+
}).then((h) => {
|
|
480
|
+
active.set(port, h);
|
|
481
|
+
starting.delete(port);
|
|
482
|
+
return h;
|
|
483
|
+
}).catch((e) => {
|
|
484
|
+
starting.delete(port);
|
|
485
|
+
throw e;
|
|
486
|
+
});
|
|
487
|
+
starting.set(port, p);
|
|
488
|
+
return p;
|
|
489
|
+
}
|
|
490
|
+
function listExposures() {
|
|
491
|
+
return [...active.values()].sort((a, b) => b.createdAt - a.createdAt).map((h) => ({
|
|
492
|
+
id: h.id,
|
|
493
|
+
port: h.port,
|
|
494
|
+
url: h.url,
|
|
495
|
+
createdAt: h.createdAt
|
|
496
|
+
}));
|
|
497
|
+
}
|
|
498
|
+
function stopExposure(port) {
|
|
499
|
+
const h = active.get(port);
|
|
500
|
+
if (!h) return false;
|
|
501
|
+
h.stop();
|
|
502
|
+
active.delete(port);
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
372
505
|
async function cmdExpose(args) {
|
|
373
506
|
let relay = DEFAULT_RELAY;
|
|
374
507
|
let port = 0;
|
|
@@ -386,74 +519,24 @@ async function cmdExpose(args) {
|
|
|
386
519
|
console.error("usage: ay expose <port> [--relay https://…] (see ay expose --help)");
|
|
387
520
|
return 1;
|
|
388
521
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
let stopped = false;
|
|
396
|
-
let ws = null;
|
|
397
|
-
let backoff = RECONNECT_MIN_MS;
|
|
398
|
-
let announced = false;
|
|
399
|
-
const connect = () => {
|
|
400
|
-
if (stopped) return;
|
|
401
|
-
ws = new WebSocket(tunnelUrl);
|
|
402
|
-
ws.binaryType = "arraybuffer";
|
|
403
|
-
const sock = ws;
|
|
404
|
-
let ping = null;
|
|
405
|
-
sock.addEventListener("open", () => {
|
|
406
|
-
sock.send(JSON.stringify({
|
|
407
|
-
t: "hello",
|
|
408
|
-
key: rec.key,
|
|
409
|
-
port,
|
|
410
|
-
claims: [claimHash],
|
|
411
|
-
v: 1
|
|
412
|
-
}));
|
|
413
|
-
});
|
|
414
|
-
sock.addEventListener("message", (ev) => {
|
|
415
|
-
if (typeof ev.data !== "string") return;
|
|
416
|
-
let msg;
|
|
417
|
-
try {
|
|
418
|
-
msg = JSON.parse(ev.data);
|
|
419
|
-
} catch {
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (msg.t === "ready") {
|
|
423
|
-
backoff = RECONNECT_MIN_MS;
|
|
424
|
-
new TunnelHost(wsTransport(sock), { port });
|
|
425
|
-
ping = setInterval(() => {
|
|
426
|
-
if (sock.readyState === WebSocket.OPEN) sock.send("ping");
|
|
427
|
-
}, PING_MS);
|
|
428
|
-
if (!announced) {
|
|
429
|
-
announced = true;
|
|
430
|
-
console.log(`[ay expose] sharing 127.0.0.1:${port}`);
|
|
431
|
-
console.log(` url: https://${publicHost}/`);
|
|
432
|
-
console.log(` claim: https://${publicHost}/_ay/claim?t=${claimToken}`);
|
|
433
|
-
console.log(` (one-time link — opens access for 8h in that browser)`);
|
|
434
|
-
} else console.log(`[ay expose] reconnected`);
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
sock.addEventListener("close", (ev) => {
|
|
438
|
-
if (ping) clearInterval(ping);
|
|
439
|
-
if (stopped) return;
|
|
440
|
-
if (ev.code === 1008) {
|
|
441
|
-
console.error(`[ay expose] relay refused this exposure (${ev.reason || "forbidden"}) — giving up`);
|
|
442
|
-
process.exit(1);
|
|
443
|
-
}
|
|
444
|
-
console.log(`[ay expose] connection lost, retrying in ${Math.round(backoff / 1e3)}s…`);
|
|
445
|
-
setTimeout(connect, backoff);
|
|
446
|
-
backoff = Math.min(backoff * 2, RECONNECT_MAX_MS);
|
|
522
|
+
let handle;
|
|
523
|
+
try {
|
|
524
|
+
handle = await startExposure({
|
|
525
|
+
port,
|
|
526
|
+
relay,
|
|
527
|
+
log: (m) => console.log(`[ay expose] ${m}`)
|
|
447
528
|
});
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
529
|
+
} catch (e) {
|
|
530
|
+
console.error(`[ay expose] ${e.message} — giving up`);
|
|
531
|
+
return 1;
|
|
532
|
+
}
|
|
533
|
+
const claimUrl = handle.mintClaim();
|
|
534
|
+
console.log(` url: ${handle.url}`);
|
|
535
|
+
console.log(` claim: ${claimUrl}`);
|
|
536
|
+
console.log(` (one-time link — opens access for 8h in that browser)`);
|
|
451
537
|
const shutdown = () => {
|
|
452
|
-
stopped = true;
|
|
453
538
|
console.log("\n[ay expose] stopped — the URL now answers 502 until you expose again");
|
|
454
|
-
|
|
455
|
-
ws?.close();
|
|
456
|
-
} catch {}
|
|
539
|
+
handle.stop();
|
|
457
540
|
process.exit(0);
|
|
458
541
|
};
|
|
459
542
|
process.on("SIGINT", shutdown);
|
|
@@ -462,5 +545,5 @@ async function cmdExpose(args) {
|
|
|
462
545
|
}
|
|
463
546
|
|
|
464
547
|
//#endregion
|
|
465
|
-
export { cmdExpose };
|
|
466
|
-
//# sourceMappingURL=expose-
|
|
548
|
+
export { cmdExpose, ensureExposure, listExposures, stopExposure };
|
|
549
|
+
//# sourceMappingURL=expose-B3AGhVhM.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-BQh1-ljE.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-DhCi7ncH.js";
|
|
4
4
|
import "./pidStore-BIvsBQ8X.js";
|
|
5
5
|
import "./globalPidIndex-CoNr7tS8.js";
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as logger } from "./logger-CDIsZ-Pp.js";
|
|
2
2
|
import "./globalPidIndex-CoNr7tS8.js";
|
|
3
3
|
import "./configShared-0MnIQ652.js";
|
|
4
|
-
import { G as hostId, J as readInbox, K as listInboxParents, O as renderLogTailLines, Q as notifyDir, S as listRecords, U as appendEvent, W as gcInboxes, X as daemonLockDir, Y as shouldStealLock, Z as daemonLockOwnerPath, _ as isPidAlive, c as deriveLiveState, q as liveWatchers } from "./subcommands-
|
|
4
|
+
import { G as hostId, J as readInbox, K as listInboxParents, O as renderLogTailLines, Q as notifyDir, S as listRecords, U as appendEvent, W as gcInboxes, X as daemonLockDir, Y as shouldStealLock, Z as daemonLockOwnerPath, _ as isPidAlive, c as deriveLiveState, q as liveWatchers } from "./subcommands-CNCgJ48t.js";
|
|
5
5
|
import "./e2e-BeKjLhmO.js";
|
|
6
6
|
import "./webrtcLink-BG0Xc4-W.js";
|
|
7
7
|
import "./remotes-CgT91bMo.js";
|
|
@@ -588,4 +588,4 @@ async function ensureDaemon() {
|
|
|
588
588
|
|
|
589
589
|
//#endregion
|
|
590
590
|
export { daemonStatus, ensureDaemon, requestDaemonStop, runDaemon };
|
|
591
|
-
//# sourceMappingURL=notifyDaemon-
|
|
591
|
+
//# sourceMappingURL=notifyDaemon-hKT38cBs.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
1
|
+
import { r as getInstalledPackage } from "./versionChecker-DhCi7ncH.js";
|
|
2
2
|
import { execFileSync } from "child_process";
|
|
3
3
|
import { existsSync, mkdirSync, unlinkSync } from "fs";
|
|
4
4
|
import { chmod, copyFile } from "fs/promises";
|
|
@@ -225,4 +225,4 @@ async function getRustBinary(options = {}) {
|
|
|
225
225
|
|
|
226
226
|
//#endregion
|
|
227
227
|
export { getRustBinary as n, findSpawnHiddenLauncher as t };
|
|
228
|
-
//# sourceMappingURL=rustBinary-
|
|
228
|
+
//# sourceMappingURL=rustBinary-Cu8q2m9s.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-BQh1-ljE.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-DhCi7ncH.js";
|
|
4
4
|
import "./pidStore-BIvsBQ8X.js";
|
|
5
5
|
import "./globalPidIndex-CoNr7tS8.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-mUaQHk4M.js";
|
|
7
7
|
import { d as resolveSpawnCwd } from "./workspaceConfig-_GtAZtsi.js";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
9
|
|
|
@@ -141,4 +141,4 @@ async function cmdSchedule(rest) {
|
|
|
141
141
|
|
|
142
142
|
//#endregion
|
|
143
143
|
export { cmdSchedule };
|
|
144
|
-
//# sourceMappingURL=schedule-
|
|
144
|
+
//# sourceMappingURL=schedule-Bb98iG9W.js.map
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-BQh1-ljE.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
4
|
-
import { t as findSpawnHiddenLauncher } from "./rustBinary-
|
|
3
|
+
import { r as getInstalledPackage } from "./versionChecker-DhCi7ncH.js";
|
|
4
|
+
import { t as findSpawnHiddenLauncher } from "./rustBinary-Cu8q2m9s.js";
|
|
5
5
|
import "./pidStore-BIvsBQ8X.js";
|
|
6
6
|
import { a as updateGlobalPidStatus } from "./globalPidIndex-CoNr7tS8.js";
|
|
7
7
|
import { t as pgidForWrapper } from "./reaper-CWF2_ATd.js";
|
|
8
8
|
import "./configShared-0MnIQ652.js";
|
|
9
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
10
|
-
import { $ as TYPING_BADGE, D as recentReadEdges, E as readPtysize, H as writeToIpc, I as snapshotStatus, O as renderLogTailLines, S as listRecords, T as readNotes, b as isUserTyping, f as extractNeedsInput, j as resolveOne, k as renderRawLog, l as deriveLiveStatus, o as controlCodeFromName, p as extractTaskCounts, u as extractBadges } from "./subcommands-
|
|
9
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-mUaQHk4M.js";
|
|
10
|
+
import { $ as TYPING_BADGE, D as recentReadEdges, E as readPtysize, H as writeToIpc, I as snapshotStatus, O as renderLogTailLines, S as listRecords, T as readNotes, b as isUserTyping, f as extractNeedsInput, j as resolveOne, k as renderRawLog, l as deriveLiveStatus, o as controlCodeFromName, p as extractTaskCounts, u as extractBadges } from "./subcommands-CNCgJ48t.js";
|
|
11
11
|
import "./e2e-BeKjLhmO.js";
|
|
12
12
|
import "./webrtcLink-BG0Xc4-W.js";
|
|
13
13
|
import "./remotes-CgT91bMo.js";
|
|
@@ -1851,7 +1851,7 @@ Options:
|
|
|
1851
1851
|
const perm = body.perm ?? "r";
|
|
1852
1852
|
if (perm !== "r" && perm !== "rw") return new Response(`invalid perm ${perm} (want r or rw)`, { status: 400 });
|
|
1853
1853
|
try {
|
|
1854
|
-
const { createScopedShare } = await import("./agentShare-
|
|
1854
|
+
const { createScopedShare } = await import("./agentShare-Boa9P2dK.js");
|
|
1855
1855
|
const share = await createScopedShare({
|
|
1856
1856
|
agent: body.agent,
|
|
1857
1857
|
perm,
|
|
@@ -1866,15 +1866,48 @@ Options:
|
|
|
1866
1866
|
}
|
|
1867
1867
|
}
|
|
1868
1868
|
if (req.method === "GET" && p === "/api/shares") {
|
|
1869
|
-
const { listShares } = await import("./agentShare-
|
|
1869
|
+
const { listShares } = await import("./agentShare-Boa9P2dK.js");
|
|
1870
1870
|
return Response.json(listShares());
|
|
1871
1871
|
}
|
|
1872
1872
|
const revokeM = /^\/api\/share\/([^/]+)$/.exec(p);
|
|
1873
1873
|
if (req.method === "DELETE" && revokeM) {
|
|
1874
|
-
const { revokeShare } = await import("./agentShare-
|
|
1874
|
+
const { revokeShare } = await import("./agentShare-Boa9P2dK.js");
|
|
1875
1875
|
const ok = revokeShare(decodeURIComponent(revokeM[1]));
|
|
1876
1876
|
return new Response(ok ? "revoked" : "no such share", { status: ok ? 200 : 404 });
|
|
1877
1877
|
}
|
|
1878
|
+
if (req.method === "POST" && p === "/api/expose") {
|
|
1879
|
+
let body;
|
|
1880
|
+
try {
|
|
1881
|
+
body = await req.json();
|
|
1882
|
+
} catch {
|
|
1883
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
1884
|
+
}
|
|
1885
|
+
const port = Number(body.port);
|
|
1886
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return new Response("valid port required", { status: 400 });
|
|
1887
|
+
try {
|
|
1888
|
+
const { ensureExposure } = await import("./expose-B3AGhVhM.js");
|
|
1889
|
+
const h = await ensureExposure(port, body.relay);
|
|
1890
|
+
return Response.json({
|
|
1891
|
+
id: h.id,
|
|
1892
|
+
port: h.port,
|
|
1893
|
+
url: h.url,
|
|
1894
|
+
claim: h.mintClaim(),
|
|
1895
|
+
createdAt: h.createdAt
|
|
1896
|
+
});
|
|
1897
|
+
} catch (e) {
|
|
1898
|
+
return new Response(`expose failed: ${e.message}`, { status: 502 });
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
if (req.method === "GET" && p === "/api/exposes") {
|
|
1902
|
+
const { listExposures } = await import("./expose-B3AGhVhM.js");
|
|
1903
|
+
return Response.json(listExposures());
|
|
1904
|
+
}
|
|
1905
|
+
const unexposeM = /^\/api\/expose\/(\d+)$/.exec(p);
|
|
1906
|
+
if (req.method === "DELETE" && unexposeM) {
|
|
1907
|
+
const { stopExposure } = await import("./expose-B3AGhVhM.js");
|
|
1908
|
+
const ok = stopExposure(Number(unexposeM[1]));
|
|
1909
|
+
return new Response(ok ? "revoked" : "no such exposure", { status: ok ? 200 : 404 });
|
|
1910
|
+
}
|
|
1878
1911
|
return new Response("Not Found", { status: 404 });
|
|
1879
1912
|
};
|
|
1880
1913
|
const uiDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "lab", "ui");
|
|
@@ -1912,8 +1945,11 @@ Options:
|
|
|
1912
1945
|
if (req.method === "GET" && p === "/r/main.js.map") return serveUiFile("rgui/dist/main.js.map", "application/json");
|
|
1913
1946
|
if (req.method === "GET" && p === "/room-client.js") return serveUiFile("room-client.js", "text/javascript; charset=utf-8");
|
|
1914
1947
|
if (req.method === "GET" && p === "/console-logic.js") return serveUiFile("console-logic.js", "text/javascript; charset=utf-8");
|
|
1948
|
+
if (req.method === "GET" && p === "/rtc.js") return serveUiFile("rtc.js", "text/javascript; charset=utf-8");
|
|
1915
1949
|
if (req.method === "GET" && p === "/e2e.js") return serveUiFile("e2e.js", "text/javascript; charset=utf-8");
|
|
1916
1950
|
if (req.method === "GET" && p === "/qrcode.js") return serveUiFile("qrcode.js", "text/javascript; charset=utf-8");
|
|
1951
|
+
if (req.method === "GET" && p === "/manifest.webmanifest") return serveUiFile("manifest.webmanifest", "application/manifest+json");
|
|
1952
|
+
if (req.method === "GET" && p === "/icon.svg") return serveUiFile("icon.svg", "image/svg+xml");
|
|
1917
1953
|
if (req.method === "GET" && p === "/favicon.ico") return new Response(null, { status: 204 });
|
|
1918
1954
|
return apiFetch(req);
|
|
1919
1955
|
};
|
|
@@ -2016,7 +2052,7 @@ Options:
|
|
|
2016
2052
|
const shutdown = (resolve) => {
|
|
2017
2053
|
if (heartbeat) clearInterval(heartbeat);
|
|
2018
2054
|
closeShare?.();
|
|
2019
|
-
import("./agentShare-
|
|
2055
|
+
import("./agentShare-Boa9P2dK.js").then((m) => m.revokeAllShares()).catch(() => {});
|
|
2020
2056
|
server?.stop();
|
|
2021
2057
|
resolve();
|
|
2022
2058
|
};
|
|
@@ -2029,4 +2065,4 @@ Options:
|
|
|
2029
2065
|
|
|
2030
2066
|
//#endregion
|
|
2031
2067
|
export { cmdServe };
|
|
2032
|
-
//# sourceMappingURL=serve-
|
|
2068
|
+
//# sourceMappingURL=serve-DVhbtjR4.js.map
|
|
@@ -32,7 +32,7 @@ async function cmdSetup(rest) {
|
|
|
32
32
|
if (!existsSync(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
|
|
33
33
|
if (noShare) return 0;
|
|
34
34
|
process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
|
|
35
|
-
const { cmdServe } = await import("./serve-
|
|
35
|
+
const { cmdServe } = await import("./serve-DVhbtjR4.js");
|
|
36
36
|
return cmdServe([
|
|
37
37
|
"install",
|
|
38
38
|
"--share",
|
|
@@ -42,4 +42,4 @@ async function cmdSetup(rest) {
|
|
|
42
42
|
|
|
43
43
|
//#endregion
|
|
44
44
|
export { cmdSetup };
|
|
45
|
-
//# sourceMappingURL=setup-
|
|
45
|
+
//# sourceMappingURL=setup-Dj8Grg0K.js.map
|
|
@@ -1146,15 +1146,15 @@ async function runSubcommand(argv) {
|
|
|
1146
1146
|
case "restart": return await cmdRestart(rest);
|
|
1147
1147
|
case "note": return await cmdNote(rest);
|
|
1148
1148
|
case "serve": {
|
|
1149
|
-
const { cmdServe } = await import("./serve-
|
|
1149
|
+
const { cmdServe } = await import("./serve-DVhbtjR4.js");
|
|
1150
1150
|
return cmdServe(rest);
|
|
1151
1151
|
}
|
|
1152
1152
|
case "setup": {
|
|
1153
|
-
const { cmdSetup } = await import("./setup-
|
|
1153
|
+
const { cmdSetup } = await import("./setup-Dj8Grg0K.js");
|
|
1154
1154
|
return cmdSetup(rest);
|
|
1155
1155
|
}
|
|
1156
1156
|
case "schedule": {
|
|
1157
|
-
const { cmdSchedule } = await import("./schedule-
|
|
1157
|
+
const { cmdSchedule } = await import("./schedule-Bb98iG9W.js");
|
|
1158
1158
|
return cmdSchedule(rest);
|
|
1159
1159
|
}
|
|
1160
1160
|
case "remote": {
|
|
@@ -1162,7 +1162,7 @@ async function runSubcommand(argv) {
|
|
|
1162
1162
|
return cmdRemote(rest);
|
|
1163
1163
|
}
|
|
1164
1164
|
case "expose": {
|
|
1165
|
-
const { cmdExpose } = await import("./expose-
|
|
1165
|
+
const { cmdExpose } = await import("./expose-B3AGhVhM.js");
|
|
1166
1166
|
return cmdExpose(rest);
|
|
1167
1167
|
}
|
|
1168
1168
|
case "reap":
|
|
@@ -3996,7 +3996,7 @@ async function cmdNotify(rest) {
|
|
|
3996
3996
|
}
|
|
3997
3997
|
const ensure = async () => {
|
|
3998
3998
|
if (!argv["ensure-daemon"]) return;
|
|
3999
|
-
const { ensureDaemon } = await import("./notifyDaemon-
|
|
3999
|
+
const { ensureDaemon } = await import("./notifyDaemon-hKT38cBs.js");
|
|
4000
4000
|
await ensureDaemon().catch(() => null);
|
|
4001
4001
|
};
|
|
4002
4002
|
await heartbeatWatcher(parent, selfStartedAt);
|
|
@@ -4070,7 +4070,7 @@ async function cmdNotifyCursor(args) {
|
|
|
4070
4070
|
}
|
|
4071
4071
|
async function cmdNotifyd(rest) {
|
|
4072
4072
|
const sub = rest[0] ?? "status";
|
|
4073
|
-
const daemon = await import("./notifyDaemon-
|
|
4073
|
+
const daemon = await import("./notifyDaemon-hKT38cBs.js");
|
|
4074
4074
|
switch (sub) {
|
|
4075
4075
|
case "run": return daemon.runDaemon();
|
|
4076
4076
|
case "once": return daemon.runDaemon({ once: true });
|
|
@@ -4097,4 +4097,4 @@ async function cmdNotifyd(rest) {
|
|
|
4097
4097
|
|
|
4098
4098
|
//#endregion
|
|
4099
4099
|
export { TYPING_BADGE as $, renderRawLogLines as A, waitForLogQuiet as B, matchKeyword as C, recentReadEdges as D, readPtysize as E, runSubcommand as F, hostId as G, writeToIpc as H, snapshotStatus as I, readInbox as J, listInboxParents as K, stdinActivityPath as L, resolveReadWindow as M, resolveResumeArgs as N, renderLogTailLines as O, restartHintLines as P, notifyDir as Q, stopTipForCli as R, listRecords as S, readNotes as T, appendEvent as U, writeKeysPaced as V, gcInboxes as W, daemonLockDir as X, shouldStealLock as Y, daemonLockOwnerPath as Z, isPidAlive as _, cmdHelp as a, isUserTyping as b, deriveLiveState as c, extractMenu as d, extractNeedsInput as f, isExitRequest as g, isAgentStuck as h, backoffWhileTyping as i, resolveOne as j, renderRawLog as k, deriveLiveStatus as l, finalizedLines as m, READ_PAGE_DEFAULT as n, controlCodeFromName as o, extractTaskCounts as p, liveWatchers as q, TYPING_WINDOW_MS as r, cursorAbs as s, GRACEFUL_EXIT_COMMANDS as t, extractBadges as u, isSlashCommand as v, menuSelectKeys as w, lastStdinAt as x, isSubcommand as y, submitAndConfirm as z };
|
|
4100
|
-
//# sourceMappingURL=subcommands-
|
|
4100
|
+
//# sourceMappingURL=subcommands-CNCgJ48t.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./logger-CDIsZ-Pp.js";
|
|
2
2
|
import "./globalPidIndex-CoNr7tS8.js";
|
|
3
3
|
import "./configShared-0MnIQ652.js";
|
|
4
|
-
import { A as renderRawLogLines, B as waitForLogQuiet, C as matchKeyword, D as recentReadEdges, E as readPtysize, F as runSubcommand, H as writeToIpc, I as snapshotStatus, L as stdinActivityPath, M as resolveReadWindow, N as resolveResumeArgs, O as renderLogTailLines, P as restartHintLines, R as stopTipForCli, S as listRecords, T as readNotes, V as writeKeysPaced, _ as isPidAlive, a as cmdHelp, b as isUserTyping, c as deriveLiveState, d as extractMenu, f as extractNeedsInput, g as isExitRequest, h as isAgentStuck, i as backoffWhileTyping, j as resolveOne, k as renderRawLog, l as deriveLiveStatus, m as finalizedLines, n as READ_PAGE_DEFAULT, o as controlCodeFromName, p as extractTaskCounts, r as TYPING_WINDOW_MS, s as cursorAbs, t as GRACEFUL_EXIT_COMMANDS, u as extractBadges, v as isSlashCommand, w as menuSelectKeys, x as lastStdinAt, y as isSubcommand, z as submitAndConfirm } from "./subcommands-
|
|
4
|
+
import { A as renderRawLogLines, B as waitForLogQuiet, C as matchKeyword, D as recentReadEdges, E as readPtysize, F as runSubcommand, H as writeToIpc, I as snapshotStatus, L as stdinActivityPath, M as resolveReadWindow, N as resolveResumeArgs, O as renderLogTailLines, P as restartHintLines, R as stopTipForCli, S as listRecords, T as readNotes, V as writeKeysPaced, _ as isPidAlive, a as cmdHelp, b as isUserTyping, c as deriveLiveState, d as extractMenu, f as extractNeedsInput, g as isExitRequest, h as isAgentStuck, i as backoffWhileTyping, j as resolveOne, k as renderRawLog, l as deriveLiveStatus, m as finalizedLines, n as READ_PAGE_DEFAULT, o as controlCodeFromName, p as extractTaskCounts, r as TYPING_WINDOW_MS, s as cursorAbs, t as GRACEFUL_EXIT_COMMANDS, u as extractBadges, v as isSlashCommand, w as menuSelectKeys, x as lastStdinAt, y as isSubcommand, z as submitAndConfirm } from "./subcommands-CNCgJ48t.js";
|
|
5
5
|
import "./e2e-BeKjLhmO.js";
|
|
6
6
|
import "./webrtcLink-BG0Xc4-W.js";
|
|
7
7
|
import "./remotes-CgT91bMo.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
|
|
2
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
2
|
+
import { r as getInstalledPackage } from "./versionChecker-DhCi7ncH.js";
|
|
3
3
|
import { t as agentYesHome } from "./agentYesHome-CtHb5b71.js";
|
|
4
4
|
import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CNMl13dC.js";
|
|
5
5
|
import { t as PidStore } from "./pidStore-BIvsBQ8X.js";
|
|
@@ -1824,4 +1824,4 @@ function sleep(ms) {
|
|
|
1824
1824
|
|
|
1825
1825
|
//#endregion
|
|
1826
1826
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1827
|
-
//# sourceMappingURL=ts-
|
|
1827
|
+
//# sourceMappingURL=ts-BQh1-ljE.js.map
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
|
|
|
7
7
|
|
|
8
8
|
//#region package.json
|
|
9
9
|
var name = "agent-yes";
|
|
10
|
-
var version = "1.
|
|
10
|
+
var version = "1.191.0";
|
|
11
11
|
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region ts/versionChecker.ts
|
|
@@ -215,4 +215,4 @@ async function displayVersion() {
|
|
|
215
215
|
|
|
216
216
|
//#endregion
|
|
217
217
|
export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
|
|
218
|
-
//# sourceMappingURL=versionChecker-
|
|
218
|
+
//# sourceMappingURL=versionChecker-DhCi7ncH.js.map
|
package/lab/ui/index.html
CHANGED
|
@@ -352,6 +352,46 @@
|
|
|
352
352
|
.app.steer-agent #shareAgentRW {
|
|
353
353
|
display: none !important;
|
|
354
354
|
}
|
|
355
|
+
/* Expose modal — ports manager list rows. */
|
|
356
|
+
.exposeList {
|
|
357
|
+
margin: 10px 0;
|
|
358
|
+
display: flex;
|
|
359
|
+
flex-direction: column;
|
|
360
|
+
gap: 6px;
|
|
361
|
+
max-height: 40vh;
|
|
362
|
+
overflow-y: auto;
|
|
363
|
+
}
|
|
364
|
+
.exposerow {
|
|
365
|
+
display: flex;
|
|
366
|
+
align-items: center;
|
|
367
|
+
gap: 8px;
|
|
368
|
+
border: 1px solid var(--line);
|
|
369
|
+
border-radius: 8px;
|
|
370
|
+
padding: 7px 9px;
|
|
371
|
+
}
|
|
372
|
+
.exposerow .exmeta {
|
|
373
|
+
flex: 1;
|
|
374
|
+
min-width: 0;
|
|
375
|
+
}
|
|
376
|
+
.exposerow .explabel {
|
|
377
|
+
font-weight: 600;
|
|
378
|
+
}
|
|
379
|
+
.exposerow .exurl {
|
|
380
|
+
font-size: 11px;
|
|
381
|
+
opacity: 0.7;
|
|
382
|
+
overflow: hidden;
|
|
383
|
+
text-overflow: ellipsis;
|
|
384
|
+
white-space: nowrap;
|
|
385
|
+
}
|
|
386
|
+
.exposerow .exhost {
|
|
387
|
+
font-size: 10px;
|
|
388
|
+
opacity: 0.55;
|
|
389
|
+
}
|
|
390
|
+
.exposePrompt {
|
|
391
|
+
border-bottom: 1px solid var(--line);
|
|
392
|
+
padding-bottom: 10px;
|
|
393
|
+
margin-bottom: 4px;
|
|
394
|
+
}
|
|
355
395
|
/* Share modal — QR + link for a single-agent view-only share. */
|
|
356
396
|
.modal-backdrop {
|
|
357
397
|
position: fixed;
|
|
@@ -1685,6 +1725,9 @@
|
|
|
1685
1725
|
<button id="foldbtn" class="viewbtn" title="fold subagent trees">⊞ subs</button>
|
|
1686
1726
|
<button id="sortbtn" class="viewbtn" title="cycle sort order">⇅ state</button>
|
|
1687
1727
|
<button id="viewbtn" class="viewbtn" title="toggle compact list">☰</button>
|
|
1728
|
+
<button id="portsbtn" class="viewbtn" title="manage exposed localhost ports">
|
|
1729
|
+
⇄ ports
|
|
1730
|
+
</button>
|
|
1688
1731
|
<button id="rguibtn" class="viewbtn" title="open the agent graph view (/r/) for this fleet">
|
|
1689
1732
|
⌗ graph
|
|
1690
1733
|
</button>
|
|
@@ -1890,6 +1933,32 @@
|
|
|
1890
1933
|
</div>
|
|
1891
1934
|
</div>
|
|
1892
1935
|
|
|
1936
|
+
<!-- Expose modal: prompt to publish a clicked localhost:PORT through
|
|
1937
|
+
agent-yes.com, plus the ports manager (list + revoke active exposures). -->
|
|
1938
|
+
<div class="modal-backdrop" id="exposeModal" hidden>
|
|
1939
|
+
<div class="sharebox" role="dialog" aria-modal="true" aria-label="Expose localhost port">
|
|
1940
|
+
<h3 id="exposeTitle">Exposed ports</h3>
|
|
1941
|
+
<div class="exposePrompt" id="exposePrompt" hidden>
|
|
1942
|
+
<p class="sub" id="exposePromptSub">Publish this local server on the internet?</p>
|
|
1943
|
+
<p class="note">
|
|
1944
|
+
A private link on <strong>agent-yes.com</strong> will tunnel to this port on the agent's
|
|
1945
|
+
machine. Only someone who opens the one-time claim link (which opens automatically for
|
|
1946
|
+
you) can reach it. Revoke anytime below.
|
|
1947
|
+
</p>
|
|
1948
|
+
<div class="srow">
|
|
1949
|
+
<button class="primary" id="exposeGo" type="button">Expose & open</button>
|
|
1950
|
+
<button id="exposeCancel" type="button">Cancel</button>
|
|
1951
|
+
</div>
|
|
1952
|
+
</div>
|
|
1953
|
+
<div class="exposeList" id="exposeList">
|
|
1954
|
+
<p class="sub" id="exposeEmpty">No ports are exposed right now.</p>
|
|
1955
|
+
</div>
|
|
1956
|
+
<div class="srow">
|
|
1957
|
+
<button id="exposeClose" type="button">Close</button>
|
|
1958
|
+
</div>
|
|
1959
|
+
</div>
|
|
1960
|
+
</div>
|
|
1961
|
+
|
|
1893
1962
|
<!-- Cmd/Ctrl+K omnibox: search agents by title (instant) then output (tail),
|
|
1894
1963
|
or spawn a new agent in the highlighted agent's cwd with the typed prompt. -->
|
|
1895
1964
|
<div class="omni" id="omni" style="display: none">
|
|
@@ -3242,6 +3311,157 @@
|
|
|
3242
3311
|
if (modal) modal.hidden = true;
|
|
3243
3312
|
pendingRw = null; // a cancelled read-write flow never minted anything
|
|
3244
3313
|
}
|
|
3314
|
+
|
|
3315
|
+
// ---- Port exposure: publish a clicked localhost:PORT through agent-yes.com ----
|
|
3316
|
+
|
|
3317
|
+
// Recognise a local-loopback URL and pull its port (default 80). Only these
|
|
3318
|
+
// are offered for exposure — a real public URL just opens normally.
|
|
3319
|
+
function localhostPort(uri) {
|
|
3320
|
+
let u;
|
|
3321
|
+
try {
|
|
3322
|
+
u = new URL(uri);
|
|
3323
|
+
} catch {
|
|
3324
|
+
return null;
|
|
3325
|
+
}
|
|
3326
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return null;
|
|
3327
|
+
const h = u.hostname;
|
|
3328
|
+
if (h !== "localhost" && h !== "127.0.0.1" && h !== "0.0.0.0" && h !== "::1") return null;
|
|
3329
|
+
const port = Number(u.port) || (u.protocol === "https:" ? 443 : 80);
|
|
3330
|
+
return port >= 1 && port <= 65535 ? port : null;
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
// The tx the expose prompt should call (the daemon whose terminal was clicked).
|
|
3334
|
+
let exposePendingTx = null;
|
|
3335
|
+
let exposePendingPort = 0;
|
|
3336
|
+
|
|
3337
|
+
// Called by the terminal link handler when a localhost URL is clicked.
|
|
3338
|
+
function promptExpose(port, tx) {
|
|
3339
|
+
exposePendingTx = tx || localTx;
|
|
3340
|
+
exposePendingPort = port;
|
|
3341
|
+
$("exposeTitle").textContent = "Expose localhost:" + port + "?";
|
|
3342
|
+
$("exposePromptSub").textContent =
|
|
3343
|
+
"Publish this local server (port " + port + ") on the internet via agent-yes.com?";
|
|
3344
|
+
$("exposePrompt").hidden = false;
|
|
3345
|
+
openExposeManager(false);
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
// Open the ports manager (list of active exposures across every host).
|
|
3349
|
+
function openExposeManager(resetPrompt) {
|
|
3350
|
+
if (resetPrompt !== false) {
|
|
3351
|
+
$("exposePrompt").hidden = true;
|
|
3352
|
+
$("exposeTitle").textContent = "Exposed ports";
|
|
3353
|
+
}
|
|
3354
|
+
const modal = $("exposeModal");
|
|
3355
|
+
if (modal) modal.hidden = false;
|
|
3356
|
+
refreshExposeList();
|
|
3357
|
+
}
|
|
3358
|
+
function closeExposeModal() {
|
|
3359
|
+
const modal = $("exposeModal");
|
|
3360
|
+
if (modal) modal.hidden = true;
|
|
3361
|
+
exposePendingTx = null;
|
|
3362
|
+
exposePendingPort = 0;
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
// Agree → mint the exposure on its host and open the claim link (which sets
|
|
3366
|
+
// the 8h cookie and redirects to the app), then refresh the manager list.
|
|
3367
|
+
async function doExpose() {
|
|
3368
|
+
const tx = exposePendingTx || localTx;
|
|
3369
|
+
const port = exposePendingPort;
|
|
3370
|
+
$("exposePrompt").hidden = true;
|
|
3371
|
+
if (!port) return;
|
|
3372
|
+
let info = null;
|
|
3373
|
+
try {
|
|
3374
|
+
const r = await tx.post("/api/expose", { port });
|
|
3375
|
+
if (r.ok) info = JSON.parse(r.text);
|
|
3376
|
+
} catch {}
|
|
3377
|
+
if (!info || !info.claim) {
|
|
3378
|
+
$("exposeEmpty").hidden = false;
|
|
3379
|
+
$("exposeEmpty").textContent = "Couldn't expose port " + port + " (is `ay serve` reachable?).";
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
window.open(info.claim, "_blank", "noopener,noreferrer");
|
|
3383
|
+
refreshExposeList();
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
// Gather active exposures from every live host and render the manager rows.
|
|
3387
|
+
async function refreshExposeList() {
|
|
3388
|
+
const list = $("exposeList");
|
|
3389
|
+
if (!list) return;
|
|
3390
|
+
const rows = [];
|
|
3391
|
+
await Promise.all(
|
|
3392
|
+
[...sources.values()].map(async (s) => {
|
|
3393
|
+
const tx = s.tx || (s.id === "local" ? localTx : null);
|
|
3394
|
+
if (!tx) return;
|
|
3395
|
+
let arr;
|
|
3396
|
+
try {
|
|
3397
|
+
arr = await tx.fetchJSON("/api/exposes");
|
|
3398
|
+
} catch {
|
|
3399
|
+
return;
|
|
3400
|
+
}
|
|
3401
|
+
if (Array.isArray(arr))
|
|
3402
|
+
for (const ex of arr) rows.push({ ...ex, _srcName: s.name || s.id, _tx: tx });
|
|
3403
|
+
}),
|
|
3404
|
+
);
|
|
3405
|
+
list.textContent = "";
|
|
3406
|
+
if (!rows.length) {
|
|
3407
|
+
const p = document.createElement("p");
|
|
3408
|
+
p.className = "sub";
|
|
3409
|
+
p.id = "exposeEmpty";
|
|
3410
|
+
p.textContent = "No ports are exposed right now.";
|
|
3411
|
+
list.appendChild(p);
|
|
3412
|
+
return;
|
|
3413
|
+
}
|
|
3414
|
+
rows.sort((a, b) => b.createdAt - a.createdAt);
|
|
3415
|
+
for (const ex of rows) list.appendChild(renderExposeRow(ex));
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
function renderExposeRow(ex) {
|
|
3419
|
+
const row = document.createElement("div");
|
|
3420
|
+
row.className = "exposerow";
|
|
3421
|
+
const meta = document.createElement("div");
|
|
3422
|
+
meta.className = "exmeta";
|
|
3423
|
+
const label = document.createElement("div");
|
|
3424
|
+
label.className = "explabel";
|
|
3425
|
+
label.textContent = "localhost:" + ex.port;
|
|
3426
|
+
const url = document.createElement("div");
|
|
3427
|
+
url.className = "exurl";
|
|
3428
|
+
url.textContent = ex.url;
|
|
3429
|
+
const host = document.createElement("div");
|
|
3430
|
+
host.className = "exhost";
|
|
3431
|
+
host.textContent = ex._srcName;
|
|
3432
|
+
meta.appendChild(label);
|
|
3433
|
+
meta.appendChild(url);
|
|
3434
|
+
meta.appendChild(host);
|
|
3435
|
+
row.appendChild(meta);
|
|
3436
|
+
|
|
3437
|
+
const openBtn = document.createElement("button");
|
|
3438
|
+
openBtn.className = "primary";
|
|
3439
|
+
openBtn.textContent = "Open";
|
|
3440
|
+
// Re-mint a fresh claim so the owner (or a guest) gets a usable link.
|
|
3441
|
+
openBtn.onclick = async () => {
|
|
3442
|
+
try {
|
|
3443
|
+
const r = await ex._tx.post("/api/expose", { port: ex.port });
|
|
3444
|
+
const info = r.ok ? JSON.parse(r.text) : null;
|
|
3445
|
+
window.open(info && info.claim ? info.claim : ex.url, "_blank", "noopener,noreferrer");
|
|
3446
|
+
} catch {
|
|
3447
|
+
window.open(ex.url, "_blank", "noopener,noreferrer");
|
|
3448
|
+
}
|
|
3449
|
+
};
|
|
3450
|
+
row.appendChild(openBtn);
|
|
3451
|
+
|
|
3452
|
+
const revoke = document.createElement("button");
|
|
3453
|
+
revoke.className = "danger";
|
|
3454
|
+
revoke.textContent = "Revoke";
|
|
3455
|
+
revoke.onclick = async () => {
|
|
3456
|
+
revoke.disabled = true;
|
|
3457
|
+
try {
|
|
3458
|
+
await ex._tx.del("/api/expose/" + ex.port);
|
|
3459
|
+
} catch {}
|
|
3460
|
+
refreshExposeList();
|
|
3461
|
+
};
|
|
3462
|
+
row.appendChild(revoke);
|
|
3463
|
+
return row;
|
|
3464
|
+
}
|
|
3245
3465
|
// Draw the QR onto a fresh canvas (white quiet-zone always, so it scans in
|
|
3246
3466
|
// dark mode too). `qrcode` is the vendored global from qrcode.js.
|
|
3247
3467
|
function renderQr(container, text) {
|
|
@@ -3291,6 +3511,18 @@
|
|
|
3291
3511
|
currentShare = null;
|
|
3292
3512
|
closeShareModal();
|
|
3293
3513
|
}
|
|
3514
|
+
(function exposeModalBoot() {
|
|
3515
|
+
$("portsbtn")?.addEventListener("click", () => openExposeManager(true));
|
|
3516
|
+
$("exposeGo")?.addEventListener("click", doExpose);
|
|
3517
|
+
$("exposeCancel")?.addEventListener("click", () => {
|
|
3518
|
+
$("exposePrompt").hidden = true;
|
|
3519
|
+
$("exposeTitle").textContent = "Exposed ports";
|
|
3520
|
+
});
|
|
3521
|
+
$("exposeClose")?.addEventListener("click", closeExposeModal);
|
|
3522
|
+
$("exposeModal")?.addEventListener("click", (e) => {
|
|
3523
|
+
if (e.target === $("exposeModal")) closeExposeModal();
|
|
3524
|
+
});
|
|
3525
|
+
})();
|
|
3294
3526
|
(function shareModalBoot() {
|
|
3295
3527
|
$("shareClose")?.addEventListener("click", closeShareModal);
|
|
3296
3528
|
$("shareCloseAlt")?.addEventListener("click", closeShareModal);
|
|
@@ -4256,9 +4488,13 @@
|
|
|
4256
4488
|
// new tab (noopener so the page can't be tampered with via window.opener).
|
|
4257
4489
|
try {
|
|
4258
4490
|
term.loadAddon(
|
|
4259
|
-
new WebLinksAddon.WebLinksAddon((
|
|
4260
|
-
|
|
4261
|
-
|
|
4491
|
+
new WebLinksAddon.WebLinksAddon((ev, uri) => {
|
|
4492
|
+
// A localhost URL isn't reachable from the viewer's browser — offer
|
|
4493
|
+
// to publish it through agent-yes.com (on THIS agent's host: txFor(e)).
|
|
4494
|
+
const port = localhostPort(uri);
|
|
4495
|
+
if (port) return promptExpose(port, txFor(e));
|
|
4496
|
+
window.open(uri, "_blank", "noopener,noreferrer");
|
|
4497
|
+
}),
|
|
4262
4498
|
);
|
|
4263
4499
|
} catch {
|
|
4264
4500
|
/* addon CDN blocked — terminal still works, just without auto-links */
|
package/package.json
CHANGED
package/ts/expose.ts
CHANGED
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
//
|
|
4
4
|
// The daemon dials OUT (wss://<relay>/_ay/tunnel/<id>), so it works behind any
|
|
5
5
|
// NAT, and runs the codehost tunnel protocol's host half against the local
|
|
6
|
-
// port. Private by default: visitors need
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// port. Private by default: visitors need a single-use claim link (it swaps
|
|
7
|
+
// for an 8h HttpOnly cookie at the edge; unauthenticated requests never reach
|
|
8
|
+
// this machine). See lab/ui/cf/exposure.ts for the edge.
|
|
9
|
+
//
|
|
10
|
+
// Two front doors share one implementation:
|
|
11
|
+
// - the CLI (`cmdExpose`), which starts one exposure and blocks; and
|
|
12
|
+
// - the in-process manager (`ensureExposure` / `listExposures` /
|
|
13
|
+
// `stopExposure`), which `ay serve` drives from POST /api/expose so the web
|
|
14
|
+
// console can expose a clicked localhost port and revoke it later.
|
|
9
15
|
|
|
10
16
|
import { randomBytes, createHash } from "node:crypto";
|
|
11
17
|
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
@@ -24,13 +30,41 @@ interface ExposureRecord {
|
|
|
24
30
|
key: string;
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
/** Live handle for one exposed port. */
|
|
34
|
+
export interface ExposureHandle {
|
|
35
|
+
/** Opaque exposure id (also the subdomain label). */
|
|
36
|
+
id: string;
|
|
37
|
+
/** Local loopback port being shared. */
|
|
38
|
+
port: number;
|
|
39
|
+
/** Public host, e.g. x….agent-yes.com (or the relay host for a dev relay). */
|
|
40
|
+
publicHost: string;
|
|
41
|
+
/** Public root URL. */
|
|
42
|
+
url: string;
|
|
43
|
+
/** Relay host this exposure is registered on. */
|
|
44
|
+
relayHost: string;
|
|
45
|
+
createdAt: number;
|
|
46
|
+
/** Mint a fresh single-use claim link and register it with the relay. The
|
|
47
|
+
* visitor opening it gets an 8h session cookie for this exposure. */
|
|
48
|
+
mintClaim(): string;
|
|
49
|
+
/** Stop sharing (closes the relay socket; the URL then answers 502). */
|
|
50
|
+
stop(): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Serializable view of an active exposure (for the console's ports manager). */
|
|
54
|
+
export interface ExposureInfo {
|
|
55
|
+
id: string;
|
|
56
|
+
port: number;
|
|
57
|
+
url: string;
|
|
58
|
+
createdAt: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
27
61
|
function exposuresPath(): string {
|
|
28
62
|
const home = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
29
63
|
mkdirSync(home, { recursive: true });
|
|
30
64
|
return path.join(home, "exposures.json");
|
|
31
65
|
}
|
|
32
66
|
|
|
33
|
-
/** Stable id+key per (relay, port): re-
|
|
67
|
+
/** Stable id+key per (relay, port): re-exposing a port keeps its URL. */
|
|
34
68
|
function loadOrCreateExposure(relayHost: string, port: number): ExposureRecord {
|
|
35
69
|
const file = exposuresPath();
|
|
36
70
|
let all: Record<string, ExposureRecord> = {};
|
|
@@ -79,6 +113,160 @@ function wsTransport(ws: WebSocket): TunnelTransport {
|
|
|
79
113
|
};
|
|
80
114
|
}
|
|
81
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Start (or fail) one exposure. Resolves once the relay has accepted the daemon
|
|
118
|
+
* and the tunnel is live; rejects if the relay refuses this exposure (bad key).
|
|
119
|
+
* Reconnects with backoff for the life of the handle.
|
|
120
|
+
*/
|
|
121
|
+
export function startExposure(opts: {
|
|
122
|
+
port: number;
|
|
123
|
+
relay?: string;
|
|
124
|
+
/** Log lifecycle transitions (CLI wants this; the manager stays quiet). */
|
|
125
|
+
log?: (msg: string) => void;
|
|
126
|
+
}): Promise<ExposureHandle> {
|
|
127
|
+
const relay = opts.relay ?? DEFAULT_RELAY;
|
|
128
|
+
const port = opts.port;
|
|
129
|
+
const log = opts.log ?? (() => {});
|
|
130
|
+
const relayUrl = new URL(relay);
|
|
131
|
+
const rec = loadOrCreateExposure(relayUrl.host, port);
|
|
132
|
+
const wsProto = relayUrl.protocol === "http:" ? "ws:" : "wss:";
|
|
133
|
+
const tunnelUrl = `${wsProto}//${relayUrl.host}/_ay/tunnel/${rec.id}`;
|
|
134
|
+
// Public hostname: <id>.<zone> on the real relay; the relay host itself (with
|
|
135
|
+
// a Host-header spoof) when pointing at a dev relay (wrangler dev).
|
|
136
|
+
const publicHost = relayUrl.host === "agent-yes.com" ? `${rec.id}.agent-yes.com` : relayUrl.host;
|
|
137
|
+
const publicUrl = `https://${publicHost}/`;
|
|
138
|
+
|
|
139
|
+
let stopped = false;
|
|
140
|
+
let sock: WebSocket | null = null;
|
|
141
|
+
let ready = false;
|
|
142
|
+
let backoff = RECONNECT_MIN_MS;
|
|
143
|
+
|
|
144
|
+
const handle: ExposureHandle = {
|
|
145
|
+
id: rec.id,
|
|
146
|
+
port,
|
|
147
|
+
publicHost,
|
|
148
|
+
url: publicUrl,
|
|
149
|
+
relayHost: relayUrl.host,
|
|
150
|
+
createdAt: Date.now(),
|
|
151
|
+
mintClaim() {
|
|
152
|
+
const token = randomBytes(18).toString("base64url");
|
|
153
|
+
const hash = createHash("sha256").update(token).digest("hex");
|
|
154
|
+
if (sock && sock.readyState === WebSocket.OPEN) {
|
|
155
|
+
sock.send(JSON.stringify({ t: "claim", claims: [hash] }));
|
|
156
|
+
}
|
|
157
|
+
return `https://${publicHost}/_ay/claim?t=${token}`;
|
|
158
|
+
},
|
|
159
|
+
stop() {
|
|
160
|
+
stopped = true;
|
|
161
|
+
try {
|
|
162
|
+
sock?.close();
|
|
163
|
+
} catch {
|
|
164
|
+
/* ignore */
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
return new Promise<ExposureHandle>((resolve, reject) => {
|
|
170
|
+
const connect = () => {
|
|
171
|
+
if (stopped) return;
|
|
172
|
+
sock = new WebSocket(tunnelUrl);
|
|
173
|
+
sock.binaryType = "arraybuffer";
|
|
174
|
+
const ws = sock;
|
|
175
|
+
let ping: ReturnType<typeof setInterval> | null = null;
|
|
176
|
+
|
|
177
|
+
ws.addEventListener("open", () => {
|
|
178
|
+
ws.send(JSON.stringify({ t: "hello", key: rec.key, port, v: 1 }));
|
|
179
|
+
});
|
|
180
|
+
ws.addEventListener("message", (ev) => {
|
|
181
|
+
if (typeof ev.data !== "string") return; // binary frames belong to the TunnelHost
|
|
182
|
+
let msg: { t?: string };
|
|
183
|
+
try {
|
|
184
|
+
msg = JSON.parse(ev.data);
|
|
185
|
+
} catch {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (msg.t === "ready") {
|
|
189
|
+
backoff = RECONNECT_MIN_MS;
|
|
190
|
+
new TunnelHost(wsTransport(ws), { port });
|
|
191
|
+
ping = setInterval(() => {
|
|
192
|
+
if (ws.readyState === WebSocket.OPEN) ws.send("ping");
|
|
193
|
+
}, PING_MS);
|
|
194
|
+
if (!ready) {
|
|
195
|
+
ready = true;
|
|
196
|
+
log(`sharing 127.0.0.1:${port} at ${publicUrl}`);
|
|
197
|
+
resolve(handle);
|
|
198
|
+
} else {
|
|
199
|
+
log(`reconnected`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
ws.addEventListener("close", (ev) => {
|
|
204
|
+
if (ping) clearInterval(ping);
|
|
205
|
+
if (stopped) return;
|
|
206
|
+
if (ev.code === 1008) {
|
|
207
|
+
const err = new Error(`relay refused exposure (${ev.reason || "forbidden"})`);
|
|
208
|
+
if (!ready) return reject(err);
|
|
209
|
+
log(err.message);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
log(`connection lost, retrying in ${Math.round(backoff / 1000)}s…`);
|
|
213
|
+
setTimeout(connect, backoff);
|
|
214
|
+
backoff = Math.min(backoff * 2, RECONNECT_MAX_MS);
|
|
215
|
+
});
|
|
216
|
+
ws.addEventListener("error", () => {
|
|
217
|
+
/* close fires right after; retry there */
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
connect();
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ---- in-process manager (driven by `ay serve` POST /api/expose) ----
|
|
225
|
+
|
|
226
|
+
const active = new Map<number, ExposureHandle>();
|
|
227
|
+
/** In-flight starts, so concurrent POSTs for the same port share one dial. */
|
|
228
|
+
const starting = new Map<number, Promise<ExposureHandle>>();
|
|
229
|
+
|
|
230
|
+
/** Start an exposure for `port` (or reuse a running one). Idempotent per port. */
|
|
231
|
+
export async function ensureExposure(port: number, relay?: string): Promise<ExposureHandle> {
|
|
232
|
+
const existing = active.get(port);
|
|
233
|
+
if (existing) return existing;
|
|
234
|
+
const inflight = starting.get(port);
|
|
235
|
+
if (inflight) return inflight;
|
|
236
|
+
const p = startExposure({ port, relay })
|
|
237
|
+
.then((h) => {
|
|
238
|
+
active.set(port, h);
|
|
239
|
+
starting.delete(port);
|
|
240
|
+
return h;
|
|
241
|
+
})
|
|
242
|
+
.catch((e) => {
|
|
243
|
+
starting.delete(port);
|
|
244
|
+
throw e;
|
|
245
|
+
});
|
|
246
|
+
starting.set(port, p);
|
|
247
|
+
return p;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function listExposures(): ExposureInfo[] {
|
|
251
|
+
return [...active.values()]
|
|
252
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
253
|
+
.map((h) => ({ id: h.id, port: h.port, url: h.url, createdAt: h.createdAt }));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function stopExposure(port: number): boolean {
|
|
257
|
+
const h = active.get(port);
|
|
258
|
+
if (!h) return false;
|
|
259
|
+
h.stop();
|
|
260
|
+
active.delete(port);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function stopAllExposures(): void {
|
|
265
|
+
for (const port of [...active.keys()]) stopExposure(port);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ---- CLI ----
|
|
269
|
+
|
|
82
270
|
export async function cmdExpose(args: string[]): Promise<number> {
|
|
83
271
|
let relay = DEFAULT_RELAY;
|
|
84
272
|
let port = 0;
|
|
@@ -102,83 +290,21 @@ export async function cmdExpose(args: string[]): Promise<number> {
|
|
|
102
290
|
return 1;
|
|
103
291
|
}
|
|
104
292
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
let stopped = false;
|
|
118
|
-
let ws: WebSocket | null = null;
|
|
119
|
-
let backoff = RECONNECT_MIN_MS;
|
|
120
|
-
let announced = false;
|
|
121
|
-
|
|
122
|
-
const connect = () => {
|
|
123
|
-
if (stopped) return;
|
|
124
|
-
ws = new WebSocket(tunnelUrl);
|
|
125
|
-
ws.binaryType = "arraybuffer";
|
|
126
|
-
const sock = ws;
|
|
127
|
-
let ping: ReturnType<typeof setInterval> | null = null;
|
|
128
|
-
|
|
129
|
-
sock.addEventListener("open", () => {
|
|
130
|
-
sock.send(JSON.stringify({ t: "hello", key: rec.key, port, claims: [claimHash], v: 1 }));
|
|
131
|
-
});
|
|
132
|
-
sock.addEventListener("message", (ev) => {
|
|
133
|
-
if (typeof ev.data !== "string") return; // binary frames belong to the TunnelHost
|
|
134
|
-
let msg: { t?: string };
|
|
135
|
-
try {
|
|
136
|
-
msg = JSON.parse(ev.data);
|
|
137
|
-
} catch {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (msg.t === "ready") {
|
|
141
|
-
backoff = RECONNECT_MIN_MS;
|
|
142
|
-
new TunnelHost(wsTransport(sock), { port });
|
|
143
|
-
ping = setInterval(() => {
|
|
144
|
-
if (sock.readyState === WebSocket.OPEN) sock.send("ping");
|
|
145
|
-
}, PING_MS);
|
|
146
|
-
if (!announced) {
|
|
147
|
-
announced = true;
|
|
148
|
-
console.log(`[ay expose] sharing 127.0.0.1:${port}`);
|
|
149
|
-
console.log(` url: https://${publicHost}/`);
|
|
150
|
-
console.log(` claim: https://${publicHost}/_ay/claim?t=${claimToken}`);
|
|
151
|
-
console.log(` (one-time link — opens access for 8h in that browser)`);
|
|
152
|
-
} else {
|
|
153
|
-
console.log(`[ay expose] reconnected`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
sock.addEventListener("close", (ev) => {
|
|
158
|
-
if (ping) clearInterval(ping);
|
|
159
|
-
if (stopped) return;
|
|
160
|
-
if (ev.code === 1008) {
|
|
161
|
-
console.error(`[ay expose] relay refused this exposure (${ev.reason || "forbidden"}) — giving up`);
|
|
162
|
-
process.exit(1);
|
|
163
|
-
}
|
|
164
|
-
console.log(`[ay expose] connection lost, retrying in ${Math.round(backoff / 1000)}s…`);
|
|
165
|
-
setTimeout(connect, backoff);
|
|
166
|
-
backoff = Math.min(backoff * 2, RECONNECT_MAX_MS);
|
|
167
|
-
});
|
|
168
|
-
sock.addEventListener("error", () => {
|
|
169
|
-
/* close fires right after; retry there */
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
connect();
|
|
293
|
+
let handle: ExposureHandle;
|
|
294
|
+
try {
|
|
295
|
+
handle = await startExposure({ port, relay, log: (m) => console.log(`[ay expose] ${m}`) });
|
|
296
|
+
} catch (e) {
|
|
297
|
+
console.error(`[ay expose] ${(e as Error).message} — giving up`);
|
|
298
|
+
return 1;
|
|
299
|
+
}
|
|
300
|
+
const claimUrl = handle.mintClaim();
|
|
301
|
+
console.log(` url: ${handle.url}`);
|
|
302
|
+
console.log(` claim: ${claimUrl}`);
|
|
303
|
+
console.log(` (one-time link — opens access for 8h in that browser)`);
|
|
173
304
|
|
|
174
305
|
const shutdown = () => {
|
|
175
|
-
stopped = true;
|
|
176
306
|
console.log("\n[ay expose] stopped — the URL now answers 502 until you expose again");
|
|
177
|
-
|
|
178
|
-
ws?.close();
|
|
179
|
-
} catch {
|
|
180
|
-
/* ignore */
|
|
181
|
-
}
|
|
307
|
+
handle.stop();
|
|
182
308
|
process.exit(0);
|
|
183
309
|
};
|
|
184
310
|
process.on("SIGINT", shutdown);
|
package/ts/serve.ts
CHANGED
|
@@ -2729,6 +2729,42 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
2729
2729
|
return new Response(ok ? "revoked" : "no such share", { status: ok ? 200 : 404 });
|
|
2730
2730
|
}
|
|
2731
2731
|
|
|
2732
|
+
// POST /api/expose body {port} → share 127.0.0.1:<port> through the edge
|
|
2733
|
+
// relay and return {url, claim} (a fresh single-use claim link each call).
|
|
2734
|
+
if (req.method === "POST" && p === "/api/expose") {
|
|
2735
|
+
let body: { port?: number; relay?: string };
|
|
2736
|
+
try {
|
|
2737
|
+
body = (await req.json()) as typeof body;
|
|
2738
|
+
} catch {
|
|
2739
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
2740
|
+
}
|
|
2741
|
+
const port = Number(body.port);
|
|
2742
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
2743
|
+
return new Response("valid port required", { status: 400 });
|
|
2744
|
+
}
|
|
2745
|
+
try {
|
|
2746
|
+
const { ensureExposure } = await import("./expose.ts");
|
|
2747
|
+
const h = await ensureExposure(port, body.relay);
|
|
2748
|
+
return Response.json({ id: h.id, port: h.port, url: h.url, claim: h.mintClaim(), createdAt: h.createdAt });
|
|
2749
|
+
} catch (e) {
|
|
2750
|
+
return new Response(`expose failed: ${(e as Error).message}`, { status: 502 });
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// GET /api/exposes → active port exposures (for the console's ports manager).
|
|
2755
|
+
if (req.method === "GET" && p === "/api/exposes") {
|
|
2756
|
+
const { listExposures } = await import("./expose.ts");
|
|
2757
|
+
return Response.json(listExposures());
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
// DELETE /api/expose/:port → revoke (the URL then answers 502).
|
|
2761
|
+
const unexposeM = /^\/api\/expose\/(\d+)$/.exec(p);
|
|
2762
|
+
if (req.method === "DELETE" && unexposeM) {
|
|
2763
|
+
const { stopExposure } = await import("./expose.ts");
|
|
2764
|
+
const ok = stopExposure(Number(unexposeM[1]));
|
|
2765
|
+
return new Response(ok ? "revoked" : "no such exposure", { status: ok ? 200 : 404 });
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2732
2768
|
return new Response("Not Found", { status: 404 });
|
|
2733
2769
|
};
|
|
2734
2770
|
|
|
@@ -2793,10 +2829,18 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
2793
2829
|
return serveUiFile("room-client.js", "text/javascript; charset=utf-8");
|
|
2794
2830
|
if (req.method === "GET" && p === "/console-logic.js")
|
|
2795
2831
|
return serveUiFile("console-logic.js", "text/javascript; charset=utf-8");
|
|
2832
|
+
// rtc.js is a STATIC import of the console module (import { RTCClient }) — a
|
|
2833
|
+
// 401 here fails the whole module link and the console never boots.
|
|
2834
|
+
if (req.method === "GET" && p === "/rtc.js")
|
|
2835
|
+
return serveUiFile("rtc.js", "text/javascript; charset=utf-8");
|
|
2796
2836
|
if (req.method === "GET" && p === "/e2e.js")
|
|
2797
2837
|
return serveUiFile("e2e.js", "text/javascript; charset=utf-8");
|
|
2798
2838
|
if (req.method === "GET" && p === "/qrcode.js")
|
|
2799
2839
|
return serveUiFile("qrcode.js", "text/javascript; charset=utf-8");
|
|
2840
|
+
if (req.method === "GET" && p === "/manifest.webmanifest")
|
|
2841
|
+
return serveUiFile("manifest.webmanifest", "application/manifest+json");
|
|
2842
|
+
if (req.method === "GET" && p === "/icon.svg")
|
|
2843
|
+
return serveUiFile("icon.svg", "image/svg+xml");
|
|
2800
2844
|
if (req.method === "GET" && p === "/favicon.ico") return new Response(null, { status: 204 });
|
|
2801
2845
|
return apiFetch(req);
|
|
2802
2846
|
};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import "./ts-CeGEhsC9.js";
|
|
2
|
-
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-DXwf5ip7.js";
|
|
4
|
-
import "./pidStore-BIvsBQ8X.js";
|
|
5
|
-
import "./globalPidIndex-CoNr7tS8.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-BB7Br0fS.js";
|
|
7
|
-
|
|
8
|
-
export { SUPPORTED_CLIS };
|