@runalabs/rill-cli 0.1.18 → 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 +357 -98
- 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."),
|
|
@@ -15896,6 +15897,7 @@ var scrollCapsuleSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="20.5" hei
|
|
|
15896
15897
|
</svg>`;
|
|
15897
15898
|
var scrollCapsuleStyles = `
|
|
15898
15899
|
.scroll-indicator { position:absolute; right:12.75px; bottom:10.75px; width:20.5px; height:42.5px; opacity:0; transform:translateY(4px) scale(.92); transform-origin:center; transition:opacity 180ms ease-out,transform 180ms ease-out; filter:drop-shadow(0 1px 1.5px rgba(12,12,18,.18)) drop-shadow(0 4px 5px rgba(12,12,18,.12)); --rill-cursor-accent-light:color-mix(in srgb,var(--rill-cursor-accent) 78%,#fff); }
|
|
15900
|
+
.scroll-label { left:auto; right:calc(100% + 7px); top:50%; transform:translateY(-50%); }
|
|
15899
15901
|
.scroll-indicator.is-visible { opacity:1; transform:none; transition:opacity 160ms ease-out,transform 260ms cubic-bezier(.2,.9,.3,1.25); }
|
|
15900
15902
|
.scroll-indicator svg { position:absolute; left:50%; top:50%; width:20.5px; height:42.5px; translate:-50% -50%; transform-origin:center; }
|
|
15901
15903
|
.scroll-indicator[data-direction="right"],.scroll-indicator[data-direction="left"] { width:42.5px; height:20.5px; }
|
|
@@ -15946,7 +15948,7 @@ function installCursorOverlay(options) {
|
|
|
15946
15948
|
pointer.innerHTML = options.pointerMarkup;
|
|
15947
15949
|
const scrollIndicator = document.createElement("div");
|
|
15948
15950
|
scrollIndicator.className = "scroll-indicator";
|
|
15949
|
-
scrollIndicator.innerHTML = options.scrollMarkup;
|
|
15951
|
+
scrollIndicator.innerHTML = options.scrollMarkup + '<span class="rill-cursor-label scroll-label">Scrolling</span>';
|
|
15950
15952
|
const chevrons = scrollIndicator.querySelector("#chevrons");
|
|
15951
15953
|
root.append(style, pointer, scrollIndicator);
|
|
15952
15954
|
document.documentElement.appendChild(host);
|
|
@@ -15993,11 +15995,11 @@ function installCursorOverlay(options) {
|
|
|
15993
15995
|
positions.set(document, { x: window.scrollX, y: window.scrollY });
|
|
15994
15996
|
document.addEventListener("scroll", (event) => {
|
|
15995
15997
|
if (!event.isTrusted || document.hidden) return;
|
|
15996
|
-
const
|
|
15997
|
-
if (
|
|
15998
|
-
const position =
|
|
15999
|
-
const previous = positions.get(
|
|
16000
|
-
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);
|
|
16001
16003
|
const dx = previous ? position.x - previous.x : 0;
|
|
16002
16004
|
const dy = previous ? position.y - previous.y : 0;
|
|
16003
16005
|
if (previous && !dx && !dy) return;
|
|
@@ -16028,13 +16030,13 @@ function installCursorOverlay(options) {
|
|
|
16028
16030
|
document.addEventListener("pointerover", move, listenerOptions);
|
|
16029
16031
|
document.addEventListener("pointerup", release, listenerOptions);
|
|
16030
16032
|
document.addEventListener("pointercancel", release, listenerOptions);
|
|
16031
|
-
const animateFeedback = (
|
|
16032
|
-
root.insertBefore(
|
|
16033
|
-
const animation =
|
|
16033
|
+
const animateFeedback = (element2, frames, duration3, easing) => {
|
|
16034
|
+
root.insertBefore(element2, pointer);
|
|
16035
|
+
const animation = element2.animate(frames, { duration: duration3, easing });
|
|
16034
16036
|
animations.add(animation);
|
|
16035
16037
|
void animation.finished.catch(() => void 0).finally(() => {
|
|
16036
16038
|
animations.delete(animation);
|
|
16037
|
-
|
|
16039
|
+
element2.remove();
|
|
16038
16040
|
});
|
|
16039
16041
|
};
|
|
16040
16042
|
document.addEventListener("pointerdown", (event) => {
|
|
@@ -16125,8 +16127,10 @@ async function writeBundle(bundle, outputPath) {
|
|
|
16125
16127
|
const output = createWriteStream(outputPath, { mode: 384 });
|
|
16126
16128
|
const gzip = createGzip({ level: 9 });
|
|
16127
16129
|
gzip.pipe(output);
|
|
16128
|
-
const { console: consoleEvents, network, timeline, ...manifest } = bundle;
|
|
16130
|
+
const { console: consoleEvents, network, timeline, actions, ...manifest } = bundle;
|
|
16129
16131
|
gzip.write(`${JSON.stringify({ kind: "manifest", ...manifest })}
|
|
16132
|
+
`);
|
|
16133
|
+
for (const event of actions ?? []) gzip.write(`${JSON.stringify(event)}
|
|
16130
16134
|
`);
|
|
16131
16135
|
for (const event of consoleEvents) gzip.write(`${JSON.stringify({ kind: "console", ...event })}
|
|
16132
16136
|
`);
|
|
@@ -16138,6 +16142,203 @@ async function writeBundle(bundle, outputPath) {
|
|
|
16138
16142
|
await once(output, "close");
|
|
16139
16143
|
}
|
|
16140
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
|
+
|
|
16141
16342
|
// ../recorder/src/environment.ts
|
|
16142
16343
|
import { existsSync } from "fs";
|
|
16143
16344
|
import { win32 as win323 } from "path";
|
|
@@ -16249,12 +16450,18 @@ var ScreencastRecorder = class {
|
|
|
16249
16450
|
frames = [];
|
|
16250
16451
|
pendingWrites = /* @__PURE__ */ new Set();
|
|
16251
16452
|
started = false;
|
|
16453
|
+
firstFrameEpochMs;
|
|
16454
|
+
get videoStartedAtMs() {
|
|
16455
|
+
return this.firstFrameEpochMs;
|
|
16456
|
+
}
|
|
16252
16457
|
frameCounter = 0;
|
|
16253
16458
|
frameBytes = 0;
|
|
16254
16459
|
captureError;
|
|
16255
16460
|
async start() {
|
|
16256
16461
|
await mkdir5(this.directory, { recursive: true, mode: 448 });
|
|
16257
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);
|
|
16258
16465
|
const bytes = Buffer.from(payload.data, "base64");
|
|
16259
16466
|
if (this.frameCounter >= 36e3 || this.frameBytes + bytes.length > ENCODING_MAX_BYTES || bytes.length > ENCODING_PART_BYTES) {
|
|
16260
16467
|
this.captureError = new Error("Capture exceeds the remote encoding frame or byte limit.");
|
|
@@ -16265,7 +16472,7 @@ var ScreencastRecorder = class {
|
|
|
16265
16472
|
const path = join8(this.directory, `frame-${String(this.frameCounter).padStart(6, "0")}.jpg`);
|
|
16266
16473
|
this.frameCounter += 1;
|
|
16267
16474
|
const write = writeFile4(path, bytes, { mode: 384 }).then(() => {
|
|
16268
|
-
this.frames.push({ path, timestamp:
|
|
16475
|
+
this.frames.push({ path, timestamp: frameEpochMs / 1e3 });
|
|
16269
16476
|
}).catch((error51) => {
|
|
16270
16477
|
this.captureError = error51 instanceof Error ? error51 : new Error(String(error51));
|
|
16271
16478
|
}).finally(() => {
|
|
@@ -16319,6 +16526,17 @@ var RecordingSession = class _RecordingSession {
|
|
|
16319
16526
|
consoleEvents = [];
|
|
16320
16527
|
networkEvents = [];
|
|
16321
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
|
+
}
|
|
16322
16540
|
requestStart = /* @__PURE__ */ new Map();
|
|
16323
16541
|
requestEvents = /* @__PURE__ */ new Map();
|
|
16324
16542
|
stopPromise;
|
|
@@ -16340,6 +16558,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
16340
16558
|
this.videoFormat = options.videoFormat ?? "webm";
|
|
16341
16559
|
this.captureSignal = options.signal;
|
|
16342
16560
|
this.recordingId = options.recordingId;
|
|
16561
|
+
this.browserActions = new BrowserActionCapture(options.recordingId);
|
|
16343
16562
|
this.cdpUrl = runtime.cdpUrl;
|
|
16344
16563
|
this.tabId = runtime.tabId;
|
|
16345
16564
|
this.browser = runtime.browser;
|
|
@@ -16409,6 +16628,10 @@ var RecordingSession = class _RecordingSession {
|
|
|
16409
16628
|
}
|
|
16410
16629
|
async finalize(reason) {
|
|
16411
16630
|
if (this.maxTimer) clearTimeout(this.maxTimer);
|
|
16631
|
+
await this.removeClickCapture?.().catch(() => {
|
|
16632
|
+
this.truncated = true;
|
|
16633
|
+
});
|
|
16634
|
+
this.removeClickCapture = void 0;
|
|
16412
16635
|
const durationSeconds = Math.max(0.1, (Date.now() - this.startedAt.getTime()) / 1e3);
|
|
16413
16636
|
const outputDirectory = this.outputDirectory;
|
|
16414
16637
|
const videoPath = join9(outputDirectory, `recording.${this.videoFormat}`);
|
|
@@ -16416,22 +16639,25 @@ var RecordingSession = class _RecordingSession {
|
|
|
16416
16639
|
const bundle = {
|
|
16417
16640
|
schemaVersion: 1,
|
|
16418
16641
|
capturePolicyVersion: "safe-default-v1",
|
|
16642
|
+
...this.screencast.videoStartedAtMs == null ? {} : { videoStartOffsetMs: this.screencast.videoStartedAtMs - this.startedAt.getTime() },
|
|
16419
16643
|
recordingId: this.recordingId,
|
|
16420
16644
|
startedAt: this.startedAt.toISOString(),
|
|
16421
16645
|
durationMs: Math.round(durationSeconds * 1e3),
|
|
16422
16646
|
bodyCaptureEnabled: this.captureNetworkBodies,
|
|
16423
16647
|
bodyCaptureRedacted: this.captureNetworkBodies,
|
|
16424
|
-
truncated: this.truncated,
|
|
16648
|
+
truncated: this.truncated || this.browserActions.truncated,
|
|
16649
|
+
actionsTruncated: this.browserActions.truncated,
|
|
16425
16650
|
console: this.consoleEvents,
|
|
16426
16651
|
network: this.networkEvents,
|
|
16427
16652
|
timeline: this.timelineEvents.toSorted((left, right) => left.offsetMs - right.offsetMs)
|
|
16428
16653
|
};
|
|
16429
16654
|
try {
|
|
16430
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;
|
|
16431
16658
|
await writeBundle(safeBundle, diagnosticsPath);
|
|
16432
16659
|
await this.screencast.stop(videoPath, durationSeconds, this.encoding === "remote", this.captureSignal);
|
|
16433
16660
|
} finally {
|
|
16434
|
-
await this.removeClickCapture?.().catch(() => void 0);
|
|
16435
16661
|
await this.removeCursorOverlay?.().catch(() => void 0);
|
|
16436
16662
|
await this.browser.close().catch(() => void 0);
|
|
16437
16663
|
if (this.browserProcess) {
|
|
@@ -16442,7 +16668,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
16442
16668
|
const consoleErrors = this.consoleEvents.filter((event) => event.severity === "error").length;
|
|
16443
16669
|
const failedRequests = this.networkEvents.filter((event) => event.failureReason).length;
|
|
16444
16670
|
const httpErrors = this.networkEvents.filter((event) => (event.status ?? 0) >= 400).length;
|
|
16445
|
-
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 } };
|
|
16446
16672
|
}
|
|
16447
16673
|
captureNetworkBodies = false;
|
|
16448
16674
|
captureSignal;
|
|
@@ -16497,7 +16723,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
16497
16723
|
}
|
|
16498
16724
|
const started = offset();
|
|
16499
16725
|
this.requestStart.set(request3, started);
|
|
16500
|
-
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()) };
|
|
16501
16727
|
if (captureNetworkBodies && request3.postData()) {
|
|
16502
16728
|
const redacted = redactBody(request3.postData(), request3.headers()["content-type"]);
|
|
16503
16729
|
event.requestBody = redacted.body;
|
|
@@ -16512,6 +16738,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
16512
16738
|
const event = this.requestEvents.get(request3);
|
|
16513
16739
|
if (!event) return;
|
|
16514
16740
|
event.failureReason = request3.failure()?.errorText ?? "request_failed";
|
|
16741
|
+
event.completedOffsetMs = offset();
|
|
16515
16742
|
event.durationMs = Math.max(0, offset() - (this.requestStart.get(request3) ?? offset()));
|
|
16516
16743
|
pushTimeline({ offsetMs: event.offsetMs, type: "network", label: `${event.method} ${event.url} \u2192 ${event.failureReason}`, severity: "error" });
|
|
16517
16744
|
});
|
|
@@ -16524,6 +16751,7 @@ var RecordingSession = class _RecordingSession {
|
|
|
16524
16751
|
const event = this.requestEvents.get(response.request());
|
|
16525
16752
|
if (!event) return;
|
|
16526
16753
|
event.status = response.status();
|
|
16754
|
+
event.completedOffsetMs = Math.max(0, Date.now() - this.startedAt.getTime());
|
|
16527
16755
|
event.mimeType = response.headers()["content-type"];
|
|
16528
16756
|
event.durationMs = Math.max(0, Date.now() - this.startedAt.getTime() - event.offsetMs);
|
|
16529
16757
|
event.transferSize = Number(response.headers()["content-length"] ?? 0) || void 0;
|
|
@@ -16572,10 +16800,10 @@ async function launchManaged(options) {
|
|
|
16572
16800
|
try {
|
|
16573
16801
|
await waitForCdp(cdpUrl, browserProcess);
|
|
16574
16802
|
const browser = await chromium2.connectOverCDP(cdpUrl, { artifactsDir: options.outputDirectory, isLocal: true });
|
|
16575
|
-
const
|
|
16576
|
-
const page =
|
|
16803
|
+
const context2 = options.blockServiceWorkers ? await browser.newContext({ serviceWorkers: "block" }) : browser.contexts()[0];
|
|
16804
|
+
const page = context2.pages()[0] ?? await context2.newPage();
|
|
16577
16805
|
await page.setViewportSize({ width: options.width, height: options.height });
|
|
16578
|
-
return { cdpUrl, browser, context, page, browserProcess, profileDirectory };
|
|
16806
|
+
return { cdpUrl, browser, context: context2, page, browserProcess, profileDirectory };
|
|
16579
16807
|
} catch (error51) {
|
|
16580
16808
|
await terminateBrowser(browserProcess);
|
|
16581
16809
|
await rm5(profileDirectory, { recursive: true, force: true }).catch(() => void 0);
|
|
@@ -16622,17 +16850,17 @@ var sessions = /* @__PURE__ */ new Map();
|
|
|
16622
16850
|
var lastActivity = Date.now();
|
|
16623
16851
|
async function runDaemon() {
|
|
16624
16852
|
if (process.platform === "win32" && process.env.RILL_SOCKET) throw new Error("RILL_SOCKET overrides are not supported by authenticated Windows IPC.");
|
|
16625
|
-
const
|
|
16626
|
-
const socketPath =
|
|
16627
|
-
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 });
|
|
16628
16856
|
await mkdir7(recordingTempRoot(), { recursive: true, mode: 448 });
|
|
16629
16857
|
await restoreRecoverableSessions();
|
|
16630
16858
|
await purgeExpiredCaptures();
|
|
16631
|
-
if (!
|
|
16859
|
+
if (!identity2) await unlink(socketPath).catch((error51) => {
|
|
16632
16860
|
if (error51.code !== "ENOENT") throw error51;
|
|
16633
16861
|
});
|
|
16634
16862
|
const server = createServer2((request3, response) => {
|
|
16635
|
-
if (
|
|
16863
|
+
if (identity2 && !acceptsWindowsIpcToken(identity2.token, request3.headers.authorization)) {
|
|
16636
16864
|
request3.resume();
|
|
16637
16865
|
json2(response, 401, { error: { code: "authentication_required", message: "Recorder authentication required." } });
|
|
16638
16866
|
return;
|
|
@@ -16642,7 +16870,7 @@ async function runDaemon() {
|
|
|
16642
16870
|
await new Promise((resolve, reject) => {
|
|
16643
16871
|
server.once("error", reject);
|
|
16644
16872
|
server.listen(socketPath, () => {
|
|
16645
|
-
if (
|
|
16873
|
+
if (identity2) resolve();
|
|
16646
16874
|
else void chmod(socketPath, 384).then(resolve, reject);
|
|
16647
16875
|
});
|
|
16648
16876
|
});
|
|
@@ -16658,7 +16886,7 @@ async function runDaemon() {
|
|
|
16658
16886
|
const close = () => {
|
|
16659
16887
|
clearInterval(idleTimer);
|
|
16660
16888
|
server.close();
|
|
16661
|
-
if (!
|
|
16889
|
+
if (!identity2) void unlink(socketPath).catch(() => void 0);
|
|
16662
16890
|
};
|
|
16663
16891
|
process.once("SIGTERM", close);
|
|
16664
16892
|
process.once("SIGINT", close);
|
|
@@ -16802,7 +17030,7 @@ function emit(value, pretty = false) {
|
|
|
16802
17030
|
function failureValue(error51) {
|
|
16803
17031
|
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
16804
17032
|
const [candidate] = message.split(":", 1);
|
|
16805
|
-
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"]);
|
|
16806
17034
|
const structured = error51 && typeof error51 === "object" ? error51 : {};
|
|
16807
17035
|
const code = structured.code && knownCodes.has(structured.code) ? structured.code : knownCodes.has(candidate) ? candidate : "internal_error";
|
|
16808
17036
|
return { error: { code, message, ...structured.recovery ? { recovery: structured.recovery } : {}, ...structured.details ? { details: structured.details } : {} } };
|
|
@@ -16816,17 +17044,17 @@ function fail(error51) {
|
|
|
16816
17044
|
function terminalColor(enabled = Boolean(process.stdout.isTTY) && !("NO_COLOR" in process.env) && process.env.TERM !== "dumb") {
|
|
16817
17045
|
return (text, code) => enabled ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
16818
17046
|
}
|
|
16819
|
-
function formatIdentity(
|
|
16820
|
-
if (
|
|
17047
|
+
function formatIdentity(identity2, color = terminalColor()) {
|
|
17048
|
+
if (identity2.status === "not_connected") return `${color("! Not connected to Rill", 33)}
|
|
16821
17049
|
|
|
16822
|
-
Sign in: ${cliCommand(
|
|
17050
|
+
Sign in: ${cliCommand(identity2.apiUrl, "login")}
|
|
16823
17051
|
`;
|
|
16824
|
-
if (
|
|
17052
|
+
if (identity2.status === "anonymous") return `${color("\u2713 Using Rill anonymously", 32)}
|
|
16825
17053
|
|
|
16826
|
-
Connect your account: ${cliCommand(
|
|
17054
|
+
Connect your account: ${cliCommand(identity2.apiUrl, "connect", "--email", "<your-email>")}
|
|
16827
17055
|
`;
|
|
16828
17056
|
return `${color("\u2713 Connected to Rill", 32)}
|
|
16829
|
-
Account: ${
|
|
17057
|
+
Account: ${identity2.email ?? "Email unavailable"}
|
|
16830
17058
|
`;
|
|
16831
17059
|
}
|
|
16832
17060
|
function formatDoctor(result, color = terminalColor()) {
|
|
@@ -16936,6 +17164,31 @@ async function withRunFeedback(session, start) {
|
|
|
16936
17164
|
|
|
16937
17165
|
// src/doctor.ts
|
|
16938
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
|
|
16939
17192
|
var defaultDependencies = {
|
|
16940
17193
|
platform: process.platform,
|
|
16941
17194
|
encoding: "remote",
|
|
@@ -16954,10 +17207,12 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
16954
17207
|
const checks = [];
|
|
16955
17208
|
let feedbackEnabled = false;
|
|
16956
17209
|
checks.push(recorderTransportCheck(dependencies.platform));
|
|
16957
|
-
if (
|
|
17210
|
+
if (supportsNodeRuntime(dependencies.nodeVersion)) {
|
|
16958
17211
|
checks.push({ name: "node", status: "pass", message: `Node.js ${dependencies.nodeVersion} is supported.`, version: dependencies.nodeVersion });
|
|
16959
17212
|
} else {
|
|
16960
|
-
|
|
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 };
|
|
16961
17216
|
}
|
|
16962
17217
|
let health = null;
|
|
16963
17218
|
try {
|
|
@@ -17024,8 +17279,8 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
17024
17279
|
}
|
|
17025
17280
|
async function executableCheck(name, path, args, run2, recovery) {
|
|
17026
17281
|
try {
|
|
17027
|
-
const
|
|
17028
|
-
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 };
|
|
17029
17284
|
} catch (error51) {
|
|
17030
17285
|
return { name, status: "fail", message: `${name === "chrome" ? "Chrome/Chromium" : "ffmpeg"} is unavailable: ${errorMessage(error51)}`, path, recovery };
|
|
17031
17286
|
}
|
|
@@ -17054,8 +17309,8 @@ ${Buffer.concat(stderr).toString("utf8")}`.trim();
|
|
|
17054
17309
|
});
|
|
17055
17310
|
});
|
|
17056
17311
|
}
|
|
17057
|
-
function normalizeVersion(
|
|
17058
|
-
return
|
|
17312
|
+
function normalizeVersion(version3) {
|
|
17313
|
+
return version3.replace(/^v/, "").split("-")[0];
|
|
17059
17314
|
}
|
|
17060
17315
|
function compareVersions(left, right) {
|
|
17061
17316
|
const leftParts = normalizeVersion(left).split(".").map(Number);
|
|
@@ -17205,9 +17460,9 @@ function watchEncoding(apiUrl, id) {
|
|
|
17205
17460
|
var program = new Command();
|
|
17206
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);
|
|
17207
17462
|
program.configureOutput({ outputError: () => void 0 }).exitOverride();
|
|
17208
|
-
function humanOutput(
|
|
17463
|
+
function humanOutput(identity2 = false) {
|
|
17209
17464
|
const options = program.opts();
|
|
17210
|
-
return !options.json && !options.pretty && (
|
|
17465
|
+
return !options.json && !options.pretty && (identity2 || options.human);
|
|
17211
17466
|
}
|
|
17212
17467
|
function emitRecording(value, pretty) {
|
|
17213
17468
|
if (humanOutput()) process.stdout.write(formatRecording(value, program.opts().apiUrl));
|
|
@@ -17250,10 +17505,10 @@ async function authorize(quiet = false) {
|
|
|
17250
17505
|
}
|
|
17251
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 () => {
|
|
17252
17507
|
const options = program.opts();
|
|
17253
|
-
const
|
|
17254
|
-
if (humanOutput(true)) process.stdout.write(formatIdentity(
|
|
17255
|
-
else emit(
|
|
17256
|
-
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;
|
|
17257
17512
|
});
|
|
17258
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) => {
|
|
17259
17514
|
if (!input.email) throw new Error("validation_failed: Supply --email <email> to start a connection.");
|
|
@@ -17525,6 +17780,10 @@ drafts.command("purge").action(async () => {
|
|
|
17525
17780
|
emit({ schemaVersion: 1, purged }, options.pretty);
|
|
17526
17781
|
});
|
|
17527
17782
|
program.hook("preAction", async (_command, actionCommand) => {
|
|
17783
|
+
if (!supportsNodeRuntime(process.version)) {
|
|
17784
|
+
if (commandPath(actionCommand).join(" ") === "doctor") return;
|
|
17785
|
+
assertNodeRuntime();
|
|
17786
|
+
}
|
|
17528
17787
|
if (commandPath(actionCommand).join(" ") === "record start") assertRecorderTransport();
|
|
17529
17788
|
const command = cliCommandCategory(commandPath(actionCommand));
|
|
17530
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"
|