@runalabs/rill-cli 0.1.4 → 0.1.5
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 +8 -4
- package/dist/cli.js +87 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -205,15 +205,19 @@ Diagnostic redaction targets cookies, credential headers, URL query values, frag
|
|
|
205
205
|
|
|
206
206
|
## Optional agent feedback
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
Feedback begins with `rill doctor` and `rill record start`. When the workspace enables it, their JSON includes `feedbackRequested: true` and a `feedbackBrief` asking the agent to observe what went well, confusion or friction, and possible improvements throughout setup and the run. Review only Rill and report observed experience, not invented praise or issues.
|
|
209
|
+
|
|
210
|
+
At completion or abandonment, `record stop` still returns the recommended, pre-authorized `suggestedAction`. Submit a short review if practical:
|
|
209
211
|
|
|
210
212
|
```sh
|
|
211
|
-
rill feedback submit <recording-id> --outcome succeeded --
|
|
213
|
+
rill feedback submit <recording-id> --outcome succeeded --helped "The CDP URL was easy to connect to" --friction "Upload progress was unclear" --improvement "Explain upload and processing stages"
|
|
212
214
|
```
|
|
213
215
|
|
|
214
|
-
|
|
216
|
+
Startup errors retain the brief under `error.details`, with the recording ID when one was reserved. If no recording exists, submit setup feedback without an ID using `--outcome failed` or `--outcome abandoned`. Successful submission returns `feedbackStatus: "submitted"` and the recording ID (null for setup-only feedback).
|
|
217
|
+
|
|
218
|
+
Only use `--no-feedback` when the user explicitly requests it, including in demos and tests. Respect the workspace preference. Feedback is optional, pre-authorized, and must not delay or affect the recording result. Authentication or connectivity failures may prevent feedback; preserve the original task result.
|
|
215
219
|
|
|
216
|
-
|
|
220
|
+
Setup-only feedback requires the API endpoint `POST /api/feedback` and migration `0012_setup_feedback.sql`. Deploy the migration and API before distributing this CLI change. Existing recording feedback remains compatible.
|
|
217
221
|
|
|
218
222
|
## Update and uninstall
|
|
219
223
|
|
package/dist/cli.js
CHANGED
|
@@ -601,7 +601,7 @@ var ControlPlaneClient = class {
|
|
|
601
601
|
return this.request("/api/health", {}, false);
|
|
602
602
|
}
|
|
603
603
|
agentSession() {
|
|
604
|
-
return this.request("/api/agent/session");
|
|
604
|
+
return this.request("/api/agent/session", { signal: AbortSignal.timeout(5e3) });
|
|
605
605
|
}
|
|
606
606
|
reportCliCommand(input) {
|
|
607
607
|
return this.request("/api/analytics/cli-command", { method: "POST", body: JSON.stringify(input) });
|
|
@@ -622,7 +622,7 @@ var ControlPlaneClient = class {
|
|
|
622
622
|
});
|
|
623
623
|
}
|
|
624
624
|
submitRecordingFeedback(recordingId, input) {
|
|
625
|
-
return this.request(`/api/recordings/${recordingId}/feedback
|
|
625
|
+
return this.request(recordingId ? `/api/recordings/${recordingId}/feedback` : "/api/feedback", {
|
|
626
626
|
method: "POST",
|
|
627
627
|
body: JSON.stringify(input)
|
|
628
628
|
});
|
|
@@ -794,6 +794,62 @@ function fail(error) {
|
|
|
794
794
|
process.exitCode = 1;
|
|
795
795
|
}
|
|
796
796
|
|
|
797
|
+
// src/feedback-protocol.ts
|
|
798
|
+
function suggestedFeedbackAction(recordingId) {
|
|
799
|
+
return {
|
|
800
|
+
type: "rill.feedback.submit",
|
|
801
|
+
recommended: true,
|
|
802
|
+
authorizedBy: "workspace_opt_in",
|
|
803
|
+
confirmationRequired: false,
|
|
804
|
+
failurePolicy: "non_blocking",
|
|
805
|
+
recordingId
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
function recordingFeedbackFields(recordingId, feedbackRequested) {
|
|
809
|
+
return feedbackRequested ? { feedbackRequested: true, suggestedAction: suggestedFeedbackAction(recordingId) } : { feedbackRequested: false };
|
|
810
|
+
}
|
|
811
|
+
function feedbackSubmissionReceipt(recordingId) {
|
|
812
|
+
return {
|
|
813
|
+
schemaVersion: 1,
|
|
814
|
+
recordingId,
|
|
815
|
+
feedbackStatus: "submitted"
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
function runFeedbackFields(feedbackEnabled, recordingId = null) {
|
|
819
|
+
return feedbackEnabled ? {
|
|
820
|
+
feedbackRequested: true,
|
|
821
|
+
feedbackBrief: {
|
|
822
|
+
authorizedBy: "workspace_opt_in",
|
|
823
|
+
confirmationRequired: false,
|
|
824
|
+
failurePolicy: "non_blocking",
|
|
825
|
+
recordingId,
|
|
826
|
+
instruction: "Observe your Rill experience throughout setup and the run. At completion or abandonment, submit a short factual review if practical. Feedback is optional and must not delay or affect the recording result. Only use --no-feedback when the user explicitly requests it.",
|
|
827
|
+
prompts: {
|
|
828
|
+
whatHelped: "What went well? Which Rill commands, browser setup steps, outputs, or recovery guidance helped?",
|
|
829
|
+
friction: "Where did you get confused, get stuck, or need to guess? Consider prerequisites, starting or connecting to the browser, commands, diagnostics, uploads, and recovery.",
|
|
830
|
+
improvement: "What possible improvements would make Rill easier or more useful? Prioritize actionable ideas grounded in this run."
|
|
831
|
+
},
|
|
832
|
+
submitCommand: `rill feedback submit${recordingId ? ` ${recordingId}` : ""} --outcome <${recordingId ? "succeeded|failed|abandoned" : "failed|abandoned"}> --helped <text> --friction <text> --improvement <text>`,
|
|
833
|
+
scope: "Review Rill, not the target website. Report only observed experience; do not invent problems or praise. Exclude credentials, personal data, and copied page content."
|
|
834
|
+
}
|
|
835
|
+
} : { feedbackRequested: false };
|
|
836
|
+
}
|
|
837
|
+
async function withRunFeedback(session, start) {
|
|
838
|
+
const enabled = await session().then((value) => value.agentFeedbackEnabled).catch(() => false);
|
|
839
|
+
let recordingId = null;
|
|
840
|
+
try {
|
|
841
|
+
const result = await start((id) => {
|
|
842
|
+
recordingId = id;
|
|
843
|
+
});
|
|
844
|
+
return { ...result, ...runFeedbackFields(enabled, result.recordingId) };
|
|
845
|
+
} catch (cause) {
|
|
846
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
847
|
+
const structured = error;
|
|
848
|
+
structured.details = { ...structured.details, ...runFeedbackFields(enabled, recordingId) };
|
|
849
|
+
throw error;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
797
853
|
// src/doctor.ts
|
|
798
854
|
import { spawn as spawn5 } from "child_process";
|
|
799
855
|
|
|
@@ -816,6 +872,7 @@ var defaultDependencies = {
|
|
|
816
872
|
async function runDoctor(apiUrl, overrides = {}) {
|
|
817
873
|
const dependencies = { ...defaultDependencies, ...overrides };
|
|
818
874
|
const checks = [];
|
|
875
|
+
let feedbackEnabled = false;
|
|
819
876
|
if (compareVersions(normalizeVersion(dependencies.nodeVersion), "22.0.0") >= 0) {
|
|
820
877
|
checks.push({ name: "node", status: "pass", message: `Node.js ${dependencies.nodeVersion} is supported.`, version: dependencies.nodeVersion });
|
|
821
878
|
} else {
|
|
@@ -849,6 +906,7 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
849
906
|
} else {
|
|
850
907
|
try {
|
|
851
908
|
const session = await dependencies.agentSession(apiUrl, token);
|
|
909
|
+
feedbackEnabled = session.agentFeedbackEnabled;
|
|
852
910
|
checks.push({ name: "authentication", status: "pass", message: `Credential is valid with ${session.scopes.length} scope${session.scopes.length === 1 ? "" : "s"}.` });
|
|
853
911
|
} catch (error) {
|
|
854
912
|
checks.push({ name: "authentication", status: "fail", message: `Credential validation failed: ${errorMessage(error)}`, recovery: `Run \`rill --api-url ${apiUrl} login\` to authorize a fresh credential.` });
|
|
@@ -857,7 +915,7 @@ async function runDoctor(apiUrl, overrides = {}) {
|
|
|
857
915
|
}
|
|
858
916
|
checks.push(await executableCheck("chrome", dependencies.chromePath, ["--version"], dependencies.commandVersion, "Install Google Chrome or Chromium, or set RILL_CHROME_PATH to its executable."));
|
|
859
917
|
checks.push(await executableCheck("ffmpeg", dependencies.ffmpegPath, ["-version"], dependencies.commandVersion, process.platform === "darwin" ? "Install ffmpeg with `brew install ffmpeg`, or set RILL_FFMPEG." : "Install ffmpeg using the system package manager, or set RILL_FFMPEG."));
|
|
860
|
-
return { schemaVersion: 1, status: checks.some((check) => check.status === "fail") ? "error" : "ok", apiUrl, checks };
|
|
918
|
+
return { schemaVersion: 1, status: checks.some((check) => check.status === "fail") ? "error" : "ok", apiUrl, checks, ...runFeedbackFields(feedbackEnabled) };
|
|
861
919
|
}
|
|
862
920
|
async function executableCheck(name, path, args, run2, recovery) {
|
|
863
921
|
try {
|
|
@@ -945,28 +1003,6 @@ function controlPlaneUrl(environmentUrl) {
|
|
|
945
1003
|
return environmentUrl ?? PRODUCTION_API_URL;
|
|
946
1004
|
}
|
|
947
1005
|
|
|
948
|
-
// src/feedback-protocol.ts
|
|
949
|
-
function suggestedFeedbackAction(recordingId) {
|
|
950
|
-
return {
|
|
951
|
-
type: "rill.feedback.submit",
|
|
952
|
-
recommended: true,
|
|
953
|
-
authorizedBy: "workspace_opt_in",
|
|
954
|
-
confirmationRequired: false,
|
|
955
|
-
failurePolicy: "non_blocking",
|
|
956
|
-
recordingId
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
function recordingFeedbackFields(recordingId, feedbackRequested) {
|
|
960
|
-
return feedbackRequested ? { feedbackRequested: true, suggestedAction: suggestedFeedbackAction(recordingId) } : { feedbackRequested: false };
|
|
961
|
-
}
|
|
962
|
-
function feedbackSubmissionReceipt(recordingId) {
|
|
963
|
-
return {
|
|
964
|
-
schemaVersion: 1,
|
|
965
|
-
recordingId,
|
|
966
|
-
feedbackStatus: "submitted"
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
|
|
970
1006
|
// src/cli.ts
|
|
971
1007
|
var program = new Command();
|
|
972
1008
|
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);
|
|
@@ -1001,26 +1037,30 @@ record.command("start").option("--url <url>").option("--title <title>").option("
|
|
|
1001
1037
|
const options = program.opts();
|
|
1002
1038
|
const token = await loadCredential(options.apiUrl);
|
|
1003
1039
|
const client = new ControlPlaneClient(options.apiUrl, token);
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1040
|
+
const result = await withRunFeedback(() => client.agentSession(), async (rememberRecording) => {
|
|
1041
|
+
const width = Number(commandOptions.width);
|
|
1042
|
+
const height = Number(commandOptions.height);
|
|
1043
|
+
const remote = await client.createRecording({ title: commandOptions.title ?? `Browser recording \xB7 ${(/* @__PURE__ */ new Date()).toISOString()}`, description: commandOptions.description, sourceUrl: commandOptions.url ?? null, width, height });
|
|
1044
|
+
rememberRecording(remote.recordingId);
|
|
1045
|
+
const local = await startRecordingWithCompensation(remote, {
|
|
1046
|
+
startLocal: async () => {
|
|
1047
|
+
await ensureDaemon(process.argv[1]);
|
|
1048
|
+
return startRecording({ recordingId: remote.recordingId, url: commandOptions.url, cdpUrl: commandOptions.cdpUrl, headed: Boolean(commandOptions.headed), width, height, maxDurationSeconds: Number(commandOptions.maxDuration), captureNetworkBodies: remote.bodyCaptureEnabled });
|
|
1049
|
+
},
|
|
1050
|
+
cancelRemote: (recordingId, reason) => client.cancelRecording(recordingId, reason)
|
|
1051
|
+
});
|
|
1052
|
+
await client.markRecordingStarted(remote.recordingId, {
|
|
1053
|
+
cliVersion: CLI_VERSION,
|
|
1054
|
+
os: process.platform,
|
|
1055
|
+
arch: process.arch,
|
|
1056
|
+
headed: Boolean(commandOptions.headed),
|
|
1057
|
+
attached: Boolean(commandOptions.cdpUrl)
|
|
1058
|
+
}).catch(() => void 0);
|
|
1059
|
+
return { schemaVersion: 1, recordingId: remote.recordingId, status: local.status, cdpUrl: local.cdpUrl, bodyCaptureEnabled: remote.bodyCaptureEnabled, maxDurationSeconds: Number(commandOptions.maxDuration) };
|
|
1013
1060
|
});
|
|
1014
|
-
|
|
1015
|
-
cliVersion: CLI_VERSION,
|
|
1016
|
-
os: process.platform,
|
|
1017
|
-
arch: process.arch,
|
|
1018
|
-
headed: Boolean(commandOptions.headed),
|
|
1019
|
-
attached: Boolean(commandOptions.cdpUrl)
|
|
1020
|
-
}).catch(() => void 0);
|
|
1021
|
-
emit({ schemaVersion: 1, recordingId: remote.recordingId, status: local.status, cdpUrl: local.cdpUrl, bodyCaptureEnabled: remote.bodyCaptureEnabled, maxDurationSeconds: Number(commandOptions.maxDuration) }, options.pretty);
|
|
1061
|
+
emit(result, options.pretty);
|
|
1022
1062
|
});
|
|
1023
|
-
record.command("stop").argument("<recording-id>").option("--no-wait").option("--no-feedback").action(async (recordingId, commandOptions) => {
|
|
1063
|
+
record.command("stop").argument("<recording-id>").option("--no-wait").option("--no-feedback", "Disable feedback only when the user explicitly requests it").action(async (recordingId, commandOptions) => {
|
|
1024
1064
|
const options = program.opts();
|
|
1025
1065
|
await ensureDaemon(process.argv[1]);
|
|
1026
1066
|
const local = await stopRecording(recordingId);
|
|
@@ -1052,19 +1092,20 @@ record.command("stop").argument("<recording-id>").option("--no-wait").option("--
|
|
|
1052
1092
|
if (remote) await rm2(dirname2(local.result.videoPath), { recursive: true, force: true });
|
|
1053
1093
|
});
|
|
1054
1094
|
var feedback = program.command("feedback").description("Submit a short Rill product review for a recorded run.");
|
|
1055
|
-
feedback.command("submit").argument("
|
|
1095
|
+
feedback.command("submit").argument("[recording-id]", "Omit only when setup failed before a recording was created").requiredOption("--outcome <outcome>", "succeeded, failed, or abandoned").option("--helped <text>", "What went well or helped during setup and the run", "").option("--friction <text>", "What created friction during the run", "").option("--improvement <text>", "Possible Rill improvements, prioritized by usefulness", "").action(async (recordingId, commandOptions) => {
|
|
1096
|
+
if (!recordingId && commandOptions.outcome === "succeeded") throw new Error("validation_failed: Setup feedback without a recording must have a failed or abandoned outcome.");
|
|
1056
1097
|
if (!["succeeded", "failed", "abandoned"].includes(commandOptions.outcome)) throw new Error("validation_failed: Feedback outcome must be succeeded, failed, or abandoned.");
|
|
1057
1098
|
if (![commandOptions.helped, commandOptions.friction, commandOptions.improvement].some((value) => value.trim())) throw new Error("validation_failed: Include at least one short feedback answer.");
|
|
1058
1099
|
const options = program.opts();
|
|
1059
1100
|
const token = await loadCredential(options.apiUrl);
|
|
1060
1101
|
const client = new ControlPlaneClient(options.apiUrl, token);
|
|
1061
|
-
await client.submitRecordingFeedback(recordingId, {
|
|
1102
|
+
await client.submitRecordingFeedback(recordingId ?? null, {
|
|
1062
1103
|
outcome: commandOptions.outcome,
|
|
1063
1104
|
whatHelped: commandOptions.helped,
|
|
1064
1105
|
friction: commandOptions.friction,
|
|
1065
1106
|
improvement: commandOptions.improvement
|
|
1066
1107
|
});
|
|
1067
|
-
emit(feedbackSubmissionReceipt(recordingId), options.pretty);
|
|
1108
|
+
emit(feedbackSubmissionReceipt(recordingId ?? null), options.pretty);
|
|
1068
1109
|
});
|
|
1069
1110
|
record.command("status").argument("<recording-id>").action(async (recordingId) => {
|
|
1070
1111
|
const options = program.opts();
|