@h-rig/cli 0.0.6-alpha.90 → 0.0.6-alpha.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +18 -19
  2. package/dist/bin/build-rig-binaries.js +22 -10
  3. package/dist/bin/rig.d.ts +71 -1
  4. package/dist/bin/rig.js +15078 -11169
  5. package/dist/config/rig-default-config.yml +5 -0
  6. package/dist/src/app/drone-ui.d.ts +11 -14
  7. package/dist/src/app/drone-ui.js +70 -86
  8. package/dist/src/commands/_async-ui.d.ts +1 -4
  9. package/dist/src/commands/_async-ui.js +9 -111
  10. package/dist/src/commands/_cli-format.d.ts +16 -9
  11. package/dist/src/commands/_cli-format.js +167 -295
  12. package/dist/src/commands/_connection-state.d.ts +11 -1
  13. package/dist/src/commands/_connection-state.js +50 -5
  14. package/dist/src/commands/_doctor-checks.d.ts +0 -6
  15. package/dist/src/commands/_doctor-checks.js +79 -382
  16. package/dist/src/commands/_help-catalog.d.ts +1 -1
  17. package/dist/src/commands/_help-catalog.js +217 -157
  18. package/dist/src/commands/_inprocess-services.d.ts +33 -0
  19. package/dist/src/commands/_inprocess-services.js +102 -0
  20. package/dist/src/commands/_json-output.js +4 -0
  21. package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
  22. package/dist/src/commands/_lazy-reconcile.js +102 -0
  23. package/dist/src/commands/_paths.js +1 -1
  24. package/dist/src/commands/_pi-frontend.d.ts +18 -10
  25. package/dist/src/commands/_pi-frontend.js +37 -715
  26. package/dist/src/commands/_pi-install.js +18 -36
  27. package/dist/src/commands/_policy.d.ts +1 -1
  28. package/dist/src/commands/_policy.js +56 -15
  29. package/dist/src/commands/_run-bridge.d.ts +114 -0
  30. package/dist/src/commands/_run-bridge.js +387 -0
  31. package/dist/src/commands/_run-diagnostics.d.ts +9 -0
  32. package/dist/src/commands/_run-diagnostics.js +51 -0
  33. package/dist/src/commands/_run-driver-helpers.d.ts +8 -81
  34. package/dist/src/commands/_run-driver-helpers.js +79 -283
  35. package/dist/src/commands/_run-projection.d.ts +50 -0
  36. package/dist/src/commands/_run-projection.js +349 -0
  37. package/dist/src/commands/_run-subcommands.d.ts +3 -0
  38. package/dist/src/commands/_run-subcommands.js +31 -0
  39. package/dist/src/commands/_spinner.js +1 -1
  40. package/dist/src/commands/agent.d.ts +1 -1
  41. package/dist/src/commands/agent.js +8559 -239
  42. package/dist/src/commands/dist.d.ts +1 -1
  43. package/dist/src/commands/dist.js +27 -19
  44. package/dist/src/commands/doctor.d.ts +1 -1
  45. package/dist/src/commands/doctor.js +93 -475
  46. package/dist/src/commands/github.d.ts +1 -1
  47. package/dist/src/commands/github.js +113 -387
  48. package/dist/src/commands/inbox.d.ts +22 -24
  49. package/dist/src/commands/inbox.js +420 -691
  50. package/dist/src/commands/init.d.ts +6 -16
  51. package/dist/src/commands/init.js +334 -971
  52. package/dist/src/commands/inspect.d.ts +19 -2
  53. package/dist/src/commands/inspect.js +644 -610
  54. package/dist/src/commands/pi.d.ts +1 -1
  55. package/dist/src/commands/plugin.d.ts +1 -1
  56. package/dist/src/commands/plugin.js +486 -7
  57. package/dist/src/commands/profile-and-review.d.ts +1 -1
  58. package/dist/src/commands/profile-and-review.js +94 -56
  59. package/dist/src/commands/queue.js +1 -21
  60. package/dist/src/commands/remote.d.ts +1 -1
  61. package/dist/src/commands/remote.js +837 -14
  62. package/dist/src/commands/repo-git-harness.d.ts +1 -1
  63. package/dist/src/commands/repo-git-harness.js +57 -14
  64. package/dist/src/commands/run.d.ts +20 -2
  65. package/dist/src/commands/run.js +17579 -1759
  66. package/dist/src/commands/server.d.ts +2 -6
  67. package/dist/src/commands/server.js +141 -723
  68. package/dist/src/commands/setup.d.ts +1 -1
  69. package/dist/src/commands/setup.js +102 -484
  70. package/dist/src/commands/stats.d.ts +13 -10
  71. package/dist/src/commands/stats.js +689 -761
  72. package/dist/src/commands/task-run-driver.d.ts +50 -88
  73. package/dist/src/commands/task-run-driver.js +116 -2717
  74. package/dist/src/commands/task.d.ts +34 -13
  75. package/dist/src/commands/task.js +668 -2523
  76. package/dist/src/commands/test.d.ts +1 -1
  77. package/dist/src/commands/triage.d.ts +11 -0
  78. package/dist/src/commands/triage.js +227 -0
  79. package/dist/src/commands/workspace.d.ts +1 -1
  80. package/dist/src/commands.d.ts +0 -16
  81. package/dist/src/commands.js +16657 -12250
  82. package/dist/src/index.js +16528 -12497
  83. package/dist/src/launcher.js +4 -0
  84. package/dist/src/operator-cli.d.ts +2 -0
  85. package/dist/src/operator-cli.js +17837 -0
  86. package/dist/src/operator-entry.d.ts +1 -0
  87. package/dist/src/operator-entry.js +3 -0
  88. package/package.json +18 -12
  89. package/dist/src/app/board.d.ts +0 -23
  90. package/dist/src/app/board.js +0 -1786
  91. package/dist/src/app/theme.d.ts +0 -47
  92. package/dist/src/app/theme.js +0 -150
  93. package/dist/src/commands/_authority-runs.d.ts +0 -22
  94. package/dist/src/commands/_authority-runs.js +0 -110
  95. package/dist/src/commands/_operator-surface.d.ts +0 -34
  96. package/dist/src/commands/_operator-surface.js +0 -220
  97. package/dist/src/commands/_operator-view.d.ts +0 -30
  98. package/dist/src/commands/_operator-view.js +0 -1070
  99. package/dist/src/commands/_preflight.d.ts +0 -22
  100. package/dist/src/commands/_preflight.js +0 -540
  101. package/dist/src/commands/_run-replay.d.ts +0 -24
  102. package/dist/src/commands/_run-replay.js +0 -142
  103. package/dist/src/commands/_server-client.d.ts +0 -186
  104. package/dist/src/commands/_server-client.js +0 -681
  105. package/dist/src/commands/_snapshot-upload.d.ts +0 -39
  106. package/dist/src/commands/_snapshot-upload.js +0 -455
  107. package/dist/src/commands/_task-picker.d.ts +0 -9
  108. package/dist/src/commands/_task-picker.js +0 -201
  109. package/dist/src/commands/browser.d.ts +0 -65
  110. package/dist/src/commands/browser.js +0 -1173
  111. package/dist/src/commands/connect.d.ts +0 -7
  112. package/dist/src/commands/connect.js +0 -419
  113. package/dist/src/commands/inspector.d.ts +0 -3
  114. package/dist/src/commands/inspector.js +0 -263
  115. package/dist/src/commands/task-report-bug.d.ts +0 -19
  116. package/dist/src/commands/task-report-bug.js +0 -1281
  117. package/dist/src/report-bug.d.ts +0 -44
  118. package/dist/src/report-bug.js +0 -260
