agent-yes 1.189.1 → 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.
Files changed (30) hide show
  1. package/dist/SUPPORTED_CLIS-P3I0D3Ka.js +8 -0
  2. package/dist/{SUPPORTED_CLIS-DcA00P5Z.js → SUPPORTED_CLIS-mUaQHk4M.js} +2 -2
  3. package/dist/{agentShare-D5qYAXeL.js → agentShare-Boa9P2dK.js} +3 -3
  4. package/dist/cli.js +8 -8
  5. package/dist/expose-B3AGhVhM.js +549 -0
  6. package/dist/{forkNested-C8q7E6mp.js → forkNested-C1kKsXi5.js} +1 -1
  7. package/dist/index.js +2 -2
  8. package/dist/{notifyDaemon-BZmlbBqN.js → notifyDaemon-hKT38cBs.js} +3 -3
  9. package/dist/{openBrowser-BO5RVYwL.js → openBrowser-DQVkxCyj.js} +1 -1
  10. package/dist/{remotes-DsTnQhyx.js → remotes-CgT91bMo.js} +2 -2
  11. package/dist/{remotes-BIHNxn1a.js → remotes-WVJBNajZ.js} +1 -1
  12. package/dist/{rustBinary-xhXsfVn-.js → rustBinary-Cu8q2m9s.js} +2 -2
  13. package/dist/{schedule-BUp_RUVP.js → schedule-Bb98iG9W.js} +5 -5
  14. package/dist/{serve-CM9NHaCH.js → serve-DVhbtjR4.js} +51 -15
  15. package/dist/{setup-BE9NyUE3.js → setup-Dj8Grg0K.js} +3 -3
  16. package/dist/{spawnGate-C5nBiUZQ.js → spawnGate-BejtWiMJ.js} +1 -1
  17. package/dist/{spawnGate-Clp9xBPX.js → spawnGate-CA5QD7D4.js} +2 -2
  18. package/dist/{subcommands-BYKJkWNP.js → subcommands-CNCgJ48t.js} +15 -10
  19. package/dist/{subcommands-qe4h_Cvx.js → subcommands-Uerol8Tz.js} +2 -2
  20. package/dist/{tray-DKwVRBwr.js → tray-PMOWFOfk.js} +1 -1
  21. package/dist/{ts-1qx-QhAe.js → ts-BQh1-ljE.js} +2 -2
  22. package/dist/{versionChecker-Do5ex5Wm.js → versionChecker-DhCi7ncH.js} +2 -2
  23. package/dist/{webrtcRemote-BLEbZnbY.js → webrtcRemote-CD5EQ3uX.js} +1 -1
  24. package/dist/{workspaceConfig-DKd6UvVm.js → workspaceConfig-_GtAZtsi.js} +1 -1
  25. package/lab/ui/index.html +239 -3
  26. package/package.json +3 -2
  27. package/ts/expose.ts +314 -0
  28. package/ts/serve.ts +44 -0
  29. package/ts/subcommands.ts +6 -0
  30. package/dist/SUPPORTED_CLIS-D8ocFVqe.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-1qx-QhAe.js";
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-DcA00P5Z.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-mUaQHk4M.js.map
@@ -1,10 +1,10 @@
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-BYKJkWNP.js";
4
+ import { j as resolveOne } from "./subcommands-CNCgJ48t.js";
5
5
  import "./e2e-BeKjLhmO.js";
6
6
  import "./webrtcLink-BG0Xc4-W.js";
7
- import "./remotes-DsTnQhyx.js";
7
+ import "./remotes-CgT91bMo.js";
8
8
  import { r as startShare } from "./share-29YbzpbU.js";
9
9
 
10
10
  //#region ts/agentShare.ts
@@ -228,4 +228,4 @@ function transformEvent(rawEvent, agentId, forwarded) {
228
228
 
229
229
  //#endregion
230
230
  export { createScopedShare, listShares, revokeAllShares, revokeShare };
231
- //# sourceMappingURL=agentShare-D5qYAXeL.js.map
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-Do5ex5Wm.js";
5
- import { n as getRustBinary } from "./rustBinary-xhXsfVn-.js";
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-qe4h_Cvx.js");
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);
@@ -299,16 +299,16 @@ await checkAndAutoUpdate();
299
299
  logger.info(versionString());
300
300
  const config = parseCliArgs(process.argv);
