@runalabs/rill-cli 0.1.13 → 0.1.14

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 (3) hide show
  1. package/README.md +13 -7
  2. package/dist/cli.js +790 -296
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5,23 +5,109 @@ var __export = (target, all) => {
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
+ // src/windows-credentials.ts
9
+ import { spawn } from "child_process";
10
+ import { createHash, randomUUID } from "crypto";
11
+ import { homedir } from "os";
12
+ import { join, win32 } from "path";
13
+ import { mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
14
+ var script = String.raw`$ErrorActionPreference='Stop'; try {
15
+ Add-Type -AssemblyName System.Security
16
+ $inputData=[Console]::In.ReadToEnd() | ConvertFrom-Json
17
+ $bytes=[Convert]::FromBase64String($inputData.data)
18
+ $entropy=[Convert]::FromBase64String($inputData.entropy)
19
+ if ($inputData.operation -eq 'protect') {
20
+ $result=[System.Security.Cryptography.ProtectedData]::Protect($bytes,$entropy,[System.Security.Cryptography.DataProtectionScope]::CurrentUser)
21
+ } elseif ($inputData.operation -eq 'unprotect') {
22
+ $result=[System.Security.Cryptography.ProtectedData]::Unprotect($bytes,$entropy,[System.Security.Cryptography.DataProtectionScope]::CurrentUser)
23
+ } else { throw 'Invalid operation' }
24
+ [Console]::Out.Write([Convert]::ToBase64String($result))
25
+ } catch { [Console]::Error.Write('Windows credential protection failed.'); exit 1 }`;
26
+ var transformWindowsSecret = async (operation, data, entropy) => {
27
+ if (process.platform !== "win32") throw new Error("Windows credential protection requires Windows.");
28
+ return new Promise((resolve, reject) => {
29
+ const executable = win32.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
30
+ const child = spawn(executable, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
31
+ let output = "";
32
+ let failed = false;
33
+ const fail2 = () => {
34
+ if (failed) return;
35
+ failed = true;
36
+ child.kill();
37
+ reject(new Error("Cannot access Windows protected credentials. Your saved identity has not been replaced."));
38
+ };
39
+ const timer = setTimeout(fail2, 15e3);
40
+ child.stdout.on("data", (chunk) => {
41
+ output += chunk.toString("ascii");
42
+ if (output.length > 256 * 1024) fail2();
43
+ });
44
+ child.stderr.resume();
45
+ child.once("error", fail2);
46
+ child.stdin.once("error", fail2);
47
+ child.once("close", (code) => {
48
+ clearTimeout(timer);
49
+ if (failed) return;
50
+ if (code !== 0 || !output || !/^[A-Za-z0-9+/]+={0,2}$/.test(output)) {
51
+ fail2();
52
+ return;
53
+ }
54
+ resolve(Buffer.from(output, "base64"));
55
+ });
56
+ child.stdin.end(JSON.stringify({ operation, data: data.toString("base64"), entropy: entropy.toString("base64") }));
57
+ });
58
+ };
59
+ var credentialRoot = () => join(process.env.RILL_HOME ?? join(homedir(), ".rill"), "windows-credentials");
60
+ var key = (apiUrl) => createHash("sha256").update(apiUrl).digest();
61
+ async function saveWindowsCredential(apiUrl, value, root = credentialRoot(), transform2 = transformWindowsSecret) {
62
+ if (!value || Buffer.byteLength(value) > 64 * 1024) throw new Error("The Windows credential is empty or too large.");
63
+ const entropy = key(apiUrl), protectedBytes = await transform2("protect", Buffer.from(value, "utf8"), entropy);
64
+ if (!protectedBytes.length || protectedBytes.length > 192 * 1024) throw new Error("Windows credential protection returned invalid data.");
65
+ await mkdir(root, { recursive: true, mode: 448 });
66
+ const path = join(root, entropy.toString("hex") + ".dpapi"), temporary = path + "." + randomUUID() + ".tmp";
67
+ try {
68
+ await writeFile(temporary, JSON.stringify({ version: 1, ciphertext: protectedBytes.toString("base64") }), { mode: 384, flag: "wx" });
69
+ await rename(temporary, path);
70
+ } finally {
71
+ await rm(temporary, { force: true });
72
+ }
73
+ }
74
+ async function loadWindowsCredential(apiUrl, root = credentialRoot(), transform2 = transformWindowsSecret) {
75
+ const entropy = key(apiUrl), path = join(root, entropy.toString("hex") + ".dpapi");
76
+ try {
77
+ const info = await stat(path);
78
+ if (!info.isFile() || info.size > 256 * 1024) throw new Error("Invalid credential file.");
79
+ const stored = JSON.parse(await readFile(path, "utf8"));
80
+ if (stored.version !== 1 || typeof stored.ciphertext !== "string" || !stored.ciphertext || !/^[A-Za-z0-9+/]+={0,2}$/.test(stored.ciphertext)) throw new Error("Invalid protected credential.");
81
+ const plain = await transform2("unprotect", Buffer.from(stored.ciphertext, "base64"), entropy);
82
+ if (!plain.length || plain.length > 64 * 1024) throw new Error("Invalid credential size.");
83
+ return plain.toString("utf8");
84
+ } catch (error51) {
85
+ if (error51.code === "ENOENT") return null;
86
+ throw new Error("Cannot read Windows protected credentials. Your saved identity has not been replaced.");
87
+ }
88
+ }
89
+
8
90
  // src/agent-session.ts
9
- import { hostname, homedir } from "os";
10
- import { mkdir, rm, stat } from "fs/promises";
11
- import { join } from "path";
12
- import { createHash, randomBytes } from "crypto";
91
+ import { hostname, homedir as homedir2 } from "os";
92
+ import { mkdir as mkdir2, rm as rm2, stat as stat2 } from "fs/promises";
93
+ import { join as join2 } from "path";
94
+ import { createHash as createHash2, randomBytes } from "crypto";
13
95
 
14
96
  // src/recording-guidance.ts
15
- function cliCommand(apiUrl, ...args) {
16
- const quote = (value) => /^[a-zA-Z0-9_./:@=-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
97
+ function cliCommandForPlatform(platform, apiUrl, ...args) {
98
+ const quote = (value) => /^[a-zA-Z0-9_./:@=-]+$/.test(value) ? value : platform === "win32" ? `'${value.replaceAll("'", "''")}'` : `'${value.replaceAll("'", "'\\''")}'`;
17
99
  return ["rill", "--api-url", apiUrl, ...args].map(quote).join(" ");
18
100
  }
19
- var transportRecovery = "Use macOS or Linux for browser recording. On Windows, use RILL_TOKEN with rill upload <video-file> for video-only sharing; diagnostics are not imported. RILL_SOCKET does not enable Windows named-pipe or TCP support in this release.";
20
- function recorderTransportCheck(platform = process.platform) {
21
- return platform === "win32" ? { name: "recorder_transport", status: "fail", message: "The local recorder transport does not support Windows.", recovery: transportRecovery } : { name: "recorder_transport", status: "pass", message: "The local recorder uses a Unix-domain socket." };
101
+ function cliCommand(apiUrl, ...args) {
102
+ return cliCommandForPlatform(process.platform, apiUrl, ...args);
103
+ }
104
+ var transportRecovery = "Unset RILL_SOCKET on Windows so Rill can use its authenticated named pipe. Use RILL_HOME to choose a separate recorder identity directory.";
105
+ function recorderTransportCheck(platform = process.platform, socketOverride = process.env.RILL_SOCKET) {
106
+ if (platform === "win32" && socketOverride) return { name: "recorder_transport", status: "fail", message: "Windows requires the authenticated recorder pipe.", recovery: transportRecovery };
107
+ return { name: "recorder_transport", status: "pass", message: platform === "win32" ? "The local recorder uses an authenticated Windows named pipe." : "The local recorder uses a Unix-domain socket." };
22
108
  }
23
- function assertRecorderTransport(platform = process.platform) {
24
- const check2 = recorderTransportCheck(platform);
109
+ function assertRecorderTransport(platform = process.platform, socketOverride = process.env.RILL_SOCKET) {
110
+ const check2 = recorderTransportCheck(platform, socketOverride);
25
111
  if (check2.status === "fail") throw Object.assign(new Error(`unsupported_platform: ${check2.message}`), { code: "unsupported_platform", recovery: check2.recovery });
26
112
  }
27
113
  function recordingNextStep(apiUrl, recordingId, status, options = {}) {
@@ -48,6 +134,7 @@ var AgentAuthError = class extends Error {
48
134
  super(`${code}: ${message}`);
49
135
  this.code = code;
50
136
  }
137
+ code;
51
138
  };
52
139
  function parseAuthorization(value) {
53
140
  if (!value?.startsWith("{")) return null;
@@ -63,7 +150,7 @@ async function request(apiUrl, path, body, form = false) {
63
150
  redirect: "error",
64
151
  signal: AbortSignal.timeout(15e3),
65
152
  headers: { "Content-Type": form ? "application/x-www-form-urlencoded" : "application/json" },
66
- body: form ? new URLSearchParams(Object.entries(body).map(([key, value2]) => [key, String(value2)])) : JSON.stringify(body)
153
+ body: form ? new URLSearchParams(Object.entries(body).map(([key2, value2]) => [key2, String(value2)])) : JSON.stringify(body)
67
154
  });
68
155
  const value = await response.json();
69
156
  if (!response.ok) throw new AgentAuthError(value.error ?? "authentication_failed", value.error_description ?? "Could not authorize Rill.");
@@ -107,19 +194,19 @@ async function ensureAnonymousCredential(apiUrl) {
107
194
  return withAuthorizationLock(apiUrl, () => createAnonymousCredential(apiUrl));
108
195
  }
109
196
  async function withAuthorizationLock(apiUrl, action) {
110
- const root = join(homedir(), ".rill", "auth-locks");
111
- await mkdir(root, { recursive: true, mode: 448 });
112
- const lock = join(root, createHash("sha256").update(apiUrl).digest("hex"));
197
+ const root = join2(homedir2(), ".rill", "auth-locks");
198
+ await mkdir2(root, { recursive: true, mode: 448 });
199
+ const lock = join2(root, createHash2("sha256").update(apiUrl).digest("hex"));
113
200
  const deadline = Date.now() + 3e4;
114
201
  while (true) {
115
202
  try {
116
- await mkdir(lock, { mode: 448 });
203
+ await mkdir2(lock, { mode: 448 });
117
204
  break;
118
205
  } catch (error51) {
119
206
  if (error51.code !== "EEXIST") throw error51;
120
- const info = await stat(lock).catch(() => null);
207
+ const info = await stat2(lock).catch(() => null);
121
208
  if (info && Date.now() - info.mtimeMs > 12e4) {
122
- await rm(lock, { recursive: true, force: true });
209
+ await rm2(lock, { recursive: true, force: true });
123
210
  continue;
124
211
  }
125
212
  if (Date.now() > deadline) throw new Error("Another Rill process is setting up authorization. Retry shortly.");
@@ -129,7 +216,7 @@ async function withAuthorizationLock(apiUrl, action) {
129
216
  try {
130
217
  return await action();
131
218
  } finally {
132
- await rm(lock, { recursive: true, force: true });
219
+ await rm2(lock, { recursive: true, force: true });
133
220
  }
134
221
  }
135
222
  async function createAnonymousCredential(apiUrl) {
@@ -246,9 +333,10 @@ async function pollSavedConnection(apiUrl, authorizationId) {
246
333
  }
247
334
 
248
335
  // src/keychain.ts
249
- import { spawn } from "child_process";
336
+ import { spawn as spawn2 } from "child_process";
250
337
  var service = "rill";
251
338
  async function saveCredential(apiUrl, token) {
339
+ if (process.platform === "win32") return saveWindowsCredential(apiUrl, token);
252
340
  if (process.platform === "darwin") {
253
341
  await run("security", ["add-generic-password", "-U", "-a", apiUrl, "-s", service, "-w", token]);
254
342
  return;
@@ -263,6 +351,7 @@ function loadCredential(apiUrl) {
263
351
  return loadAgentCredential(apiUrl);
264
352
  }
265
353
  async function loadStoredCredential(apiUrl) {
354
+ if (process.platform === "win32") return loadWindowsCredential(apiUrl);
266
355
  try {
267
356
  if (process.platform === "darwin") return (await run("security", ["find-generic-password", "-a", apiUrl, "-s", service, "-w"])).trim() || null;
268
357
  if (process.platform === "linux") return (await run("secret-tool", ["lookup", "service", service, "api-url", apiUrl])).trim() || null;
@@ -276,7 +365,7 @@ async function loadStoredCredential(apiUrl) {
276
365
  }
277
366
  function run(command, args, input) {
278
367
  return new Promise((resolve, reject) => {
279
- const child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
368
+ const child = spawn2(command, args, { stdio: ["pipe", "pipe", "pipe"] });
280
369
  const stdout = [];
281
370
  const stderr = [];
282
371
  child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
@@ -290,7 +379,7 @@ function run(command, args, input) {
290
379
 
291
380
  // src/control-plane.ts
292
381
  import { createReadStream } from "fs";
293
- import { readFile, stat as stat2 } from "fs/promises";
382
+ import { readFile as readFile2, stat as stat3 } from "fs/promises";
294
383
  import { Upload } from "tus-js-client";
295
384
  var ControlPlaneError = class extends Error {
296
385
  constructor(code, message, recovery, details, httpStatus) {
@@ -301,6 +390,10 @@ var ControlPlaneError = class extends Error {
301
390
  this.httpStatus = httpStatus;
302
391
  this.name = "ControlPlaneError";
303
392
  }
393
+ code;
394
+ recovery;
395
+ details;
396
+ httpStatus;
304
397
  };
305
398
  var ControlPlaneHttpError = class extends Error {
306
399
  constructor(httpStatus) {
@@ -308,12 +401,15 @@ var ControlPlaneHttpError = class extends Error {
308
401
  this.httpStatus = httpStatus;
309
402
  this.name = "ControlPlaneHttpError";
310
403
  }
404
+ httpStatus;
311
405
  };
312
406
  var ControlPlaneClient = class {
313
407
  constructor(apiUrl, token) {
314
408
  this.apiUrl = apiUrl;
315
409
  this.token = token;
316
410
  }
411
+ apiUrl;
412
+ token;
317
413
  recordingStatusCommand(id) {
318
414
  return cliCommand(this.apiUrl, "record", "status", id);
319
415
  }
@@ -402,7 +498,7 @@ var ControlPlaneClient = class {
402
498
  throw new ControlPlaneError("processing_pending", "Upload complete; the recording is not playable yet.", `Run ${cliCommand(this.apiUrl, "record", "status", recordingId)} to continue checking.`, { recordingId, nextCommand: cliCommand(this.apiUrl, "record", "status", recordingId) });
403
499
  }
404
500
  async uploadVideo(uploadUrl, filePath) {
405
- const file2 = await stat2(filePath);
501
+ const file2 = await stat3(filePath);
406
502
  if (uploadUrl.startsWith(this.apiUrl)) {
407
503
  const response = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Length": String(file2.size) }, body: createReadStream(filePath), duplex: "half" });
408
504
  if (!response.ok) throw new Error(`Video upload failed (${response.status}).`);
@@ -424,7 +520,7 @@ var ControlPlaneClient = class {
424
520
  process.stderr.write("\nUpload complete; playback processing may continue.\n");
425
521
  }
426
522
  async uploadDiagnostics(recordingId, filePath, summary) {
427
- const body = await readFile(filePath);
523
+ const body = await readFile2(filePath);
428
524
  return this.request(`/api/recordings/${recordingId}/diagnostics`, { method: "PUT", headers: { "Content-Encoding": "gzip", "Content-Type": "application/x-ndjson", "Content-Length": String(body.length), "X-Rill-Summary": JSON.stringify(summary) }, body });
429
525
  }
430
526
  listRecordings() {
@@ -436,16 +532,33 @@ var ControlPlaneClient = class {
436
532
  pollDeviceAuthorization(deviceCode) {
437
533
  return this.request("/api/device/token", { method: "POST", body: JSON.stringify({ deviceCode }) }, false);
438
534
  }
439
- inspect(shareUrl, cursor) {
535
+ async inspect(shareUrl, cursor) {
440
536
  if (/^rec_[a-zA-Z0-9_-]+$/.test(shareUrl)) return this.request(`/api/recordings/${encodeURIComponent(shareUrl)}`);
441
537
  const url2 = new URL(shareUrl);
442
538
  const slug = url2.pathname.split("/").filter(Boolean).at(-1);
443
539
  if (!slug) throw new Error("The share URL does not contain a slug.");
444
- return fetch(`${url2.origin}/api/public/shares/${encodeURIComponent(slug)}/context${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`, { headers: { Accept: "application/json" } }).then(async (response) => {
540
+ return fetch(`${url2.origin}/api/public/shares/${encodeURIComponent(slug)}/context${cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""}`, { headers: await this.reviewHeaders(url2.origin) }).then(async (response) => {
445
541
  if (!response.ok) throw new Error(`Could not inspect the share link (${response.status}).`);
446
542
  return response.json();
447
543
  });
448
544
  }
545
+ async reviewHeaders(origin) {
546
+ const headers = new Headers({ Accept: "application/json" });
547
+ if (origin !== new URL(this.apiUrl).origin) return headers;
548
+ const token = typeof this.token === "function" ? await this.token() : this.token;
549
+ if (token) headers.set("Authorization", `Bearer ${token}`);
550
+ this.identifyAgent(headers);
551
+ const id = process.env.RILL_AGENT_ID;
552
+ if (id && /^[a-zA-Z0-9_-]{8,80}$/.test(id)) headers.set("x-rill-agent-id", id);
553
+ if (id && process.env.RILL_ANONYMOUS_ANALYTICS !== "0" && process.env.DO_NOT_TRACK !== "1") headers.set("x-rill-analytics", "1");
554
+ return headers;
555
+ }
556
+ identifyAgent(headers) {
557
+ if (process.env.DO_NOT_TRACK === "1" || process.env.RILL_ANONYMOUS_ANALYTICS === "0") headers.set("x-rill-analytics", "0");
558
+ const client = process.env.RILL_AGENT_CLIENT || (process.env.CODEX_THREAD_ID ? "Codex" : process.env.CLAUDECODE ? "Claude" : process.env.CURSOR_TRACE_ID ? "Cursor" : void 0);
559
+ if (client) headers.set("x-rill-agent-client", client.replace(/[\x00-\x1f\x7f]/g, "").slice(0, 80));
560
+ if (process.env.RILL_AGENT_NAME) headers.set("x-rill-agent-name", process.env.RILL_AGENT_NAME.replace(/[\x00-\x1f\x7f]/g, "").slice(0, 48));
561
+ }
449
562
  reviewPath(shareUrl) {
450
563
  const url2 = new URL(shareUrl);
451
564
  if (url2.origin !== new URL(this.apiUrl).origin) throw new Error("The share URL must match --api-url before sending agent credentials.");
@@ -461,6 +574,7 @@ var ControlPlaneClient = class {
461
574
  }
462
575
  async request(path, init = {}, authenticated = true) {
463
576
  const headers = new Headers(init.headers);
577
+ if (authenticated) this.identifyAgent(headers);
464
578
  if (init.body && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
465
579
  if (authenticated) {
466
580
  const token = typeof this.token === "function" ? await this.token() : this.token;
@@ -500,8 +614,8 @@ async function whoami(apiUrl) {
500
614
  }
501
615
 
502
616
  // src/remote-encoding.ts
503
- import { open, readFile as readFile2 } from "fs/promises";
504
- import { join as join2 } from "path";
617
+ import { open, readFile as readFile3 } from "fs/promises";
618
+ import { join as join3 } from "path";
505
619
 
506
620
  // ../../node_modules/zod/v4/classic/external.js
507
621
  var external_exports = {};
@@ -1225,9 +1339,9 @@ function floatSafeRemainder(val, step) {
1225
1339
  return ratio - roundedRatio;
1226
1340
  }
1227
1341
  var EVALUATING = /* @__PURE__ */ Symbol("evaluating");
1228
- function defineLazy(object2, key, getter) {
1342
+ function defineLazy(object2, key2, getter) {
1229
1343
  let value = void 0;
1230
- Object.defineProperty(object2, key, {
1344
+ Object.defineProperty(object2, key2, {
1231
1345
  get() {
1232
1346
  if (value === EVALUATING) {
1233
1347
  return void 0;
@@ -1239,7 +1353,7 @@ function defineLazy(object2, key, getter) {
1239
1353
  return value;
1240
1354
  },
1241
1355
  set(v) {
1242
- Object.defineProperty(object2, key, {
1356
+ Object.defineProperty(object2, key2, {
1243
1357
  value: v
1244
1358
  // configurable: true,
1245
1359
  });
@@ -1272,11 +1386,11 @@ function cloneDef(schema) {
1272
1386
  function getElementAtPath(obj, path) {
1273
1387
  if (!path)
1274
1388
  return obj;
1275
- return path.reduce((acc, key) => acc?.[key], obj);
1389
+ return path.reduce((acc, key2) => acc?.[key2], obj);
1276
1390
  }
1277
1391
  function promiseAllObject(promisesObj) {
1278
1392
  const keys = Object.keys(promisesObj);
1279
- const promises = keys.map((key) => promisesObj[key]);
1393
+ const promises = keys.map((key2) => promisesObj[key2]);
1280
1394
  return Promise.all(promises).then((results) => {
1281
1395
  const resolvedObj = {};
1282
1396
  for (let i = 0; i < keys.length; i++) {
@@ -1348,8 +1462,8 @@ function shallowClone(o) {
1348
1462
  }
1349
1463
  function numKeys(data) {
1350
1464
  let keyCount = 0;
1351
- for (const key in data) {
1352
- if (Object.prototype.hasOwnProperty.call(data, key)) {
1465
+ for (const key2 in data) {
1466
+ if (Object.prototype.hasOwnProperty.call(data, key2)) {
1353
1467
  keyCount++;
1354
1468
  }
1355
1469
  }
@@ -1499,13 +1613,13 @@ function pick(schema, mask) {
1499
1613
  const def = mergeDefs(schema._zod.def, {
1500
1614
  get shape() {
1501
1615
  const newShape = {};
1502
- for (const key in mask) {
1503
- if (!(key in currDef.shape)) {
1504
- throw new Error(`Unrecognized key: "${key}"`);
1616
+ for (const key2 in mask) {
1617
+ if (!(key2 in currDef.shape)) {
1618
+ throw new Error(`Unrecognized key: "${key2}"`);
1505
1619
  }
1506
- if (!mask[key])
1620
+ if (!mask[key2])
1507
1621
  continue;
1508
- newShape[key] = currDef.shape[key];
1622
+ newShape[key2] = currDef.shape[key2];
1509
1623
  }
1510
1624
  assignProp(this, "shape", newShape);
1511
1625
  return newShape;
@@ -1524,13 +1638,13 @@ function omit(schema, mask) {
1524
1638
  const def = mergeDefs(schema._zod.def, {
1525
1639
  get shape() {
1526
1640
  const newShape = { ...schema._zod.def.shape };
1527
- for (const key in mask) {
1528
- if (!(key in currDef.shape)) {
1529
- throw new Error(`Unrecognized key: "${key}"`);
1641
+ for (const key2 in mask) {
1642
+ if (!(key2 in currDef.shape)) {
1643
+ throw new Error(`Unrecognized key: "${key2}"`);
1530
1644
  }
1531
- if (!mask[key])
1645
+ if (!mask[key2])
1532
1646
  continue;
1533
- delete newShape[key];
1647
+ delete newShape[key2];
1534
1648
  }
1535
1649
  assignProp(this, "shape", newShape);
1536
1650
  return newShape;
@@ -1547,8 +1661,8 @@ function extend(schema, shape) {
1547
1661
  const hasChecks = checks && checks.length > 0;
1548
1662
  if (hasChecks) {
1549
1663
  const existingShape = schema._zod.def.shape;
1550
- for (const key in shape) {
1551
- if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) {
1664
+ for (const key2 in shape) {
1665
+ if (Object.getOwnPropertyDescriptor(existingShape, key2) !== void 0) {
1552
1666
  throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
1553
1667
  }
1554
1668
  }
@@ -1604,23 +1718,23 @@ function partial(Class2, schema, mask) {
1604
1718
  const oldShape = schema._zod.def.shape;
1605
1719
  const shape = { ...oldShape };
1606
1720
  if (mask) {
1607
- for (const key in mask) {
1608
- if (!(key in oldShape)) {
1609
- throw new Error(`Unrecognized key: "${key}"`);
1721
+ for (const key2 in mask) {
1722
+ if (!(key2 in oldShape)) {
1723
+ throw new Error(`Unrecognized key: "${key2}"`);
1610
1724
  }
1611
- if (!mask[key])
1725
+ if (!mask[key2])
1612
1726
  continue;
1613
- shape[key] = Class2 ? new Class2({
1727
+ shape[key2] = Class2 ? new Class2({
1614
1728
  type: "optional",
1615
- innerType: oldShape[key]
1616
- }) : oldShape[key];
1729
+ innerType: oldShape[key2]
1730
+ }) : oldShape[key2];
1617
1731
  }
1618
1732
  } else {
1619
- for (const key in oldShape) {
1620
- shape[key] = Class2 ? new Class2({
1733
+ for (const key2 in oldShape) {
1734
+ shape[key2] = Class2 ? new Class2({
1621
1735
  type: "optional",
1622
- innerType: oldShape[key]
1623
- }) : oldShape[key];
1736
+ innerType: oldShape[key2]
1737
+ }) : oldShape[key2];
1624
1738
  }
1625
1739
  }
1626
1740
  assignProp(this, "shape", shape);
@@ -1636,22 +1750,22 @@ function required(Class2, schema, mask) {
1636
1750
  const oldShape = schema._zod.def.shape;
1637
1751
  const shape = { ...oldShape };
1638
1752
  if (mask) {
1639
- for (const key in mask) {
1640
- if (!(key in shape)) {
1641
- throw new Error(`Unrecognized key: "${key}"`);
1753
+ for (const key2 in mask) {
1754
+ if (!(key2 in shape)) {
1755
+ throw new Error(`Unrecognized key: "${key2}"`);
1642
1756
  }
1643
- if (!mask[key])
1757
+ if (!mask[key2])
1644
1758
  continue;
1645
- shape[key] = new Class2({
1759
+ shape[key2] = new Class2({
1646
1760
  type: "nonoptional",
1647
- innerType: oldShape[key]
1761
+ innerType: oldShape[key2]
1648
1762
  });
1649
1763
  }
1650
1764
  } else {
1651
- for (const key in oldShape) {
1652
- shape[key] = new Class2({
1765
+ for (const key2 in oldShape) {
1766
+ shape[key2] = new Class2({
1653
1767
  type: "nonoptional",
1654
- innerType: oldShape[key]
1768
+ innerType: oldShape[key2]
1655
1769
  });
1656
1770
  }
1657
1771
  }
@@ -3432,13 +3546,13 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
3432
3546
  return payload;
3433
3547
  };
3434
3548
  });
3435
- function handlePropertyResult(result, final, key, input, isOptionalIn, isOptionalOut) {
3436
- const isPresent = key in input;
3549
+ function handlePropertyResult(result, final, key2, input, isOptionalIn, isOptionalOut) {
3550
+ const isPresent = key2 in input;
3437
3551
  if (result.issues.length) {
3438
3552
  if (isOptionalIn && isOptionalOut && !isPresent) {
3439
3553
  return;
3440
3554
  }
3441
- final.issues.push(...prefixIssues(key, result.issues));
3555
+ final.issues.push(...prefixIssues(key2, result.issues));
3442
3556
  }
3443
3557
  if (!isPresent && !isOptionalIn) {
3444
3558
  if (!result.issues.length) {
@@ -3446,17 +3560,17 @@ function handlePropertyResult(result, final, key, input, isOptionalIn, isOptiona
3446
3560
  code: "invalid_type",
3447
3561
  expected: "nonoptional",
3448
3562
  input: void 0,
3449
- path: [key]
3563
+ path: [key2]
3450
3564
  });
3451
3565
  }
3452
3566
  return;
3453
3567
  }
3454
3568
  if (result.value === void 0) {
3455
3569
  if (isPresent) {
3456
- final.value[key] = void 0;
3570
+ final.value[key2] = void 0;
3457
3571
  }
3458
3572
  } else {
3459
- final.value[key] = result.value;
3573
+ final.value[key2] = result.value;
3460
3574
  }
3461
3575
  }
3462
3576
  function normalizeDef(def) {
@@ -3482,20 +3596,20 @@ function handleCatchall(proms, input, payload, ctx, def, inst) {
3482
3596
  const t = _catchall.def.type;
3483
3597
  const isOptionalIn = _catchall.optin === "optional";
3484
3598
  const isOptionalOut = _catchall.optout === "optional";
3485
- for (const key in input) {
3486
- if (key === "__proto__")
3599
+ for (const key2 in input) {
3600
+ if (key2 === "__proto__")
3487
3601
  continue;
3488
- if (keySet.has(key))
3602
+ if (keySet.has(key2))
3489
3603
  continue;
3490
3604
  if (t === "never") {
3491
- unrecognized.push(key);
3605
+ unrecognized.push(key2);
3492
3606
  continue;
3493
3607
  }
3494
- const r = _catchall.run({ value: input[key], issues: [] }, ctx);
3608
+ const r = _catchall.run({ value: input[key2], issues: [] }, ctx);
3495
3609
  if (r instanceof Promise) {
3496
- proms.push(r.then((r2) => handlePropertyResult(r2, payload, key, input, isOptionalIn, isOptionalOut)));
3610
+ proms.push(r.then((r2) => handlePropertyResult(r2, payload, key2, input, isOptionalIn, isOptionalOut)));
3497
3611
  } else {
3498
- handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut);
3612
+ handlePropertyResult(r, payload, key2, input, isOptionalIn, isOptionalOut);
3499
3613
  }
3500
3614
  }
3501
3615
  if (unrecognized.length) {
@@ -3531,12 +3645,12 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
3531
3645
  defineLazy(inst._zod, "propValues", () => {
3532
3646
  const shape = def.shape;
3533
3647
  const propValues = {};
3534
- for (const key in shape) {
3535
- const field = shape[key]._zod;
3648
+ for (const key2 in shape) {
3649
+ const field = shape[key2]._zod;
3536
3650
  if (field.values) {
3537
- propValues[key] ?? (propValues[key] = /* @__PURE__ */ new Set());
3651
+ propValues[key2] ?? (propValues[key2] = /* @__PURE__ */ new Set());
3538
3652
  for (const v of field.values)
3539
- propValues[key].add(v);
3653
+ propValues[key2].add(v);
3540
3654
  }
3541
3655
  }
3542
3656
  return propValues;
@@ -3559,15 +3673,15 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
3559
3673
  payload.value = {};
3560
3674
  const proms = [];
3561
3675
  const shape = value.shape;
3562
- for (const key of value.keys) {
3563
- const el = shape[key];
3676
+ for (const key2 of value.keys) {
3677
+ const el = shape[key2];
3564
3678
  const isOptionalIn = el._zod.optin === "optional";
3565
3679
  const isOptionalOut = el._zod.optout === "optional";
3566
- const r = el._zod.run({ value: input[key], issues: [] }, ctx);
3680
+ const r = el._zod.run({ value: input[key2], issues: [] }, ctx);
3567
3681
  if (r instanceof Promise) {
3568
- proms.push(r.then((r2) => handlePropertyResult(r2, payload, key, input, isOptionalIn, isOptionalOut)));
3682
+ proms.push(r.then((r2) => handlePropertyResult(r2, payload, key2, input, isOptionalIn, isOptionalOut)));
3569
3683
  } else {
3570
- handlePropertyResult(r, payload, key, input, isOptionalIn, isOptionalOut);
3684
+ handlePropertyResult(r, payload, key2, input, isOptionalIn, isOptionalOut);
3571
3685
  }
3572
3686
  }
3573
3687
  if (!catchall) {
@@ -3583,24 +3697,24 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
3583
3697
  const generateFastpass = (shape) => {
3584
3698
  const doc = new Doc(["shape", "payload", "ctx"]);
3585
3699
  const normalized = _normalized.value;
3586
- const parseStr = (key) => {
3587
- const k = esc(key);
3700
+ const parseStr = (key2) => {
3701
+ const k = esc(key2);
3588
3702
  return `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;
3589
3703
  };
3590
3704
  doc.write(`const input = payload.value;`);
3591
3705
  const ids = /* @__PURE__ */ Object.create(null);
3592
3706
  let counter = 0;
3593
- for (const key of normalized.keys) {
3594
- ids[key] = `key_${counter++}`;
3707
+ for (const key2 of normalized.keys) {
3708
+ ids[key2] = `key_${counter++}`;
3595
3709
  }
3596
3710
  doc.write(`const newResult = {};`);
3597
- for (const key of normalized.keys) {
3598
- const id = ids[key];
3599
- const k = esc(key);
3600
- const schema = shape[key];
3711
+ for (const key2 of normalized.keys) {
3712
+ const id = ids[key2];
3713
+ const k = esc(key2);
3714
+ const schema = shape[key2];
3601
3715
  const isOptionalIn = schema?._zod?.optin === "optional";
3602
3716
  const isOptionalOut = schema?._zod?.optout === "optional";
3603
- doc.write(`const ${id} = ${parseStr(key)};`);
3717
+ doc.write(`const ${id} = ${parseStr(key2)};`);
3604
3718
  if (isOptionalIn && isOptionalOut) {
3605
3719
  doc.write(`
3606
3720
  if (${id}.issues.length) {
@@ -3912,17 +4026,17 @@ function mergeValues(a, b) {
3912
4026
  }
3913
4027
  if (isPlainObject(a) && isPlainObject(b)) {
3914
4028
  const bKeys = Object.keys(b);
3915
- const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
4029
+ const sharedKeys = Object.keys(a).filter((key2) => bKeys.indexOf(key2) !== -1);
3916
4030
  const newObj = { ...a, ...b };
3917
- for (const key of sharedKeys) {
3918
- const sharedValue = mergeValues(a[key], b[key]);
4031
+ for (const key2 of sharedKeys) {
4032
+ const sharedValue = mergeValues(a[key2], b[key2]);
3919
4033
  if (!sharedValue.valid) {
3920
4034
  return {
3921
4035
  valid: false,
3922
- mergeErrorPath: [key, ...sharedValue.mergeErrorPath]
4036
+ mergeErrorPath: [key2, ...sharedValue.mergeErrorPath]
3923
4037
  };
3924
4038
  }
3925
- newObj[key] = sharedValue.data;
4039
+ newObj[key2] = sharedValue.data;
3926
4040
  }
3927
4041
  return { valid: true, data: newObj };
3928
4042
  }
@@ -4057,9 +4171,9 @@ var $ZodTuple = /* @__PURE__ */ $constructor("$ZodTuple", (inst, def) => {
4057
4171
  return handleTupleResults(itemResults, payload, items, input, optoutStart);
4058
4172
  };
4059
4173
  });
4060
- function getTupleOptStart(items, key) {
4174
+ function getTupleOptStart(items, key2) {
4061
4175
  for (let i = items.length - 1; i >= 0; i--) {
4062
- if (items[i]._zod[key] !== "optional")
4176
+ if (items[i]._zod[key2] !== "optional")
4063
4177
  return i + 1;
4064
4178
  }
4065
4179
  return 0;
@@ -4110,10 +4224,10 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
4110
4224
  if (values) {
4111
4225
  payload.value = {};
4112
4226
  const recordKeys = /* @__PURE__ */ new Set();
4113
- for (const key of values) {
4114
- if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
4115
- recordKeys.add(typeof key === "number" ? key.toString() : key);
4116
- const keyResult = def.keyType._zod.run({ value: key, issues: [] }, ctx);
4227
+ for (const key2 of values) {
4228
+ if (typeof key2 === "string" || typeof key2 === "number" || typeof key2 === "symbol") {
4229
+ recordKeys.add(typeof key2 === "number" ? key2.toString() : key2);
4230
+ const keyResult = def.keyType._zod.run({ value: key2, issues: [] }, ctx);
4117
4231
  if (keyResult instanceof Promise) {
4118
4232
  throw new Error("Async schemas not supported in object keys currently");
4119
4233
  }
@@ -4122,34 +4236,34 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
4122
4236
  code: "invalid_key",
4123
4237
  origin: "record",
4124
4238
  issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
4125
- input: key,
4126
- path: [key],
4239
+ input: key2,
4240
+ path: [key2],
4127
4241
  inst
4128
4242
  });
4129
4243
  continue;
4130
4244
  }
4131
4245
  const outKey = keyResult.value;
4132
- const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
4246
+ const result = def.valueType._zod.run({ value: input[key2], issues: [] }, ctx);
4133
4247
  if (result instanceof Promise) {
4134
4248
  proms.push(result.then((result2) => {
4135
4249
  if (result2.issues.length) {
4136
- payload.issues.push(...prefixIssues(key, result2.issues));
4250
+ payload.issues.push(...prefixIssues(key2, result2.issues));
4137
4251
  }
4138
4252
  payload.value[outKey] = result2.value;
4139
4253
  }));
4140
4254
  } else {
4141
4255
  if (result.issues.length) {
4142
- payload.issues.push(...prefixIssues(key, result.issues));
4256
+ payload.issues.push(...prefixIssues(key2, result.issues));
4143
4257
  }
4144
4258
  payload.value[outKey] = result.value;
4145
4259
  }
4146
4260
  }
4147
4261
  }
4148
4262
  let unrecognized;
4149
- for (const key in input) {
4150
- if (!recordKeys.has(key)) {
4263
+ for (const key2 in input) {
4264
+ if (!recordKeys.has(key2)) {
4151
4265
  unrecognized = unrecognized ?? [];
4152
- unrecognized.push(key);
4266
+ unrecognized.push(key2);
4153
4267
  }
4154
4268
  }
4155
4269
  if (unrecognized && unrecognized.length > 0) {
@@ -4162,18 +4276,18 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
4162
4276
  }
4163
4277
  } else {
4164
4278
  payload.value = {};
4165
- for (const key of Reflect.ownKeys(input)) {
4166
- if (key === "__proto__")
4279
+ for (const key2 of Reflect.ownKeys(input)) {
4280
+ if (key2 === "__proto__")
4167
4281
  continue;
4168
- if (!Object.prototype.propertyIsEnumerable.call(input, key))
4282
+ if (!Object.prototype.propertyIsEnumerable.call(input, key2))
4169
4283
  continue;
4170
- let keyResult = def.keyType._zod.run({ value: key, issues: [] }, ctx);
4284
+ let keyResult = def.keyType._zod.run({ value: key2, issues: [] }, ctx);
4171
4285
  if (keyResult instanceof Promise) {
4172
4286
  throw new Error("Async schemas not supported in object keys currently");
4173
4287
  }
4174
- const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length;
4288
+ const checkNumericKey = typeof key2 === "string" && number.test(key2) && keyResult.issues.length;
4175
4289
  if (checkNumericKey) {
4176
- const retryResult = def.keyType._zod.run({ value: Number(key), issues: [] }, ctx);
4290
+ const retryResult = def.keyType._zod.run({ value: Number(key2), issues: [] }, ctx);
4177
4291
  if (retryResult instanceof Promise) {
4178
4292
  throw new Error("Async schemas not supported in object keys currently");
4179
4293
  }
@@ -4183,30 +4297,30 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
4183
4297
  }
4184
4298
  if (keyResult.issues.length) {
4185
4299
  if (def.mode === "loose") {
4186
- payload.value[key] = input[key];
4300
+ payload.value[key2] = input[key2];
4187
4301
  } else {
4188
4302
  payload.issues.push({
4189
4303
  code: "invalid_key",
4190
4304
  origin: "record",
4191
4305
  issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
4192
- input: key,
4193
- path: [key],
4306
+ input: key2,
4307
+ path: [key2],
4194
4308
  inst
4195
4309
  });
4196
4310
  }
4197
4311
  continue;
4198
4312
  }
4199
- const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
4313
+ const result = def.valueType._zod.run({ value: input[key2], issues: [] }, ctx);
4200
4314
  if (result instanceof Promise) {
4201
4315
  proms.push(result.then((result2) => {
4202
4316
  if (result2.issues.length) {
4203
- payload.issues.push(...prefixIssues(key, result2.issues));
4317
+ payload.issues.push(...prefixIssues(key2, result2.issues));
4204
4318
  }
4205
4319
  payload.value[keyResult.value] = result2.value;
4206
4320
  }));
4207
4321
  } else {
4208
4322
  if (result.issues.length) {
4209
- payload.issues.push(...prefixIssues(key, result.issues));
4323
+ payload.issues.push(...prefixIssues(key2, result.issues));
4210
4324
  }
4211
4325
  payload.value[keyResult.value] = result.value;
4212
4326
  }
@@ -4233,15 +4347,15 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
4233
4347
  }
4234
4348
  const proms = [];
4235
4349
  payload.value = /* @__PURE__ */ new Map();
4236
- for (const [key, value] of input) {
4237
- const keyResult = def.keyType._zod.run({ value: key, issues: [] }, ctx);
4350
+ for (const [key2, value] of input) {
4351
+ const keyResult = def.keyType._zod.run({ value: key2, issues: [] }, ctx);
4238
4352
  const valueResult = def.valueType._zod.run({ value, issues: [] }, ctx);
4239
4353
  if (keyResult instanceof Promise || valueResult instanceof Promise) {
4240
4354
  proms.push(Promise.all([keyResult, valueResult]).then(([keyResult2, valueResult2]) => {
4241
- handleMapResult(keyResult2, valueResult2, payload, key, input, inst, ctx);
4355
+ handleMapResult(keyResult2, valueResult2, payload, key2, input, inst, ctx);
4242
4356
  }));
4243
4357
  } else {
4244
- handleMapResult(keyResult, valueResult, payload, key, input, inst, ctx);
4358
+ handleMapResult(keyResult, valueResult, payload, key2, input, inst, ctx);
4245
4359
  }
4246
4360
  }
4247
4361
  if (proms.length)
@@ -4249,10 +4363,10 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
4249
4363
  return payload;
4250
4364
  };
4251
4365
  });
4252
- function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
4366
+ function handleMapResult(keyResult, valueResult, final, key2, input, inst, ctx) {
4253
4367
  if (keyResult.issues.length) {
4254
- if (propertyKeyTypes.has(typeof key)) {
4255
- final.issues.push(...prefixIssues(key, keyResult.issues));
4368
+ if (propertyKeyTypes.has(typeof key2)) {
4369
+ final.issues.push(...prefixIssues(key2, keyResult.issues));
4256
4370
  } else {
4257
4371
  final.issues.push({
4258
4372
  code: "invalid_key",
@@ -4264,15 +4378,15 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
4264
4378
  }
4265
4379
  }
4266
4380
  if (valueResult.issues.length) {
4267
- if (propertyKeyTypes.has(typeof key)) {
4268
- final.issues.push(...prefixIssues(key, valueResult.issues));
4381
+ if (propertyKeyTypes.has(typeof key2)) {
4382
+ final.issues.push(...prefixIssues(key2, valueResult.issues));
4269
4383
  } else {
4270
4384
  final.issues.push({
4271
4385
  origin: "map",
4272
4386
  code: "invalid_element",
4273
4387
  input,
4274
4388
  inst,
4275
- key,
4389
+ key: key2,
4276
4390
  issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx, config()))
4277
4391
  });
4278
4392
  }
@@ -12033,8 +12147,8 @@ function extractDefs(ctx, schema) {
12033
12147
  if (defId)
12034
12148
  seen.defId = defId;
12035
12149
  const schema2 = seen.schema;
12036
- for (const key in schema2) {
12037
- delete schema2[key];
12150
+ for (const key2 in schema2) {
12151
+ delete schema2[key2];
12038
12152
  }
12039
12153
  schema2.$ref = ref;
12040
12154
  };
@@ -12103,20 +12217,20 @@ function finalize(ctx, schema) {
12103
12217
  Object.assign(schema2, _cached);
12104
12218
  const isParentRef = zodSchema._zod.parent === ref;
12105
12219
  if (isParentRef) {
12106
- for (const key in schema2) {
12107
- if (key === "$ref" || key === "allOf")
12220
+ for (const key2 in schema2) {
12221
+ if (key2 === "$ref" || key2 === "allOf")
12108
12222
  continue;
12109
- if (!(key in _cached)) {
12110
- delete schema2[key];
12223
+ if (!(key2 in _cached)) {
12224
+ delete schema2[key2];
12111
12225
  }
12112
12226
  }
12113
12227
  }
12114
12228
  if (refSchema.$ref && refSeen.def) {
12115
- for (const key in schema2) {
12116
- if (key === "$ref" || key === "allOf")
12229
+ for (const key2 in schema2) {
12230
+ if (key2 === "$ref" || key2 === "allOf")
12117
12231
  continue;
12118
- if (key in refSeen.def && JSON.stringify(schema2[key]) === JSON.stringify(refSeen.def[key])) {
12119
- delete schema2[key];
12232
+ if (key2 in refSeen.def && JSON.stringify(schema2[key2]) === JSON.stringify(refSeen.def[key2])) {
12233
+ delete schema2[key2];
12120
12234
  }
12121
12235
  }
12122
12236
  }
@@ -12128,11 +12242,11 @@ function finalize(ctx, schema) {
12128
12242
  if (parentSeen?.schema.$ref) {
12129
12243
  schema2.$ref = parentSeen.schema.$ref;
12130
12244
  if (parentSeen.def) {
12131
- for (const key in schema2) {
12132
- if (key === "$ref" || key === "allOf")
12245
+ for (const key2 in schema2) {
12246
+ if (key2 === "$ref" || key2 === "allOf")
12133
12247
  continue;
12134
- if (key in parentSeen.def && JSON.stringify(schema2[key]) === JSON.stringify(parentSeen.def[key])) {
12135
- delete schema2[key];
12248
+ if (key2 in parentSeen.def && JSON.stringify(schema2[key2]) === JSON.stringify(parentSeen.def[key2])) {
12249
+ delete schema2[key2];
12136
12250
  }
12137
12251
  }
12138
12252
  }
@@ -12233,8 +12347,8 @@ function isTransforming(_schema, _ctx) {
12233
12347
  return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);
12234
12348
  }
12235
12349
  if (def.type === "object") {
12236
- for (const key in def.shape) {
12237
- if (isTransforming(def.shape[key], ctx))
12350
+ for (const key2 in def.shape) {
12351
+ if (isTransforming(def.shape[key2], ctx))
12238
12352
  return true;
12239
12353
  }
12240
12354
  return false;
@@ -12523,15 +12637,15 @@ var objectProcessor = (schema, ctx, _json, params) => {
12523
12637
  json3.type = "object";
12524
12638
  json3.properties = {};
12525
12639
  const shape = def.shape;
12526
- for (const key in shape) {
12527
- json3.properties[key] = process2(shape[key], ctx, {
12640
+ for (const key2 in shape) {
12641
+ json3.properties[key2] = process2(shape[key2], ctx, {
12528
12642
  ...params,
12529
- path: [...params.path, "properties", key]
12643
+ path: [...params.path, "properties", key2]
12530
12644
  });
12531
12645
  }
12532
12646
  const allKeys = new Set(Object.keys(shape));
12533
- const requiredKeys = new Set([...allKeys].filter((key) => {
12534
- const v = def.shape[key]._zod;
12647
+ const requiredKeys = new Set([...allKeys].filter((key2) => {
12648
+ const v = def.shape[key2]._zod;
12535
12649
  if (ctx.io === "input") {
12536
12650
  return v.optin === void 0;
12537
12651
  } else {
@@ -12797,9 +12911,9 @@ function toJSONSchema(input, params) {
12797
12911
  };
12798
12912
  ctx2.external = external;
12799
12913
  for (const entry of registry2._idmap.entries()) {
12800
- const [key, schema] = entry;
12914
+ const [key2, schema] = entry;
12801
12915
  extractDefs(ctx2, schema);
12802
- schemas[key] = finalize(ctx2, schema);
12916
+ schemas[key2] = finalize(ctx2, schema);
12803
12917
  }
12804
12918
  if (Object.keys(defs).length > 0) {
12805
12919
  const defsSegment = ctx2.target === "draft-2020-12" ? "$defs" : "definitions";
@@ -13205,14 +13319,14 @@ function _installLazyMethods(inst, group, methods) {
13205
13319
  if (installed.has(group))
13206
13320
  return;
13207
13321
  installed.add(group);
13208
- for (const key in methods) {
13209
- const fn = methods[key];
13210
- Object.defineProperty(proto, key, {
13322
+ for (const key2 in methods) {
13323
+ const fn = methods[key2];
13324
+ Object.defineProperty(proto, key2, {
13211
13325
  configurable: true,
13212
13326
  enumerable: false,
13213
13327
  get() {
13214
13328
  const bound = fn.bind(this);
13215
- Object.defineProperty(this, key, {
13329
+ Object.defineProperty(this, key2, {
13216
13330
  configurable: true,
13217
13331
  writable: true,
13218
13332
  enumerable: true,
@@ -13221,7 +13335,7 @@ function _installLazyMethods(inst, group, methods) {
13221
13335
  return bound;
13222
13336
  },
13223
13337
  set(v) {
13224
- Object.defineProperty(this, key, {
13338
+ Object.defineProperty(this, key2, {
13225
13339
  configurable: true,
13226
13340
  writable: true,
13227
13341
  enumerable: true,
@@ -14609,11 +14723,11 @@ function resolveRef(ref, ctx) {
14609
14723
  }
14610
14724
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
14611
14725
  if (path[0] === defsKey) {
14612
- const key = path[1];
14613
- if (!key || !ctx.defs[key]) {
14726
+ const key2 = path[1];
14727
+ if (!key2 || !ctx.defs[key2]) {
14614
14728
  throw new Error(`Reference not found: ${ref}`);
14615
14729
  }
14616
- return ctx.defs[key];
14730
+ return ctx.defs[key2];
14617
14731
  }
14618
14732
  throw new Error(`Reference not found: ${ref}`);
14619
14733
  }
@@ -14799,9 +14913,9 @@ function convertBaseSchema(schema, ctx) {
14799
14913
  const shape = {};
14800
14914
  const properties = schema.properties || {};
14801
14915
  const requiredSet = new Set(schema.required || []);
14802
- for (const [key, propSchema] of Object.entries(properties)) {
14916
+ for (const [key2, propSchema] of Object.entries(properties)) {
14803
14917
  const propZodSchema = convertSchema(propSchema, ctx);
14804
- shape[key] = requiredSet.has(key) ? propZodSchema : propZodSchema.optional();
14918
+ shape[key2] = requiredSet.has(key2) ? propZodSchema : propZodSchema.optional();
14805
14919
  }
14806
14920
  if (schema.propertyNames) {
14807
14921
  const keySchema = convertSchema(schema.propertyNames, ctx);
@@ -14942,20 +15056,20 @@ function convertSchema(schema, ctx) {
14942
15056
  }
14943
15057
  const extraMeta = {};
14944
15058
  const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
14945
- for (const key of coreMetadataKeys) {
14946
- if (key in schema) {
14947
- extraMeta[key] = schema[key];
15059
+ for (const key2 of coreMetadataKeys) {
15060
+ if (key2 in schema) {
15061
+ extraMeta[key2] = schema[key2];
14948
15062
  }
14949
15063
  }
14950
15064
  const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
14951
- for (const key of contentMetadataKeys) {
14952
- if (key in schema) {
14953
- extraMeta[key] = schema[key];
15065
+ for (const key2 of contentMetadataKeys) {
15066
+ if (key2 in schema) {
15067
+ extraMeta[key2] = schema[key2];
14954
15068
  }
14955
15069
  }
14956
- for (const key of Object.keys(schema)) {
14957
- if (!RECOGNIZED_KEYS.has(key)) {
14958
- extraMeta[key] = schema[key];
15070
+ for (const key2 of Object.keys(schema)) {
15071
+ if (!RECOGNIZED_KEYS.has(key2)) {
15072
+ extraMeta[key2] = schema[key2];
14959
15073
  }
14960
15074
  }
14961
15075
  if (Object.keys(extraMeta).length > 0) {
@@ -15050,18 +15164,28 @@ Frame upload interrupted; retrying in ${delay / 1e3}s.
15050
15164
  }
15051
15165
  }
15052
15166
  async function submitFrameCapture(client, id, directory) {
15053
- const manifest = frameManifestSchema.parse(JSON.parse(await readFile2(join2(directory, "capture-manifest.json"), "utf8")));
15167
+ const manifest = frameManifestSchema.parse(JSON.parse(await readFile3(join3(directory, "capture-manifest.json"), "utf8")));
15054
15168
  let status = await client.beginEncoding(id, manifest);
15055
15169
  if (status.status === "failed") status = await client.retryEncoding(id);
15056
15170
  if (status.status !== "uploading") {
15057
15171
  if (["expired", "cancelled"].includes(status.status)) throw new Error(`Capture ${status.status}; it cannot be retried.`);
15058
15172
  return status;
15059
15173
  }
15060
- const file2 = await open(join2(directory, "frames.bin"), "r");
15174
+ const file2 = await open(join3(directory, "frames.bin"), "r");
15061
15175
  try {
15062
15176
  const total = manifest.frames.reduce((n, f) => n + f.size, 0);
15063
15177
  if ((await file2.stat()).size !== total) throw new Error("Capture bytes do not match the manifest.");
15064
15178
  const uploaded = new Set(status.uploadedParts);
15179
+ let acceptedBytes = [...uploaded].reduce((sum, part) => sum + (Number.isInteger(part) && part >= 0 && part < Math.ceil(total / ENCODING_PART_BYTES) ? Math.min(ENCODING_PART_BYTES, total - part * ENCODING_PART_BYTES) : 0), 0);
15180
+ let transferredBytes = 0;
15181
+ const startedAt = Date.now();
15182
+ const progress = () => {
15183
+ const seconds = (Date.now() - startedAt) / 1e3;
15184
+ const rate = seconds > 0 ? transferredBytes / seconds : 0;
15185
+ const remaining = rate > 0 ? ` \xB7 about ${Math.ceil((total - acceptedBytes) / rate)}s left` : "";
15186
+ process.stderr.write(`\ruploading frames ${Math.floor(acceptedBytes / total * 100)}% \xB7 ${(acceptedBytes / 1048576).toFixed(2)}/${(total / 1048576).toFixed(2)} MiB${rate > 0 ? ` \xB7 ${(rate / 1048576).toFixed(2)} MiB/s` : ""}${remaining}`);
15187
+ };
15188
+ progress();
15065
15189
  for (let part = 0; part < Math.ceil(total / ENCODING_PART_BYTES); part++) {
15066
15190
  if (uploaded.has(part)) continue;
15067
15191
  const bytes = Buffer.alloc(Math.min(ENCODING_PART_BYTES, total - part * ENCODING_PART_BYTES));
@@ -15072,7 +15196,9 @@ async function submitFrameCapture(client, id, directory) {
15072
15196
  offset += r.bytesRead;
15073
15197
  }
15074
15198
  await withFrameUploadRetry(() => client.uploadEncodingPart(id, part, bytes));
15075
- process.stderr.write(`\ruploading frames ${Math.round(Math.min(total, (part + 1) * ENCODING_PART_BYTES) / total * 100)}%`);
15199
+ acceptedBytes += bytes.length;
15200
+ transferredBytes += bytes.length;
15201
+ progress();
15076
15202
  }
15077
15203
  } finally {
15078
15204
  await file2.close();
@@ -15093,35 +15219,129 @@ async function waitForEncoding(client, id, timeoutMs = 30 * 6e4) {
15093
15219
  }
15094
15220
 
15095
15221
  // src/cli.ts
15096
- import { spawn as spawn6 } from "child_process";
15097
- import { hostname as hostname4 } from "os";
15222
+ import { spawn as spawn8 } from "child_process";
15223
+ import { hostname as hostname4, tmpdir as tmpdir3 } from "os";
15098
15224
  import { Command } from "commander";
15099
- import { basename, dirname as dirname2 } from "path";
15100
- import { readdir as readdir2, rm as rm5, stat as stat4 } from "fs/promises";
15225
+ import { basename, dirname as dirname2, join as join11 } from "path";
15226
+ import { readdir as readdir2, rm as rm7, stat as stat5 } from "fs/promises";
15101
15227
 
15102
- // ../recorder/src/client.ts
15103
- import { request as request2 } from "http";
15104
- import { spawn as spawn2 } from "child_process";
15228
+ // ../recorder/src/windows-ipc.ts
15229
+ import { spawn as spawn3 } from "child_process";
15230
+ import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
15231
+ import { link, lstat, mkdir as mkdir3, readFile as readFile4, rm as rm3, writeFile as writeFile2 } from "fs/promises";
15232
+ import { join as join5, win32 as win322 } from "path";
15105
15233
 
15106
15234
  // ../recorder/src/paths.ts
15107
- import { homedir as homedir2, tmpdir } from "os";
15108
- import { join as join3 } from "path";
15109
- var rillDirectory = () => process.env.RILL_HOME ?? join3(homedir2(), ".rill");
15110
- var daemonSocketPath = () => process.env.RILL_SOCKET ?? join3(rillDirectory(), "recorder-v2.sock");
15111
- var daemonStatePath = () => join3(rillDirectory(), "sessions-v2.json");
15112
- var recordingTempRoot = () => process.env.RILL_TEMP ?? join3(tmpdir(), "rill-recordings");
15235
+ import { homedir as homedir3, tmpdir } from "os";
15236
+ import { join as join4 } from "path";
15237
+ var rillDirectory = () => process.env.RILL_HOME ?? join4(homedir3(), ".rill");
15238
+ var daemonSocketPath = () => process.env.RILL_SOCKET ?? join4(rillDirectory(), "recorder-v2.sock");
15239
+ var daemonStatePath = () => join4(rillDirectory(), "sessions-v2.json");
15240
+ var recordingTempRoot = () => process.env.RILL_TEMP ?? join4(tmpdir(), "rill-recordings");
15241
+
15242
+ // ../recorder/src/windows-ipc.ts
15243
+ var aclScript = String.raw`$ErrorActionPreference='Stop';try {
15244
+ $path=([Console]::In.ReadToEnd() | ConvertFrom-Json).path
15245
+ $sid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User
15246
+ $acl=[System.Security.AccessControl.DirectorySecurity]::new()
15247
+ $acl.SetOwner($sid)
15248
+ $acl.SetAccessRuleProtection($true,$false)
15249
+ $rule=[System.Security.AccessControl.FileSystemAccessRule]::new($sid,[System.Security.AccessControl.FileSystemRights]::FullControl,[System.Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit',[System.Security.AccessControl.PropagationFlags]::None,[System.Security.AccessControl.AccessControlType]::Allow)
15250
+ $acl.AddAccessRule($rule)
15251
+ ([System.IO.DirectoryInfo]::new($path)).SetAccessControl($acl)
15252
+ [Console]::Out.Write('secured')
15253
+ }catch{[Console]::Error.Write('Recorder directory protection failed.');exit 1}`;
15254
+ async function secureWindowsIpcDirectory(path) {
15255
+ if (process.platform !== "win32") throw new Error("Windows IPC directory protection requires Windows.");
15256
+ await new Promise((resolve, reject) => {
15257
+ const executable = win322.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
15258
+ const child = spawn3(executable, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", aclScript], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
15259
+ let output = "", failed = false;
15260
+ const fail2 = () => {
15261
+ if (failed) return;
15262
+ failed = true;
15263
+ child.kill();
15264
+ reject(new Error("Cannot secure the Windows recorder directory. Recording has not started."));
15265
+ };
15266
+ const timer = setTimeout(fail2, 15e3);
15267
+ child.once("error", fail2);
15268
+ child.stdin.once("error", fail2);
15269
+ child.stderr.resume();
15270
+ child.stdout.on("data", (chunk) => {
15271
+ output += chunk.toString("ascii");
15272
+ if (output.length > 1024) fail2();
15273
+ });
15274
+ child.once("close", (code) => {
15275
+ clearTimeout(timer);
15276
+ if (failed) return;
15277
+ if (code !== 0 || output !== "secured") {
15278
+ fail2();
15279
+ return;
15280
+ }
15281
+ resolve();
15282
+ });
15283
+ child.stdin.end(JSON.stringify({ path }));
15284
+ });
15285
+ }
15286
+ function validWindowsIpcIdentity(value) {
15287
+ const v = value;
15288
+ return v?.version === 1 && typeof v.pipe === "string" && /^\\\\\.\\pipe\\rill-v3-[a-f0-9]{64}$/.test(v.pipe) && typeof v.token === "string" && /^[a-f0-9]{64}$/.test(v.token);
15289
+ }
15290
+ function acceptsWindowsIpcToken(expected, header) {
15291
+ if (typeof header !== "string" || !/^Bearer [a-f0-9]{64}$/.test(header)) return false;
15292
+ const supplied = header.slice(7);
15293
+ return supplied.length === expected.length && timingSafeEqual(Buffer.from(supplied), Buffer.from(expected));
15294
+ }
15295
+ async function windowsIpcIdentity(directory = join5(rillDirectory(), "windows-ipc-v3"), secure = secureWindowsIpcDirectory) {
15296
+ await mkdir3(directory, { recursive: true, mode: 448 });
15297
+ if ((await lstat(directory)).isSymbolicLink()) throw new Error("Recorder directory cannot be a symbolic link.");
15298
+ await secure(directory);
15299
+ const path = join5(directory, "identity.json");
15300
+ const load = async () => {
15301
+ const info = await lstat(path);
15302
+ if (!info.isFile() || info.isSymbolicLink() || info.size > 4096) throw new Error("Invalid recorder identity.");
15303
+ const value = JSON.parse(await readFile4(path, "utf8"));
15304
+ if (!validWindowsIpcIdentity(value)) throw new Error("Invalid recorder identity.");
15305
+ return value;
15306
+ };
15307
+ try {
15308
+ return await load();
15309
+ } catch (e) {
15310
+ if (e.code !== "ENOENT") throw new Error("Cannot read the protected recorder identity.");
15311
+ }
15312
+ const identity = { version: 1, pipe: "\\\\.\\pipe\\rill-v3-" + randomBytes2(32).toString("hex"), token: randomBytes2(32).toString("hex") };
15313
+ const temporary = join5(directory, randomBytes2(16).toString("hex") + ".tmp");
15314
+ try {
15315
+ await writeFile2(temporary, JSON.stringify(identity), { mode: 384, flag: "wx" });
15316
+ try {
15317
+ await link(temporary, path);
15318
+ } catch (e) {
15319
+ if (e.code !== "EEXIST") throw e;
15320
+ }
15321
+ return await load();
15322
+ } finally {
15323
+ await rm3(temporary, { force: true });
15324
+ }
15325
+ }
15113
15326
 
15114
15327
  // ../recorder/src/client.ts
15328
+ import { request as request2 } from "http";
15329
+ import { spawn as spawn4 } from "child_process";
15115
15330
  async function ensureDaemon(cliEntryPath) {
15116
15331
  try {
15117
15332
  await callDaemon("GET", "/health");
15118
15333
  return;
15119
15334
  } catch {
15120
15335
  }
15121
- const child = spawn2(process.execPath, [cliEntryPath, "daemon"], { detached: true, stdio: "ignore", env: process.env });
15336
+ const child = spawn4(process.execPath, [cliEntryPath, "daemon"], { detached: true, stdio: "ignore", env: process.env, windowsHide: true });
15337
+ let startupError;
15338
+ child.once("error", () => {
15339
+ startupError = new Error("The local recorder process could not be launched.");
15340
+ });
15122
15341
  child.unref();
15123
15342
  const deadline = Date.now() + 1e4;
15124
15343
  while (Date.now() < deadline) {
15344
+ if (startupError) throw startupError;
15125
15345
  try {
15126
15346
  await callDaemon("GET", "/health");
15127
15347
  return;
@@ -15140,10 +15360,16 @@ function stopRecording(recordingId) {
15140
15360
  function recordingStatus(recordingId) {
15141
15361
  return callDaemon("GET", `/sessions/${encodeURIComponent(recordingId)}`);
15142
15362
  }
15143
- function callDaemon(method, path, body) {
15363
+ var windowsIdentityPromise;
15364
+ async function callDaemon(method, path, body) {
15365
+ if (process.platform === "win32" && process.env.RILL_SOCKET) throw new Error("RILL_SOCKET overrides are not supported by authenticated Windows IPC. Unset it before recording.");
15366
+ const identity = process.platform === "win32" ? await (windowsIdentityPromise ??= windowsIpcIdentity().catch((error51) => {
15367
+ windowsIdentityPromise = void 0;
15368
+ throw error51;
15369
+ })) : void 0;
15144
15370
  return new Promise((resolve, reject) => {
15145
15371
  const payload = body === void 0 ? void 0 : Buffer.from(JSON.stringify(body));
15146
- const outgoing = request2({ socketPath: daemonSocketPath(), method, path, headers: payload ? { "Content-Type": "application/json", "Content-Length": payload.length } : void 0 }, (incoming) => {
15372
+ const outgoing = request2({ socketPath: identity?.pipe ?? daemonSocketPath(), method, path, headers: { ...payload ? { "Content-Type": "application/json", "Content-Length": payload.length } : {}, ...identity ? { Authorization: "Bearer " + identity.token } : {} } }, (incoming) => {
15147
15373
  const chunks = [];
15148
15374
  incoming.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
15149
15375
  incoming.on("end", () => {
@@ -15156,6 +15382,7 @@ function callDaemon(method, path, body) {
15156
15382
  }
15157
15383
  });
15158
15384
  });
15385
+ outgoing.setTimeout(path === "/health" ? 2e3 : 12e4, () => outgoing.destroy(new Error("Local recorder request timed out.")));
15159
15386
  outgoing.once("error", reject);
15160
15387
  if (payload) outgoing.write(payload);
15161
15388
  outgoing.end();
@@ -15163,17 +15390,17 @@ function callDaemon(method, path, body) {
15163
15390
  }
15164
15391
 
15165
15392
  // ../recorder/src/retention.ts
15166
- import { readdir, readFile as readFile3, rm as rm2 } from "fs/promises";
15167
- import { join as join4 } from "path";
15393
+ import { readdir, readFile as readFile5, rm as rm4 } from "fs/promises";
15394
+ import { join as join6 } from "path";
15168
15395
  async function purgeExpiredRemoteCaptures(root, now = Date.now()) {
15169
15396
  const removed = [];
15170
15397
  for (const id of await readdir(root).catch(() => [])) {
15171
15398
  if (!/^rec_[a-zA-Z0-9_-]+$/.test(id)) continue;
15172
- const directory = join4(root, id);
15399
+ const directory = join6(root, id);
15173
15400
  try {
15174
- const marker = JSON.parse(await readFile3(join4(directory, "remote-capture.json"), "utf8"));
15401
+ const marker = JSON.parse(await readFile5(join6(directory, "remote-capture.json"), "utf8"));
15175
15402
  if (typeof marker.expiresAt !== "number" || marker.expiresAt > now) continue;
15176
- await rm2(directory, { recursive: true, force: true });
15403
+ await rm4(directory, { recursive: true, force: true });
15177
15404
  removed.push(id);
15178
15405
  } catch {
15179
15406
  }
@@ -15184,7 +15411,7 @@ async function hasRetainedRemoteCaptures(root) {
15184
15411
  for (const id of await readdir(root).catch(() => [])) {
15185
15412
  if (!/^rec_[a-zA-Z0-9_-]+$/.test(id)) continue;
15186
15413
  try {
15187
- const marker = JSON.parse(await readFile3(join4(root, id, "remote-capture.json"), "utf8"));
15414
+ const marker = JSON.parse(await readFile5(join6(root, id, "remote-capture.json"), "utf8"));
15188
15415
  if (typeof marker.expiresAt === "number" && Number.isFinite(marker.expiresAt)) return true;
15189
15416
  } catch {
15190
15417
  }
@@ -15194,8 +15421,189 @@ async function hasRetainedRemoteCaptures(root) {
15194
15421
 
15195
15422
  // ../recorder/src/daemon.ts
15196
15423
  import { createServer as createServer2 } from "http";
15197
- import { mkdir as mkdir5, readFile as readFile5, unlink, writeFile as writeFile4, rename, stat as stat3 } from "fs/promises";
15198
- import { dirname, join as join8 } from "path";
15424
+ import { mkdir as mkdir7, readFile as readFile7, unlink, writeFile as writeFile6, rename as rename2, stat as stat4, chmod } from "fs/promises";
15425
+ import { dirname, join as join10 } from "path";
15426
+
15427
+ // ../contracts/src/cloud-blocklist.ts
15428
+ var blockedCloudDomains = [
15429
+ // Torrent indexes and unofficial software-download destinations.
15430
+ // Explicit domains only: do not block legitimate torrent clients or all downloads.
15431
+ "yasdl.com",
15432
+ "soft98.ir",
15433
+ "downloadha.com",
15434
+ "patoghu.com",
15435
+ "thepiratebay.org",
15436
+ "1337x.to",
15437
+ "yts.mx",
15438
+ "eztvx.to",
15439
+ "fitgirl-repacks.site",
15440
+ "dodi-repacks.site",
15441
+ // Search and reference
15442
+ "google.com",
15443
+ "google.co.uk",
15444
+ "google.co.in",
15445
+ "google.com.ng",
15446
+ "google.ca",
15447
+ "google.com.au",
15448
+ "google.de",
15449
+ "google.fr",
15450
+ "google.co.jp",
15451
+ "google.com.br",
15452
+ "google.es",
15453
+ "bing.com",
15454
+ "yahoo.com",
15455
+ "duckduckgo.com",
15456
+ "baidu.com",
15457
+ "yandex.com",
15458
+ "yandex.ru",
15459
+ "wikipedia.org",
15460
+ "quora.com",
15461
+ // Social networks and media
15462
+ "reddit.com",
15463
+ "redd.it",
15464
+ "facebook.com",
15465
+ "fb.com",
15466
+ "instagram.com",
15467
+ "threads.net",
15468
+ "threads.com",
15469
+ "twitter.com",
15470
+ "x.com",
15471
+ "t.co",
15472
+ "linkedin.com",
15473
+ "tiktok.com",
15474
+ "pinterest.com",
15475
+ "snapchat.com",
15476
+ "youtube.com",
15477
+ "youtu.be",
15478
+ "twitch.tv",
15479
+ "discord.com",
15480
+ "discord.gg",
15481
+ "telegram.org",
15482
+ "t.me",
15483
+ "whatsapp.com",
15484
+ // Shopping and marketplaces
15485
+ "amazon.com",
15486
+ "amazon.co.uk",
15487
+ "amazon.ca",
15488
+ "amazon.de",
15489
+ "amazon.fr",
15490
+ "amazon.in",
15491
+ "amazon.co.jp",
15492
+ "amazon.com.au",
15493
+ "amazon.it",
15494
+ "amazon.es",
15495
+ "ebay.com",
15496
+ "ebay.co.uk",
15497
+ "etsy.com",
15498
+ "walmart.com",
15499
+ "target.com",
15500
+ "aliexpress.com",
15501
+ "alibaba.com",
15502
+ "temu.com",
15503
+ "shein.com",
15504
+ "craigslist.org",
15505
+ "facebookmarketplace.com",
15506
+ "bestbuy.com",
15507
+ "costco.com",
15508
+ "homedepot.com",
15509
+ "wayfair.com",
15510
+ "mercadolibre.com",
15511
+ "shopee.com",
15512
+ "lazada.com",
15513
+ "jiji.ng",
15514
+ "jumia.com.ng",
15515
+ "konga.com",
15516
+ "propertypro.ng",
15517
+ "property24.com",
15518
+ // Travel, property, jobs and business directories
15519
+ "airbnb.com",
15520
+ "airbnb.co.uk",
15521
+ "airbnb.ca",
15522
+ "airbnb.com.au",
15523
+ "airbnb.fr",
15524
+ "airbnb.de",
15525
+ "booking.com",
15526
+ "expedia.com",
15527
+ "tripadvisor.com",
15528
+ "agoda.com",
15529
+ "hotels.com",
15530
+ "kayak.com",
15531
+ "skyscanner.net",
15532
+ "zillow.com",
15533
+ "realtor.com",
15534
+ "rightmove.co.uk",
15535
+ "zoopla.co.uk",
15536
+ "indeed.com",
15537
+ "glassdoor.com",
15538
+ "yelp.com",
15539
+ "yellowpages.com",
15540
+ "trustpilot.com",
15541
+ "crunchbase.com",
15542
+ "g2.com",
15543
+ "capterra.com",
15544
+ "clutch.co",
15545
+ "zoominfo.com",
15546
+ "apollo.io",
15547
+ "monster.com",
15548
+ "ziprecruiter.com",
15549
+ "seek.com.au",
15550
+ "naukri.com",
15551
+ "redfin.com",
15552
+ "trulia.com",
15553
+ "apartments.com",
15554
+ "realtor.ca",
15555
+ "realestate.com.au",
15556
+ "vrbo.com",
15557
+ "trip.com",
15558
+ "trivago.com",
15559
+ "hostelworld.com"
15560
+ ];
15561
+ var blockedCloudTargetMessage = "This website is not supported for cloud runs. Use your own website or app instead.";
15562
+ function isBlockedCloudTarget(value) {
15563
+ try {
15564
+ const hostname5 = new URL(value).hostname.toLowerCase().replace(/\.+$/, "");
15565
+ return blockedCloudDomains.some((domain2) => hostname5 === domain2 || hostname5.endsWith(`.${domain2}`));
15566
+ } catch {
15567
+ return false;
15568
+ }
15569
+ }
15570
+
15571
+ // ../contracts/src/cloud-run.ts
15572
+ var publicTarget = external_exports.string().url().max(2048).refine((value) => {
15573
+ let u;
15574
+ try {
15575
+ u = new URL(value);
15576
+ } catch {
15577
+ return false;
15578
+ }
15579
+ return ["http:", "https:"].includes(u.protocol) && !u.username && !u.password && !u.port && !u.hash && u.hostname.includes(".") && !/^[0-9.]+$/.test(u.hostname) && !u.hostname.endsWith(".exe.xyz") && !u.hostname.endsWith(".local") && !u.hostname.endsWith(".internal") && !u.hostname.endsWith(".localhost");
15580
+ }, "Use a public HTTP(S) URL without credentials, fragments, or a custom port.");
15581
+ var cloudRunTarget = publicTarget.refine((value) => !isBlockedCloudTarget(value), blockedCloudTargetMessage);
15582
+ var interactionFields = {
15583
+ mode: external_exports.enum(["browse", "interact"]).optional(),
15584
+ interactionConfirmed: external_exports.boolean().optional(),
15585
+ // Legacy payload compatibility only; Interact automatically permits API destinations.
15586
+ allowedWriteOrigins: external_exports.array(cloudRunTarget.refine((v) => new URL(v).origin === v, "Use an exact origin.")).max(5).optional()
15587
+ };
15588
+ var sideEffectSchema = external_exports.object({
15589
+ action: external_exports.enum(["fill", "check", "select", "press", "click"]),
15590
+ page: external_exports.string().max(2048),
15591
+ target: external_exports.string().max(160),
15592
+ status: external_exports.enum(["performed", "unknown"])
15593
+ }).strict();
15594
+ var interactionReportFields = {
15595
+ mode: external_exports.enum(["browse", "interact"]).optional(),
15596
+ sideEffects: external_exports.array(sideEffectSchema).max(30).optional(),
15597
+ cleanup: external_exports.literal("not_attempted").optional()
15598
+ };
15599
+ var cloudSubmission = external_exports.object({ ...interactionFields, id: external_exports.string().uuid(), url: cloudRunTarget, prompt: external_exports.string().trim().min(1).max(2e3), expectation: external_exports.string().trim().min(1).max(1e3).optional(), account: external_exports.string().regex(/^[a-z0-9-]{1,60}$/).optional() }).strict();
15600
+ var siteAccountSchema = external_exports.object({
15601
+ name: external_exports.string().regex(/^[a-z0-9-]{1,60}$/),
15602
+ origin: publicTarget.refine((v) => new URL(v).protocol === "https:" && new URL(v).origin === v, "Use an exact HTTPS origin."),
15603
+ loginUrl: publicTarget.refine((v) => new URL(v).protocol === "https:").optional(),
15604
+ username: external_exports.string().min(1).max(320),
15605
+ password: external_exports.string().min(1).max(1e3)
15606
+ }).strict().refine((v) => !v.loginUrl || new URL(v.loginUrl).origin === v.origin, "Login must use the saved website origin.");
15199
15607
 
15200
15608
  // ../contracts/src/recording.ts
15201
15609
  var cursorAppearanceSchema = external_exports.object({
@@ -15236,6 +15644,7 @@ var recordingSchema = external_exports.object({
15236
15644
  })
15237
15645
  });
15238
15646
  var agentOutcomeSchema = external_exports.object({
15647
+ ...interactionReportFields,
15239
15648
  outcome: external_exports.enum(["walkthrough", "passed", "failed", "inconclusive"]),
15240
15649
  summary: external_exports.string().min(1).max(1e3),
15241
15650
  evidence: external_exports.string().max(1e3)
@@ -15323,15 +15732,15 @@ async function attachCursorOverlay(page, cdp, input) {
15323
15732
  const appearance = cursorAppearanceSchema.parse(input ?? {});
15324
15733
  if (!appearance.enabled) return async () => {
15325
15734
  };
15326
- const key = `__rillCursor_${crypto.randomUUID().replaceAll("-", "")}`;
15327
- const source = `(${installCursorOverlay.toString()})(${JSON.stringify({ ...appearance, key })})`;
15735
+ const key2 = `__rillCursor_${crypto.randomUUID().replaceAll("-", "")}`;
15736
+ const source = `(${installCursorOverlay.toString()})(${JSON.stringify({ ...appearance, key: key2 })})`;
15328
15737
  await cdp.send("Page.enable");
15329
15738
  const { identifier } = await cdp.send("Page.addScriptToEvaluateOnNewDocument", { source });
15330
15739
  let stopped = false;
15331
- const removeFromFrame = (frame) => frame.evaluate((key2) => {
15332
- const cleanup2 = window[key2];
15740
+ const removeFromFrame = (frame) => frame.evaluate((key3) => {
15741
+ const cleanup2 = window[key3];
15333
15742
  if (typeof cleanup2 === "function") cleanup2();
15334
- }, key).catch(() => void 0);
15743
+ }, key2).catch(() => void 0);
15335
15744
  const onNavigation = (frame) => {
15336
15745
  if (stopped) return;
15337
15746
  void frame.evaluate(source).then(() => {
@@ -15356,10 +15765,10 @@ async function attachCursorOverlay(page, cdp, input) {
15356
15765
 
15357
15766
  // ../recorder/src/session.ts
15358
15767
  import { chromium } from "playwright-core";
15359
- import { spawn as spawn4 } from "child_process";
15768
+ import { spawn as spawn6 } from "child_process";
15360
15769
  import { createServer } from "net";
15361
- import { mkdir as mkdir4, mkdtemp, rm as rm3, writeFile as writeFile3 } from "fs/promises";
15362
- import { join as join7 } from "path";
15770
+ import { mkdir as mkdir6, mkdtemp, rm as rm5, writeFile as writeFile5 } from "fs/promises";
15771
+ import { join as join9 } from "path";
15363
15772
  import { tmpdir as tmpdir2 } from "os";
15364
15773
 
15365
15774
  // ../recorder/src/bundle.ts
@@ -15385,11 +15794,16 @@ async function writeBundle(bundle, outputPath) {
15385
15794
 
15386
15795
  // ../recorder/src/environment.ts
15387
15796
  import { existsSync } from "fs";
15388
- function findChromeExecutable() {
15389
- if (process.env.RILL_CHROME_PATH) return process.env.RILL_CHROME_PATH;
15390
- if (process.platform === "darwin") return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
15797
+ import { win32 as win323 } from "path";
15798
+ function findChromeExecutable(platform = process.platform, env = process.env, exists = existsSync) {
15799
+ if (env.RILL_CHROME_PATH) return env.RILL_CHROME_PATH;
15800
+ if (platform === "darwin") return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
15801
+ if (platform === "win32") {
15802
+ const candidates2 = [env.PROGRAMFILES ?? env.ProgramFiles, env["PROGRAMFILES(X86)"] ?? env["ProgramFiles(x86)"], env.LOCALAPPDATA].filter((root) => Boolean(root)).map((root) => win323.join(root, "Google", "Chrome", "Application", "chrome.exe"));
15803
+ return candidates2.find(exists) ?? win323.join(env.PROGRAMFILES ?? env.ProgramFiles ?? "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe");
15804
+ }
15391
15805
  const candidates = ["/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium", "/usr/bin/chromium-browser"];
15392
- return candidates.find((candidate) => existsSync(candidate)) ?? "google-chrome";
15806
+ return candidates.find(exists) ?? "google-chrome";
15393
15807
  }
15394
15808
 
15395
15809
  // ../recorder/src/redaction.ts
@@ -15401,7 +15815,7 @@ function sanitizeUrl(input) {
15401
15815
  url2.username = "";
15402
15816
  url2.password = "";
15403
15817
  url2.hash = "";
15404
- for (const key of [...url2.searchParams.keys()]) url2.searchParams.set(key, "[REDACTED]");
15818
+ for (const key2 of [...url2.searchParams.keys()]) url2.searchParams.set(key2, "[REDACTED]");
15405
15819
  return url2.toString();
15406
15820
  } catch {
15407
15821
  return "[invalid-url]";
@@ -15432,16 +15846,16 @@ function redactJson(value, depth = 0) {
15432
15846
  if (depth > 12) return "[TRUNCATED]";
15433
15847
  if (Array.isArray(value)) return value.slice(0, 100).map((item) => redactJson(item, depth + 1));
15434
15848
  if (value && typeof value === "object") {
15435
- return Object.fromEntries(Object.entries(value).slice(0, 200).map(([key, child]) => [key, secretKeyPattern.test(key) ? "[REDACTED]" : redactJson(child, depth + 1)]));
15849
+ return Object.fromEntries(Object.entries(value).slice(0, 200).map(([key2, child]) => [key2, secretKeyPattern.test(key2) ? "[REDACTED]" : redactJson(child, depth + 1)]));
15436
15850
  }
15437
15851
  if (typeof value === "string") return redactText(value, 8e3);
15438
15852
  return value;
15439
15853
  }
15440
15854
 
15441
15855
  // ../recorder/src/encoding.ts
15442
- import { open as open2, writeFile, mkdir as mkdir2 } from "fs/promises";
15443
- import { spawn as spawn3 } from "child_process";
15444
- import { join as join5 } from "path";
15856
+ import { open as open2, writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
15857
+ import { spawn as spawn5 } from "child_process";
15858
+ import { join as join7 } from "path";
15445
15859
  var active = 0;
15446
15860
  var waiting = [];
15447
15861
  function encodingConcurrency() {
@@ -15449,8 +15863,22 @@ function encodingConcurrency() {
15449
15863
  if (!Number.isInteger(n) || n < 1 || n > 8) throw new Error("Invalid RILL_ENCODING_CONCURRENCY");
15450
15864
  return n;
15451
15865
  }
15452
- async function withEncodingSlot(work) {
15453
- if (active >= encodingConcurrency()) await new Promise((resolve) => waiting.push(resolve));
15866
+ async function withEncodingSlot(work, signal) {
15867
+ signal?.throwIfAborted();
15868
+ if (active >= encodingConcurrency()) await new Promise((resolve, reject) => {
15869
+ const granted = () => {
15870
+ signal?.removeEventListener("abort", abort);
15871
+ resolve();
15872
+ };
15873
+ const abort = () => {
15874
+ const i = waiting.indexOf(granted);
15875
+ if (i >= 0) waiting.splice(i, 1);
15876
+ reject(new Error("encoding_aborted"));
15877
+ };
15878
+ waiting.push(granted);
15879
+ signal?.addEventListener("abort", abort, { once: true });
15880
+ if (signal?.aborted) abort();
15881
+ });
15454
15882
  else active++;
15455
15883
  try {
15456
15884
  return await work();
@@ -15471,11 +15899,15 @@ async function encodeFrames(directory, manifest, output, signal) {
15471
15899
  lines.push(`file 'frame-${String(i).padStart(6, "0")}.jpg'`, `duration ${Math.max(1 / 60, duration3).toFixed(6)}`);
15472
15900
  }
15473
15901
  lines.push(`file 'frame-${String(manifest.frames.length - 1).padStart(6, "0")}.jpg'`);
15474
- await writeFile(join5(directory, "frames.ffconcat"), lines.join("\n") + "\n", { mode: 384 });
15902
+ await writeFile3(join7(directory, "frames.ffconcat"), lines.join("\n") + "\n", { mode: 384 });
15475
15903
  const threads = Number(process.env.RILL_FFMPEG_THREADS ?? 1);
15476
15904
  if (!Number.isInteger(threads) || threads < 1 || threads > 16) throw new Error("Invalid RILL_FFMPEG_THREADS");
15905
+ signal?.throwIfAborted();
15906
+ const dimensions = `scale=${manifest.width}:${manifest.height}:force_original_aspect_ratio=decrease,pad=${manifest.width}:${manifest.height}:(ow-iw)/2:(oh-ih)/2,setsar=1`;
15907
+ const filters = output.endsWith(".mp4") ? `${dimensions},fps=30,tpad=stop_mode=clone:stop_duration=1` : dimensions;
15908
+ const codec2 = output.endsWith(".mp4") ? ["-c:v", "libx264", "-preset", "veryfast", "-crf", "23", "-movflags", "+faststart", "-fps_mode", "cfr", "-t", String(Math.min(60, manifest.durationSeconds))] : ["-c:v", "libvpx-vp9", "-deadline", "good", "-cpu-used", "4", "-b:v", "0", "-crf", "32"];
15477
15909
  await new Promise((resolve, reject) => {
15478
- const child = spawn3(process.env.RILL_FFMPEG ?? "ffmpeg", ["-hide_banner", "-loglevel", "error", "-protocol_whitelist", "file", "-f", "concat", "-safe", "1", "-i", "frames.ffconcat", "-vsync", "vfr", "-pix_fmt", "yuv420p", "-c:v", "libvpx-vp9", "-deadline", "good", "-cpu-used", "4", "-threads", String(threads), "-b:v", "0", "-crf", "32", "-y", output], { cwd: directory, stdio: ["ignore", "ignore", "pipe"] });
15910
+ const child = spawn5(process.env.RILL_FFMPEG ?? "ffmpeg", ["-hide_banner", "-loglevel", "error", "-protocol_whitelist", "file", "-f", "concat", "-safe", "1", "-i", "frames.ffconcat", "-vsync", "vfr", "-pix_fmt", "yuv420p", "-vf", filters, ...codec2, "-threads", String(threads), "-y", output], { cwd: directory, windowsHide: true, stdio: ["ignore", "ignore", "pipe"] });
15479
15911
  let stderr = "";
15480
15912
  child.stderr.on("data", (chunk) => {
15481
15913
  stderr = (stderr + String(chunk)).slice(-2e3);
@@ -15483,6 +15915,7 @@ async function encodeFrames(directory, manifest, output, signal) {
15483
15915
  const abort = () => child.kill("SIGKILL");
15484
15916
  const timer = setTimeout(abort, 20 * 6e4);
15485
15917
  signal?.addEventListener("abort", abort, { once: true });
15918
+ if (signal?.aborted) abort();
15486
15919
  const clean = () => {
15487
15920
  clearTimeout(timer);
15488
15921
  signal?.removeEventListener("abort", abort);
@@ -15496,12 +15929,12 @@ async function encodeFrames(directory, manifest, output, signal) {
15496
15929
  code === 0 ? resolve() : reject(new Error(`ffmpeg failed (${code}): ${stderr}`));
15497
15930
  });
15498
15931
  });
15499
- });
15932
+ }, signal);
15500
15933
  }
15501
15934
 
15502
15935
  // ../recorder/src/screencast.ts
15503
- import { mkdir as mkdir3, writeFile as writeFile2, readFile as readFile4, open as open3 } from "fs/promises";
15504
- import { join as join6 } from "path";
15936
+ import { mkdir as mkdir5, writeFile as writeFile4, readFile as readFile6, open as open3 } from "fs/promises";
15937
+ import { join as join8 } from "path";
15505
15938
  var ScreencastRecorder = class {
15506
15939
  constructor(session, directory, width, height) {
15507
15940
  this.session = session;
@@ -15509,6 +15942,10 @@ var ScreencastRecorder = class {
15509
15942
  this.width = width;
15510
15943
  this.height = height;
15511
15944
  }
15945
+ session;
15946
+ directory;
15947
+ width;
15948
+ height;
15512
15949
  frames = [];
15513
15950
  pendingWrites = /* @__PURE__ */ new Set();
15514
15951
  started = false;
@@ -15516,7 +15953,7 @@ var ScreencastRecorder = class {
15516
15953
  frameBytes = 0;
15517
15954
  captureError;
15518
15955
  async start() {
15519
- await mkdir3(this.directory, { recursive: true, mode: 448 });
15956
+ await mkdir5(this.directory, { recursive: true, mode: 448 });
15520
15957
  this.session.on("Page.screencastFrame", (payload) => {
15521
15958
  const bytes = Buffer.from(payload.data, "base64");
15522
15959
  if (this.frameCounter >= 36e3 || this.frameBytes + bytes.length > ENCODING_MAX_BYTES || bytes.length > ENCODING_PART_BYTES) {
@@ -15525,9 +15962,9 @@ var ScreencastRecorder = class {
15525
15962
  return;
15526
15963
  }
15527
15964
  this.frameBytes += bytes.length;
15528
- const path = join6(this.directory, `frame-${String(this.frameCounter).padStart(6, "0")}.jpg`);
15965
+ const path = join8(this.directory, `frame-${String(this.frameCounter).padStart(6, "0")}.jpg`);
15529
15966
  this.frameCounter += 1;
15530
- const write = writeFile2(path, bytes, { mode: 384 }).then(() => {
15967
+ const write = writeFile4(path, bytes, { mode: 384 }).then(() => {
15531
15968
  this.frames.push({ path, timestamp: payload.metadata.timestamp ?? Date.now() / 1e3 });
15532
15969
  }).catch((error51) => {
15533
15970
  this.captureError = error51 instanceof Error ? error51 : new Error(String(error51));
@@ -15541,7 +15978,7 @@ var ScreencastRecorder = class {
15541
15978
  await this.session.send("Page.startScreencast", { format: "jpeg", quality: 82, maxWidth: this.width, maxHeight: this.height, everyNthFrame: 1 });
15542
15979
  this.started = true;
15543
15980
  }
15544
- async stop(outputPath, expectedDurationSeconds, captureOnly = false) {
15981
+ async stop(outputPath, expectedDurationSeconds, captureOnly = false, signal) {
15545
15982
  if (this.started) await this.session.send("Page.stopScreencast").catch(() => void 0);
15546
15983
  await new Promise((resolve) => setTimeout(resolve, 120));
15547
15984
  await Promise.all(this.pendingWrites);
@@ -15549,10 +15986,10 @@ var ScreencastRecorder = class {
15549
15986
  if (!this.frames.length) throw new Error("No video frames were captured.");
15550
15987
  const ordered = this.frames.toSorted((a, b) => a.timestamp - b.timestamp);
15551
15988
  const frames = [];
15552
- const bundle = await open3(join6(this.directory, "frames.bin"), "w", 384);
15989
+ const bundle = await open3(join8(this.directory, "frames.bin"), "w", 384);
15553
15990
  try {
15554
15991
  for (const frame of ordered) {
15555
- const bytes = await readFile4(frame.path);
15992
+ const bytes = await readFile6(frame.path);
15556
15993
  await bundle.writeFile(bytes);
15557
15994
  frames.push({ timestamp: frame.timestamp, size: bytes.length });
15558
15995
  }
@@ -15560,8 +15997,8 @@ var ScreencastRecorder = class {
15560
15997
  await bundle.close();
15561
15998
  }
15562
15999
  const manifest = frameManifestSchema.parse({ version: 1, durationSeconds: Math.min(600, expectedDurationSeconds), width: this.width, height: this.height, frames });
15563
- await writeFile2(join6(this.directory, "capture-manifest.json"), JSON.stringify(manifest), { mode: 384 });
15564
- if (!captureOnly) await encodeFrames(this.directory, manifest, outputPath);
16000
+ await writeFile4(join8(this.directory, "capture-manifest.json"), JSON.stringify(manifest), { mode: 384 });
16001
+ if (!captureOnly) await encodeFrames(this.directory, manifest, outputPath, signal);
15565
16002
  return { frameCount: this.frames.length, outputPath };
15566
16003
  }
15567
16004
  };
@@ -15585,6 +16022,7 @@ var RecordingSession = class _RecordingSession {
15585
16022
  requestEvents = /* @__PURE__ */ new Map();
15586
16023
  stopPromise;
15587
16024
  maxTimer;
16025
+ maxDurationSeconds;
15588
16026
  removeCursorOverlay;
15589
16027
  diagnosticsPaused = false;
15590
16028
  secretValues = [];
@@ -15595,7 +16033,10 @@ var RecordingSession = class _RecordingSession {
15595
16033
  bodyBytes = 0;
15596
16034
  truncated = false;
15597
16035
  constructor(options, runtime) {
16036
+ this.maxDurationSeconds = options.maxDurationSeconds ?? 600;
15598
16037
  this.encoding = options.encoding ?? "local";
16038
+ this.videoFormat = options.videoFormat ?? "webm";
16039
+ this.captureSignal = options.signal;
15599
16040
  this.recordingId = options.recordingId;
15600
16041
  this.cdpUrl = runtime.cdpUrl;
15601
16042
  this.browser = runtime.browser;
@@ -15609,15 +16050,17 @@ var RecordingSession = class _RecordingSession {
15609
16050
  static async start(options) {
15610
16051
  const width = options.width ?? 1280;
15611
16052
  const height = options.height ?? 720;
15612
- await mkdir4(options.outputDirectory, { recursive: true, mode: 448 });
15613
- if (options.encoding === "remote") await writeFile3(join7(options.outputDirectory, "remote-capture.json"), JSON.stringify({ expiresAt: Date.now() + 864e5 }), { mode: 384 });
15614
- const runtime = options.cdpUrl ? await connectAttached(options.cdpUrl, options.outputDirectory) : await launchManaged({ headed: options.headed ?? false, width, height, outputDirectory: options.outputDirectory, proxyServer: options.proxyServer });
16053
+ await mkdir6(options.outputDirectory, { recursive: true, mode: 448 });
16054
+ if (options.encoding === "remote") await writeFile5(join9(options.outputDirectory, "remote-capture.json"), JSON.stringify({ expiresAt: Date.now() + 864e5 }), { mode: 384 });
16055
+ const runtime = options.cdpUrl ? await connectAttached(options.cdpUrl, options.outputDirectory) : await launchManaged({ headed: options.headed ?? false, width, height, outputDirectory: options.outputDirectory, proxyServer: options.proxyServer, blockServiceWorkers: options.blockServiceWorkers });
15615
16056
  const cdpSession = await runtime.context.newCDPSession(runtime.page);
15616
- const screencast = new ScreencastRecorder(cdpSession, join7(options.outputDirectory, "frames"), width, height);
16057
+ const screencast = new ScreencastRecorder(cdpSession, join9(options.outputDirectory, "frames"), width, height);
15617
16058
  const session = new _RecordingSession(options, { ...runtime, screencast });
15618
16059
  try {
15619
16060
  await session.attachDiagnostics(options.captureNetworkBodies ?? false);
15620
- session.removeCursorOverlay = await attachCursorOverlay(runtime.page, cdpSession, options.cursorAppearance);
16061
+ if (!options.hideCursor) session.removeCursorOverlay = await attachCursorOverlay(runtime.page, cdpSession, options.cursorAppearance);
16062
+ await options.beforeCapture?.(runtime.page);
16063
+ session.startedAt = /* @__PURE__ */ new Date();
15621
16064
  await screencast.start();
15622
16065
  if (options.url) await session.page.goto(options.url, { waitUntil: "domcontentloaded" });
15623
16066
  } catch (error51) {
@@ -15626,7 +16069,7 @@ var RecordingSession = class _RecordingSession {
15626
16069
  }
15627
16070
  session.maxTimer = setTimeout(() => {
15628
16071
  void session.stop("max_duration").catch(() => void 0);
15629
- }, (options.maxDurationSeconds ?? 600) * 1e3);
16072
+ }, Math.max(0, session.startedAt.getTime() + session.maxDurationSeconds * 1e3 - Date.now()));
15630
16073
  return session;
15631
16074
  }
15632
16075
  // In-process agents share this connection so exposed diagnostic bindings are
@@ -15635,7 +16078,17 @@ var RecordingSession = class _RecordingSession {
15635
16078
  return this.page;
15636
16079
  }
15637
16080
  status() {
15638
- return { recordingId: this.recordingId, cdpUrl: this.cdpUrl, startedAt: this.startedAt.toISOString(), status: this.stopPromise ? "finalizing" : "recording" };
16081
+ const elapsedSeconds = Math.max(0, (Date.now() - this.startedAt.getTime()) / 1e3);
16082
+ return {
16083
+ recordingId: this.recordingId,
16084
+ cdpUrl: this.cdpUrl,
16085
+ startedAt: this.startedAt.toISOString(),
16086
+ maxDurationSeconds: this.maxDurationSeconds,
16087
+ elapsedSeconds,
16088
+ remainingSeconds: this.stopPromise ? 0 : Math.max(0, this.maxDurationSeconds - elapsedSeconds),
16089
+ stopsAt: new Date(this.startedAt.getTime() + this.maxDurationSeconds * 1e3).toISOString(),
16090
+ status: this.stopPromise ? "finalizing" : "recording"
16091
+ };
15639
16092
  }
15640
16093
  async stop(reason = "agent_request") {
15641
16094
  this.stopPromise ??= this.finalize(reason);
@@ -15645,8 +16098,8 @@ var RecordingSession = class _RecordingSession {
15645
16098
  if (this.maxTimer) clearTimeout(this.maxTimer);
15646
16099
  const durationSeconds = Math.max(0.1, (Date.now() - this.startedAt.getTime()) / 1e3);
15647
16100
  const outputDirectory = this.outputDirectory;
15648
- const videoPath = join7(outputDirectory, "recording.webm");
15649
- const diagnosticsPath = join7(outputDirectory, "diagnostics-v1.ndjson.gz");
16101
+ const videoPath = join9(outputDirectory, `recording.${this.videoFormat}`);
16102
+ const diagnosticsPath = join9(outputDirectory, "diagnostics-v1.ndjson.gz");
15650
16103
  const bundle = {
15651
16104
  schemaVersion: 1,
15652
16105
  capturePolicyVersion: "safe-default-v1",
@@ -15663,21 +16116,23 @@ var RecordingSession = class _RecordingSession {
15663
16116
  try {
15664
16117
  const safeBundle = JSON.parse(this.secretValues.reduce((text, secret) => text.split(JSON.stringify(secret).slice(1, -1)).join("[redacted]"), JSON.stringify(bundle)));
15665
16118
  await writeBundle(safeBundle, diagnosticsPath);
15666
- await this.screencast.stop(videoPath, durationSeconds, this.encoding === "remote");
16119
+ await this.screencast.stop(videoPath, durationSeconds, this.encoding === "remote", this.captureSignal);
15667
16120
  } finally {
15668
16121
  await this.removeCursorOverlay?.();
15669
16122
  if (this.browserProcess) {
15670
16123
  await this.browser.close().catch(() => void 0);
15671
16124
  await terminateBrowser(this.browserProcess);
15672
- if (this.profileDirectory) await rm3(this.profileDirectory, { recursive: true, force: true }).catch(() => void 0);
16125
+ if (this.profileDirectory) await rm5(this.profileDirectory, { recursive: true, force: true }).catch(() => void 0);
15673
16126
  }
15674
16127
  }
15675
16128
  const consoleErrors = this.consoleEvents.filter((event) => event.severity === "error").length;
15676
16129
  const failedRequests = this.networkEvents.filter((event) => event.failureReason).length;
15677
16130
  const httpErrors = this.networkEvents.filter((event) => (event.status ?? 0) >= 400).length;
15678
- return { recordingId: this.recordingId, videoPath, ...this.encoding === "remote" ? { frameDirectory: join7(outputDirectory, "frames") } : {}, diagnosticsPath, durationSeconds, stoppedReason: reason, diagnostics: { consoleErrors, failedRequests, httpErrors, truncated: this.truncated, bodyCaptureEnabled: this.captureNetworkBodies } };
16131
+ return { recordingId: this.recordingId, videoPath, ...this.encoding === "remote" ? { frameDirectory: join9(outputDirectory, "frames") } : {}, diagnosticsPath, durationSeconds, stoppedReason: reason, diagnostics: { consoleErrors, failedRequests, httpErrors, truncated: this.truncated, bodyCaptureEnabled: this.captureNetworkBodies } };
15679
16132
  }
15680
16133
  captureNetworkBodies = false;
16134
+ captureSignal;
16135
+ videoFormat = "webm";
15681
16136
  encoding = "local";
15682
16137
  outputDirectory = "";
15683
16138
  async attachDiagnostics(captureNetworkBodies) {
@@ -15787,7 +16242,7 @@ var RecordingSession = class _RecordingSession {
15787
16242
  };
15788
16243
  async function launchManaged(options) {
15789
16244
  const port = await getAvailablePort();
15790
- const profileDirectory = await mkdtemp(join7(tmpdir2(), "rill-chrome-"));
16245
+ const profileDirectory = await mkdtemp(join9(tmpdir2(), "rill-chrome-"));
15791
16246
  const executable = findChromeExecutable();
15792
16247
  const args = [
15793
16248
  `--remote-debugging-port=${port}`,
@@ -15804,19 +16259,19 @@ async function launchManaged(options) {
15804
16259
  ...options.headed ? [] : ["--headless=new", "--hide-scrollbars"],
15805
16260
  "about:blank"
15806
16261
  ];
15807
- const env = options.proxyServer ? Object.fromEntries(Object.entries(process.env).filter(([key]) => ["PATH", "HOME", "TMPDIR", "DISPLAY", "LANG", "LD_LIBRARY_PATH", "XDG_RUNTIME_DIR"].includes(key))) : process.env;
15808
- const browserProcess = spawn4(executable, args, { stdio: ["ignore", "ignore", "pipe"], detached: false, env });
16262
+ const env = options.proxyServer ? Object.fromEntries(Object.entries(process.env).filter(([key2]) => ["PATH", "HOME", "TMPDIR", "DISPLAY", "LANG", "LD_LIBRARY_PATH", "XDG_RUNTIME_DIR"].includes(key2))) : process.env;
16263
+ const browserProcess = spawn6(executable, args, { stdio: ["ignore", "ignore", "pipe"], detached: false, windowsHide: true, env });
15809
16264
  const cdpUrl = `http://127.0.0.1:${port}`;
15810
16265
  try {
15811
16266
  await waitForCdp(cdpUrl, browserProcess);
15812
16267
  const browser = await chromium.connectOverCDP(cdpUrl, { artifactsDir: options.outputDirectory, isLocal: true });
15813
- const context = browser.contexts()[0];
16268
+ const context = options.blockServiceWorkers ? await browser.newContext({ serviceWorkers: "block" }) : browser.contexts()[0];
15814
16269
  const page = context.pages()[0] ?? await context.newPage();
15815
16270
  await page.setViewportSize({ width: options.width, height: options.height });
15816
16271
  return { cdpUrl, browser, context, page, browserProcess, profileDirectory };
15817
16272
  } catch (error51) {
15818
16273
  await terminateBrowser(browserProcess);
15819
- await rm3(profileDirectory, { recursive: true, force: true }).catch(() => void 0);
16274
+ await rm5(profileDirectory, { recursive: true, force: true }).catch(() => void 0);
15820
16275
  throw error51;
15821
16276
  }
15822
16277
  }
@@ -15866,16 +16321,31 @@ async function waitForCdp(cdpUrl, browserProcess) {
15866
16321
  var sessions = /* @__PURE__ */ new Map();
15867
16322
  var lastActivity = Date.now();
15868
16323
  async function runDaemon() {
15869
- const socketPath = daemonSocketPath();
15870
- await mkdir5(dirname(socketPath), { recursive: true, mode: 448 });
15871
- await mkdir5(recordingTempRoot(), { recursive: true, mode: 448 });
16324
+ if (process.platform === "win32" && process.env.RILL_SOCKET) throw new Error("RILL_SOCKET overrides are not supported by authenticated Windows IPC.");
16325
+ const identity = process.platform === "win32" ? await windowsIpcIdentity() : void 0;
16326
+ const socketPath = identity?.pipe ?? daemonSocketPath();
16327
+ await mkdir7(identity ? dirname(daemonStatePath()) : dirname(socketPath), { recursive: true, mode: 448 });
16328
+ await mkdir7(recordingTempRoot(), { recursive: true, mode: 448 });
15872
16329
  await restoreRecoverableSessions();
15873
16330
  await purgeExpiredCaptures();
15874
- await unlink(socketPath).catch((error51) => {
16331
+ if (!identity) await unlink(socketPath).catch((error51) => {
15875
16332
  if (error51.code !== "ENOENT") throw error51;
15876
16333
  });
15877
- const server = createServer2((request3, response) => void route(request3, response));
15878
- server.listen(socketPath, () => process.platform !== "win32" && import("fs/promises").then(({ chmod }) => chmod(socketPath, 384)));
16334
+ const server = createServer2((request3, response) => {
16335
+ if (identity && !acceptsWindowsIpcToken(identity.token, request3.headers.authorization)) {
16336
+ request3.resume();
16337
+ json2(response, 401, { error: { code: "authentication_required", message: "Recorder authentication required." } });
16338
+ return;
16339
+ }
16340
+ void route(request3, response);
16341
+ });
16342
+ await new Promise((resolve, reject) => {
16343
+ server.once("error", reject);
16344
+ server.listen(socketPath, () => {
16345
+ if (identity) resolve();
16346
+ else void chmod(socketPath, 384).then(resolve, reject);
16347
+ });
16348
+ });
15879
16349
  const idleTimer = setInterval(() => {
15880
16350
  void (async () => {
15881
16351
  await purgeExpiredCaptures();
@@ -15888,7 +16358,7 @@ async function runDaemon() {
15888
16358
  const close = () => {
15889
16359
  clearInterval(idleTimer);
15890
16360
  server.close();
15891
- void unlink(socketPath).catch(() => void 0);
16361
+ if (!identity) void unlink(socketPath).catch(() => void 0);
15892
16362
  };
15893
16363
  process.once("SIGTERM", close);
15894
16364
  process.once("SIGINT", close);
@@ -15902,12 +16372,12 @@ async function route(request3, response) {
15902
16372
  try {
15903
16373
  const input = await readJson(request3);
15904
16374
  if (!/^rec_[a-zA-Z0-9_-]+$/.test(input.recordingId)) return json2(response, 400, { error: { code: "validation_failed", message: "recordingId is required." } });
15905
- const outputDirectory = join8(recordingTempRoot(), input.recordingId);
16375
+ const outputDirectory = join10(recordingTempRoot(), input.recordingId);
15906
16376
  const session = await RecordingSession.start({ ...input, outputDirectory });
15907
16377
  const status = session.status();
15908
16378
  const entry = { session, base: { recordingId: status.recordingId, cdpUrl: status.cdpUrl, startedAt: status.startedAt }, state: "recording" };
15909
16379
  sessions.set(input.recordingId, entry);
15910
- const maxTimer = setTimeout(() => void finalizeEntry(entry, "max_duration"), (input.maxDurationSeconds ?? 600) * 1e3 + 250);
16380
+ const maxTimer = setTimeout(() => void finalizeEntry(entry, "max_duration"), Math.max(0, Date.parse(status.stopsAt) - Date.now()) + 250);
15911
16381
  maxTimer.unref();
15912
16382
  await persistState();
15913
16383
  return json2(response, 201, session.status());
@@ -15942,11 +16412,18 @@ async function finalizeEntry(entry, reason) {
15942
16412
  await persistState();
15943
16413
  }
15944
16414
  function serializeEntry(entry) {
15945
- return { ...entry.base, status: entry.state, result: entry.result, error: entry.error };
16415
+ return {
16416
+ ...entry.base,
16417
+ ...entry.session?.status(),
16418
+ status: entry.state,
16419
+ ...entry.state !== "recording" ? { remainingSeconds: 0, ...entry.result ? { elapsedSeconds: entry.result.durationSeconds } : {} } : {},
16420
+ result: entry.result,
16421
+ error: entry.error
16422
+ };
15946
16423
  }
15947
16424
  async function restoreRecoverableSessions() {
15948
16425
  try {
15949
- const value = JSON.parse(await readFile5(daemonStatePath(), "utf8"));
16426
+ const value = JSON.parse(await readFile7(daemonStatePath(), "utf8"));
15950
16427
  for (const [id, entry] of Object.entries(value)) {
15951
16428
  if (entry.status !== "ready" && entry.status !== "failed") continue;
15952
16429
  sessions.set(id, { base: { recordingId: entry.recordingId, cdpUrl: entry.cdpUrl, startedAt: entry.startedAt }, state: entry.status, result: entry.result, error: entry.error });
@@ -15957,9 +16434,9 @@ async function restoreRecoverableSessions() {
15957
16434
  async function persistState() {
15958
16435
  const state = Object.fromEntries([...sessions.entries()].map(([id, entry]) => [id, serializeEntry(entry)]));
15959
16436
  const temporary = daemonStatePath() + "." + crypto.randomUUID() + ".tmp";
15960
- await writeFile4(temporary, `${JSON.stringify(state, null, 2)}
16437
+ await writeFile6(temporary, `${JSON.stringify(state, null, 2)}
15961
16438
  `, { mode: 384 });
15962
- await rename(temporary, daemonStatePath());
16439
+ await rename2(temporary, daemonStatePath());
15963
16440
  }
15964
16441
  async function readJson(request3) {
15965
16442
  const chunks = [];
@@ -15980,7 +16457,7 @@ async function purgeExpiredCaptures() {
15980
16457
  const removed = await purgeExpiredRemoteCaptures(recordingTempRoot());
15981
16458
  for (const id of removed) sessions.delete(id);
15982
16459
  let changed = removed.length > 0;
15983
- for (const [id, entry] of sessions) if (entry.result?.frameDirectory && !await stat3(entry.result.frameDirectory).catch(() => null)) {
16460
+ for (const [id, entry] of sessions) if (entry.result?.frameDirectory && !await stat4(entry.result.frameDirectory).catch(() => null)) {
15984
16461
  sessions.delete(id);
15985
16462
  changed = true;
15986
16463
  }
@@ -16092,7 +16569,7 @@ async function withRunFeedback(session, start) {
16092
16569
  }
16093
16570
 
16094
16571
  // src/doctor.ts
16095
- import { spawn as spawn5 } from "child_process";
16572
+ import { spawn as spawn7 } from "child_process";
16096
16573
 
16097
16574
  // src/version.ts
16098
16575
  import { createRequire } from "module";
@@ -16153,7 +16630,7 @@ async function runDoctor(apiUrl, overrides = {}) {
16153
16630
  credentialError = error51;
16154
16631
  }
16155
16632
  if (credentialError) {
16156
- checks.push({ name: "authentication", status: "fail", message: errorMessage(credentialError), recovery: "Unlock the OS credential store, or use RILL_TOKEN. Windows local recording remains unsupported." });
16633
+ checks.push({ name: "authentication", status: "fail", message: errorMessage(credentialError), recovery: "Unlock the OS credential store, or use RILL_TOKEN. On Windows, use the same Windows account that saved the credential." });
16157
16634
  } else if (!token) {
16158
16635
  checks.push({ name: "authentication", status: health.anonymousAccess ? "warn" : "fail", message: health.anonymousAccess ? "The first recording will create an anonymous identity in your OS credential store. Anonymous recordings expire after seven days." : "No Rill agent credential is available.", recovery: `Run \`rill --api-url ${apiUrl} login\` or set RILL_TOKEN.` });
16159
16636
  } else {
@@ -16168,7 +16645,7 @@ async function runDoctor(apiUrl, overrides = {}) {
16168
16645
  }
16169
16646
  checks.push(await executableCheck("chrome", dependencies.chromePath, ["--version"], dependencies.commandVersion, "Install Google Chrome or Chromium, or set RILL_CHROME_PATH to its executable."));
16170
16647
  if (dependencies.encoding === "local") {
16171
- checks.push(await executableCheck("ffmpeg", dependencies.ffmpegPath, ["-version"], dependencies.commandVersion, process.platform === "darwin" ? "Install ffmpeg with `brew install ffmpeg`, or set RILL_FFMPEG." : "Install ffmpeg using the system package manager, or set RILL_FFMPEG."));
16648
+ checks.push(await executableCheck("ffmpeg", dependencies.ffmpegPath, ["-version"], dependencies.commandVersion, dependencies.platform === "darwin" ? "Install ffmpeg with `brew install ffmpeg`, or set RILL_FFMPEG." : "Install ffmpeg using the system package manager, or set RILL_FFMPEG."));
16172
16649
  } else {
16173
16650
  checks.push({ name: "ffmpeg", status: "skip", message: "Remote encoding does not require local FFmpeg." });
16174
16651
  try {
@@ -16176,7 +16653,7 @@ async function runDoctor(apiUrl, overrides = {}) {
16176
16653
  if (!(token ? (await dependencies.encodingCapabilities(apiUrl, token)).enabled : health?.anonymousAccess && health.anonymousRemoteEncoding)) throw new Error("Remote encoding is not enabled for this workspace.");
16177
16654
  checks.push({ name: "encoding", status: "pass", message: token ? "Remote encoding is enabled." : "Remote encoding is available for the first anonymous recording." });
16178
16655
  } catch (error51) {
16179
- checks.push({ name: "encoding", status: "fail", message: errorMessage(error51), recovery: "Remote encoding must be enabled on the server before using the default recording workflow." });
16656
+ checks.push({ name: "encoding", status: "fail", message: errorMessage(error51), recovery: "Run doctor --encoding local to check local FFmpeg, then record start --encoding local, or ask the workspace owner to enable remote encoding." });
16180
16657
  }
16181
16658
  }
16182
16659
  return { schemaVersion: 1, status: checks.some((check2) => check2.status === "fail") ? "error" : "ok", apiUrl, checks, ...runFeedbackFields(feedbackEnabled) };
@@ -16191,7 +16668,7 @@ async function executableCheck(name, path, args, run2, recovery) {
16191
16668
  }
16192
16669
  function commandVersion(command, args) {
16193
16670
  return new Promise((resolve, reject) => {
16194
- const child = spawn5(command, args, { stdio: ["ignore", "pipe", "pipe"] });
16671
+ const child = spawn7(command, args, { stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
16195
16672
  const stdout = [];
16196
16673
  const stderr = [];
16197
16674
  const timeout = setTimeout(() => {
@@ -16252,16 +16729,24 @@ function formatCloudRun(run2) {
16252
16729
  }
16253
16730
 
16254
16731
  // src/recording-lifecycle.ts
16255
- async function startRecordingWithCompensation(remote, dependencies) {
16732
+ async function startRecordingWithCompensation(remote, dependencies, apiUrl = "https://userill.dev") {
16256
16733
  try {
16257
16734
  return await dependencies.startLocal();
16258
16735
  } catch (startupError) {
16259
16736
  try {
16260
16737
  await dependencies.cancelRemote(remote.recordingId, "local_start_failed");
16261
16738
  } catch (cleanupError) {
16262
- throw new AggregateError(
16263
- [startupError, cleanupError],
16264
- "Local recording startup failed and the remote reservation could not be cancelled. Run `rill record cancel <recording-id>`."
16739
+ const nextCommand = cliCommand(apiUrl, "record", "cancel", remote.recordingId);
16740
+ throw Object.assign(
16741
+ new AggregateError(
16742
+ [startupError, cleanupError],
16743
+ "browser_unavailable: Local startup failed and its remote reservation could not be cancelled."
16744
+ ),
16745
+ {
16746
+ code: "browser_unavailable",
16747
+ recovery: `Run ${nextCommand} to release this failed startup reservation.`,
16748
+ details: { recordingId: remote.recordingId, nextCommand }
16749
+ }
16265
16750
  );
16266
16751
  }
16267
16752
  throw startupError;
@@ -16279,7 +16764,16 @@ async function remoteAwareRecordingStatus(recordingId, dependencies, apiUrl = "h
16279
16764
  try {
16280
16765
  const local = await dependencies.localStatus(recordingId);
16281
16766
  const status = local.status === "ready" ? "captured" : local.status;
16282
- return { ...local, status, source: "local", ...recordingNextStep(apiUrl, recordingId, String(status)) };
16767
+ return {
16768
+ ...local,
16769
+ status,
16770
+ source: "local",
16771
+ ...recordingNextStep(apiUrl, recordingId, String(status)),
16772
+ ...status === "failed" ? {
16773
+ nextCommand: cliCommand(apiUrl, "record", "cancel", recordingId),
16774
+ message: "Local capture failed; the remote reservation may still hold a slot. Preserve any local draft you need, then cancel this failed attempt to release its slot. Cancellation does not delete local drafts."
16775
+ } : {}
16776
+ };
16283
16777
  } catch {
16284
16778
  return remoteView;
16285
16779
  }
@@ -16295,10 +16789,10 @@ function controlPlaneUrl(environmentUrl) {
16295
16789
  async function removeLocalCapture(id) {
16296
16790
  await ensureDaemon(process.argv[1]);
16297
16791
  const local = await recordingStatus(id).catch(() => null);
16298
- if (local?.result?.frameDirectory) await rm5(dirname2(local.result.videoPath), { recursive: true, force: true });
16792
+ if (local?.result?.frameDirectory) await rm7(dirname2(local.result.videoPath), { recursive: true, force: true });
16299
16793
  }
16300
16794
  function watchEncoding(apiUrl, id) {
16301
- const child = spawn6(process.execPath, [process.argv[1], "--api-url", apiUrl, "encoding-watch", id], { detached: true, stdio: "ignore", env: { ...process.env } });
16795
+ const child = spawn8(process.execPath, [process.argv[1], "--api-url", apiUrl, "encoding-watch", id], { detached: true, stdio: "ignore", env: { ...process.env }, windowsHide: true });
16302
16796
  child.on("error", () => void 0);
16303
16797
  child.unref();
16304
16798
  }
@@ -16320,7 +16814,7 @@ async function authorize(quiet = false) {
16320
16814
  process.stderr.write(`Open ${authorization.verificationUri} and enter ${authorization.userCode}
16321
16815
  `);
16322
16816
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
16323
- const child = spawn6(opener, [authorization.verificationUri], { stdio: "ignore", detached: true });
16817
+ const child = spawn8(opener, [authorization.verificationUri], { stdio: "ignore", detached: true });
16324
16818
  child.on("error", () => void 0);
16325
16819
  child.unref();
16326
16820
  const deadline = Date.now() + authorization.expiresIn * 1e3;
@@ -16406,7 +16900,7 @@ record2.command("start").option("--encoding <mode>", "remote or local", "remote"
16406
16900
  return startRecording({ encoding: commandOptions.encoding, recordingId: remote.recordingId, url: commandOptions.url, cdpUrl: commandOptions.cdpUrl, headed: Boolean(commandOptions.headed), width, height, maxDurationSeconds: Math.min(Number(commandOptions.maxDuration), remote.maxDurationSeconds ?? 600), captureNetworkBodies: remote.bodyCaptureEnabled, cursorAppearance: remote.cursorAppearance });
16407
16901
  },
16408
16902
  cancelRemote: (recordingId, reason) => client.cancelRecording(recordingId, reason)
16409
- });
16903
+ }, options.apiUrl);
16410
16904
  await client.markRecordingStarted(remote.recordingId, {
16411
16905
  cliVersion: CLI_VERSION,
16412
16906
  os: process.platform,
@@ -16414,7 +16908,7 @@ record2.command("start").option("--encoding <mode>", "remote or local", "remote"
16414
16908
  headed: Boolean(commandOptions.headed),
16415
16909
  attached: Boolean(commandOptions.cdpUrl)
16416
16910
  }).catch(() => void 0);
16417
- return { schemaVersion: 1, recordingId: remote.recordingId, status: local.status, cdpUrl: local.cdpUrl, browserAttachment: browserAttachment(local.cdpUrl), bodyCaptureEnabled: remote.bodyCaptureEnabled, anonymous: remote.anonymous ?? false, expiresAt: remote.expiresAt ?? null, maxDurationSeconds: Math.min(Number(commandOptions.maxDuration), remote.maxDurationSeconds ?? 600) };
16911
+ return { schemaVersion: 1, recordingId: remote.recordingId, status: local.status, cdpUrl: local.cdpUrl, browserAttachment: browserAttachment(local.cdpUrl), startedAt: local.startedAt, stopsAt: local.stopsAt, elapsedSeconds: local.elapsedSeconds, remainingSeconds: local.remainingSeconds, bodyCaptureEnabled: remote.bodyCaptureEnabled, anonymous: remote.anonymous ?? false, expiresAt: remote.expiresAt ?? null, maxDurationSeconds: Math.min(Number(commandOptions.maxDuration), remote.maxDurationSeconds ?? 600) };
16418
16912
  });
16419
16913
  emit(result, options.pretty);
16420
16914
  });
@@ -16435,11 +16929,11 @@ record2.command("stop").argument("<recording-id>").option("--no-wait").option("-
16435
16929
  const accepted = await submitFrameCapture(client, recordingId, local.result.frameDirectory);
16436
16930
  const result2 = commandOptions.wait && record2.opts().wait !== false ? await waitForEncoding(client, recordingId) : accepted;
16437
16931
  emit({ schemaVersion: 1, ...result2, ...recordingNextStep(options.apiUrl, recordingId, result2.status, { resumableFrames: true }), ...recordingFeedbackFields(recordingId, stopped?.feedbackRequested ?? false) }, options.pretty);
16438
- if (result2.status === "ready") await rm5(dirname2(local.result.videoPath), { recursive: true, force: true });
16932
+ if (result2.status === "ready") await rm7(dirname2(local.result.videoPath), { recursive: true, force: true });
16439
16933
  else watchEncoding(options.apiUrl, recordingId);
16440
16934
  return;
16441
16935
  }
16442
- const video = await stat4(local.result.videoPath);
16936
+ const video = await stat5(local.result.videoPath);
16443
16937
  const provisioned = await client.provisionVideo(recordingId, { sizeBytes: video.size, filename: basename(local.result.videoPath), uploadKind: "recorded_browser" });
16444
16938
  await Promise.all([client.uploadVideo(provisioned.uploadUrl, local.result.videoPath), client.uploadDiagnostics(recordingId, local.result.diagnosticsPath, local.result.diagnostics)]);
16445
16939
  const remote = commandOptions.wait === false || record2.opts().wait === false ? null : await client.waitUntilPlayable(recordingId);
@@ -16457,7 +16951,7 @@ record2.command("stop").argument("<recording-id>").option("--no-wait").option("-
16457
16951
  ...recordingFeedbackFields(recordingId, stopped?.feedbackRequested ?? false)
16458
16952
  };
16459
16953
  emit(result, options.pretty);
16460
- if (remote) await rm5(dirname2(local.result.videoPath), { recursive: true, force: true });
16954
+ if (remote) await rm7(dirname2(local.result.videoPath), { recursive: true, force: true });
16461
16955
  });
16462
16956
  record2.command("retry").argument("<recording-id>").option("--no-wait").action(async (recordingId, commandOptions) => {
16463
16957
  const options = program.opts();
@@ -16489,7 +16983,7 @@ program.command("encoding-watch", { hidden: true }).argument("<recording-id>").a
16489
16983
  if (result.status === "ready") {
16490
16984
  await ensureDaemon(process.argv[1]);
16491
16985
  const local = await recordingStatus(id).catch(() => null);
16492
- if (local?.result?.frameDirectory) await rm5(dirname2(local.result.videoPath), { recursive: true, force: true });
16986
+ if (local?.result?.frameDirectory) await rm7(dirname2(local.result.videoPath), { recursive: true, force: true });
16493
16987
  }
16494
16988
  });
16495
16989
  var feedback = program.command("feedback").description("Submit a short Rill product review for a recorded run.");
@@ -16544,7 +17038,7 @@ program.command("upload").argument("<video-file>").option("--title <title>").opt
16544
17038
  const options = program.opts();
16545
17039
  await ensureAnonymousCredential(options.apiUrl);
16546
17040
  const client = new ControlPlaneClient(options.apiUrl, () => loadCredential(options.apiUrl));
16547
- const file2 = await stat4(videoFile);
17041
+ const file2 = await stat5(videoFile);
16548
17042
  const recording = await client.createRecording({ title: commandOptions.title ?? basename(videoFile), sourceUrl: null });
16549
17043
  const upload = await client.provisionVideo(recording.recordingId, { sizeBytes: file2.size, filename: basename(videoFile), uploadKind: "existing_video" });
16550
17044
  await client.uploadVideo(upload.uploadUrl, videoFile);
@@ -16573,20 +17067,20 @@ comments.command("edit").argument("<share-url>").requiredOption("--comment <id>"
16573
17067
  var drafts = program.command("drafts");
16574
17068
  drafts.command("list").action(async () => {
16575
17069
  const options = program.opts();
16576
- const root = process.env.RILL_TEMP ?? `${process.env.TMPDIR ?? "/tmp"}/rill-recordings`;
17070
+ const root = process.env.RILL_TEMP ?? join11(tmpdir3(), "rill-recordings");
16577
17071
  const names = await readdir2(root).catch(() => []);
16578
17072
  emit({ schemaVersion: 1, drafts: names }, options.pretty);
16579
17073
  });
16580
17074
  drafts.command("purge").action(async () => {
16581
17075
  const options = program.opts();
16582
- const root = process.env.RILL_TEMP ?? `${process.env.TMPDIR ?? "/tmp"}/rill-recordings`;
17076
+ const root = process.env.RILL_TEMP ?? join11(tmpdir3(), "rill-recordings");
16583
17077
  const names = await readdir2(root).catch(() => []);
16584
17078
  let purged = 0;
16585
17079
  for (const name of names) {
16586
- const path = `${root}/${name}`;
16587
- const metadata = await stat4(path);
17080
+ const path = join11(root, name);
17081
+ const metadata = await stat5(path);
16588
17082
  if (Date.now() - metadata.mtimeMs > 24 * 60 * 60 * 1e3) {
16589
- await rm5(path, { recursive: true, force: true });
17083
+ await rm7(path, { recursive: true, force: true });
16590
17084
  purged += 1;
16591
17085
  }
16592
17086
  }