@@ -1,1173 +0,0 @@
1
- // @bun
2
- // packages/cli/src/commands/browser.ts
3
- import { mkdirSync, rmSync } from "fs";
4
- import { resolve } from "path";
5
- import { spawn } from "child_process";
6
- import { emitKeypressEvents } from "readline";
7
- import { pathToFileURL } from "url";
8
-
9
- // packages/cli/src/app/drone-ui.ts
10
- import { ProcessTerminal, TUI, Text, Input, SelectList, matchesKey } from "@earendil-works/pi-tui";
11
-
12
- // packages/cli/src/app/theme.ts
13
- var RIG_PALETTE = {
14
- ink: "#f2f3f6",
15
- ink2: "#aeb0ba",
16
- ink3: "#6c6e79",
17
- ink4: "#44464f",
18
- accent: "#ccff4d",
19
- accentDim: "#a9d63f",
20
- cyan: "#56d8ff",
21
- red: "#ff5d5d",
22
- yellow: "#ffd24d"
23
- };
24
- function hexToRgb(hex) {
25
- const value = hex.replace("#", "");
26
- return [
27
- Number.parseInt(value.slice(0, 2), 16),
28
- Number.parseInt(value.slice(2, 4), 16),
29
- Number.parseInt(value.slice(4, 6), 16)
30
- ];
31
- }
32
- function fg(hex) {
33
- const [r, g, b] = hexToRgb(hex);
34
- return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
35
- }
36
- var ink = fg(RIG_PALETTE.ink);
37
- var ink2 = fg(RIG_PALETTE.ink2);
38
- var ink3 = fg(RIG_PALETTE.ink3);
39
- var ink4 = fg(RIG_PALETTE.ink4);
40
- var accent = fg(RIG_PALETTE.accent);
41
- var accentDim = fg(RIG_PALETTE.accentDim);
42
- var cyan = fg(RIG_PALETTE.cyan);
43
- var red = fg(RIG_PALETTE.red);
44
- var yellow = fg(RIG_PALETTE.yellow);
45
- function bold(text) {
46
- return `\x1B[1m${text}\x1B[22m`;
47
- }
48
- var DRONE_ART = [
49
- " .-=-. .-=-. ",
50
- " ( !!! ) ( !!! ) ",
51
- " '-=-'._ _.'-=-' ",
52
- " '._ _.' ",
53
- " '=$$$$$$$=.' ",
54
- " =$$$$$$$$$$$= ",
55
- " $$$@@@@@@@@@@$$$ ",
56
- " $$$@@ @@$$$ ",
57
- " $$@ ? @$$$ ",
58
- " $$$@ '-' @$$$ ",
59
- " $$$@@ @@$$$ ",
60
- " $$$@@@@@@@@@@$$$ ",
61
- " =$$$$$$$$$$$= ",
62
- " '=$$$$$$$=.' ",
63
- " _.' '._ ",
64
- " .-=-.' '.-=-. ",
65
- " ( !!! ) ( !!! ) ",
66
- " '-=-' '-=-' "
67
- ];
68
- var EYE_FRAMES = ["@", "o", "."];
69
- var DRONE_WIDTH = DRONE_ART[0].length;
70
- var DRONE_HEIGHT = DRONE_ART.length;
71
- var MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
72
- function microDroneFrame(tick) {
73
- const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
74
- const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
75
- return `(${blade})${eye}(${blade})`;
76
- }
77
- var MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
78
- function renderMicroDroneFrame(tick) {
79
- const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
80
- const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
81
- return `${ink4("(")}${cyan(blade)}${ink4(")")}${bold(accent(eye))}${ink4("(")}${cyan(blade)}${ink4(")")}`;
82
- }
83
-
84
- // packages/cli/src/commands/_spinner.ts
85
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
86
- function createTtySpinner(input) {
87
- const output = input.output ?? process.stdout;
88
- const isTty = output.isTTY === true;
89
- const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
90
- let label = input.label;
91
- let frame = 0;
92
- let paused = false;
93
- let stopped = false;
94
- let lastPrintedLabel = "";
95
- const render = () => {
96
- if (stopped || paused)
97
- return;
98
- if (!isTty) {
99
- if (label !== lastPrintedLabel) {
100
- output.write(`${label}
101
- `);
102
- lastPrintedLabel = label;
103
- }
104
- return;
105
- }
106
- frame = (frame + 1) % frames.length;
107
- const glyph = frames[frame] ?? frames[0] ?? "";
108
- output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
109
- };
110
- const clearLine = () => {
111
- if (isTty)
112
- output.write("\r\x1B[2K");
113
- };
114
- render();
115
- const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
116
- return {
117
- setLabel(next) {
118
- label = next;
119
- render();
120
- },
121
- pause() {
122
- paused = true;
123
- clearLine();
124
- },
125
- resume() {
126
- if (stopped)
127
- return;
128
- paused = false;
129
- render();
130
- },
131
- stop(finalLine) {
132
- if (stopped)
133
- return;
134
- stopped = true;
135
- if (timer)
136
- clearInterval(timer);
137
- clearLine();
138
- if (finalLine)
139
- output.write(`${finalLine}
140
- `);
141
- }
142
- };
143
- }
144
-
145
- // packages/cli/src/app/drone-ui.ts
146
- var isTty = () => Boolean(process.stdout.isTTY);
147
- function hairline(width = Math.min(process.stdout.columns ?? 80, 100)) {
148
- return ink4("\u2500".repeat(Math.max(10, width)));
149
- }
150
- function droneIntro(title, subtitle) {
151
- console.log("");
152
- console.log(` ${accent("\u258D")}${bold(ink(title))}${subtitle ? ink3(` \u2014 ${subtitle}`) : ""}`);
153
- console.log(hairline());
154
- }
155
- function droneOutro(text) {
156
- console.log(hairline());
157
- console.log(` ${accent("\u25C6")} ${ink2(text)}`);
158
- console.log("");
159
- }
160
- function droneNote(message, title) {
161
- if (title)
162
- console.log(` ${accentDim("\u25C7")} ${bold(ink2(title))}`);
163
- for (const line of message.split(`
164
- `)) {
165
- console.log(` ${ink4("\u2502")} ${line}`);
166
- }
167
- }
168
- function droneInfo(text) {
169
- console.log(` ${cyan("\xB7")} ${ink2(text)}`);
170
- }
171
- function droneWarn(text) {
172
- console.log(` ${yellow("\u25B2")} ${ink2(text)}`);
173
- }
174
- function droneCancel(text) {
175
- console.log(` ${red("\u2716")} ${ink3(text)}`);
176
- }
177
- function droneSpinner() {
178
- let active = null;
179
- return {
180
- start(message) {
181
- active = createTtySpinner({
182
- label: message,
183
- frames: MICRO_DRONE_FRAMES,
184
- styleFrame: (frame) => renderMicroDroneFrame(Math.max(0, MICRO_DRONE_FRAMES.indexOf(frame)))
185
- });
186
- },
187
- stop(message) {
188
- active?.stop(message ? ` ${accent("\u25C6")} ${ink2(message)}` : undefined);
189
- active = null;
190
- },
191
- error(message) {
192
- active?.stop(message ? ` ${red("\u2716")} ${ink2(message)}` : undefined);
193
- active = null;
194
- }
195
- };
196
- }
197
- var SELECT_THEME = {
198
- selectedPrefix: (text) => accent(text),
199
- selectedText: (text) => bold(ink(text)),
200
- description: (text) => ink3(text),
201
- scrollInfo: (text) => ink4(text),
202
- noMatch: (text) => ink3(text)
203
- };
204
- async function runMiniTui(build) {
205
- const terminal = new ProcessTerminal;
206
- const tui = new TUI(terminal);
207
- let settled = false;
208
- return await new Promise((resolve) => {
209
- const finish = (result) => {
210
- if (settled)
211
- return;
212
- settled = true;
213
- tui.stop();
214
- resolve(result);
215
- };
216
- build(tui, finish);
217
- tui.start();
218
- });
219
- }
220
- async function droneSelect(input) {
221
- if (!isTty() || input.options.length === 0) {
222
- return input.initialValue ?? input.options[0]?.value ?? null;
223
- }
224
- return runMiniTui((tui, finish) => {
225
- tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}`));
226
- const items = input.options.map((option) => ({
227
- value: option.value,
228
- label: option.label,
229
- ...option.hint ? { description: option.hint } : {}
230
- }));
231
- const list = new SelectList(items, Math.min(items.length, 12), SELECT_THEME);
232
- const initialIndex = input.initialValue ? items.findIndex((item) => item.value === input.initialValue) : -1;
233
- if (initialIndex > 0)
234
- list.setSelectedIndex(initialIndex);
235
- list.onSelect = (item) => finish(item.value);
236
- list.onCancel = () => finish(null);
237
- tui.addChild(list);
238
- tui.addChild(new Text(ink4(" \u2191\u2193 navigate \xB7 enter select \xB7 esc cancel")));
239
- tui.setFocus(list);
240
- tui.addInputListener((data) => {
241
- if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
242
- finish(null);
243
- return { consume: true };
244
- }
245
- return;
246
- });
247
- });
248
- }
249
- async function droneText(input) {
250
- if (!isTty())
251
- return input.initialValue ?? null;
252
- return runMiniTui((tui, finish) => {
253
- tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}${input.placeholder ? ink4(` (${input.placeholder})`) : ""}`));
254
- const field = new Input;
255
- if (input.initialValue)
256
- field.setValue(input.initialValue);
257
- field.onSubmit = (value) => finish(value);
258
- field.onEscape = () => finish(null);
259
- tui.addChild(field);
260
- tui.addChild(new Text(ink4(" enter submit \xB7 esc cancel")));
261
- tui.setFocus(field);
262
- tui.addInputListener((data) => {
263
- if (matchesKey(data, "ctrl+c")) {
264
- finish(null);
265
- return { consume: true };
266
- }
267
- return;
268
- });
269
- });
270
- }
271
- async function droneConfirm(input) {
272
- const answer = await droneSelect({
273
- message: input.message,
274
- options: [
275
- { value: "yes", label: "Yes" },
276
- { value: "no", label: "No" }
277
- ],
278
- initialValue: input.initialValue === false ? "no" : "yes"
279
- });
280
- return answer === null ? null : answer === "yes";
281
- }
282
-
283
- // packages/cli/src/commands/browser.ts
284
- import pc2 from "picocolors";
285
-
286
- // packages/cli/src/runner.ts
287
- import { EventBus } from "@rig/runtime/control-plane/runtime/events";
288
- import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
289
- import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
290
- import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
291
-
292
- class CliError extends RuntimeCliError {
293
- hint;
294
- constructor(message, exitCode = 1, options = {}) {
295
- super(message, exitCode);
296
- if (options.hint?.trim()) {
297
- this.hint = options.hint.trim();
298
- }
299
- }
300
- }
301
- function takeFlag(args, flag) {
302
- const rest = [];
303
- let value = false;
304
- for (const arg of args) {
305
- if (arg === flag) {
306
- value = true;
307
- continue;
308
- }
309
- rest.push(arg);
310
- }
311
- return { value, rest };
312
- }
313
- function takeOption(args, option) {
314
- const rest = [];
315
- let value;
316
- for (let index = 0;index < args.length; index += 1) {
317
- const current = args[index];
318
- if (current === option) {
319
- const next = args[index + 1];
320
- if (!next || next.startsWith("-")) {
321
- throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
322
- }
323
- value = next;
324
- index += 1;
325
- continue;
326
- }
327
- if (current !== undefined) {
328
- rest.push(current);
329
- }
330
- }
331
- return { value, rest };
332
- }
333
- function requireNoExtraArgs(args, usage) {
334
- if (args.length > 0) {
335
- throw new CliError(`Unexpected arguments: ${args.join(" ")}
336
- Usage: ${usage}`);
337
- }
338
- }
339
-
340
- // packages/cli/src/commands/browser.ts
341
- import { runCapture as runCapture2 } from "@rig/runtime/control-plane/native/utils";
342
-
343
- // packages/cli/src/commands/task-report-bug.ts
344
- import pc from "picocolors";
345
- import {
346
- appendJsonlRecord,
347
- readJsonFile,
348
- readJsonlFile,
349
- writeJsonFile
350
- } from "@rig/runtime/control-plane/authority-files";
351
- import { runCapture, unique } from "@rig/runtime/control-plane/native/utils";
352
-
353
- // packages/cli/src/commands/_paths.ts
354
- import { resolveMonorepoRoot } from "@rig/runtime/control-plane/native/utils";
355
-
356
- // packages/cli/src/commands/task-report-bug.ts
357
- async function promptBugText(message, defaultValue, options = {}) {
358
- for (;; ) {
359
- const result = await droneText({
360
- message,
361
- ...options.placeholder ? { placeholder: options.placeholder } : {},
362
- ...defaultValue?.trim() ? { initialValue: defaultValue } : {}
363
- });
364
- if (result === null) {
365
- droneCancel("Bug report cancelled.");
366
- throw new CliError("Bug report cancelled by user.");
367
- }
368
- const invalid = options.required ? validateRequiredBugPromptValue(result, message) : undefined;
369
- if (!invalid)
370
- return result.trim();
371
- droneInfo(invalid);
372
- }
373
- }
374
- function validateRequiredBugPromptValue(value, label) {
375
- return value?.trim() ? undefined : `${label} is required.`;
376
- }
377
- async function promptBugConfirm(message, initialValue) {
378
- const result = await droneConfirm({ message, initialValue });
379
- if (result === null) {
380
- droneCancel("Bug report cancelled.");
381
- throw new CliError("Bug report cancelled by user.");
382
- }
383
- return result;
384
- }
385
-
386
- // packages/cli/src/commands/browser.ts
387
- function shouldColorizeCliOutput() {
388
- return Boolean(process.stdout.isTTY) && process.env.NO_COLOR === undefined && process.env.TERM !== "dumb";
389
- }
390
- function browserHelpText(options = {}) {
391
- const c = pc2.createColors(options.color ?? shouldColorizeCliOutput());
392
- const title = (value) => c.bold(c.cyan(value));
393
- const section = (value) => c.bold(c.blue(value));
394
- const command = (value) => c.green(value);
395
- const code = (value) => c.yellow(`\`${value}\``);
396
- return [
397
- title("rig browser - Rig Browser workstation commands"),
398
- "",
399
- section("Purpose:"),
400
- " Rig Browser is the deterministic browser workstation for browser-heavy agent work.",
401
- " For hp-next it pins the app preset, persistent profile, state directory, and Chrome DevTools MCP port.",
402
- " Use it instead of ad hoc personal browser tabs for auth, routing, chunk-load, console, network, and visual debugging.",
403
- "",
404
- section("How agents use it inside a run:"),
405
- ` Browser-required tasks get a Browser section in ${code("rig task info --task <id>")}.`,
406
- ` The runtime writes browser metadata into ${code("runtime-context.json")}, exports ${code("RIG_BROWSER_*")} env vars,`,
407
- " and injects browser helper commands onto PATH. Agents should read this runtime contract instead",
408
- " of guessing app paths, profiles, or ports.",
409
- "",
410
- section("Recommended agent sequence:"),
411
- ` ${command("rig-browser-launch")}`,
412
- ` ${command("rig-browser-check")}`,
413
- ` ${command("rig-browser-attach-info")}`,
414
- ` ${command("npx chrome-devtools-mcp@latest --browser-url=$RIG_BROWSER_ATTACH_URL")}`,
415
- "",
416
- section("Runtime helper commands:"),
417
- ` ${command("rig-browser-launch")}: Launches the task-resolved Rig Browser using the configured`,
418
- " launch command, profile, state dir, preset, and port. Use at the start of browser-required",
419
- ` task work. Pass ${code("--dev")} when you need the dev/watch browser command.`,
420
- ` ${command("rig-browser-check")}: Validates that the helper-launched browser exposes the expected CDP`,
421
- " endpoint. Use before attaching Chrome DevTools MCP or before blaming app code.",
422
- ` ${command("rig-browser-attach-info")}: Prints the effective attach URL, profile, base profile, preset,`,
423
- " and state directory chosen for this runtime. Use this when sharing instructions or attaching",
424
- " MCP, because isolated runtimes may derive a profile/port from the base config.",
425
- ` ${command("rig-browser-e2e")}: Runs the browser e2e command through the same workstation contract.`,
426
- " Use after a browser fix to validate the actual app-owned browser surface.",
427
- ` ${command("rig-browser-reset-profile")}: Clears the effective browser profile. Use when stale cookies,`,
428
- " localStorage, auth state, or cache are suspected; do not use when the bug depends on persisted auth.",
429
- "",
430
- section("rig browser command family:"),
431
- ` ${command("rig browser --help")}`,
432
- ` ${command("rig browser explain")}`,
433
- ` ${command("rig browser hp-next dev")}: Starts hp-next Rig Browser in dev/watch mode.`,
434
- " Use while implementing or debugging local frontend changes.",
435
- ` ${command("rig browser hp-next start")}: Builds/starts the hp-next browser workstation without watch mode.`,
436
- " Use for stable manual verification or CI-like local checks.",
437
- ` ${command("rig browser hp-next check")}: Validates the hp-next CDP endpoint on the configured port.`,
438
- " Use before MCP attach or after relaunching the browser.",
439
- ` ${command("rig browser hp-next e2e")}: Runs hp-next browser e2e smoke checks through Rig Browser.`,
440
- " Use before handing off browser/auth/routing fixes.",
441
- ` ${command("rig browser hp-next reset")}: Resets the hp-next browser profile.`,
442
- " Use when stale browser state is the suspected cause.",
443
- ` ${command("rig browser demo")}: Launches a real guided agent/browser demo.`,
444
- " Use to experience the browser-required agent task workflow end to end.",
445
- ` ${command("rig browser cdp-probe")}: Runs an isolated CDP attach/evaluate/layout probe.`,
446
- " Use to distinguish browser-workstation failure from hp-next app failure.",
447
- ` ${command("rig browser profile-persistence")}: Verifies that a named profile persists browser state.`,
448
- " Use when debugging login/session persistence.",
449
- ` ${command("rig browser profile-lock-check")}: Verifies concurrent launches reject the same profile.`,
450
- " Use when profile ownership or parallel agent collisions are suspected.",
451
- ` ${command("rig browser smoke-test")}: Runs the browser package smoke test.`,
452
- " Use for a quick package-level workstation sanity check.",
453
- "",
454
- section("Repo script mapping:"),
455
- ` ${command("rig browser hp-next dev")}: Runs ${code("bun run app:dev:browser:hp-next")}.`,
456
- ` ${command("rig browser hp-next start")}: Runs ${code("bun run app:start:browser:hp-next")}.`,
457
- ` ${command("rig browser hp-next check")}: Runs ${code("bun run app:check:browser:hp-next")}.`,
458
- ` ${command("rig browser hp-next e2e")}: Runs ${code("bun run app:e2e:browser:hp-next")}.`,
459
- ` ${command("rig browser hp-next reset")}: Runs ${code("bun run app:reset:browser:hp-next")}.`,
460
- ` ${command("rig browser demo")}: Builds the browser, launches Electron, checks CDP, then attaches to the live page.`,
461
- ` ${command("rig browser cdp-probe")}: Runs ${code("bun run --filter=@rig/browser cdp-probe")}.`,
462
- ` ${command("rig browser profile-persistence")}: Runs ${code("bun run --filter=@rig/browser profile-persistence")}.`,
463
- ` ${command("rig browser profile-lock-check")}: Runs ${code("bun run --filter=@rig/browser profile-lock-check")}.`,
464
- ` ${command("rig browser smoke-test")}: Runs ${code("bun run --filter=@rig/browser smoke-test")}.`,
465
- "",
466
- section("hp-next defaults:"),
467
- ` Local attach URL: ${code("http://127.0.0.1:9333")}`,
468
- ` Local preset/profile: ${code("hp-next-local")}`,
469
- ` Local state dir: ${code(".tmp/rig-browser/hp-next")}`,
470
- "",
471
- section("Chrome DevTools MCP:"),
472
- ` ${command("npx chrome-devtools-mcp@latest --browser-url=http://127.0.0.1:9333")}`,
473
- "",
474
- section("Profiles:"),
475
- " A profile is the browser state bucket: cookies, localStorage, auth session, and cache.",
476
- " Use hp-next-local to preserve local auth, hp-next-shared for shared-dev, or a bug-specific",
477
- " profile such as bug-otp-auth when you need a clean/reproducible auth run.",
478
- "",
479
- section("First debug targets:"),
480
- " Inspect page URL/title, console errors, failed network requests, auth cookies/localStorage,",
481
- " route state, chunk requests, and responsive layout before changing app code."
482
- ].join(`
483
- `);
484
- }
485
- function browserAgentUsageText() {
486
- return browserHelpText();
487
- }
488
- function browserDemoHelpText(options = {}) {
489
- const c = pc2.createColors(options.color ?? shouldColorizeCliOutput());
490
- const section = (value) => c.bold(c.blue(value));
491
- const command = (value) => c.green(value);
492
- const code = (value) => c.yellow(`\`${value}\``);
493
- return [
494
- c.bold(c.cyan("rig browser demo - real agent/browser walkthrough")),
495
- "",
496
- section("What it does:"),
497
- " Places you in the agent's seat inside a browser-required task run.",
498
- " It launches a real Rig Browser window, exposes a real CDP endpoint,",
499
- " asks you to type the same simple commands an agent runs, then prints each command output.",
500
- " It attaches over the live Chrome DevTools Protocol endpoint and performs the same inspect/mutate workflow an agent uses.",
501
- "",
502
- section("Command:"),
503
- ` ${command("rig browser demo")} ${code("[--port <n>] [--profile <name>] [--state-dir <path>] [--target-url <url>] [--keep-open] [--no-build]")}`,
504
- "",
505
- section("Commands you will type:"),
506
- ` ${command("rig-browser-attach-info")}`,
507
- ` ${command("rig-browser-launch")}`,
508
- ` ${command("rig-browser-check")}`,
509
- ` ${command("npx chrome-devtools-mcp@latest --browser-url=$RIG_BROWSER_ATTACH_URL")}`,
510
- "",
511
- section("Real steps:"),
512
- " 1. Prepare the demo runtime contract and build the browser bundle unless --no-build is passed.",
513
- " 2. Ask you to type rig-browser-attach-info, then print the resolved runtime metadata.",
514
- " 3. Ask you to type rig-browser-launch, then launch real Electron with RIG_BROWSER_* env vars.",
515
- " 4. Ask you to type rig-browser-check, then check /json/version and /json/list on the live CDP endpoint.",
516
- " 5. Ask you to type the DevTools MCP attach command, then run the live CDP inspect/update action.",
517
- "",
518
- section("Default endpoint:"),
519
- " http://127.0.0.1:9336"
520
- ].join(`
521
- `);
522
- }
523
- function buildBrowserDemoRuntime(projectRoot, options = {}) {
524
- const profile = options.profile || `agent-demo-${process.pid}`;
525
- const port = options.port ?? 9336;
526
- const stateDir = options.stateDir || `.tmp/rig-browser/${profile}`;
527
- const targetUrl = options.targetUrl || buildBrowserDemoTargetUrl();
528
- return {
529
- attachUrl: `http://127.0.0.1:${port}`,
530
- browserDir: resolve(projectRoot, "apps/native-app/apps/browser"),
531
- env: {
532
- RIG_BROWSER_DEV: "1",
533
- RIG_STATE_DIR: stateDir,
534
- T3CODE_STATE_DIR: stateDir,
535
- RIG_BROWSER_PRESET: "hp-next-local",
536
- RIG_BROWSER_PROFILE: profile,
537
- RIG_BROWSER_REMOTE_DEBUGGING_PORT: String(port),
538
- RIG_BROWSER_TARGET_URL: targetUrl
539
- },
540
- helperCommands: [
541
- "rig-browser-launch",
542
- "rig-browser-check",
543
- "rig-browser-attach-info",
544
- "rig-browser-e2e",
545
- "rig-browser-reset-profile"
546
- ],
547
- port,
548
- profile,
549
- stateDir,
550
- targetUrl
551
- };
552
- }
553
- function buildBrowserDemoTargetUrl() {
554
- const html = [
555
- "<!doctype html>",
556
- "<html>",
557
- "<head>",
558
- '<meta charset="utf-8">',
559
- "<title>Rig Browser Agent Demo</title>",
560
- "<style>",
561
- "body{font-family:ui-sans-serif,system-ui;margin:48px;background:#f7efe3;color:#1f2933}",
562
- ".card{max-width:760px;border:2px solid #1f2933;border-radius:20px;padding:28px;background:white;box-shadow:8px 8px 0 #1f2933}",
563
- "#status{font-weight:700;color:#8a4b0f}",
564
- "</style>",
565
- "</head>",
566
- "<body>",
567
- '<main class="card">',
568
- "<p>Rig Browser task run</p>",
569
- "<h1>You are the agent now</h1>",
570
- '<p id="status">Waiting for the agent to attach over CDP.</p>',
571
- '<p id="details">The CLI will inspect this real page and then update it.</p>',
572
- "</main>",
573
- "<script>console.log('rig-browser-demo-page-ready')</script>",
574
- "</body>",
575
- "</html>"
576
- ].join("");
577
- return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
578
- }
579
- function buildBrowserDemoAgentCommands(runtime) {
580
- const attachWithEnv = "npx chrome-devtools-mcp@latest --browser-url=$RIG_BROWSER_ATTACH_URL";
581
- const attachWithUrl = `npx chrome-devtools-mcp@latest --browser-url=${runtime.attachUrl}`;
582
- return {
583
- attachInfo: {
584
- primary: "rig-browser-attach-info",
585
- accepted: ["rig-browser-attach-info"]
586
- },
587
- launch: {
588
- primary: "rig-browser-launch",
589
- accepted: ["rig-browser-launch"]
590
- },
591
- check: {
592
- primary: "rig-browser-check",
593
- accepted: ["rig-browser-check"]
594
- },
595
- attach: {
596
- primary: attachWithEnv,
597
- accepted: [attachWithEnv, attachWithUrl]
598
- }
599
- };
600
- }
601
- function normalizeBrowserDemoCommand(value) {
602
- return value.trim().replace(/\s+/g, " ").replace(/--browser-url\s+/g, "--browser-url=").replace(/--browser-url\s*=\s*/g, "--browser-url=");
603
- }
604
- function browserDemoCommandMatches(value, command) {
605
- const normalized = normalizeBrowserDemoCommand(value);
606
- return command.accepted.map(normalizeBrowserDemoCommand).includes(normalized);
607
- }
608
- function browserDemoCommandCompletions(line, command) {
609
- if (line.trim().length === 0) {
610
- return [[command.primary], line];
611
- }
612
- const normalizedLine = normalizeBrowserDemoCommand(line);
613
- const matches = command.accepted.filter((accepted) => normalizeBrowserDemoCommand(accepted).startsWith(normalizedLine));
614
- return [matches.length > 0 ? matches : [command.primary], line];
615
- }
616
- function formatBrowserDemoCommandOutput(command, output) {
617
- const normalizedOutput = output.trim() || "(no output)";
618
- return `$ ${command}
619
- ${normalizedOutput}`;
620
- }
621
- function printBrowserDemoCommandOutput(command, output) {
622
- const c = pc2.createColors(shouldColorizeCliOutput());
623
- console.log("");
624
- console.log(c.bold("Agent terminal output"));
625
- console.log(c.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
626
- console.log(formatBrowserDemoCommandOutput(command, output));
627
- console.log(c.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
628
- console.log("");
629
- }
630
- async function executeBrowser(context, args) {
631
- const [command = "help", ...rest] = args;
632
- if (command === "help" || command === "--help" || command === "-h") {
633
- console.log(browserHelpText());
634
- return { ok: true, group: "browser", command: "help" };
635
- }
636
- if (command === "explain") {
637
- requireNoExtraArgs(rest, "rig browser explain");
638
- console.log(browserAgentUsageText());
639
- return { ok: true, group: "browser", command: "explain" };
640
- }
641
- if (command === "demo") {
642
- return executeBrowserDemo(context, rest);
643
- }
644
- if (command === "app" || command === "hp-next") {
645
- const appSlug = command === "hp-next" ? "hp-next" : process.env.RIG_BROWSER_APP?.trim() || "";
646
- if (!appSlug) {
647
- throw new CliError(`rig browser app: set RIG_BROWSER_APP=<app-slug> to select which project app scripts to invoke.
648
- ` + `Scripts are invoked as 'bun run app:<mode>:browser:<app-slug>'.
649
-
650
- ${browserHelpText()}`);
651
- }
652
- const [subcommand = "help", ...appRest] = rest;
653
- if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
654
- console.log(browserHelpText());
655
- return { ok: true, group: "browser", command: `${command}-help` };
656
- }
657
- const modes = ["dev", "start", "check", "e2e", "reset"];
658
- if (!modes.includes(subcommand)) {
659
- throw new CliError(`Unknown browser ${command} command: ${subcommand}. Valid modes: ${modes.join(", ")}.
660
-
661
- ${browserHelpText()}`);
662
- }
663
- requireNoExtraArgs(appRest, `rig browser ${command} ${subcommand}`);
664
- await context.runCommand(["bun", "run", `app:${subcommand}:browser:${appSlug}`]);
665
- return { ok: true, group: "browser", command: `${command}-${subcommand}` };
666
- }
667
- const packageScripts = {
668
- "cdp-probe": "cdp-probe",
669
- "profile-persistence": "profile-persistence",
670
- "profile-lock-check": "profile-lock-check",
671
- "smoke-test": "smoke-test"
672
- };
673
- const packageScript = packageScripts[command];
674
- if (packageScript) {
675
- requireNoExtraArgs(rest, `rig browser ${command}`);
676
- await context.runCommand(["bun", "run", "--filter=@rig/browser", packageScript]);
677
- return { ok: true, group: "browser", command };
678
- }
679
- throw new CliError(`Unknown browser command: ${command}
680
-
681
- ${browserHelpText()}`, 1, { hint: "Run `rig browser help` for the full browser command reference." });
682
- }
683
- async function executeBrowserDemo(context, args) {
684
- const [maybeHelp] = args;
685
- if (maybeHelp === "help" || maybeHelp === "--help" || maybeHelp === "-h") {
686
- console.log(browserDemoHelpText());
687
- return { ok: true, group: "browser", command: "demo-help" };
688
- }
689
- let pending = args;
690
- const portResult = takeOption(pending, "--port");
691
- pending = portResult.rest;
692
- const profileResult = takeOption(pending, "--profile");
693
- pending = profileResult.rest;
694
- const stateDirResult = takeOption(pending, "--state-dir");
695
- pending = stateDirResult.rest;
696
- const targetUrlResult = takeOption(pending, "--target-url");
697
- pending = targetUrlResult.rest;
698
- const keepOpenFlag = takeFlag(pending, "--keep-open");
699
- pending = keepOpenFlag.rest;
700
- const noBuildFlag = takeFlag(pending, "--no-build");
701
- pending = noBuildFlag.rest;
702
- requireNoExtraArgs(pending, "rig browser demo [--port <n>] [--profile <name>] [--state-dir <path>] [--target-url <url>] [--keep-open] [--no-build]");
703
- if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
704
- throw new CliError("rig browser demo requires an interactive TTY in text mode.", 1, { hint: "Run `rig browser demo` from an interactive terminal (no --json, no piped stdin)." });
705
- }
706
- const port = portResult.value ? Number.parseInt(portResult.value, 10) : undefined;
707
- if (port !== undefined && (!Number.isFinite(port) || port <= 0)) {
708
- throw new CliError(`Invalid --port value: ${portResult.value}`, 1, { hint: "Pass a positive port number, e.g. `--port 9222`." });
709
- }
710
- const runtime = buildBrowserDemoRuntime(context.projectRoot, {
711
- ...port !== undefined ? { port } : {},
712
- ...profileResult.value ? { profile: profileResult.value } : {},
713
- ...stateDirResult.value ? { stateDir: stateDirResult.value } : {},
714
- ...targetUrlResult.value ? { targetUrl: targetUrlResult.value } : {}
715
- });
716
- droneIntro("rig browser demo");
717
- droneNote([
718
- "You are now acting as the agent inside a browser-required task run.",
719
- "Type each simple command when prompted. The CLI will run the real backing action",
720
- "and print the same kind of output an agent would inspect before the next step."
721
- ].join(`
722
- `), "Agent task-run walkthrough");
723
- droneNote(formatBrowserDemoRuntime(runtime), "Runtime contract the agent receives");
724
- const agentCommands = buildBrowserDemoAgentCommands(runtime);
725
- await promptBrowserDemoAgentCommand("Agent command 1/4: print browser attach info", agentCommands.attachInfo);
726
- printBrowserDemoCommandOutput(agentCommands.attachInfo.primary, formatBrowserDemoAttachInfoOutput(runtime));
727
- await promptBrowserDemoAgentCommand("Agent command 2/4: launch Rig Browser", agentCommands.launch);
728
- if (!noBuildFlag.value) {
729
- const spinner = droneSpinner();
730
- spinner.start("Preparing browser bundle for the launch command");
731
- try {
732
- runBrowserDemoBuild(context.projectRoot);
733
- spinner.stop("Browser bundle built");
734
- } catch (error) {
735
- spinner.error("Browser bundle build failed");
736
- throw error;
737
- }
738
- }
739
- const child = await launchBrowserDemo(runtime);
740
- let keepOpen = keepOpenFlag.value;
741
- try {
742
- printBrowserDemoCommandOutput(agentCommands.launch.primary, formatBrowserDemoLaunchOutput(runtime, child.pid));
743
- await promptBrowserDemoAgentCommand("Agent command 3/4: check live browser endpoint", agentCommands.check);
744
- const readySpinner = droneSpinner();
745
- readySpinner.start(`Waiting for live CDP endpoint at ${runtime.attachUrl}`);
746
- const { version, pageTarget, targets } = await waitForBrowserDemoReady(runtime, child);
747
- readySpinner.stop("Live CDP endpoint is ready");
748
- printBrowserDemoCommandOutput(agentCommands.check.primary, formatBrowserDemoCheckOutput(runtime, version, pageTarget, targets));
749
- await promptBugText("Look at the launched browser page, then type ready", undefined, { required: true, placeholder: "ready" });
750
- await promptBrowserDemoAgentCommand("Agent command 4/4: attach DevTools MCP", agentCommands.attach);
751
- droneNote([
752
- "The attach step evaluates JavaScript inside the live browser page.",
753
- "It records DOM state before the change, changes #status, #details, and the page background,",
754
- "then records DOM state and layout again so the transcript proves what changed."
755
- ].join(`
756
- `), "What mutationEvaluation means");
757
- const cdpSpinner = droneSpinner();
758
- cdpSpinner.start("Agent attaching over WebSocket CDP");
759
- const cdpResult = await runBrowserDemoCdpAction(pageTarget.webSocketDebuggerUrl);
760
- cdpSpinner.stop("Agent CDP action completed");
761
- printBrowserDemoCommandOutput(agentCommands.attach.primary, formatBrowserDemoCdpResult(cdpResult));
762
- await promptBugConfirm("Did you see the page update after the agent action?", true);
763
- if (!keepOpen) {
764
- keepOpen = await promptBugConfirm("Keep the demo browser open after this command exits?", false);
765
- }
766
- droneOutro(keepOpen ? "Demo complete. Browser left open by request." : "Demo complete. Closing browser.");
767
- return {
768
- ok: true,
769
- group: "browser",
770
- command: "demo",
771
- details: {
772
- attachUrl: runtime.attachUrl,
773
- browser: version.Browser,
774
- pageTitle: pageTarget.title,
775
- profile: runtime.profile,
776
- stateDir: runtime.stateDir,
777
- cdpResult,
778
- keptOpen: keepOpen
779
- }
780
- };
781
- } finally {
782
- if (!keepOpen) {
783
- await stopBrowserDemo(child);
784
- }
785
- }
786
- }
787
- function formatBrowserDemoRuntime(runtime) {
788
- return [
789
- `RIG_BROWSER_ATTACH_URL=${runtime.attachUrl}`,
790
- `RIG_BROWSER_PROFILE=${runtime.profile}`,
791
- `RIG_BROWSER_STATE_DIR=${runtime.stateDir}`,
792
- `RIG_BROWSER_REMOTE_DEBUGGING_PORT=${runtime.port}`,
793
- `helpers=${runtime.helperCommands.join(", ")}`
794
- ].join(`
795
- `);
796
- }
797
- function formatBrowserDemoAttachInfoOutput(runtime) {
798
- return [
799
- "Rig Browser",
800
- " Preset: hp-next-local",
801
- " Mode: persistent",
802
- ` State dir: ${runtime.stateDir}`,
803
- " Default profile: hp-next-local",
804
- ` Effective profile: ${runtime.profile}`,
805
- ` Effective attach URL: ${runtime.attachUrl}`,
806
- " Launch helper: rig-browser-launch",
807
- " Check helper: rig-browser-check",
808
- " E2E helper: rig-browser-e2e",
809
- " Reset helper: rig-browser-reset-profile"
810
- ].join(`
811
- `);
812
- }
813
- function formatBrowserDemoLaunchOutput(runtime, pid) {
814
- return [
815
- "Rig Browser launch requested.",
816
- ` pid: ${pid ?? "(unknown)"}`,
817
- ` profile: ${runtime.profile}`,
818
- ` stateDir: ${runtime.stateDir}`,
819
- ` attachUrl: ${runtime.attachUrl}`
820
- ].join(`
821
- `);
822
- }
823
- function formatBrowserDemoCheckOutput(runtime, version, pageTarget, targets) {
824
- return JSON.stringify({
825
- attachUrl: runtime.attachUrl,
826
- pageTarget,
827
- targets,
828
- version
829
- }, null, 2);
830
- }
831
- function formatBrowserDemoCdpResult(result) {
832
- return JSON.stringify({
833
- title: result.title ?? null,
834
- mutationExplanation: {
835
- command: "Runtime.evaluate",
836
- summary: "The agent executes JavaScript inside the live page through CDP, not against a mock.",
837
- changes: [
838
- "reads the DOM before mutation",
839
- "changes #status, #details, and the page background",
840
- "returns the browser-side mutation result",
841
- "reads the DOM and layout again after mutation"
842
- ],
843
- payloadGuide: {
844
- beforeDomState: "DOM state captured before Runtime.evaluate mutates the page.",
845
- mutationEvaluation: "The exact Runtime.evaluate response returned by the browser-side mutation function.",
846
- afterDomState: "DOM state captured after mutation to prove the page changed.",
847
- consoleEvents: "Runtime.consoleAPICalled events observed during the attach session.",
848
- layoutMetrics: "Full Page.getLayoutMetrics response from the live page."
849
- }
850
- },
851
- beforeDomState: result.beforeDomState,
852
- mutationEvaluation: result.mutationEvaluation,
853
- afterDomState: result.afterDomState,
854
- consoleEvents: result.consoleEvents,
855
- layoutMetrics: result.layoutMetrics
856
- }, null, 2);
857
- }
858
- function runBrowserDemoBuild(projectRoot) {
859
- const result = runCapture2(["bun", "run", "--filter=@rig/browser", "build"], projectRoot);
860
- if (result.exitCode !== 0) {
861
- const details = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join(`
862
- `);
863
- throw new CliError(`Browser demo build failed.${details ? `
864
- ${details}` : ""}`, result.exitCode);
865
- }
866
- }
867
- async function promptBrowserDemoAgentCommand(message, command) {
868
- for (;; ) {
869
- const value = await readBrowserDemoAgentCommand(message, command);
870
- if (browserDemoCommandMatches(value, command)) {
871
- return normalizeBrowserDemoCommand(value);
872
- }
873
- droneWarn(`Expected one of:
874
- ${command.accepted.map((accepted) => ` ${accepted}`).join(`
875
- `)}`);
876
- }
877
- }
878
- async function readBrowserDemoAgentCommand(message, command) {
879
- const c = pc2.createColors(shouldColorizeCliOutput());
880
- const alternatives = command.accepted.filter((accepted) => normalizeBrowserDemoCommand(accepted) !== normalizeBrowserDemoCommand(command.primary));
881
- console.log("");
882
- console.log(`${c.cyan("\u25C6")} ${c.bold(message)}`);
883
- console.log(` ${c.dim("Type the command, or press Tab to fill the placeholder:")}`);
884
- console.log(` ${c.dim(command.primary)}`);
885
- if (alternatives.length > 0) {
886
- console.log(` ${c.dim("Also accepted:")}`);
887
- for (const alternative of alternatives) {
888
- console.log(` ${c.dim(alternative)}`);
889
- }
890
- }
891
- return readBrowserDemoCommandLine(command);
892
- }
893
- function readBrowserDemoCommandLine(command) {
894
- const stdin = process.stdin;
895
- const stdout = process.stdout;
896
- const wasRaw = Boolean(stdin.isRaw);
897
- let line = "";
898
- const render = () => {
899
- stdout.write(`\r\x1B[2K> ${line}`);
900
- };
901
- return new Promise((resolveInput, rejectInput) => {
902
- const cleanup = () => {
903
- stdin.off("keypress", onKeypress);
904
- if (stdin.isTTY) {
905
- stdin.setRawMode(wasRaw);
906
- }
907
- };
908
- const completeLine = () => {
909
- const [matches] = browserDemoCommandCompletions(line, command);
910
- line = matches[0] ?? command.primary;
911
- render();
912
- };
913
- const onKeypress = (characters, key) => {
914
- if (key.ctrl && key.name === "c") {
915
- cleanup();
916
- stdout.write(`
917
- `);
918
- rejectInput(new CliError("Browser demo cancelled."));
919
- return;
920
- }
921
- if (key.name === "return" || key.name === "enter") {
922
- cleanup();
923
- stdout.write(`
924
- `);
925
- resolveInput(line);
926
- return;
927
- }
928
- if (key.name === "tab") {
929
- completeLine();
930
- return;
931
- }
932
- if (key.name === "backspace") {
933
- line = line.slice(0, -1);
934
- render();
935
- return;
936
- }
937
- if (key.ctrl && key.name === "u") {
938
- line = "";
939
- render();
940
- return;
941
- }
942
- if (!key.ctrl && !key.meta && characters) {
943
- line += characters;
944
- render();
945
- }
946
- };
947
- emitKeypressEvents(stdin);
948
- stdin.resume();
949
- stdin.on("keypress", onKeypress);
950
- if (stdin.isTTY) {
951
- stdin.setRawMode(true);
952
- }
953
- render();
954
- });
955
- }
956
- async function launchBrowserDemo(runtime) {
957
- rmSync(resolve(runtime.browserDir, runtime.stateDir), { recursive: true, force: true });
958
- mkdirSync(resolve(runtime.browserDir, runtime.stateDir), { recursive: true });
959
- const launcherPath = resolve(runtime.browserDir, "scripts/electron-launcher.mjs");
960
- const launcher = await import(pathToFileURL(launcherPath).href);
961
- const electronPath = await launcher.resolveElectronPath();
962
- const env = { ...process.env, ...runtime.env };
963
- delete env.ELECTRON_RUN_AS_NODE;
964
- const child = spawn(electronPath, ["dist-electron/main.js"], {
965
- cwd: runtime.browserDir,
966
- env,
967
- stdio: ["ignore", "pipe", "pipe"]
968
- });
969
- child.stdout?.on("data", () => {});
970
- child.stderr?.on("data", () => {});
971
- return child;
972
- }
973
- async function waitForBrowserDemoReady(runtime, child) {
974
- const version = await waitForBrowserDemo(() => fetchBrowserDemoJson(runtime, "/json/version"), child, "CDP /json/version");
975
- const { targets, pageTarget } = await waitForBrowserDemo(async () => {
976
- const nextTargets = await fetchBrowserDemoJson(runtime, "/json/list");
977
- const nextPageTarget = findBrowserDemoPageTarget(runtime, nextTargets);
978
- if (!nextPageTarget) {
979
- throw new Error("demo page target is not ready yet");
980
- }
981
- return { targets: nextTargets, pageTarget: nextPageTarget };
982
- }, child, "CDP demo page target");
983
- if (!version.Browser) {
984
- throw new CliError("Browser demo reached CDP but /json/version did not include Browser metadata.");
985
- }
986
- return { version, targets, pageTarget };
987
- }
988
- function findBrowserDemoPageTarget(runtime, targets) {
989
- const attachableTargets = targets.filter((target) => target.type === "page" && typeof target.webSocketDebuggerUrl === "string");
990
- return attachableTargets.find((target) => target.url === runtime.targetUrl && target.title === "Rig Browser Agent Demo") ?? attachableTargets.find((target) => target.title === "Rig Browser Agent Demo") ?? null;
991
- }
992
- async function waitForBrowserDemo(check, child, label, timeoutMs = 18000) {
993
- const startedAt = Date.now();
994
- let lastError = null;
995
- for (;; ) {
996
- if (child.exitCode !== null || child.signalCode !== null) {
997
- throw new CliError(`Rig Browser exited before ${label} became ready.`);
998
- }
999
- try {
1000
- return await check();
1001
- } catch (error) {
1002
- lastError = error;
1003
- if (Date.now() - startedAt > timeoutMs) {
1004
- throw new CliError(`${label} did not become ready within ${timeoutMs}ms: ${String(lastError)}`);
1005
- }
1006
- await new Promise((resolveDelay) => setTimeout(resolveDelay, 150));
1007
- }
1008
- }
1009
- }
1010
- async function fetchBrowserDemoJson(runtime, path) {
1011
- const response = await fetch(`${runtime.attachUrl}${path}`);
1012
- if (!response.ok) {
1013
- throw new Error(`${path} returned ${response.status}`);
1014
- }
1015
- return response.json();
1016
- }
1017
- async function runBrowserDemoCdpAction(wsUrl) {
1018
- return withBrowserDemoCdpSession(wsUrl, async ({ send, consoleEvents }) => {
1019
- await send("Runtime.enable");
1020
- await send("Page.enable");
1021
- const beforeDomState = await send("Runtime.evaluate", {
1022
- expression: [
1023
- "(() => ({",
1024
- "title: document.title,",
1025
- "status: document.querySelector('#status')?.textContent ?? null,",
1026
- "details: document.querySelector('#details')?.textContent ?? null,",
1027
- "bodyBackground: getComputedStyle(document.body).backgroundColor,",
1028
- "url: location.href",
1029
- "}))()"
1030
- ].join(""),
1031
- returnByValue: true
1032
- });
1033
- if (beforeDomState.exceptionDetails) {
1034
- throw new Error(`Runtime.evaluate before state failed: ${JSON.stringify(beforeDomState)}`);
1035
- }
1036
- const mutationEvaluation = await send("Runtime.evaluate", {
1037
- expression: [
1038
- "(() => {",
1039
- "console.log('rig-browser-demo-agent-action');",
1040
- "const status = document.querySelector('#status');",
1041
- "const details = document.querySelector('#details');",
1042
- "if (status) status.textContent='Agent CDP command executed.';",
1043
- "if (details) details.textContent='The CLI attached over the live browser debugging endpoint and changed this page.';",
1044
- "document.body.style.background='#e4f7ea';",
1045
- "return {",
1046
- "title: document.title,",
1047
- "status: status?.textContent ?? null,",
1048
- "details: details?.textContent ?? null,",
1049
- "bodyBackground: getComputedStyle(document.body).backgroundColor,",
1050
- "url: location.href",
1051
- "};",
1052
- "})()"
1053
- ].join(""),
1054
- returnByValue: true
1055
- });
1056
- if (mutationEvaluation.exceptionDetails) {
1057
- throw new Error(`Runtime.evaluate mutation failed: ${JSON.stringify(mutationEvaluation)}`);
1058
- }
1059
- const afterDomState = await send("Runtime.evaluate", {
1060
- expression: [
1061
- "(() => ({",
1062
- "title: document.title,",
1063
- "status: document.querySelector('#status')?.textContent ?? null,",
1064
- "details: document.querySelector('#details')?.textContent ?? null,",
1065
- "bodyBackground: getComputedStyle(document.body).backgroundColor,",
1066
- "url: location.href",
1067
- "}))()"
1068
- ].join(""),
1069
- returnByValue: true
1070
- });
1071
- if (afterDomState.exceptionDetails) {
1072
- throw new Error(`Runtime.evaluate after state failed: ${JSON.stringify(afterDomState)}`);
1073
- }
1074
- const layoutMetrics = await send("Page.getLayoutMetrics");
1075
- return {
1076
- afterDomState,
1077
- beforeDomState,
1078
- consoleEvents,
1079
- layoutMetrics,
1080
- mutationEvaluation,
1081
- title: afterDomState.result?.value?.title ?? mutationEvaluation.result?.value?.title
1082
- };
1083
- });
1084
- }
1085
- async function withBrowserDemoCdpSession(wsUrl, run) {
1086
- const socket = new WebSocket(wsUrl);
1087
- const pending = new Map;
1088
- const consoleEvents = [];
1089
- let nextId = 1;
1090
- const send = (method, params = {}) => new Promise((resolveSend, rejectSend) => {
1091
- const id = nextId;
1092
- nextId += 1;
1093
- pending.set(id, {
1094
- method,
1095
- reject: rejectSend,
1096
- resolve: (value) => resolveSend(value),
1097
- timeout: setTimeout(() => {
1098
- pending.delete(id);
1099
- rejectSend(new Error(`${method} timed out waiting for CDP response`));
1100
- }, 5000)
1101
- });
1102
- socket.send(JSON.stringify({ id, method, params }));
1103
- });
1104
- socket.addEventListener("message", (event) => {
1105
- const message = JSON.parse(String(event.data));
1106
- if (message.id !== undefined) {
1107
- const request = pending.get(message.id);
1108
- if (!request)
1109
- return;
1110
- pending.delete(message.id);
1111
- clearTimeout(request.timeout);
1112
- if (message.error) {
1113
- request.reject(new Error(`${request.method} failed: ${JSON.stringify(message.error)}`));
1114
- return;
1115
- }
1116
- request.resolve(message.result);
1117
- return;
1118
- }
1119
- if (message.method === "Runtime.consoleAPICalled") {
1120
- consoleEvents.push(message.params);
1121
- }
1122
- });
1123
- socket.addEventListener("close", () => {
1124
- for (const request of pending.values()) {
1125
- clearTimeout(request.timeout);
1126
- request.reject(new Error(`CDP WebSocket closed before ${request.method} completed`));
1127
- }
1128
- pending.clear();
1129
- }, { once: true });
1130
- await new Promise((resolveOpen, rejectOpen) => {
1131
- const timeout = setTimeout(() => rejectOpen(new Error("CDP WebSocket failed to open within 5000ms")), 5000);
1132
- socket.addEventListener("open", () => resolveOpen(), { once: true });
1133
- socket.addEventListener("error", () => rejectOpen(new Error("CDP WebSocket failed to open")), { once: true });
1134
- socket.addEventListener("open", () => clearTimeout(timeout), { once: true });
1135
- socket.addEventListener("error", () => clearTimeout(timeout), { once: true });
1136
- });
1137
- try {
1138
- return await run({ consoleEvents, send });
1139
- } finally {
1140
- socket.close();
1141
- await new Promise((resolveClose) => {
1142
- socket.addEventListener("close", resolveClose, { once: true });
1143
- setTimeout(resolveClose, 500);
1144
- });
1145
- }
1146
- }
1147
- async function stopBrowserDemo(child) {
1148
- if (child.exitCode !== null || child.signalCode !== null) {
1149
- return;
1150
- }
1151
- child.kill("SIGTERM");
1152
- await Promise.race([
1153
- new Promise((resolveExit) => child.once("exit", resolveExit)),
1154
- new Promise((resolveDelay) => setTimeout(resolveDelay, 2000))
1155
- ]);
1156
- if (child.exitCode === null && child.signalCode === null) {
1157
- child.kill("SIGKILL");
1158
- }
1159
- }
1160
- export {
1161
- normalizeBrowserDemoCommand,
1162
- formatBrowserDemoCommandOutput,
1163
- formatBrowserDemoCheckOutput,
1164
- formatBrowserDemoCdpResult,
1165
- findBrowserDemoPageTarget,
1166
- executeBrowserDemo,
1167
- executeBrowser,
1168
- buildBrowserDemoRuntime,
1169
- buildBrowserDemoAgentCommands,
1170
- browserHelpText,
1171
- browserDemoCommandMatches,
1172
- browserDemoCommandCompletions
1173
- };