@runalabs/rill-cli 0.1.15 → 0.1.16
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 +20 -3
- package/dist/cli.js +121 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,6 +25,10 @@ The following local-recording instructions remain available through `record star
|
|
|
25
25
|
|
|
26
26
|
## Install and verify
|
|
27
27
|
|
|
28
|
+
Check `node --version`, `node -p "process.platform"`, and `rill --version` before installing. Reuse an installed compatible CLI. Commands execute in the agent's environment, which may be a separate sandbox from the user's desktop. Chrome must be accessible there, and the agent must be able to attach over CDP; doctor does not verify browser-control capability.
|
|
29
|
+
|
|
30
|
+
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
|
+
|
|
28
32
|
Browser recording runs on macOS, Linux, and Windows. Install Node.js 22+ and 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.
|
|
29
33
|
|
|
30
34
|
Then install the scoped package:
|
|
@@ -42,7 +46,7 @@ Do not use `npx rill`: the unscoped package belongs to a different product. Doct
|
|
|
42
46
|
|
|
43
47
|
### Connect with pollable approval
|
|
44
48
|
|
|
45
|
-
`rill whoami`
|
|
49
|
+
`rill whoami` shows a short account summary without starting sign-in. Agents should use `rill whoami --json` for verified identity fields. `rill connect --email you@example.com` waits for human approval by default. For agents that cannot hold a foreground command open:
|
|
46
50
|
|
|
47
51
|
```sh
|
|
48
52
|
rill connect --email you@example.com --no-wait
|
|
@@ -221,7 +225,20 @@ Use `rill --help` or `<command> --help` for supported flags.
|
|
|
221
225
|
|
|
222
226
|
## Output and errors
|
|
223
227
|
|
|
224
|
-
|
|
228
|
+
`whoami` shows a concise human summary by default. Use `rill whoami --json` for the existing schema-versioned identity response, including email, workspace, scopes and expiry. Its exit codes are unchanged: missing credentials exit 1; connected and anonymous access exit 0.
|
|
229
|
+
|
|
230
|
+
Doctor and local recording commands retain JSON on stdout by default. Use `--human` for readable doctor, login, recording start/stop/status/retry summaries and errors. Doctor shows the command execution OS, named prerequisite checks, recovery guidance, and a pass/fail/warning count. Recording summaries show the ID, status, duration when available, share URL when returned, and the exact next command. Pending processing is distinct from a ready recording; a private recording may have no share URL.
|
|
231
|
+
|
|
232
|
+
Explicit `--json` or `--pretty` takes precedence over `--human`. Use `--pretty` for indented JSON. Human output uses restrained terminal color only on a TTY, respects `NO_COLOR` and `TERM=dumb`, and retains status symbols and text without color. Human errors go to stderr; JSON errors remain on stdout. Help and version commands print plain text. Feedback and attachment metadata remain available in JSON; selecting a human view does not disable workspace feedback.
|
|
233
|
+
|
|
234
|
+
```sh
|
|
235
|
+
rill whoami
|
|
236
|
+
rill whoami --json
|
|
237
|
+
rill --human doctor
|
|
238
|
+
rill --human record start --url https://example.com --title "Smoke test" --headed
|
|
239
|
+
rill --human record stop <recording-id>
|
|
240
|
+
rill --human record status <recording-id>
|
|
241
|
+
```
|
|
225
242
|
|
|
226
243
|
```sh
|
|
227
244
|
rill --pretty record status <recording-id>
|
|
@@ -357,7 +374,7 @@ Use `rill record list` to list accessible recordings and `rill inspect <recordin
|
|
|
357
374
|
|
|
358
375
|
### Check the connected account
|
|
359
376
|
|
|
360
|
-
Run `rill whoami`
|
|
377
|
+
Run `rill whoami` for a short human account summary, or `rill whoami --json` (also `rill --pretty whoami`) for structured identity fields. JSON includes status, account email when available, workspace, scopes, and access expiry. It uses RILL_TOKEN when set, otherwise the saved OS credential. Existing access can renew automatically; it never creates an identity or opens an approval flow. Missing credentials return `not_connected` in JSON and exit 1. Invalid or revoked credentials return an error.
|
|
361
378
|
|
|
362
379
|
### Capture time and upload progress (0.1.14)
|
|
363
380
|
|
package/dist/cli.js
CHANGED
|
@@ -442,7 +442,7 @@ var ControlPlaneClient = class {
|
|
|
442
442
|
return this.request(`/api/cloud/runs/${encodeURIComponent(id)}/cancel`, { method: "POST" });
|
|
443
443
|
}
|
|
444
444
|
health() {
|
|
445
|
-
return this.request("/api/health", {}, false);
|
|
445
|
+
return this.request("/api/health", { signal: AbortSignal.timeout(5e3) }, false);
|
|
446
446
|
}
|
|
447
447
|
agentSession() {
|
|
448
448
|
return this.request("/api/agent/session", { signal: AbortSignal.timeout(5e3) });
|
|
@@ -15244,7 +15244,7 @@ async function waitForEncoding(client, id, timeoutMs = 30 * 6e4) {
|
|
|
15244
15244
|
// src/cli.ts
|
|
15245
15245
|
import { spawn as spawn8 } from "child_process";
|
|
15246
15246
|
import { hostname as hostname4, tmpdir as tmpdir3 } from "os";
|
|
15247
|
-
import { Command } from "commander";
|
|
15247
|
+
import { Command, CommanderError } from "commander";
|
|
15248
15248
|
import { basename, dirname as dirname2, join as join11 } from "path";
|
|
15249
15249
|
import { readdir as readdir2, rm as rm7, stat as stat5 } from "fs/promises";
|
|
15250
15250
|
|
|
@@ -16647,6 +16647,72 @@ function fail(error51) {
|
|
|
16647
16647
|
process.exitCode = 1;
|
|
16648
16648
|
}
|
|
16649
16649
|
|
|
16650
|
+
// src/human-output.ts
|
|
16651
|
+
function terminalColor(enabled = Boolean(process.stdout.isTTY) && !("NO_COLOR" in process.env) && process.env.TERM !== "dumb") {
|
|
16652
|
+
return (text, code) => enabled ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
16653
|
+
}
|
|
16654
|
+
function formatIdentity(identity, color = terminalColor()) {
|
|
16655
|
+
if (identity.status === "not_connected") return `${color("! Not connected to Rill", 33)}
|
|
16656
|
+
|
|
16657
|
+
Sign in: ${cliCommand(identity.apiUrl, "login")}
|
|
16658
|
+
`;
|
|
16659
|
+
if (identity.status === "anonymous") return `${color("\u2713 Using Rill anonymously", 32)}
|
|
16660
|
+
|
|
16661
|
+
Connect your account: ${cliCommand(identity.apiUrl, "connect", "--email", "<your-email>")}
|
|
16662
|
+
`;
|
|
16663
|
+
return `${color("\u2713 Connected to Rill", 32)}
|
|
16664
|
+
Account: ${identity.email ?? "Email unavailable"}
|
|
16665
|
+
`;
|
|
16666
|
+
}
|
|
16667
|
+
function formatDoctor(result, color = terminalColor()) {
|
|
16668
|
+
const labels = { node: "Node.js", api: "API connectivity", cli_version: "CLI version", authentication: "Authentication", chrome: "Browser", ffmpeg: "FFmpeg", encoding: "Encoding", recorder_transport: "Recorder transport" };
|
|
16669
|
+
const symbols = { pass: "\u2713", fail: "\xD7", warn: "!", skip: "\u2013" };
|
|
16670
|
+
const colors = { pass: 32, fail: 31, warn: 33, skip: 90 };
|
|
16671
|
+
const lines = [color("Rill CLI Doctor", 34), `Execution environment: ${result.environment.platform}`, ""];
|
|
16672
|
+
for (const check2 of result.checks) {
|
|
16673
|
+
lines.push(color(`${symbols[check2.status]} ${labels[check2.name]}: ${check2.message}`, colors[check2.status]));
|
|
16674
|
+
if (check2.recovery) lines.push(` \u2192 ${check2.recovery}`);
|
|
16675
|
+
}
|
|
16676
|
+
const warnings = result.checks.filter((c) => c.status === "warn").length;
|
|
16677
|
+
lines.push(
|
|
16678
|
+
"",
|
|
16679
|
+
"Browser control: not verified. Your agent must attach to the returned CDP endpoint.",
|
|
16680
|
+
"",
|
|
16681
|
+
`${result.checks.filter((c) => c.status === "pass").length} passed \xB7 ${result.checks.filter((c) => c.status === "fail").length} failed \xB7 ${warnings} ${warnings === 1 ? "warning" : "warnings"}`
|
|
16682
|
+
);
|
|
16683
|
+
return lines.join("\n") + "\n";
|
|
16684
|
+
}
|
|
16685
|
+
function formatRecording(value, apiUrl, color = terminalColor()) {
|
|
16686
|
+
const artifact = value.result && typeof value.result === "object" ? value.result : {};
|
|
16687
|
+
const id = String(value.recordingId ?? value.id ?? "");
|
|
16688
|
+
const status = String(value.status ?? "unknown");
|
|
16689
|
+
const titles = { recording: "Recording started", ready: "Recording ready", captured: "Captured locally \xB7 upload required", uploading: "Uploading recording", queued: "Recording queued for encoding", encoding: "Encoding recording", processing: "Processing recording", finalizing: "Finalizing recording", failed: "Recording failed", cancelled: "Recording cancelled", expired: "Recording expired" };
|
|
16690
|
+
const lines = [color(`${["recording", "ready"].includes(status) ? "\u2713" : ["failed", "expired"].includes(status) ? "\xD7" : "\u2013"} ${titles[status] ?? `Recording status: ${status}`}`, ["failed", "expired"].includes(status) ? 31 : status === "ready" || status === "recording" ? 32 : 33)];
|
|
16691
|
+
if (id) lines.push(`ID: ${id}`);
|
|
16692
|
+
const duration3 = value.durationSeconds ?? value.duration_seconds ?? artifact.durationSeconds;
|
|
16693
|
+
if (typeof duration3 === "number" && Number.isFinite(duration3)) lines.push(`Duration: ${Math.floor(duration3 / 60)}m ${Math.floor(duration3 % 60)}s`);
|
|
16694
|
+
if (typeof value.remainingSeconds === "number") lines.push(`Time remaining: ${Math.max(0, Math.ceil(value.remainingSeconds))}s`);
|
|
16695
|
+
if (typeof value.cdpUrl === "string") lines.push(`Browser connection: ${value.cdpUrl}`);
|
|
16696
|
+
if (typeof value.tabId === "string") lines.push(`Recorded tab: ${value.tabId}`);
|
|
16697
|
+
if (typeof value.shareUrl === "string" && value.shareUrl) lines.push("", `Share: ${value.shareUrl}`);
|
|
16698
|
+
else if (status === "ready") lines.push("", "Ready. No share link returned; open this recording in your Rill account.");
|
|
16699
|
+
const message = value.message ?? value.error;
|
|
16700
|
+
if (typeof message === "string" && message) lines.push("", message);
|
|
16701
|
+
const next = typeof value.nextCommand === "string" ? value.nextCommand : status === "recording" && id ? cliCommand(apiUrl, "record", "stop", id) : void 0;
|
|
16702
|
+
if (next) lines.push("", status === "recording" ? "Perform your test, then stop and upload:" : "Next:", ` ${next}`);
|
|
16703
|
+
return lines.join("\n") + "\n";
|
|
16704
|
+
}
|
|
16705
|
+
function formatFailure(error51, color = terminalColor()) {
|
|
16706
|
+
const { error: failure } = failureValue(error51);
|
|
16707
|
+
let message = failure.message.startsWith(`${failure.code}: `) ? failure.message.slice(failure.code.length + 2) : failure.message;
|
|
16708
|
+
if (failure.recovery && message.endsWith(`: ${failure.recovery}`)) message = message.slice(0, -failure.recovery.length - 2);
|
|
16709
|
+
const lines = [color(`\xD7 ${message}`, 31)];
|
|
16710
|
+
if (typeof failure.details?.recordingId === "string") lines.push(`ID: ${failure.details.recordingId}`);
|
|
16711
|
+
if (failure.recovery) lines.push("", `Next: ${failure.recovery}`);
|
|
16712
|
+
else if (typeof failure.details?.nextCommand === "string") lines.push("", `Next: ${failure.details.nextCommand}`);
|
|
16713
|
+
return lines.join("\n") + "\n";
|
|
16714
|
+
}
|
|
16715
|
+
|
|
16650
16716
|
// src/feedback-protocol.ts
|
|
16651
16717
|
function suggestedFeedbackAction(recordingId) {
|
|
16652
16718
|
return {
|
|
@@ -16741,9 +16807,11 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
16741
16807
|
if (!health.ok || health.service !== "rill") throw new Error("The endpoint did not identify itself as Rill.");
|
|
16742
16808
|
checks.push({ name: "api", status: "pass", message: `Rill API is reachable at ${apiUrl}.` });
|
|
16743
16809
|
} catch (error51) {
|
|
16744
|
-
checks.push({ name: "api", status: "fail", message: `Rill API is unreachable: ${errorMessage(error51)}`, recovery: `Check
|
|
16810
|
+
checks.push({ name: "api", status: "fail", message: `Rill API is unreachable: ${errorMessage(error51)}`, recovery: `Check network access in this ${dependencies.platform} execution environment and the API URL, then rerun \`rill --api-url ${apiUrl} doctor\`. An agent sandbox may have different network access from your desktop.` });
|
|
16745
16811
|
}
|
|
16746
|
-
if (!health
|
|
16812
|
+
if (!health) {
|
|
16813
|
+
checks.push({ name: "cli_version", status: "skip", message: `CLI ${dependencies.cliVersion} compatibility was not checked because the API is unreachable.`, version: dependencies.cliVersion });
|
|
16814
|
+
} else if (!health.minimumCliVersion || !health.recommendedCliVersion) {
|
|
16747
16815
|
checks.push({ name: "cli_version", status: "fail", message: `CLI ${dependencies.cliVersion} could not be checked against the API compatibility policy.`, version: dependencies.cliVersion, recovery: "Deploy a Rill API that advertises minimumCliVersion and recommendedCliVersion." });
|
|
16748
16816
|
} else if (compareVersions(dependencies.cliVersion, health.minimumCliVersion) < 0) {
|
|
16749
16817
|
checks.push({ name: "cli_version", status: "fail", message: `CLI ${dependencies.cliVersion} is older than the minimum supported version ${health.minimumCliVersion}.`, version: dependencies.cliVersion, recovery: `Run \`npm install --global @runalabs/rill-cli@${health.recommendedCliVersion}\`.` });
|
|
@@ -16778,20 +16846,23 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
16778
16846
|
}
|
|
16779
16847
|
}
|
|
16780
16848
|
}
|
|
16781
|
-
checks.push(await executableCheck("chrome", dependencies.chromePath, ["--version"], dependencies.commandVersion,
|
|
16849
|
+
checks.push(await executableCheck("chrome", dependencies.chromePath, ["--version"], dependencies.commandVersion, `Chrome must be available in this ${dependencies.platform} execution environment. Install Chrome/Chromium here or set RILL_CHROME_PATH. A browser on a separate desktop is not automatically accessible; use that desktop's terminal to record locally.`));
|
|
16782
16850
|
if (dependencies.encoding === "local") {
|
|
16783
16851
|
checks.push(await executableCheck("ffmpeg", dependencies.ffmpegPath, ["-version"], dependencies.commandVersion, dependencies.platform === "darwin" ? "Install ffmpeg with `brew install ffmpeg`, or set RILL_FFMPEG." : "Install ffmpeg using the system package manager, or set RILL_FFMPEG."));
|
|
16784
16852
|
} else {
|
|
16785
16853
|
checks.push({ name: "ffmpeg", status: "skip", message: "Remote encoding does not require local FFmpeg." });
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16790
|
-
|
|
16791
|
-
|
|
16854
|
+
if (!health) checks.push({ name: "encoding", status: "skip", message: "Remote encoding was not checked because the API is unreachable." });
|
|
16855
|
+
else {
|
|
16856
|
+
try {
|
|
16857
|
+
const token = await dependencies.loadCredential(apiUrl);
|
|
16858
|
+
if (!(token ? (await dependencies.encodingCapabilities(apiUrl, token)).enabled : health?.anonymousAccess && health.anonymousRemoteEncoding)) throw new Error("Remote encoding is not enabled for this workspace.");
|
|
16859
|
+
checks.push({ name: "encoding", status: "pass", message: token ? "Remote encoding is enabled." : "Remote encoding is available for the first anonymous recording." });
|
|
16860
|
+
} catch (error51) {
|
|
16861
|
+
checks.push({ name: "encoding", status: "fail", message: errorMessage(error51), recovery: "Run doctor --encoding local to check local FFmpeg, then record start --encoding local, or ask the workspace owner to enable remote encoding." });
|
|
16862
|
+
}
|
|
16792
16863
|
}
|
|
16793
16864
|
}
|
|
16794
|
-
return { schemaVersion: 1, status: checks.some((check2) => check2.status === "fail") ? "error" : "ok", apiUrl, checks, ...runFeedbackFields(feedbackEnabled) };
|
|
16865
|
+
return { schemaVersion: 1, status: checks.some((check2) => check2.status === "fail") ? "error" : "ok", apiUrl, environment: { platform: dependencies.platform }, checks, ...runFeedbackFields(feedbackEnabled) };
|
|
16795
16866
|
}
|
|
16796
16867
|
async function executableCheck(name, path, args, run2, recovery) {
|
|
16797
16868
|
try {
|
|
@@ -16974,13 +17045,23 @@ function watchEncoding(apiUrl, id) {
|
|
|
16974
17045
|
child.unref();
|
|
16975
17046
|
}
|
|
16976
17047
|
var program = new Command();
|
|
16977
|
-
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
|
|
17048
|
+
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);
|
|
17049
|
+
program.configureOutput({ outputError: () => void 0 }).exitOverride();
|
|
17050
|
+
function humanOutput(identity = false) {
|
|
17051
|
+
const options = program.opts();
|
|
17052
|
+
return !options.json && !options.pretty && (identity || options.human);
|
|
17053
|
+
}
|
|
17054
|
+
function emitRecording(value, pretty) {
|
|
17055
|
+
if (humanOutput()) process.stdout.write(formatRecording(value, program.opts().apiUrl));
|
|
17056
|
+
else emit(value, pretty);
|
|
17057
|
+
}
|
|
16978
17058
|
var activeTelemetry = null;
|
|
16979
17059
|
program.command("daemon", { hidden: true }).action(() => runDaemon());
|
|
16980
17060
|
program.command("doctor").description("Validate Rill prerequisites before recording.").option("--encoding <mode>", "remote or local", "remote").action(async (commandOptions) => {
|
|
16981
17061
|
const options = program.opts();
|
|
16982
17062
|
const result = await runDoctor(options.apiUrl, { encoding: commandOptions.encoding });
|
|
16983
|
-
|
|
17063
|
+
if (humanOutput()) process.stdout.write(formatDoctor(result));
|
|
17064
|
+
else emit(result, options.pretty);
|
|
16984
17065
|
if (result.status === "error") process.exitCode = 1;
|
|
16985
17066
|
});
|
|
16986
17067
|
async function authorize(quiet = false) {
|
|
@@ -17001,15 +17082,19 @@ async function authorize(quiet = false) {
|
|
|
17001
17082
|
if (result.status === "pending") continue;
|
|
17002
17083
|
if (result.status !== "approved" || !result.token) throw new Error(`Device authorization ${result.status}.`);
|
|
17003
17084
|
await saveCredential(options.apiUrl, result.token);
|
|
17004
|
-
if (!quiet)
|
|
17085
|
+
if (!quiet) {
|
|
17086
|
+
if (humanOutput()) process.stdout.write("\u2713 Signed in to Rill\nCheck your account: rill whoami\n");
|
|
17087
|
+
else emit({ schemaVersion: 1, status: "authenticated", expiresAt: result.expiresAt, apiUrl: options.apiUrl }, options.pretty);
|
|
17088
|
+
}
|
|
17005
17089
|
return result.token;
|
|
17006
17090
|
}
|
|
17007
17091
|
throw new Error("Device authorization expired.");
|
|
17008
17092
|
}
|
|
17009
|
-
program.command("whoami").description("Show the current verified
|
|
17093
|
+
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 () => {
|
|
17010
17094
|
const options = program.opts();
|
|
17011
17095
|
const identity = await whoami(options.apiUrl);
|
|
17012
|
-
|
|
17096
|
+
if (humanOutput(true)) process.stdout.write(formatIdentity(identity));
|
|
17097
|
+
else emit(identity, options.pretty);
|
|
17013
17098
|
if (!identity.authenticated) process.exitCode = 1;
|
|
17014
17099
|
});
|
|
17015
17100
|
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) => {
|
|
@@ -17101,7 +17186,7 @@ record2.command("start").option("--encoding <mode>", "remote or local", "remote"
|
|
|
17101
17186
|
}).catch(() => void 0);
|
|
17102
17187
|
return { schemaVersion: 1, recordingId: remote.recordingId, status: local.status, cdpUrl: local.cdpUrl, ...local.tabId ? { tabId: local.tabId } : {}, browserAttachment: browserAttachment(local.cdpUrl, local.tabId), startedAt: local.startedAt, stopsAt: local.stopsAt, elapsedSeconds: local.elapsedSeconds, remainingSeconds: local.remainingSeconds, bodyCaptureEnabled: remote.bodyCaptureEnabled, anonymous: remote.anonymous ?? false, expiresAt: remote.expiresAt ?? null, maxDurationSeconds: Math.min(Number(commandOptions.maxDuration), remote.maxDurationSeconds ?? 600) };
|
|
17103
17188
|
});
|
|
17104
|
-
|
|
17189
|
+
emitRecording(result, options.pretty);
|
|
17105
17190
|
});
|
|
17106
17191
|
record2.command("stop").argument("<recording-id>").option("--no-wait").option("--no-feedback", "Disable feedback only when the user explicitly requests it").action(async (recordingId, commandOptions) => {
|
|
17107
17192
|
const options = program.opts();
|
|
@@ -17123,7 +17208,7 @@ record2.command("stop").argument("<recording-id>").option("--no-wait").option("-
|
|
|
17123
17208
|
await client.uploadDiagnostics(recordingId, local.result.diagnosticsPath, local.result.diagnostics);
|
|
17124
17209
|
const accepted = await submitFrameCapture(client, recordingId, local.result.frameDirectory);
|
|
17125
17210
|
const result2 = commandOptions.wait && record2.opts().wait !== false ? await waitForEncoding(client, recordingId) : accepted;
|
|
17126
|
-
|
|
17211
|
+
emitRecording({ schemaVersion: 1, ...result2, recordingId, ...recordingNextStep(options.apiUrl, recordingId, result2.status, { resumableFrames: true }), ...recordingFeedbackFields(recordingId, stopped?.feedbackRequested ?? false) }, options.pretty);
|
|
17127
17212
|
if (result2.status === "ready") await rm7(dirname2(local.result.videoPath), { recursive: true, force: true });
|
|
17128
17213
|
else watchEncoding(options.apiUrl, recordingId);
|
|
17129
17214
|
return;
|
|
@@ -17145,7 +17230,7 @@ record2.command("stop").argument("<recording-id>").option("--no-wait").option("-
|
|
|
17145
17230
|
...recordingNextStep(options.apiUrl, recordingId, remote ? "ready" : "processing"),
|
|
17146
17231
|
...recordingFeedbackFields(recordingId, stopped?.feedbackRequested ?? false)
|
|
17147
17232
|
};
|
|
17148
|
-
|
|
17233
|
+
emitRecording(result, options.pretty);
|
|
17149
17234
|
if (remote) await rm7(dirname2(local.result.videoPath), { recursive: true, force: true });
|
|
17150
17235
|
});
|
|
17151
17236
|
record2.command("retry").argument("<recording-id>").option("--no-wait").action(async (recordingId, commandOptions) => {
|
|
@@ -17166,7 +17251,7 @@ record2.command("retry").argument("<recording-id>").option("--no-wait").action(a
|
|
|
17166
17251
|
if (!accepted) throw new Error("Encoding job is unavailable.");
|
|
17167
17252
|
if (["expired", "cancelled"].includes(accepted.status)) throw new Error(`Recording ${accepted.status}.`);
|
|
17168
17253
|
const result = commandOptions.wait && record2.opts().wait !== false ? await waitForEncoding(client, recordingId) : accepted;
|
|
17169
|
-
|
|
17254
|
+
emitRecording({ schemaVersion: 1, ...result, recordingId, ...recordingNextStep(options.apiUrl, recordingId, result.status, { resumableFrames: true }) }, options.pretty);
|
|
17170
17255
|
if (result.status === "ready") await removeLocalCapture(recordingId);
|
|
17171
17256
|
else watchEncoding(options.apiUrl, recordingId);
|
|
17172
17257
|
});
|
|
@@ -17208,7 +17293,7 @@ record2.command("status").argument("<recording-id>").action(async (recordingId)
|
|
|
17208
17293
|
try {
|
|
17209
17294
|
const encoding = await client.encodingStatus(recordingId);
|
|
17210
17295
|
const remote = encoding.status === "ready" ? await client.recordingStatus(recordingId) : {};
|
|
17211
|
-
|
|
17296
|
+
emitRecording({ schemaVersion: 1, ...encoding, ...remote, recordingId, source: "remote", ...recordingNextStep(options.apiUrl, recordingId, encoding.status, { resumableFrames: true }) }, options.pretty);
|
|
17212
17297
|
if (encoding.status === "ready") await removeLocalCapture(recordingId);
|
|
17213
17298
|
return;
|
|
17214
17299
|
} catch (error51) {
|
|
@@ -17221,7 +17306,7 @@ record2.command("status").argument("<recording-id>").action(async (recordingId)
|
|
|
17221
17306
|
},
|
|
17222
17307
|
remoteStatus: (id) => client.recordingStatus(id)
|
|
17223
17308
|
}, options.apiUrl);
|
|
17224
|
-
|
|
17309
|
+
emitRecording({ schemaVersion: 1, ...status, recordingId }, options.pretty);
|
|
17225
17310
|
});
|
|
17226
17311
|
record2.command("cancel").argument("<recording-id>").description("Cancel an active recording and release its remote quota slot.").action(async (recordingId) => {
|
|
17227
17312
|
const options = program.opts();
|
|
@@ -17301,8 +17386,20 @@ program.hook("postAction", async () => {
|
|
|
17301
17386
|
await finishCliTelemetry(process.exitCode ? new Error("command_reported_failure: Command returned a failure status.") : null);
|
|
17302
17387
|
});
|
|
17303
17388
|
program.parseAsync().catch(async (error51) => {
|
|
17389
|
+
if (error51 instanceof CommanderError && error51.exitCode === 0) return;
|
|
17390
|
+
if (error51 instanceof CommanderError) {
|
|
17391
|
+
const action = program.args[0] === "record" ? program.args[1] : void 0;
|
|
17392
|
+
const missingRecording = error51.code === "commander.missingArgument" && ["stop", "status", "retry", "cancel"].includes(action ?? "");
|
|
17393
|
+
error51 = Object.assign(new Error(`validation_failed: ${missingRecording ? "A recording ID is required." : error51.message.replace(/^error: /, "")}`), {
|
|
17394
|
+
code: "validation_failed",
|
|
17395
|
+
recovery: missingRecording ? `Run rill record ${action} <recording-id>. Use rill record list to find the ID.` : "Run rill --help for available commands and options."
|
|
17396
|
+
});
|
|
17397
|
+
}
|
|
17304
17398
|
await finishCliTelemetry(error51);
|
|
17305
|
-
|
|
17399
|
+
if (humanOutput(program.args[0] === "whoami")) {
|
|
17400
|
+
process.stderr.write(formatFailure(error51));
|
|
17401
|
+
process.exitCode = 1;
|
|
17402
|
+
} else fail(error51);
|
|
17306
17403
|
});
|
|
17307
17404
|
function commandPath(actionCommand) {
|
|
17308
17405
|
const path = [];
|