@runalabs/rill-cli 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -2
- package/dist/cli.js +355 -97
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @runalabs/rill-cli
|
|
2
2
|
|
|
3
|
+
## Runtime security requirement
|
|
4
|
+
|
|
5
|
+
Use Node.js **24.22.1 LTS or newer on a patched release line**. The minimum
|
|
6
|
+
supported versions are **22.23.4**, **24.22.1**, and **26.12.1**; Node 23, Node 25,
|
|
7
|
+
and prerelease builds are not supported. These releases address the October 2026
|
|
8
|
+
[eMudhra certificate compromise](https://emudhra.we-have-a-problem.com/).
|
|
9
|
+
|
|
10
|
+
Rill 0.1.20 checks Node before command actions, authentication, or telemetry.
|
|
11
|
+
An unsupported runtime receives `unsupported_node_version` and an upgrade step.
|
|
12
|
+
`rill doctor` reports that failure without contacting the API or reading saved
|
|
13
|
+
credentials. `rill --help` and `rill --version` remain available.
|
|
14
|
+
|
|
15
|
+
The CLI uses your installed Node and Chrome/Chromium. Updating Rill alone does
|
|
16
|
+
not replace their certificate stores. Keep Chrome/Chromium updated, and restart
|
|
17
|
+
long-running recorder processes after saving active recordings when updating
|
|
18
|
+
their runtimes. Doctor's browser version check confirms availability, not that
|
|
19
|
+
every certificate revocation is enforced. Rill does not disable TLS validation
|
|
20
|
+
or accept a custom CA workaround in place of the patched Node requirement.
|
|
21
|
+
|
|
3
22
|
Record a browser flow, keep its video and runtime evidence together, and return an inspectable share link. Use Rill to reproduce a bug, verify a change, or hand a browser run to another developer or agent.
|
|
4
23
|
|
|
5
24
|
Rill starts a local Chromium recorder. Your browser tool drives the returned CDP endpoint; Rill captures the selected page and uploads the result when you stop.
|
|
@@ -8,7 +27,7 @@ Rill starts a local Chromium recorder. Your browser tool drives the returned CDP
|
|
|
8
27
|
|
|
9
28
|
## Cloud agent runs
|
|
10
29
|
|
|
11
|
-
Invited workspaces can supply a URL and prompt. Cloud runs need Node.js
|
|
30
|
+
Invited workspaces can supply a URL and prompt. Cloud runs need a patched Node.js version listed above and a Rill login; Chrome and ffmpeg run on Rill's server.
|
|
12
31
|
|
|
13
32
|
```sh
|
|
14
33
|
rill record https://example.com -p "Open pricing and describe the plans."
|
|
@@ -29,7 +48,7 @@ Check `node --version`, `node -p "process.platform"`, and `rill --version` befor
|
|
|
29
48
|
|
|
30
49
|
If the CLI is missing, check npm with `npm ping --fetch-timeout=5000 --fetch-retries=0` before installation. See the [canonical skill](https://userill.dev/skill.md) for the bounded Rill connectivity check and Windows Terminal fallback. Report blocked network access instead of repeating installation attempts.
|
|
31
50
|
|
|
32
|
-
Browser recording runs on macOS, Linux, and Windows. Install Node.js
|
|
51
|
+
Browser recording runs on macOS, Linux, and Windows. Install a patched Node.js version listed above and up-to-date Google Chrome or Chromium. Rill encodes recordings remotely by default; local FFmpeg is optional. Chrome stays on your machine and can access localhost. Raw frames upload after recording stops.
|
|
33
52
|
|
|
34
53
|
Then install the scoped package:
|
|
35
54
|
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
-
var __export = (
|
|
3
|
+
var __export = (target2, all) => {
|
|
4
4
|
for (var name in all)
|
|
5
|
-
__defProp(
|
|
5
|
+
__defProp(target2, name, { get: all[name], enumerable: true });
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/windows-credentials.ts
|
|
@@ -1390,8 +1390,8 @@ function defineLazy(object2, key2, getter) {
|
|
|
1390
1390
|
function objectClone(obj) {
|
|
1391
1391
|
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
|
|
1392
1392
|
}
|
|
1393
|
-
function assignProp(
|
|
1394
|
-
Object.defineProperty(
|
|
1393
|
+
function assignProp(target2, prop, value) {
|
|
1394
|
+
Object.defineProperty(target2, prop, {
|
|
1395
1395
|
value,
|
|
1396
1396
|
writable: true,
|
|
1397
1397
|
enumerable: true,
|
|
@@ -1574,35 +1574,35 @@ function normalizeParams(_params) {
|
|
|
1574
1574
|
return params;
|
|
1575
1575
|
}
|
|
1576
1576
|
function createTransparentProxy(getter) {
|
|
1577
|
-
let
|
|
1577
|
+
let target2;
|
|
1578
1578
|
return new Proxy({}, {
|
|
1579
1579
|
get(_, prop, receiver) {
|
|
1580
|
-
|
|
1581
|
-
return Reflect.get(
|
|
1580
|
+
target2 ?? (target2 = getter());
|
|
1581
|
+
return Reflect.get(target2, prop, receiver);
|
|
1582
1582
|
},
|
|
1583
1583
|
set(_, prop, value, receiver) {
|
|
1584
|
-
|
|
1585
|
-
return Reflect.set(
|
|
1584
|
+
target2 ?? (target2 = getter());
|
|
1585
|
+
return Reflect.set(target2, prop, value, receiver);
|
|
1586
1586
|
},
|
|
1587
1587
|
has(_, prop) {
|
|
1588
|
-
|
|
1589
|
-
return Reflect.has(
|
|
1588
|
+
target2 ?? (target2 = getter());
|
|
1589
|
+
return Reflect.has(target2, prop);
|
|
1590
1590
|
},
|
|
1591
1591
|
deleteProperty(_, prop) {
|
|
1592
|
-
|
|
1593
|
-
return Reflect.deleteProperty(
|
|
1592
|
+
target2 ?? (target2 = getter());
|
|
1593
|
+
return Reflect.deleteProperty(target2, prop);
|
|
1594
1594
|
},
|
|
1595
1595
|
ownKeys(_) {
|
|
1596
|
-
|
|
1597
|
-
return Reflect.ownKeys(
|
|
1596
|
+
target2 ?? (target2 = getter());
|
|
1597
|
+
return Reflect.ownKeys(target2);
|
|
1598
1598
|
},
|
|
1599
1599
|
getOwnPropertyDescriptor(_, prop) {
|
|
1600
|
-
|
|
1601
|
-
return Reflect.getOwnPropertyDescriptor(
|
|
1600
|
+
target2 ?? (target2 = getter());
|
|
1601
|
+
return Reflect.getOwnPropertyDescriptor(target2, prop);
|
|
1602
1602
|
},
|
|
1603
1603
|
defineProperty(_, prop, descriptor) {
|
|
1604
|
-
|
|
1605
|
-
return Reflect.defineProperty(
|
|
1604
|
+
target2 ?? (target2 = getter());
|
|
1605
|
+
return Reflect.defineProperty(target2, prop, descriptor);
|
|
1606
1606
|
}
|
|
1607
1607
|
});
|
|
1608
1608
|
}
|
|
@@ -2237,10 +2237,10 @@ var nanoid = /^[a-zA-Z0-9_-]{21}$/;
|
|
|
2237
2237
|
var duration = /^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/;
|
|
2238
2238
|
var extendedDuration = /^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/;
|
|
2239
2239
|
var guid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
|
|
2240
|
-
var uuid = (
|
|
2241
|
-
if (!
|
|
2240
|
+
var uuid = (version3) => {
|
|
2241
|
+
if (!version3)
|
|
2242
2242
|
return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
|
|
2243
|
-
return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${
|
|
2243
|
+
return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version3}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);
|
|
2244
2244
|
};
|
|
2245
2245
|
var uuid4 = /* @__PURE__ */ uuid(4);
|
|
2246
2246
|
var uuid6 = /* @__PURE__ */ uuid(6);
|
|
@@ -11701,10 +11701,10 @@ function _slugify() {
|
|
|
11701
11701
|
return /* @__PURE__ */ _overwrite((input) => slugify(input));
|
|
11702
11702
|
}
|
|
11703
11703
|
// @__NO_SIDE_EFFECTS__
|
|
11704
|
-
function _array(Class2,
|
|
11704
|
+
function _array(Class2, element2, params) {
|
|
11705
11705
|
return new Class2({
|
|
11706
11706
|
type: "array",
|
|
11707
|
-
element,
|
|
11707
|
+
element: element2,
|
|
11708
11708
|
// get element() {
|
|
11709
11709
|
// return element;
|
|
11710
11710
|
// },
|
|
@@ -12054,15 +12054,15 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
|
|
|
12054
12054
|
|
|
12055
12055
|
// ../../node_modules/zod/v4/core/to-json-schema.js
|
|
12056
12056
|
function initializeContext(params) {
|
|
12057
|
-
let
|
|
12058
|
-
if (
|
|
12059
|
-
|
|
12060
|
-
if (
|
|
12061
|
-
|
|
12057
|
+
let target2 = params?.target ?? "draft-2020-12";
|
|
12058
|
+
if (target2 === "draft-4")
|
|
12059
|
+
target2 = "draft-04";
|
|
12060
|
+
if (target2 === "draft-7")
|
|
12061
|
+
target2 = "draft-07";
|
|
12062
12062
|
return {
|
|
12063
12063
|
processors: params.processors ?? {},
|
|
12064
12064
|
metadataRegistry: params?.metadata ?? globalRegistry,
|
|
12065
|
-
target,
|
|
12065
|
+
target: target2,
|
|
12066
12066
|
unrepresentable: params?.unrepresentable ?? "throw",
|
|
12067
12067
|
override: params?.override ?? (() => {
|
|
12068
12068
|
}),
|
|
@@ -12404,8 +12404,8 @@ var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
|
|
|
12404
12404
|
return finalize(ctx, schema);
|
|
12405
12405
|
};
|
|
12406
12406
|
var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
|
|
12407
|
-
const { libraryOptions, target } = params ?? {};
|
|
12408
|
-
const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
|
|
12407
|
+
const { libraryOptions, target: target2 } = params ?? {};
|
|
12408
|
+
const ctx = initializeContext({ ...libraryOptions ?? {}, target: target2, io, processors });
|
|
12409
12409
|
process2(schema, ctx);
|
|
12410
12410
|
extractDefs(ctx, schema);
|
|
12411
12411
|
return finalize(ctx, schema);
|
|
@@ -13464,8 +13464,8 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
13464
13464
|
catch(params) {
|
|
13465
13465
|
return _catch2(this, params);
|
|
13466
13466
|
},
|
|
13467
|
-
pipe(
|
|
13468
|
-
return pipe(this,
|
|
13467
|
+
pipe(target2) {
|
|
13468
|
+
return pipe(this, target2);
|
|
13469
13469
|
},
|
|
13470
13470
|
readonly() {
|
|
13471
13471
|
return readonly(this);
|
|
@@ -13986,8 +13986,8 @@ var ZodArray = /* @__PURE__ */ $constructor("ZodArray", (inst, def) => {
|
|
|
13986
13986
|
}
|
|
13987
13987
|
});
|
|
13988
13988
|
});
|
|
13989
|
-
function array(
|
|
13990
|
-
return _array(ZodArray,
|
|
13989
|
+
function array(element2, params) {
|
|
13990
|
+
return _array(ZodArray, element2, params);
|
|
13991
13991
|
}
|
|
13992
13992
|
function keyof(schema) {
|
|
13993
13993
|
const shape = schema._zod.def.shape;
|
|
@@ -15024,8 +15024,8 @@ function convertBaseSchema(schema, ctx) {
|
|
|
15024
15024
|
zodSchema = zodSchema.check(z.maxLength(schema.maxItems));
|
|
15025
15025
|
}
|
|
15026
15026
|
} else if (items !== void 0) {
|
|
15027
|
-
const
|
|
15028
|
-
let arraySchema = z.array(
|
|
15027
|
+
const element2 = convertSchema(items, ctx);
|
|
15028
|
+
let arraySchema = z.array(element2);
|
|
15029
15029
|
if (typeof schema.minItems === "number") {
|
|
15030
15030
|
arraySchema = arraySchema.min(schema.minItems);
|
|
15031
15031
|
}
|
|
@@ -15116,10 +15116,10 @@ function fromJSONSchema(schema, params) {
|
|
|
15116
15116
|
} catch {
|
|
15117
15117
|
throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
|
|
15118
15118
|
}
|
|
15119
|
-
const
|
|
15119
|
+
const version3 = detectVersion(normalized, params?.defaultTarget);
|
|
15120
15120
|
const defs = normalized.$defs || normalized.definitions || {};
|
|
15121
15121
|
const ctx = {
|
|
15122
|
-
version:
|
|
15122
|
+
version: version3,
|
|
15123
15123
|
defs,
|
|
15124
15124
|
refs: /* @__PURE__ */ new Map(),
|
|
15125
15125
|
processing: /* @__PURE__ */ new Set(),
|
|
@@ -15354,10 +15354,10 @@ async function windowsIpcIdentity(directory = join5(rillDirectory(), "windows-ip
|
|
|
15354
15354
|
} catch (e) {
|
|
15355
15355
|
if (e.code !== "ENOENT") throw new Error("Cannot read the protected recorder identity.");
|
|
15356
15356
|
}
|
|
15357
|
-
const
|
|
15357
|
+
const identity2 = { version: 1, pipe: "\\\\.\\pipe\\rill-v3-" + randomBytes2(32).toString("hex"), token: randomBytes2(32).toString("hex") };
|
|
15358
15358
|
const temporary = join5(directory, randomBytes2(16).toString("hex") + ".tmp");
|
|
15359
15359
|
try {
|
|
15360
|
-
await writeFile2(temporary, JSON.stringify(
|
|
15360
|
+
await writeFile2(temporary, JSON.stringify(identity2), { mode: 384, flag: "wx" });
|
|
15361
15361
|
try {
|
|
15362
15362
|
await link(temporary, path);
|
|
15363
15363
|
} catch (e) {
|
|
@@ -15428,16 +15428,16 @@ function validateTabSelection(options) {
|
|
|
15428
15428
|
}
|
|
15429
15429
|
async function tabs(browser) {
|
|
15430
15430
|
const result = [];
|
|
15431
|
-
for (const
|
|
15431
|
+
for (const context2 of browser.contexts()) for (const page of context2.pages()) {
|
|
15432
15432
|
if (page.isClosed()) continue;
|
|
15433
|
-
const session = await
|
|
15433
|
+
const session = await context2.newCDPSession(page).catch(() => null);
|
|
15434
15434
|
if (!session) {
|
|
15435
15435
|
if (page.isClosed()) continue;
|
|
15436
15436
|
throw new Error("browser_unavailable: Could not inspect an open tab.");
|
|
15437
15437
|
}
|
|
15438
15438
|
try {
|
|
15439
15439
|
const { targetInfo } = await session.send("Target.getTargetInfo");
|
|
15440
|
-
result.push({ page, context, tab: { tabId: targetInfo.targetId, url: /^(?:https?:|about:)/i.test(page.url()) ? sanitizeUrl(page.url()) : `[${page.url().split(":", 1)[0]} URL omitted]`, title: redactText(targetInfo.title, 200) } });
|
|
15440
|
+
result.push({ page, context: context2, tab: { tabId: targetInfo.targetId, url: /^(?:https?:|about:)/i.test(page.url()) ? sanitizeUrl(page.url()) : `[${page.url().split(":", 1)[0]} URL omitted]`, title: redactText(targetInfo.title, 200) } });
|
|
15441
15441
|
} catch (error51) {
|
|
15442
15442
|
if (!page.isClosed()) throw error51;
|
|
15443
15443
|
} finally {
|
|
@@ -15517,13 +15517,13 @@ function recordingStatus(recordingId) {
|
|
|
15517
15517
|
var windowsIdentityPromise;
|
|
15518
15518
|
async function callDaemon(method, path, body) {
|
|
15519
15519
|
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.");
|
|
15520
|
-
const
|
|
15520
|
+
const identity2 = process.platform === "win32" ? await (windowsIdentityPromise ??= windowsIpcIdentity().catch((error51) => {
|
|
15521
15521
|
windowsIdentityPromise = void 0;
|
|
15522
15522
|
throw error51;
|
|
15523
15523
|
})) : void 0;
|
|
15524
15524
|
return new Promise((resolve, reject) => {
|
|
15525
15525
|
const payload = body === void 0 ? void 0 : Buffer.from(JSON.stringify(body));
|
|
15526
|
-
const outgoing = request2({ socketPath:
|
|
15526
|
+
const outgoing = request2({ socketPath: identity2?.pipe ?? daemonSocketPath(), method, path, headers: { ...payload ? { "Content-Type": "application/json", "Content-Length": payload.length } : {}, ...identity2 ? { Authorization: "Bearer " + identity2.token } : {} } }, (incoming) => {
|
|
15527
15527
|
const chunks = [];
|
|
15528
15528
|
incoming.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
15529
15529
|
incoming.on("end", () => {
|
|
@@ -15604,16 +15604,16 @@ async function captureClicks(page, session, onClick) {
|
|
|
15604
15604
|
let identifier;
|
|
15605
15605
|
session.on("Runtime.bindingCalled", receive);
|
|
15606
15606
|
const cleanup = async () => {
|
|
15607
|
-
session.off("Runtime.bindingCalled", receive);
|
|
15608
15607
|
if (identifier) await session.send("Page.removeScriptToEvaluateOnNewDocument", { identifier }).catch(() => void 0);
|
|
15609
15608
|
await Promise.all(page.frames().map((frame) => frame.evaluate((key2) => {
|
|
15610
|
-
const
|
|
15611
|
-
const listener =
|
|
15609
|
+
const target2 = window;
|
|
15610
|
+
const listener = target2[key2 + "_listener"];
|
|
15612
15611
|
if (typeof listener === "function") document.removeEventListener("click", listener, { capture: true });
|
|
15613
|
-
delete
|
|
15614
|
-
delete
|
|
15612
|
+
delete target2[key2 + "_listener"];
|
|
15613
|
+
delete target2[key2];
|
|
15615
15614
|
}, binding).catch(() => void 0)));
|
|
15616
15615
|
await session.send("Runtime.removeBinding", { name: binding }).catch(() => void 0);
|
|
15616
|
+
session.off("Runtime.bindingCalled", receive);
|
|
15617
15617
|
};
|
|
15618
15618
|
try {
|
|
15619
15619
|
await session.send("Runtime.enable");
|
|
@@ -15800,7 +15800,8 @@ var interactionReportFields = {
|
|
|
15800
15800
|
cleanup: external_exports.literal("not_attempted").optional()
|
|
15801
15801
|
};
|
|
15802
15802
|
var cloudRunPrompt = external_exports.string().min(1).max(2e3).refine((value) => Boolean(value.trim()), "Describe what you want Rill to do.");
|
|
15803
|
-
var
|
|
15803
|
+
var practiceReference = external_exports.object({ missionId: external_exports.string().regex(/^[a-z]+-[a-z]+$/).max(60), version: external_exports.literal(1) }).strict();
|
|
15804
|
+
var cloudSubmission = external_exports.object({ ...interactionFields, id: external_exports.string().uuid(), url: cloudRunTarget, prompt: cloudRunPrompt, expectation: external_exports.string().trim().min(1).max(1e3).optional(), account: external_exports.string().regex(/^[a-z0-9-]{1,60}$/).optional(), practice: practiceReference.optional() }).strict();
|
|
15804
15805
|
var siteAccountSchema = external_exports.object({
|
|
15805
15806
|
name: external_exports.string().regex(/^[a-z0-9-]{1,60}$/),
|
|
15806
15807
|
origin: publicTarget.refine((v) => new URL(v).protocol === "https:" && new URL(v).origin === v, "Use an exact HTTPS origin."),
|
|
@@ -15994,11 +15995,11 @@ function installCursorOverlay(options) {
|
|
|
15994
15995
|
positions.set(document, { x: window.scrollX, y: window.scrollY });
|
|
15995
15996
|
document.addEventListener("scroll", (event) => {
|
|
15996
15997
|
if (!event.isTrusted || document.hidden) return;
|
|
15997
|
-
const
|
|
15998
|
-
if (
|
|
15999
|
-
const position =
|
|
16000
|
-
const previous = positions.get(
|
|
16001
|
-
positions.set(
|
|
15998
|
+
const target2 = event.target;
|
|
15999
|
+
if (target2 !== document && !(target2 instanceof Element)) return;
|
|
16000
|
+
const position = target2 === document ? { x: window.scrollX, y: window.scrollY } : { x: target2.scrollLeft, y: target2.scrollTop };
|
|
16001
|
+
const previous = positions.get(target2);
|
|
16002
|
+
positions.set(target2, position);
|
|
16002
16003
|
const dx = previous ? position.x - previous.x : 0;
|
|
16003
16004
|
const dy = previous ? position.y - previous.y : 0;
|
|
16004
16005
|
if (previous && !dx && !dy) return;
|
|
@@ -16029,13 +16030,13 @@ function installCursorOverlay(options) {
|
|
|
16029
16030
|
document.addEventListener("pointerover", move, listenerOptions);
|
|
16030
16031
|
document.addEventListener("pointerup", release, listenerOptions);
|
|
16031
16032
|
document.addEventListener("pointercancel", release, listenerOptions);
|
|
16032
|
-
const animateFeedback = (
|
|
16033
|
-
root.insertBefore(
|
|
16034
|
-
const animation =
|
|
16033
|
+
const animateFeedback = (element2, frames, duration3, easing) => {
|
|
16034
|
+
root.insertBefore(element2, pointer);
|
|
16035
|
+
const animation = element2.animate(frames, { duration: duration3, easing });
|
|
16035
16036
|
animations.add(animation);
|
|
16036
16037
|
void animation.finished.catch(() => void 0).finally(() => {
|
|
16037
16038
|
animations.delete(animation);
|
|
16038
|
-
|
|
16039
|
+
element2.remove();
|
|
16039
16040
|
});
|
|
16040
16041
|
};
|
|
16041
16042
|
document.addEventListener("pointerdown", (event) => {
|
|
@@ -16126,8 +16127,10 @@ async function writeBundle(bundle, outputPath) {
|
|
|
16126
16127
|
const output = createWriteStream(outputPath, { mode: 384 });
|
|
16127
16128
|
const gzip = createGzip({ level: 9 });
|
|
16128
16129
|
gzip.pipe(output);
|
|
16129
|
-
const { console: consoleEvents, network, timeline, ...manifest } = bundle;
|
|
16130
|
+
const { console: consoleEvents, network, timeline, actions, ...manifest } = bundle;
|
|
16130
16131
|
gzip.write(`${JSON.stringify({ kind: "manifest", ...manifest })}
|
|
16132
|
+
`);
|
|
16133
|
+
for (const event of actions ?? []) gzip.write(`${JSON.stringify(event)}
|
|
16131
16134
|
`);
|
|
16132
16135
|
for (const event of consoleEvents) gzip.write(`${JSON.stringify({ kind: "console", ...event })}
|
|
16133
16136
|
`);
|
|
@@ -16139,6 +16142,203 @@ async function writeBundle(bundle, outputPath) {
|
|
|
16139
16142
|
await once(output, "close");
|
|
16140
16143
|
}
|
|
16141
16144
|
|
|
16145
|
+
// ../contracts/src/browser.ts
|
|
16146
|
+
var browserContractVersion = 1;
|
|
16147
|
+
var identity = external_exports.string().min(1).max(100);
|
|
16148
|
+
var element = external_exports.number().int().min(1).max(100);
|
|
16149
|
+
var version2 = { schemaVersion: external_exports.literal(browserContractVersion) };
|
|
16150
|
+
var target = { observationId: identity, element };
|
|
16151
|
+
var browserActionSchema = external_exports.enum(["observe", "click", "fill", "check", "select", "press", "navigate", "scroll", "finish"]);
|
|
16152
|
+
var browserCommandSchema = external_exports.discriminatedUnion("action", [
|
|
16153
|
+
external_exports.object({ ...version2, action: external_exports.literal("observe") }).strict(),
|
|
16154
|
+
external_exports.object({ ...version2, action: external_exports.literal("click"), ...target }).strict(),
|
|
16155
|
+
external_exports.object({ ...version2, action: external_exports.literal("fill"), ...target, value: external_exports.string().max(2e3) }).strict(),
|
|
16156
|
+
external_exports.object({ ...version2, action: external_exports.literal("check"), ...target, checked: external_exports.boolean() }).strict(),
|
|
16157
|
+
external_exports.object({ ...version2, action: external_exports.literal("select"), ...target, value: external_exports.string().max(200) }).strict(),
|
|
16158
|
+
external_exports.object({ ...version2, action: external_exports.literal("press"), ...target, key: external_exports.enum(["Enter", "Tab", "ArrowDown", "ArrowUp", "Escape"]) }).strict(),
|
|
16159
|
+
external_exports.object({ ...version2, action: external_exports.literal("navigate"), url: external_exports.string().url().max(2048) }).strict(),
|
|
16160
|
+
external_exports.object({ ...version2, action: external_exports.literal("scroll"), observationId: identity, direction: external_exports.enum(["up", "down"]) }).strict(),
|
|
16161
|
+
external_exports.object({ ...version2, action: external_exports.literal("finish"), outcome: external_exports.enum(["walkthrough", "passed", "failed", "inconclusive"]), summary: external_exports.string().min(1).max(1e3), evidence: external_exports.string().max(1e3) }).strict()
|
|
16162
|
+
]);
|
|
16163
|
+
var context = { recordingId: identity, pageId: identity, frameId: external_exports.literal("main"), documentId: identity };
|
|
16164
|
+
var browserObservationSchema = external_exports.object({
|
|
16165
|
+
...version2,
|
|
16166
|
+
kind: external_exports.literal("observation"),
|
|
16167
|
+
...context,
|
|
16168
|
+
observationId: identity,
|
|
16169
|
+
offsetMs: external_exports.number().nonnegative(),
|
|
16170
|
+
url: external_exports.string(),
|
|
16171
|
+
title: external_exports.string().max(500),
|
|
16172
|
+
text: external_exports.string().max(12e3),
|
|
16173
|
+
viewport: external_exports.object({ width: external_exports.number().nonnegative(), height: external_exports.number().nonnegative(), scrollX: external_exports.number(), scrollY: external_exports.number(), documentHeight: external_exports.number().nonnegative(), text: external_exports.string().max(12e3), textTruncated: external_exports.boolean(), evidence: external_exports.literal("dom_geometry") }).strict(),
|
|
16174
|
+
elements: external_exports.array(external_exports.object({
|
|
16175
|
+
kind: external_exports.literal("dom"),
|
|
16176
|
+
id: element,
|
|
16177
|
+
tag: external_exports.string(),
|
|
16178
|
+
label: external_exports.string().max(160),
|
|
16179
|
+
type: external_exports.string().nullable(),
|
|
16180
|
+
role: external_exports.string().nullable(),
|
|
16181
|
+
disabled: external_exports.boolean(),
|
|
16182
|
+
checked: external_exports.union([external_exports.boolean(), external_exports.string(), external_exports.null()]),
|
|
16183
|
+
options: external_exports.array(external_exports.object({ value: external_exports.string().max(200), label: external_exports.string().max(100), selected: external_exports.boolean() }).strict()).max(50).optional(),
|
|
16184
|
+
optionsTruncated: external_exports.boolean().optional(),
|
|
16185
|
+
validation: external_exports.string().max(160).optional()
|
|
16186
|
+
}).strict()).max(100),
|
|
16187
|
+
completeness: external_exports.object({
|
|
16188
|
+
scope: external_exports.literal("main_document"),
|
|
16189
|
+
childFrames: external_exports.literal("excluded"),
|
|
16190
|
+
shadowRoots: external_exports.literal("excluded"),
|
|
16191
|
+
targetsTruncated: external_exports.boolean(),
|
|
16192
|
+
textTruncated: external_exports.boolean(),
|
|
16193
|
+
targetLimit: external_exports.literal(100),
|
|
16194
|
+
textLimit: external_exports.literal(12e3)
|
|
16195
|
+
}).strict(),
|
|
16196
|
+
blockedRequests: external_exports.number().int().nonnegative(),
|
|
16197
|
+
submissionOutcomeUnknown: external_exports.boolean()
|
|
16198
|
+
}).strict();
|
|
16199
|
+
var browserErrorSchema = external_exports.enum([
|
|
16200
|
+
"invalid_browser_command",
|
|
16201
|
+
"browser_busy",
|
|
16202
|
+
"run_finished",
|
|
16203
|
+
"stale_observation",
|
|
16204
|
+
"stale_target",
|
|
16205
|
+
"observation_unavailable",
|
|
16206
|
+
"interaction_required",
|
|
16207
|
+
"consequential_action_blocked",
|
|
16208
|
+
"unsupported_control",
|
|
16209
|
+
"target_not_allowed",
|
|
16210
|
+
"submission_outcome_unknown",
|
|
16211
|
+
"action_outcome_unknown",
|
|
16212
|
+
"browser_action_unavailable",
|
|
16213
|
+
"verification_requires_expectation_and_evidence"
|
|
16214
|
+
]);
|
|
16215
|
+
var browserActionReceiptSchema = external_exports.object({
|
|
16216
|
+
...version2,
|
|
16217
|
+
kind: external_exports.literal("action"),
|
|
16218
|
+
...context,
|
|
16219
|
+
actionId: identity,
|
|
16220
|
+
action: browserActionSchema.nullable(),
|
|
16221
|
+
inputObservationId: identity.nullable(),
|
|
16222
|
+
target: external_exports.object({ kind: external_exports.literal("dom"), element }).strict().optional(),
|
|
16223
|
+
startedAtMs: external_exports.number().nonnegative(),
|
|
16224
|
+
finishedAtMs: external_exports.number().nonnegative(),
|
|
16225
|
+
status: external_exports.enum(["completed", "rejected", "failed"]),
|
|
16226
|
+
effectState: external_exports.enum(["none", "dispatched", "unknown"]),
|
|
16227
|
+
next: external_exports.enum(["observe", "inspect_only", "finished"]),
|
|
16228
|
+
errorCode: browserErrorSchema.optional(),
|
|
16229
|
+
blockedRequests: external_exports.number().int().nonnegative()
|
|
16230
|
+
}).strict().refine((value) => value.finishedAtMs >= value.startedAtMs, "Receipt timestamps must be ordered").refine((value) => value.status !== "rejected" || value.effectState === "none", "Rejected actions cannot have effects").refine((value) => value.status === "completed" === (value.errorCode === void 0), "Errors require a non-completed status").refine((value) => value.effectState !== "unknown" || value.next === "inspect_only", "Unknown effects require inspection");
|
|
16231
|
+
|
|
16232
|
+
// ../contracts/src/diagnostics.ts
|
|
16233
|
+
var diagnosticSeveritySchema = external_exports.enum(["debug", "info", "warning", "error"]);
|
|
16234
|
+
var consoleEventSchema = external_exports.object({
|
|
16235
|
+
id: external_exports.string(),
|
|
16236
|
+
offsetMs: external_exports.number().nonnegative(),
|
|
16237
|
+
severity: diagnosticSeveritySchema,
|
|
16238
|
+
text: external_exports.string(),
|
|
16239
|
+
sourceUrl: external_exports.string().optional(),
|
|
16240
|
+
lineNumber: external_exports.number().optional(),
|
|
16241
|
+
stack: external_exports.array(external_exports.string()).default([])
|
|
16242
|
+
});
|
|
16243
|
+
var networkEventSchema = external_exports.object({
|
|
16244
|
+
id: external_exports.string(),
|
|
16245
|
+
offsetMs: external_exports.number().nonnegative(),
|
|
16246
|
+
method: external_exports.string(),
|
|
16247
|
+
url: external_exports.string(),
|
|
16248
|
+
resourceType: external_exports.string(),
|
|
16249
|
+
status: external_exports.number().int().optional(),
|
|
16250
|
+
mimeType: external_exports.string().optional(),
|
|
16251
|
+
durationMs: external_exports.number().nonnegative().optional(),
|
|
16252
|
+
transferSize: external_exports.number().nonnegative().optional(),
|
|
16253
|
+
failureReason: external_exports.string().optional(),
|
|
16254
|
+
completedOffsetMs: external_exports.number().nonnegative().optional(),
|
|
16255
|
+
pageUrl: external_exports.string().optional(),
|
|
16256
|
+
blockedBy: external_exports.enum(["rill"]).optional(),
|
|
16257
|
+
blockReason: external_exports.enum(["write_policy", "navigation_policy", "interception_error"]).optional(),
|
|
16258
|
+
requestBody: external_exports.string().optional(),
|
|
16259
|
+
responseBody: external_exports.string().optional(),
|
|
16260
|
+
bodyOmittedReason: external_exports.string().optional()
|
|
16261
|
+
});
|
|
16262
|
+
var timelineEventSchema = external_exports.object({
|
|
16263
|
+
id: external_exports.string(),
|
|
16264
|
+
offsetMs: external_exports.number().nonnegative(),
|
|
16265
|
+
type: external_exports.enum(["navigation", "click", "console", "network", "exception", "lifecycle"]),
|
|
16266
|
+
label: external_exports.string(),
|
|
16267
|
+
severity: diagnosticSeveritySchema.optional()
|
|
16268
|
+
});
|
|
16269
|
+
var browserActionEventSchema = external_exports.object({
|
|
16270
|
+
schemaVersion: external_exports.literal(1),
|
|
16271
|
+
kind: external_exports.literal("browser_action"),
|
|
16272
|
+
id: external_exports.string().regex(/^action_[1-9][0-9]{0,3}$/),
|
|
16273
|
+
offsetMs: external_exports.number().int().min(0).max(864e5),
|
|
16274
|
+
finishedAtMs: external_exports.number().int().min(0).max(864e5),
|
|
16275
|
+
action: browserActionSchema.nullable(),
|
|
16276
|
+
status: external_exports.enum(["completed", "rejected", "failed"]),
|
|
16277
|
+
effectState: external_exports.enum(["none", "dispatched", "unknown"]),
|
|
16278
|
+
next: external_exports.enum(["observe", "inspect_only", "finished"]),
|
|
16279
|
+
errorCode: browserErrorSchema.optional(),
|
|
16280
|
+
blockedRequests: external_exports.number().int().nonnegative()
|
|
16281
|
+
}).strict().refine((event) => event.finishedAtMs >= event.offsetMs, "Action timestamps must be ordered").refine((event) => event.status !== "rejected" || event.effectState === "none", "Rejected actions cannot have effects").refine((event) => event.status === "completed" === (event.errorCode === void 0), "Errors require a non-completed status").refine((event) => event.effectState !== "unknown" || event.next === "inspect_only", "Unknown effects require inspection");
|
|
16282
|
+
var diagnosticBundleSchema = external_exports.object({
|
|
16283
|
+
schemaVersion: external_exports.literal(1),
|
|
16284
|
+
capturePolicyVersion: external_exports.string(),
|
|
16285
|
+
videoStartOffsetMs: external_exports.number().optional(),
|
|
16286
|
+
recordingId: external_exports.string(),
|
|
16287
|
+
startedAt: external_exports.string().datetime(),
|
|
16288
|
+
durationMs: external_exports.number().nonnegative(),
|
|
16289
|
+
bodyCaptureEnabled: external_exports.boolean(),
|
|
16290
|
+
bodyCaptureRedacted: external_exports.boolean(),
|
|
16291
|
+
truncated: external_exports.boolean(),
|
|
16292
|
+
actions: external_exports.array(browserActionEventSchema).max(1e3).optional(),
|
|
16293
|
+
actionsTruncated: external_exports.boolean().optional(),
|
|
16294
|
+
console: external_exports.array(consoleEventSchema),
|
|
16295
|
+
network: external_exports.array(networkEventSchema),
|
|
16296
|
+
timeline: external_exports.array(timelineEventSchema)
|
|
16297
|
+
});
|
|
16298
|
+
|
|
16299
|
+
// ../recorder/src/browser-actions.ts
|
|
16300
|
+
var BrowserActionCapture = class {
|
|
16301
|
+
constructor(recordingId) {
|
|
16302
|
+
this.recordingId = recordingId;
|
|
16303
|
+
}
|
|
16304
|
+
recordingId;
|
|
16305
|
+
events = [];
|
|
16306
|
+
truncated = false;
|
|
16307
|
+
seen = /* @__PURE__ */ new Set();
|
|
16308
|
+
add(input) {
|
|
16309
|
+
const parsed = browserActionReceiptSchema.safeParse(input);
|
|
16310
|
+
if (!parsed.success || parsed.data.recordingId !== this.recordingId) {
|
|
16311
|
+
this.truncated = true;
|
|
16312
|
+
return;
|
|
16313
|
+
}
|
|
16314
|
+
const receipt = parsed.data;
|
|
16315
|
+
if (this.seen.has(receipt.actionId)) return;
|
|
16316
|
+
if (this.events.length >= 1e3) {
|
|
16317
|
+
this.truncated = true;
|
|
16318
|
+
return;
|
|
16319
|
+
}
|
|
16320
|
+
const event = browserActionEventSchema.safeParse({
|
|
16321
|
+
schemaVersion: 1,
|
|
16322
|
+
kind: "browser_action",
|
|
16323
|
+
id: `action_${this.events.length + 1}`,
|
|
16324
|
+
offsetMs: Math.round(receipt.startedAtMs),
|
|
16325
|
+
finishedAtMs: Math.round(receipt.finishedAtMs),
|
|
16326
|
+
action: receipt.action,
|
|
16327
|
+
status: receipt.status,
|
|
16328
|
+
effectState: receipt.effectState,
|
|
16329
|
+
next: receipt.next,
|
|
16330
|
+
...receipt.errorCode ? { errorCode: receipt.errorCode } : {},
|
|
16331
|
+
blockedRequests: receipt.blockedRequests
|
|
16332
|
+
});
|
|
16333
|
+
if (!event.success) {
|
|
16334
|
+
this.truncated = true;
|
|
16335
|
+
return;
|
|
16336
|
+
}
|
|
16337
|
+
this.seen.add(receipt.actionId);
|
|
16338
|
+
this.events.push(event.data);
|
|
16339
|
+
}
|
|
16340
|
+
};
|
|
16341
|
+
|
|
16142
16342
|
// ../recorder/src/environment.ts
|
|
16143
16343
|
import { existsSync } from "fs";
|
|
16144
16344
|
import { win32 as win323 } from "path";
|
|
@@ -16250,12 +16450,18 @@ var ScreencastRecorder = class {
|
|
|
16250
16450
|
frames = [];
|
|
16251
16451
|
pendingWrites = /* @__PURE__ */ new Set();
|
|
16252
16452
|
started = false;
|
|
16453
|
+
firstFrameEpochMs;
|
|
16454
|
+
get videoStartedAtMs() {
|
|
16455
|
+
return this.firstFrameEpochMs;
|
|
16456
|
+
}
|
|
16253
16457
|
frameCounter = 0;
|
|
16254
16458
|
frameBytes = 0;
|
|
16255
16459
|
captureError;
|
|
16256
16460
|
async start() {
|
|
16257
16461
|
await mkdir5(this.directory, { recursive: true, mode: 448 });
|
|
16258
16462
|
this.session.on("Page.screencastFrame", (payload) => {
|
|
16463
|
+
const frameEpochMs = (payload.metadata.timestamp ?? Date.now() / 1e3) * 1e3;
|
|
16464
|
+
this.firstFrameEpochMs = Math.min(this.firstFrameEpochMs ?? frameEpochMs, frameEpochMs);
|
|
16259
16465
|
const bytes = Buffer.from(payload.data, "base64");
|
|
16260
16466
|
if (this.frameCounter >= 36e3 || this.frameBytes + bytes.length > ENCODING_MAX_BYTES || bytes.length > ENCODING_PART_BYTES) {
|
|
16261
16467
|
this.captureError = new Error("Capture exceeds the remote encoding frame or byte limit.");
|
|
@@ -16266,7 +16472,7 @@ var ScreencastRecorder = class {
|
|
|
16266
16472
|
const path = join8(this.directory, `frame-${String(this.frameCounter).padStart(6, "0")}.jpg`);
|
|
16267
16473
|
this.frameCounter += 1;
|
|
16268
16474
|
const write = writeFile4(path, bytes, { mode: 384 }).then(() => {
|
|
16269
|
-
this.frames.push({ path, timestamp:
|
|
16475
|
+
this.frames.push({ path, timestamp: frameEpochMs / 1e3 });
|
|
16270
16476
|
}).catch((error51) => {
|
|
16271
16477
|
this.captureError = error51 instanceof Error ? error51 : new Error(String(error51));
|
|
16272
16478
|
}).finally(() => {
|
|
@@ -16320,6 +16526,17 @@ var RecordingSession = class _RecordingSession {
|
|
|
16320
16526
|
consoleEvents = [];
|
|
16321
16527
|
networkEvents = [];
|
|
16322
16528
|
timelineEvents = [];
|
|
16529
|
+
browserActions;
|
|
16530
|
+
captureBrowserAction(receipt) {
|
|
16531
|
+
if (!this.diagnosticsPaused && !this.stopPromise) this.browserActions.add(receipt);
|
|
16532
|
+
}
|
|
16533
|
+
captureRequestBlock(request3, reason) {
|
|
16534
|
+
const event = this.requestEvents.get(request3);
|
|
16535
|
+
if (event) {
|
|
16536
|
+
event.blockedBy = "rill";
|
|
16537
|
+
event.blockReason = reason;
|
|
16538
|
+
}
|
|
16539
|
+
}
|
|
16323
16540
|
requestStart = /* @__PURE__ */ new Map();
|
|
16324
16541
|
requestEvents = /* @__PURE__ */ new Map();
|
|
16325
16542
|
stopPromise;
|
|
@@ -16341,6 +16558,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
16341
16558
|
this.videoFormat = options.videoFormat ?? "webm";
|
|
16342
16559
|
this.captureSignal = options.signal;
|
|
16343
16560
|
this.recordingId = options.recordingId;
|
|
16561
|
+
this.browserActions = new BrowserActionCapture(options.recordingId);
|
|
16344
16562
|
this.cdpUrl = runtime.cdpUrl;
|
|
16345
16563
|
this.tabId = runtime.tabId;
|
|
16346
16564
|
this.browser = runtime.browser;
|
|
@@ -16410,6 +16628,10 @@ var RecordingSession = class _RecordingSession {
|
|
|
16410
16628
|
}
|
|
16411
16629
|
async finalize(reason) {
|
|
16412
16630
|
if (this.maxTimer) clearTimeout(this.maxTimer);
|
|
16631
|
+
await this.removeClickCapture?.().catch(() => {
|
|
16632
|
+
this.truncated = true;
|
|
16633
|
+
});
|
|
16634
|
+
this.removeClickCapture = void 0;
|
|
16413
16635
|
const durationSeconds = Math.max(0.1, (Date.now() - this.startedAt.getTime()) / 1e3);
|
|
16414
16636
|
const outputDirectory = this.outputDirectory;
|
|
16415
16637
|
const videoPath = join9(outputDirectory, `recording.${this.videoFormat}`);
|
|
@@ -16417,22 +16639,25 @@ var RecordingSession = class _RecordingSession {
|
|
|
16417
16639
|
const bundle = {
|
|
16418
16640
|
schemaVersion: 1,
|
|
16419
16641
|
capturePolicyVersion: "safe-default-v1",
|
|
16642
|
+
...this.screencast.videoStartedAtMs == null ? {} : { videoStartOffsetMs: this.screencast.videoStartedAtMs - this.startedAt.getTime() },
|
|
16420
16643
|
recordingId: this.recordingId,
|
|
16421
16644
|
startedAt: this.startedAt.toISOString(),
|
|
16422
16645
|
durationMs: Math.round(durationSeconds * 1e3),
|
|
16423
16646
|
bodyCaptureEnabled: this.captureNetworkBodies,
|
|
16424
16647
|
bodyCaptureRedacted: this.captureNetworkBodies,
|
|
16425
|
-
truncated: this.truncated,
|
|
16648
|
+
truncated: this.truncated || this.browserActions.truncated,
|
|
16649
|
+
actionsTruncated: this.browserActions.truncated,
|
|
16426
16650
|
console: this.consoleEvents,
|
|
16427
16651
|
network: this.networkEvents,
|
|
16428
16652
|
timeline: this.timelineEvents.toSorted((left, right) => left.offsetMs - right.offsetMs)
|
|
16429
16653
|
};
|
|
16430
16654
|
try {
|
|
16431
16655
|
const safeBundle = JSON.parse(this.secretValues.reduce((text, secret) => text.split(JSON.stringify(secret).slice(1, -1)).join("[redacted]"), JSON.stringify(bundle)));
|
|
16656
|
+
safeBundle.actions = this.browserActions.events;
|
|
16657
|
+
safeBundle.actionsTruncated = this.browserActions.truncated;
|
|
16432
16658
|
await writeBundle(safeBundle, diagnosticsPath);
|
|
16433
16659
|
await this.screencast.stop(videoPath, durationSeconds, this.encoding === "remote", this.captureSignal);
|
|
16434
16660
|
} finally {
|
|
16435
|
-
await this.removeClickCapture?.().catch(() => void 0);
|
|
16436
16661
|
await this.removeCursorOverlay?.().catch(() => void 0);
|
|
16437
16662
|
await this.browser.close().catch(() => void 0);
|
|
16438
16663
|
if (this.browserProcess) {
|
|
@@ -16443,7 +16668,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
16443
16668
|
const consoleErrors = this.consoleEvents.filter((event) => event.severity === "error").length;
|
|
16444
16669
|
const failedRequests = this.networkEvents.filter((event) => event.failureReason).length;
|
|
16445
16670
|
const httpErrors = this.networkEvents.filter((event) => (event.status ?? 0) >= 400).length;
|
|
16446
|
-
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 } };
|
|
16671
|
+
return { recordingId: this.recordingId, videoPath, ...this.encoding === "remote" ? { frameDirectory: join9(outputDirectory, "frames") } : {}, diagnosticsPath, durationSeconds, stoppedReason: reason, diagnostics: { consoleErrors, failedRequests, httpErrors, truncated: this.truncated || this.browserActions.truncated, bodyCaptureEnabled: this.captureNetworkBodies } };
|
|
16447
16672
|
}
|
|
16448
16673
|
captureNetworkBodies = false;
|
|
16449
16674
|
captureSignal;
|
|
@@ -16498,7 +16723,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
16498
16723
|
}
|
|
16499
16724
|
const started = offset();
|
|
16500
16725
|
this.requestStart.set(request3, started);
|
|
16501
|
-
const event = { id: `net_${this.networkEvents.length + 1}`, offsetMs: started, method: request3.method(), url: sanitizeUrl(request3.url()), resourceType: request3.resourceType() };
|
|
16726
|
+
const event = { id: `net_${this.networkEvents.length + 1}`, offsetMs: started, method: request3.method(), url: sanitizeUrl(request3.url()), resourceType: request3.resourceType(), pageUrl: sanitizeUrl(this.page.url()) };
|
|
16502
16727
|
if (captureNetworkBodies && request3.postData()) {
|
|
16503
16728
|
const redacted = redactBody(request3.postData(), request3.headers()["content-type"]);
|
|
16504
16729
|
event.requestBody = redacted.body;
|
|
@@ -16513,6 +16738,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
16513
16738
|
const event = this.requestEvents.get(request3);
|
|
16514
16739
|
if (!event) return;
|
|
16515
16740
|
event.failureReason = request3.failure()?.errorText ?? "request_failed";
|
|
16741
|
+
event.completedOffsetMs = offset();
|
|
16516
16742
|
event.durationMs = Math.max(0, offset() - (this.requestStart.get(request3) ?? offset()));
|
|
16517
16743
|
pushTimeline({ offsetMs: event.offsetMs, type: "network", label: `${event.method} ${event.url} \u2192 ${event.failureReason}`, severity: "error" });
|
|
16518
16744
|
});
|
|
@@ -16525,6 +16751,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
16525
16751
|
const event = this.requestEvents.get(response.request());
|
|
16526
16752
|
if (!event) return;
|
|
16527
16753
|
event.status = response.status();
|
|
16754
|
+
event.completedOffsetMs = Math.max(0, Date.now() - this.startedAt.getTime());
|
|
16528
16755
|
event.mimeType = response.headers()["content-type"];
|
|
16529
16756
|
event.durationMs = Math.max(0, Date.now() - this.startedAt.getTime() - event.offsetMs);
|
|
16530
16757
|
event.transferSize = Number(response.headers()["content-length"] ?? 0) || void 0;
|
|
@@ -16573,10 +16800,10 @@ async function launchManaged(options) {
|
|
|
16573
16800
|
try {
|
|
16574
16801
|
await waitForCdp(cdpUrl, browserProcess);
|
|
16575
16802
|
const browser = await chromium2.connectOverCDP(cdpUrl, { artifactsDir: options.outputDirectory, isLocal: true });
|
|
16576
|
-
const
|
|
16577
|
-
const page =
|
|
16803
|
+
const context2 = options.blockServiceWorkers ? await browser.newContext({ serviceWorkers: "block" }) : browser.contexts()[0];
|
|
16804
|
+
const page = context2.pages()[0] ?? await context2.newPage();
|
|
16578
16805
|
await page.setViewportSize({ width: options.width, height: options.height });
|
|
16579
|
-
return { cdpUrl, browser, context, page, browserProcess, profileDirectory };
|
|
16806
|
+
return { cdpUrl, browser, context: context2, page, browserProcess, profileDirectory };
|
|
16580
16807
|
} catch (error51) {
|
|
16581
16808
|
await terminateBrowser(browserProcess);
|
|
16582
16809
|
await rm5(profileDirectory, { recursive: true, force: true }).catch(() => void 0);
|
|
@@ -16623,17 +16850,17 @@ var sessions = /* @__PURE__ */ new Map();
|
|
|
16623
16850
|
var lastActivity = Date.now();
|
|
16624
16851
|
async function runDaemon() {
|
|
16625
16852
|
if (process.platform === "win32" && process.env.RILL_SOCKET) throw new Error("RILL_SOCKET overrides are not supported by authenticated Windows IPC.");
|
|
16626
|
-
const
|
|
16627
|
-
const socketPath =
|
|
16628
|
-
await mkdir7(
|
|
16853
|
+
const identity2 = process.platform === "win32" ? await windowsIpcIdentity() : void 0;
|
|
16854
|
+
const socketPath = identity2?.pipe ?? daemonSocketPath();
|
|
16855
|
+
await mkdir7(identity2 ? dirname(daemonStatePath()) : dirname(socketPath), { recursive: true, mode: 448 });
|
|
16629
16856
|
await mkdir7(recordingTempRoot(), { recursive: true, mode: 448 });
|
|
16630
16857
|
await restoreRecoverableSessions();
|
|
16631
16858
|
await purgeExpiredCaptures();
|
|
16632
|
-
if (!
|
|
16859
|
+
if (!identity2) await unlink(socketPath).catch((error51) => {
|
|
16633
16860
|
if (error51.code !== "ENOENT") throw error51;
|
|
16634
16861
|
});
|
|
16635
16862
|
const server = createServer2((request3, response) => {
|
|
16636
|
-
if (
|
|
16863
|
+
if (identity2 && !acceptsWindowsIpcToken(identity2.token, request3.headers.authorization)) {
|
|
16637
16864
|
request3.resume();
|
|
16638
16865
|
json2(response, 401, { error: { code: "authentication_required", message: "Recorder authentication required." } });
|
|
16639
16866
|
return;
|
|
@@ -16643,7 +16870,7 @@ async function runDaemon() {
|
|
|
16643
16870
|
await new Promise((resolve, reject) => {
|
|
16644
16871
|
server.once("error", reject);
|
|
16645
16872
|
server.listen(socketPath, () => {
|
|
16646
|
-
if (
|
|
16873
|
+
if (identity2) resolve();
|
|
16647
16874
|
else void chmod(socketPath, 384).then(resolve, reject);
|
|
16648
16875
|
});
|
|
16649
16876
|
});
|
|
@@ -16659,7 +16886,7 @@ async function runDaemon() {
|
|
|
16659
16886
|
const close = () => {
|
|
16660
16887
|
clearInterval(idleTimer);
|
|
16661
16888
|
server.close();
|
|
16662
|
-
if (!
|
|
16889
|
+
if (!identity2) void unlink(socketPath).catch(() => void 0);
|
|
16663
16890
|
};
|
|
16664
16891
|
process.once("SIGTERM", close);
|
|
16665
16892
|
process.once("SIGINT", close);
|
|
@@ -16803,7 +17030,7 @@ function emit(value, pretty = false) {
|
|
|
16803
17030
|
function failureValue(error51) {
|
|
16804
17031
|
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
16805
17032
|
const [candidate] = message.split(":", 1);
|
|
16806
|
-
const knownCodes = /* @__PURE__ */ new Set(["authentication_required", "authorization_not_found", "expired_token", "access_denied", "unsupported_platform", "scope_denied", "quota_exceeded", "browser_unavailable", "local_capture_failed", "recording_not_found", "upload_interrupted", "processing_pending", "processing_failed", "share_revoked", "validation_failed"]);
|
|
17033
|
+
const knownCodes = /* @__PURE__ */ new Set(["authentication_required", "authorization_not_found", "expired_token", "access_denied", "unsupported_platform", "unsupported_node_version", "scope_denied", "quota_exceeded", "browser_unavailable", "local_capture_failed", "recording_not_found", "upload_interrupted", "processing_pending", "processing_failed", "share_revoked", "validation_failed"]);
|
|
16807
17034
|
const structured = error51 && typeof error51 === "object" ? error51 : {};
|
|
16808
17035
|
const code = structured.code && knownCodes.has(structured.code) ? structured.code : knownCodes.has(candidate) ? candidate : "internal_error";
|
|
16809
17036
|
return { error: { code, message, ...structured.recovery ? { recovery: structured.recovery } : {}, ...structured.details ? { details: structured.details } : {} } };
|
|
@@ -16817,17 +17044,17 @@ function fail(error51) {
|
|
|
16817
17044
|
function terminalColor(enabled = Boolean(process.stdout.isTTY) && !("NO_COLOR" in process.env) && process.env.TERM !== "dumb") {
|
|
16818
17045
|
return (text, code) => enabled ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
16819
17046
|
}
|
|
16820
|
-
function formatIdentity(
|
|
16821
|
-
if (
|
|
17047
|
+
function formatIdentity(identity2, color = terminalColor()) {
|
|
17048
|
+
if (identity2.status === "not_connected") return `${color("! Not connected to Rill", 33)}
|
|
16822
17049
|
|
|
16823
|
-
Sign in: ${cliCommand(
|
|
17050
|
+
Sign in: ${cliCommand(identity2.apiUrl, "login")}
|
|
16824
17051
|
`;
|
|
16825
|
-
if (
|
|
17052
|
+
if (identity2.status === "anonymous") return `${color("\u2713 Using Rill anonymously", 32)}
|
|
16826
17053
|
|
|
16827
|
-
Connect your account: ${cliCommand(
|
|
17054
|
+
Connect your account: ${cliCommand(identity2.apiUrl, "connect", "--email", "<your-email>")}
|
|
16828
17055
|
`;
|
|
16829
17056
|
return `${color("\u2713 Connected to Rill", 32)}
|
|
16830
|
-
Account: ${
|
|
17057
|
+
Account: ${identity2.email ?? "Email unavailable"}
|
|
16831
17058
|
`;
|
|
16832
17059
|
}
|
|
16833
17060
|
function formatDoctor(result, color = terminalColor()) {
|
|
@@ -16937,6 +17164,31 @@ async function withRunFeedback(session, start) {
|
|
|
16937
17164
|
|
|
16938
17165
|
// src/doctor.ts
|
|
16939
17166
|
import { spawn as spawn7 } from "child_process";
|
|
17167
|
+
|
|
17168
|
+
// src/node-runtime.ts
|
|
17169
|
+
var SUPPORTED_NODE_RANGE = "^22.23.4 || ^24.22.1 || >=26.12.1";
|
|
17170
|
+
var NODE_UPGRADE_RECOVERY = "Install Node.js 24.22.1 LTS or a newer patched release (22.23.4+, 24.22.1+, or 26.12.1+), then rerun the command. Update Chrome/Chromium separately for local recording.";
|
|
17171
|
+
function supportsNodeRuntime(version3) {
|
|
17172
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:\+[\w.-]+)?$/.exec(version3);
|
|
17173
|
+
if (!match) return false;
|
|
17174
|
+
const [major, minor, patch] = match.slice(1).map(Number);
|
|
17175
|
+
const minimum = major === 22 ? [23, 4] : major === 24 ? [22, 1] : major === 26 ? [12, 1] : null;
|
|
17176
|
+
if (!minimum) return major > 26;
|
|
17177
|
+
return minor > minimum[0] || minor === minimum[0] && patch >= minimum[1];
|
|
17178
|
+
}
|
|
17179
|
+
var UnsupportedNodeRuntimeError = class extends Error {
|
|
17180
|
+
code = "unsupported_node_version";
|
|
17181
|
+
recovery = NODE_UPGRADE_RECOVERY;
|
|
17182
|
+
constructor(version3) {
|
|
17183
|
+
super(`Node.js ${version3} does not meet Rill's patched runtime requirement for the eMudhra certificate compromise. Supported versions: ${SUPPORTED_NODE_RANGE}.`);
|
|
17184
|
+
this.name = "UnsupportedNodeRuntimeError";
|
|
17185
|
+
}
|
|
17186
|
+
};
|
|
17187
|
+
function assertNodeRuntime(version3 = process.version) {
|
|
17188
|
+
if (!supportsNodeRuntime(version3)) throw new UnsupportedNodeRuntimeError(version3);
|
|
17189
|
+
}
|
|
17190
|
+
|
|
17191
|
+
// src/doctor.ts
|
|
16940
17192
|
var defaultDependencies = {
|
|
16941
17193
|
platform: process.platform,
|
|
16942
17194
|
encoding: "remote",
|
|
@@ -16955,10 +17207,12 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
16955
17207
|
const checks = [];
|
|
16956
17208
|
let feedbackEnabled = false;
|
|
16957
17209
|
checks.push(recorderTransportCheck(dependencies.platform));
|
|
16958
|
-
if (
|
|
17210
|
+
if (supportsNodeRuntime(dependencies.nodeVersion)) {
|
|
16959
17211
|
checks.push({ name: "node", status: "pass", message: `Node.js ${dependencies.nodeVersion} is supported.`, version: dependencies.nodeVersion });
|
|
16960
17212
|
} else {
|
|
16961
|
-
|
|
17213
|
+
const error51 = new UnsupportedNodeRuntimeError(dependencies.nodeVersion);
|
|
17214
|
+
checks.push({ name: "node", status: "fail", message: error51.message, version: dependencies.nodeVersion, recovery: error51.recovery });
|
|
17215
|
+
return { schemaVersion: 1, status: "error", apiUrl, environment: { platform: dependencies.platform }, checks };
|
|
16962
17216
|
}
|
|
16963
17217
|
let health = null;
|
|
16964
17218
|
try {
|
|
@@ -17025,8 +17279,8 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
17025
17279
|
}
|
|
17026
17280
|
async function executableCheck(name, path, args, run2, recovery) {
|
|
17027
17281
|
try {
|
|
17028
|
-
const
|
|
17029
|
-
return { name, status: "pass", message: `${name === "chrome" ? "Chrome/Chromium" : "ffmpeg"} is available.`, version:
|
|
17282
|
+
const version3 = firstLine(await run2(path, args));
|
|
17283
|
+
return { name, status: "pass", message: `${name === "chrome" ? "Chrome/Chromium" : "ffmpeg"} is available.`, version: version3, path };
|
|
17030
17284
|
} catch (error51) {
|
|
17031
17285
|
return { name, status: "fail", message: `${name === "chrome" ? "Chrome/Chromium" : "ffmpeg"} is unavailable: ${errorMessage(error51)}`, path, recovery };
|
|
17032
17286
|
}
|
|
@@ -17055,8 +17309,8 @@ ${Buffer.concat(stderr).toString("utf8")}`.trim();
|
|
|
17055
17309
|
});
|
|
17056
17310
|
});
|
|
17057
17311
|
}
|
|
17058
|
-
function normalizeVersion(
|
|
17059
|
-
return
|
|
17312
|
+
function normalizeVersion(version3) {
|
|
17313
|
+
return version3.replace(/^v/, "").split("-")[0];
|
|
17060
17314
|
}
|
|
17061
17315
|
function compareVersions(left, right) {
|
|
17062
17316
|
const leftParts = normalizeVersion(left).split(".").map(Number);
|
|
@@ -17206,9 +17460,9 @@ function watchEncoding(apiUrl, id) {
|
|
|
17206
17460
|
var program = new Command();
|
|
17207
17461
|
program.name("rill").description("Record, inspect, and share agent browser runs.").version(CLI_VERSION).option("--api-url <url>", "Rill control plane", controlPlaneUrl(process.env.RILL_API_URL)).option("--pretty", "Pretty-print JSON output", false).option("--json", "Output machine-readable JSON (including whoami)", false).option("--human", "Readable doctor, login, whoami and recording start/stop/status/retry output", false);
|
|
17208
17462
|
program.configureOutput({ outputError: () => void 0 }).exitOverride();
|
|
17209
|
-
function humanOutput(
|
|
17463
|
+
function humanOutput(identity2 = false) {
|
|
17210
17464
|
const options = program.opts();
|
|
17211
|
-
return !options.json && !options.pretty && (
|
|
17465
|
+
return !options.json && !options.pretty && (identity2 || options.human);
|
|
17212
17466
|
}
|
|
17213
17467
|
function emitRecording(value, pretty) {
|
|
17214
17468
|
if (humanOutput()) process.stdout.write(formatRecording(value, program.opts().apiUrl));
|
|
@@ -17251,10 +17505,10 @@ async function authorize(quiet = false) {
|
|
|
17251
17505
|
}
|
|
17252
17506
|
program.command("whoami").description("Show the current verified identity without starting sign-in.").addHelpText("after", "\nShows a short account summary by default. Use rill whoami --json for agents, or rill --pretty whoami for indented JSON.").action(async () => {
|
|
17253
17507
|
const options = program.opts();
|
|
17254
|
-
const
|
|
17255
|
-
if (humanOutput(true)) process.stdout.write(formatIdentity(
|
|
17256
|
-
else emit(
|
|
17257
|
-
if (!
|
|
17508
|
+
const identity2 = await whoami(options.apiUrl);
|
|
17509
|
+
if (humanOutput(true)) process.stdout.write(formatIdentity(identity2));
|
|
17510
|
+
else emit(identity2, options.pretty);
|
|
17511
|
+
if (!identity2.authenticated) process.exitCode = 1;
|
|
17258
17512
|
});
|
|
17259
17513
|
var connect = program.command("connect").description("Connect this agent once, with renewable account access.").option("--email <email>", "Email of the Rill account approving access").option("--no-wait", "Return approval instructions immediately; poll with connect status").action(async (input) => {
|
|
17260
17514
|
if (!input.email) throw new Error("validation_failed: Supply --email <email> to start a connection.");
|
|
@@ -17526,6 +17780,10 @@ drafts.command("purge").action(async () => {
|
|
|
17526
17780
|
emit({ schemaVersion: 1, purged }, options.pretty);
|
|
17527
17781
|
});
|
|
17528
17782
|
program.hook("preAction", async (_command, actionCommand) => {
|
|
17783
|
+
if (!supportsNodeRuntime(process.version)) {
|
|
17784
|
+
if (commandPath(actionCommand).join(" ") === "doctor") return;
|
|
17785
|
+
assertNodeRuntime();
|
|
17786
|
+
}
|
|
17529
17787
|
if (commandPath(actionCommand).join(" ") === "record start") assertRecorderTransport();
|
|
17530
17788
|
const command = cliCommandCategory(commandPath(actionCommand));
|
|
17531
17789
|
if (!command) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runalabs/rill-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "Record, inspect, and share agent-controlled browser runs with Rill.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"bugs": "https://github.com/RunaLabsDev/rill-web/issues",
|
|
13
13
|
"type": "module",
|
|
14
14
|
"engines": {
|
|
15
|
-
"node": ">=
|
|
15
|
+
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
|
|
16
16
|
},
|
|
17
17
|
"bin": {
|
|
18
18
|
"rill": "dist/cli.js"
|