301
301
  if (config.tray) {
302
- const { startTray } = await import("./tray-DKwVRBwr.js");
302
+ const { startTray } = await import("./tray-PMOWFOfk.js");
303
303
  await startTray();
304
304
  await new Promise(() => {});
305
305
  }
306
306
  {
307
- const { ensureTray } = await import("./tray-DKwVRBwr.js");
307
+ const { ensureTray } = await import("./tray-PMOWFOfk.js");
308
308
  ensureTray();
309
309
  }
310
310
  if (!config.showVersion && !config.appendPrompt && !config.tray) {
311
- const { waitForSpawnCapacity } = await import("./spawnGate-C5nBiUZQ.js");
311
+ const { waitForSpawnCapacity } = await import("./spawnGate-BejtWiMJ.js");
312
312
  await waitForSpawnCapacity({
313
313
  onWait: (reason, waitedMs) => {
314
314
  if (config.verbose || waitedMs === 0) console.error(`[agent-yes] spawn gate: ${reason} — waiting…`);
@@ -327,14 +327,14 @@ if (config.useRust) {
327
327
  }
328
328
  }
329
329
  if (rustBinary) {
330
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-D8ocFVqe.js");
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}`);
334
334
  console.log(`[rust] Args: ${rustArgs.join(" ")}`);
335
335
  }
336
336
  const attach = config.attach || process.env.AGENT_YES_ATTACH === "1";
337
- const { shouldForkNested, buildSpawnTutorial, waitForFifo } = await import("./forkNested-C8q7E6mp.js");
337
+ const { shouldForkNested, buildSpawnTutorial, waitForFifo } = await import("./forkNested-C1kKsXi5.js");
338
338
  if (shouldForkNested({
339
339
  isTTY: Boolean(process.stdout.isTTY),
340
340
  ayPid: process.env.AGENT_YES_PID,
@@ -0,0 +1,549 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { homedir } from "node:os";
3
+ import path from "node:path";
4
+ import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
5
+
6
+ //#region node_modules/codehost/src/tunnel/protocol.ts
7
+ let Op = /* @__PURE__ */ function(Op) {
8
+ Op[Op["HttpReq"] = 1] = "HttpReq";
9
+ Op[Op["HttpReqBody"] = 2] = "HttpReqBody";
10
+ Op[Op["HttpReqEnd"] = 3] = "HttpReqEnd";
11
+ Op[Op["HttpResHead"] = 4] = "HttpResHead";
12
+ Op[Op["HttpResBody"] = 5] = "HttpResBody";
13
+ Op[Op["HttpResEnd"] = 6] = "HttpResEnd";
14
+ Op[Op["WsOpen"] = 7] = "WsOpen";
15
+ Op[Op["WsText"] = 9] = "WsText";
16
+ Op[Op["WsBin"] = 10] = "WsBin";
17
+ Op[Op["WsClose"] = 11] = "WsClose";
18
+ Op[Op["WsOpenAck"] = 8] = "WsOpenAck";
19
+ Op[Op["Error"] = 12] = "Error";
20
+ Op[Op["WsCont"] = 13] = "WsCont";
21
+ return Op;
22
+ }({});
23
+ const FRAME_HEADER = 5;
24
+ const MAX_FRAME = 64 * 1024;
25
+ /** Max payload bytes per frame; larger bodies/messages are split across frames. */
26
+ const MAX_CHUNK = MAX_FRAME - FRAME_HEADER;
27
+ const enc = new TextEncoder();
28
+ const dec = new TextDecoder();
29
+ function encodeFrame(op, streamId, payload) {
30
+ const len = payload?.byteLength ?? 0;
31
+ const buf = new Uint8Array(5 + len);
32
+ buf[0] = op;
33
+ new DataView(buf.buffer).setUint32(1, streamId >>> 0, false);
34
+ if (payload && len) buf.set(payload, 5);
35
+ return buf;
36
+ }
37
+ function encodeJson(op, streamId, obj) {
38
+ return encodeFrame(op, streamId, enc.encode(JSON.stringify(obj)));
39
+ }
40
+ function decodeFrame(data) {
41
+ const u8 = data instanceof Uint8Array ? data : new Uint8Array(data);
42
+ return {
43
+ op: u8[0],
44
+ streamId: new DataView(u8.buffer, u8.byteOffset, u8.byteLength).getUint32(1, false),
45
+ payload: u8.subarray(5)
46
+ };
47
+ }
48
+ function payloadJson(payload) {
49
+ return JSON.parse(dec.decode(payload));
50
+ }
51
+ /** Split a body into MAX_CHUNK-sized slices (copies, safe to transfer). */
52
+ function* chunk(body) {
53
+ for (let off = 0; off < body.byteLength; off += MAX_CHUNK) yield body.slice(off, Math.min(off + MAX_CHUNK, body.byteLength));
54
+ }
55
+ function concatBytes(parts) {
56
+ if (parts.length === 1) return parts[0];
57
+ const total = parts.reduce((n, p) => n + p.byteLength, 0);
58
+ const out = new Uint8Array(total);
59
+ let off = 0;
60
+ for (const p of parts) {
61
+ out.set(p, off);
62
+ off += p.byteLength;
63
+ }
64
+ return out;
65
+ }
66
+ /**
67
+ * Frames for one WebSocket message. Messages that fit in a single frame emit
68
+ * just a WsText/WsBin frame (back-compatible). Larger ones are split into
69
+ * WsCont frames carrying the leading bytes, terminated by the WsText/WsBin
70
+ * frame with the final bytes; the receiver concatenates them in order.
71
+ */
72
+ function* wsMessageFrames(terminal, streamId, payload) {
73
+ let off = 0;
74
+ while (payload.byteLength - off > MAX_CHUNK) {
75
+ yield encodeFrame(Op.WsCont, streamId, payload.subarray(off, off + MAX_CHUNK));
76
+ off += MAX_CHUNK;
77
+ }
78
+ yield encodeFrame(terminal, streamId, payload.subarray(off));
79
+ }
80
+ /**
81
+ * Reassembles WsCont + terminal frames back into whole WebSocket messages,
82
+ * keyed by streamId. Feed it every WsCont/WsText/WsBin payload; it returns the
83
+ * complete message bytes on a terminal frame, or null while buffering.
84
+ */
85
+ var WsReassembler = class {
86
+ pending = /* @__PURE__ */ new Map();
87
+ cont(streamId, payload) {
88
+ const buf = this.pending.get(streamId);
89
+ if (buf) buf.push(payload.slice());
90
+ else this.pending.set(streamId, [payload.slice()]);
91
+ }
92
+ finish(streamId, payload) {
93
+ const buf = this.pending.get(streamId);
94
+ if (!buf) return payload;
95
+ this.pending.delete(streamId);
96
+ buf.push(payload);
97
+ return concatBytes(buf);
98
+ }
99
+ drop(streamId) {
100
+ this.pending.delete(streamId);
101
+ }
102
+ };
103
+
104
+ //#endregion
105
+ //#region node_modules/codehost/src/tunnel/host.ts
106
+ const textDecoder = new TextDecoder();
107
+ const HIGH_WATER = 4 * 1024 * 1024;
108
+ const LOW_WATER = 1 * 1024 * 1024;
109
+ const HOP_BY_HOP = new Set([
110
+ "connection",
111
+ "keep-alive",
112
+ "proxy-authenticate",
113
+ "proxy-authorization",
114
+ "te",
115
+ "trailer",
116
+ "transfer-encoding",
117
+ "upgrade",
118
+ "content-length",
119
+ "host"
120
+ ]);
121
+ /**
122
+ * Server half of the tunnel: bridges one transport to a local HTTP/WS server
123
+ * on 127.0.0.1. Multiplexes concurrent HTTP requests and WebSocket
124
+ * connections by streamId.
125
+ */
126
+ var TunnelHost = class {
127
+ httpStreams = /* @__PURE__ */ new Map();
128
+ wsConns = /* @__PURE__ */ new Map();
129
+ wsRx = new WsReassembler();
130
+ origin;
131
+ wsOrigin;
132
+ port;
133
+ stripPrefix;
134
+ onLocal;
135
+ constructor(transport, opts) {
136
+ this.transport = transport;
137
+ this.port = opts.port;
138
+ this.stripPrefix = opts.stripPrefix;
139
+ this.onLocal = opts.onLocal;
140
+ this.origin = `http://127.0.0.1:${opts.port}`;
141
+ this.wsOrigin = `ws://127.0.0.1:${opts.port}`;
142
+ transport.setBufferedAmountLow?.(LOW_WATER, () => this.drainWaiter?.());
143
+ transport.onFrame((data) => void this.onFrame(data));
144
+ transport.onClose(() => this.closeAll());
145
+ }
146
+ /** Map a tunneled path to the local server's path, stripping the base prefix. */
147
+ localPath(path) {
148
+ if (this.stripPrefix && path.startsWith(this.stripPrefix)) {
149
+ const rest = path.slice(this.stripPrefix.length);
150
+ return rest.startsWith("/") ? rest : `/${rest}`;
151
+ }
152
+ return path;
153
+ }
154
+ async onFrame(data) {
155
+ const { op, streamId, payload } = decodeFrame(data);
156
+ switch (op) {
157
+ case Op.HttpReq:
158
+ this.httpStreams.set(streamId, {
159
+ head: payloadJson(payload),
160
+ body: []
161
+ });
162
+ break;
163
+ case Op.HttpReqBody:
164
+ this.httpStreams.get(streamId)?.body.push(payload.slice());
165
+ break;
166
+ case Op.HttpReqEnd:
167
+ await this.doHttp(streamId);
168
+ break;
169
+ case Op.WsOpen:
170
+ this.openWs(streamId, payloadJson(payload));
171
+ break;
172
+ case Op.WsCont:
173
+ this.wsRx.cont(streamId, payload);
174
+ break;
175
+ case Op.WsText:
176
+ this.wsConns.get(streamId)?.send(textDecoder.decode(this.wsRx.finish(streamId, payload)));
177
+ break;
178
+ case Op.WsBin:
179
+ this.wsConns.get(streamId)?.send(this.wsRx.finish(streamId, payload));
180
+ break;
181
+ case Op.WsClose:
182
+ this.wsRx.drop(streamId);
183
+ this.wsConns.get(streamId)?.close();
184
+ this.wsConns.delete(streamId);
185
+ break;
186
+ }
187
+ }
188
+ async doHttp(streamId) {
189
+ const stream = this.httpStreams.get(streamId);
190
+ if (!stream) return;
191
+ this.httpStreams.delete(streamId);
192
+ const { method, path, headers } = stream.head;
193
+ const reqHeaders = new Headers();
194
+ let forwardedHost = "";
195
+ for (const [k, v] of Object.entries(headers)) {
196
+ const lk = k.toLowerCase();
197
+ if (lk === "x-forwarded-host") {
198
+ forwardedHost = v;
199
+ continue;
200
+ }
201
+ if (!HOP_BY_HOP.has(lk)) reqHeaders.set(k, v);
202
+ }
203
+ reqHeaders.set("host", forwardedHost || `127.0.0.1:${this.port}`);
204
+ const body = method !== "GET" && method !== "HEAD" && stream.body.length > 0 ? concatBytes(stream.body) : void 0;
205
+ const wantsGzip = reqHeaders.get("x-codehost-accept-gzip") === "1";
206
+ reqHeaders.delete("x-codehost-accept-gzip");
207
+ if (wantsGzip) reqHeaders.set("accept-encoding", "gzip");
208
+ try {
209
+ const local = this.onLocal?.({
210
+ method,
211
+ path,
212
+ headers: reqHeaders,
213
+ body
214
+ });
215
+ const res = local ? await local : await fetch(this.origin + this.localPath(path), {
216
+ method,
217
+ headers: reqHeaders,
218
+ body,
219
+ redirect: "manual",
220
+ ...wantsGzip ? { decompress: false } : {}
221
+ });
222
+ const resHeaders = {};
223
+ res.headers.forEach((v, k) => {
224
+ if (!HOP_BY_HOP.has(k.toLowerCase())) resHeaders[k] = v;
225
+ });
226
+ await this.send(encodeJson(Op.HttpResHead, streamId, {
227
+ status: res.status,
228
+ statusText: res.statusText,
229
+ headers: resHeaders
230
+ }));
231
+ if (res.body) {
232
+ const reader = res.body.getReader();
233
+ for (;;) {
234
+ const { done, value } = await reader.read();
235
+ if (done) break;
236
+ for (const part of chunk(value)) await this.send(encodeFrame(Op.HttpResBody, streamId, part));
237
+ }
238
+ }
239
+ await this.send(encodeFrame(Op.HttpResEnd, streamId));
240
+ } catch (err) {
241
+ await this.send(encodeJson(Op.Error, streamId, { message: `proxy error: ${String(err)}` }));
242
+ }
243
+ }
244
+ openWs(streamId, info) {
245
+ let ws;
246
+ try {
247
+ ws = new WebSocket(this.wsOrigin + this.localPath(info.path), info.protocols);
248
+ } catch (err) {
249
+ this.send(encodeJson(Op.WsOpenAck, streamId, {
250
+ ok: false,
251
+ error: String(err)
252
+ }));
253
+ return;
254
+ }
255
+ ws.binaryType = "arraybuffer";
256
+ this.wsConns.set(streamId, ws);
257
+ ws.onopen = () => {
258
+ this.send(encodeJson(Op.WsOpenAck, streamId, {
259
+ ok: true,
260
+ protocol: ws.protocol
261
+ }));
262
+ };
263
+ ws.onmessage = (ev) => {
264
+ const [terminal, u8] = typeof ev.data === "string" ? [Op.WsText, new TextEncoder().encode(ev.data)] : [Op.WsBin, ev.data instanceof ArrayBuffer ? new Uint8Array(ev.data) : new Uint8Array(ev.data)];
265
+ for (const frame of wsMessageFrames(terminal, streamId, u8)) this.send(frame);
266
+ };
267
+ ws.onclose = (ev) => {
268
+ this.send(encodeJson(Op.WsClose, streamId, {
269
+ code: ev.code,
270
+ reason: ev.reason
271
+ }));
272
+ this.wsConns.delete(streamId);
273
+ };
274
+ ws.onerror = () => {
275
+ this.send(encodeJson(Op.WsClose, streamId, {
276
+ code: 1006,
277
+ reason: "error"
278
+ }));
279
+ this.wsConns.delete(streamId);
280
+ };
281
+ }
282
+ sendTail = Promise.resolve();
283
+ send(frame) {
284
+ const p = this.sendTail.then(async () => {
285
+ await this.waitForDrain();
286
+ if (!this.transport.isOpen()) return;
287
+ this.transport.send(frame);
288
+ });
289
+ this.sendTail = p.catch(() => {});
290
+ return p;
291
+ }
292
+ drainWaiter = null;
293
+ waitForDrain() {
294
+ if (!this.transport.isOpen() || this.transport.bufferedAmount() < HIGH_WATER) return Promise.resolve();
295
+ return new Promise((resolve) => {
296
+ let settled = false;
297
+ const finish = () => {
298
+ if (settled) return;
299
+ settled = true;
300
+ clearInterval(timer);
301
+ this.drainWaiter = null;
302
+ resolve();
303
+ };
304
+ this.drainWaiter = finish;
305
+ const timer = setInterval(() => {
306
+ if (!this.transport.isOpen() || this.transport.bufferedAmount() < HIGH_WATER) finish();
307
+ }, 100);
308
+ });
309
+ }
310
+ closeAll() {
311
+ for (const ws of this.wsConns.values()) try {
312
+ ws.close();
313
+ } catch {}
314
+ this.wsConns.clear();
315
+ this.httpStreams.clear();
316
+ }
317
+ };
318
+
319
+ //#endregion
320
+ //#region ts/expose.ts
321
+ const DEFAULT_RELAY = "https://agent-yes.com";
322
+ const PING_MS = 25e3;
323
+ const RECONNECT_MIN_MS = 1e3;
324
+ const RECONNECT_MAX_MS = 3e4;
325
+ function exposuresPath() {
326
+ const home = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
327
+ mkdirSync(home, { recursive: true });
328
+ return path.join(home, "exposures.json");
329
+ }
330
+ /** Stable id+key per (relay, port): re-exposing a port keeps its URL. */
331
+ function loadOrCreateExposure(relayHost, port) {
332
+ const file = exposuresPath();
333
+ let all = {};
334
+ try {
335
+ all = JSON.parse(readFileSync(file, "utf8"));
336
+ } catch {}
337
+ const slot = `${relayHost}:${port}`;
338
+ const existing = all[slot];
339
+ if (existing?.id && existing.key) return existing;
340
+ const rec = {
341
+ id: "x" + randomBytes(8).toString("hex").slice(0, 15),
342
+ key: randomBytes(32).toString("hex")
343
+ };
344
+ all[slot] = rec;
345
+ const tmp = file + ".tmp";
346
+ writeFileSync(tmp, JSON.stringify(all, null, 2) + "\n");
347
+ chmodSync(tmp, 384);
348
+ renameSync(tmp, file);
349
+ return rec;
350
+ }
351
+ /** Bun's browser-style WebSocket as a TunnelTransport. No bufferedAmountLow
352
+ * event exists here — TunnelHost's 100ms safety poll covers drain resume. */
353
+ function wsTransport(ws) {
354
+ return {
355
+ send: (frame) => {
356
+ const copy = new Uint8Array(frame.byteLength);
357
+ copy.set(frame);
358
+ ws.send(copy);
359
+ },
360
+ isOpen: () => ws.readyState === WebSocket.OPEN,
361
+ bufferedAmount: () => ws.bufferedAmount,
362
+ onFrame: (cb) => {
363
+ ws.addEventListener("message", (ev) => {
364
+ if (typeof ev.data === "string") return;
365
+ const d = ev.data;
366
+ cb(d instanceof Uint8Array ? d : new Uint8Array(d));
367
+ });
368
+ },
369
+ onClose: (cb) => ws.addEventListener("close", () => cb())
370
+ };
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
+ }
505
+ async function cmdExpose(args) {
506
+ let relay = DEFAULT_RELAY;
507
+ let port = 0;
508
+ for (let i = 0; i < args.length; i++) {
509
+ const a = args[i];
510
+ if (a === "--relay") relay = args[++i] ?? relay;
511
+ else if (a.startsWith("--relay=")) relay = a.slice(8);
512
+ else if (/^\d+$/.test(a)) port = Number(a);
513
+ else if (a === "-h" || a === "--help") {
514
+ console.log(`Usage: ay expose <port> [--relay ${DEFAULT_RELAY}]\n\nShares http://127.0.0.1:<port> at https://<id>.agent-yes.com (private:\nvisitors need the printed one-time claim link, which sets an 8h cookie).\nThe URL is stable per port on this machine. Ctrl-C stops sharing.`);
515
+ return 0;
516
+ }
517
+ }
518
+ if (!port || port < 1 || port > 65535) {
519
+ console.error("usage: ay expose <port> [--relay https://…] (see ay expose --help)");
520
+ return 1;
521
+ }
522
+ let handle;
523
+ try {
524
+ handle = await startExposure({
525
+ port,
526
+ relay,
527
+ log: (m) => console.log(`[ay expose] ${m}`)
528
+ });
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)`);
537
+ const shutdown = () => {
538
+ console.log("\n[ay expose] stopped — the URL now answers 502 until you expose again");
539
+ handle.stop();
540
+ process.exit(0);
541
+ };
542
+ process.on("SIGINT", shutdown);
543
+ process.on("SIGTERM", shutdown);
544
+ return new Promise(() => {});
545
+ }
546
+
547
+ //#endregion
548
+ export { cmdExpose, ensureExposure, listExposures, stopExposure };
549
+ //# sourceMappingURL=expose-B3AGhVhM.js.map
@@ -56,4 +56,4 @@ async function waitForFifo(pid, timeoutMs = 2e3, aborted) {
56
56
 
57
57
  //#endregion
58
58
  export { buildSpawnTutorial, shouldForkNested, waitForFifo };
59
- //# sourceMappingURL=forkNested-C8q7E6mp.js.map
59
+ //# sourceMappingURL=forkNested-C1kKsXi5.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-1qx-QhAe.js";
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-Do5ex5Wm.js";
3
+ import "./versionChecker-DhCi7ncH.js";
4
4
  import "./pidStore-BIvsBQ8X.js";
5
5
  import "./globalPidIndex-CoNr7tS8.js";
6
6
 
@@ -1,10 +1,10 @@
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-BYKJkWNP.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-CNCgJ48t.js";
5
5
  import "./e2e-BeKjLhmO.js";
6
6
  import "./webrtcLink-BG0Xc4-W.js";
7
- import "./remotes-DsTnQhyx.js";
7
+ import "./remotes-CgT91bMo.js";
8
8
  import { mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
9
9
  import { randomUUID } from "node:crypto";
10
10
  import { execFile } from "node:child_process";
@@ -588,4 +588,4 @@ async function ensureDaemon() {
588
588
 
589
589
  //#endregion
590
590
  export { daemonStatus, ensureDaemon, requestDaemonStop, runDaemon };
591
- //# sourceMappingURL=notifyDaemon-BZmlbBqN.js.map
591
+ //# sourceMappingURL=notifyDaemon-hKT38cBs.js.map
@@ -68,4 +68,4 @@ async function offerOpenInBrowser(url, prompt = "Open in browser now? [Y/n] ") {
68
68
 
69
69
  //#endregion
70
70
  export { offerOpenInBrowser };
71
- //# sourceMappingURL=openBrowser-BO5RVYwL.js.map
71
+ //# sourceMappingURL=openBrowser-DQVkxCyj.js.map