opera-browser-cli 0.1.34 → 0.1.36
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 +83 -28
- package/SKILL.md +35 -3
- package/dist/src/bridge.d.ts +12 -3
- package/dist/src/bridge.js +36 -4
- package/dist/src/bridge.js.map +1 -1
- package/dist/src/cli.d.ts +5 -1
- package/dist/src/cli.js +251 -75
- package/dist/src/cli.js.map +1 -1
- package/dist/src/client.d.ts +5 -5
- package/dist/src/client.js +16 -4
- package/dist/src/client.js.map +1 -1
- package/dist/src/run.js +5 -4
- package/dist/src/run.js.map +1 -1
- package/dist/src/snapshot.d.ts +54 -2
- package/dist/src/snapshot.js +375 -6
- package/dist/src/snapshot.js.map +1 -1
- package/openclaw/.env.example +14 -0
- package/openclaw/Dockerfile +14 -0
- package/openclaw/README.md +173 -0
- package/openclaw/docker-compose.yml +49 -0
- package/package.json +3 -2
package/dist/src/snapshot.js
CHANGED
|
@@ -1,29 +1,311 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Convert a canonical MCP ref ("2_4") to display form ("2.4"). */
|
|
2
|
+
export function refToDisplay(mcpRef) {
|
|
3
|
+
return mcpRef.replace(/_/g, ".");
|
|
4
|
+
}
|
|
5
|
+
/** Convert any ref form — "@2.4", "@2_4", "2.4", "2_4" — to MCP wire form "2_4". */
|
|
6
|
+
export function refToMcp(ref) {
|
|
7
|
+
return ref.replace(/^@/, "").replace(/\./g, "_");
|
|
8
|
+
}
|
|
9
|
+
/** Count interactive refs in snapshot text (accepts both uid= and compact @X.Y form). */
|
|
2
10
|
export function countRefs(snapshot) {
|
|
3
|
-
const matches = snapshot.match(
|
|
11
|
+
const matches = snapshot.match(/^\s*(?:uid=\S+|@\d[\d.]*)\b/gm);
|
|
4
12
|
return matches ? matches.length : 0;
|
|
5
13
|
}
|
|
6
14
|
/** Extract ref IDs with labels and types from snapshot text. */
|
|
7
15
|
export function extractRefs(snapshot) {
|
|
8
16
|
const refs = [];
|
|
9
17
|
for (const line of snapshot.split("\n")) {
|
|
10
|
-
|
|
18
|
+
// Accept both uid=X_Y (raw MCP) and @X.Y (compact) forms;
|
|
19
|
+
// avoid \b before @ since @ is a non-word character
|
|
20
|
+
const m = line.match(/(?:uid=(\S+)|(?:^|[ \t])@([\d.]+))\s+([\w]+)\s+"([^"]*)"/);
|
|
11
21
|
if (!m)
|
|
12
22
|
continue;
|
|
13
|
-
|
|
23
|
+
const rawRef = m[1] ?? m[2];
|
|
24
|
+
// Always return in display form so suggestion strings emit @X.Y refs
|
|
25
|
+
const ref = m[1] ? refToDisplay(rawRef) : rawRef;
|
|
26
|
+
refs.push({ ref, type: m[3], label: m[4] });
|
|
14
27
|
}
|
|
15
28
|
return refs;
|
|
16
29
|
}
|
|
17
|
-
/** Extract page title from snapshot (RootWebArea or first heading). */
|
|
30
|
+
/** Extract page title from snapshot (RootWebArea/root root node or first heading). */
|
|
18
31
|
export function extractTitle(snapshot) {
|
|
19
|
-
const rootMatch = snapshot.match(/RootWebArea\s+"([^"]+)"/);
|
|
32
|
+
const rootMatch = snapshot.match(/(?:RootWebArea|root)\s+"([^"]+)"/);
|
|
20
33
|
if (rootMatch)
|
|
21
34
|
return rootMatch[1];
|
|
35
|
+
// Compact markdown heading after compactSnapshot: `@X.Y ## Title`
|
|
36
|
+
const mdMatch = snapshot.match(/^(?:@\S+\s+)?#{1,6}\s+(.+)$/m);
|
|
37
|
+
if (mdMatch)
|
|
38
|
+
return mdMatch[1].trim();
|
|
22
39
|
const headingMatch = snapshot.match(/\bheading\s+"([^"]+)"/);
|
|
23
40
|
if (headingMatch)
|
|
24
41
|
return headingMatch[1];
|
|
25
42
|
return "";
|
|
26
43
|
}
|
|
44
|
+
// Query-string keys issued by external ad/analytics platforms that carry no functional
|
|
45
|
+
// meaning for the destination page — safe to drop on any site.
|
|
46
|
+
const NOISE_PARAM_EXACT = new Set([
|
|
47
|
+
// Google Ads click IDs
|
|
48
|
+
"gclid", "gbraid", "wbraid", "dclid", "gad_source",
|
|
49
|
+
// Social / messaging platform click IDs
|
|
50
|
+
"fbclid", // Meta/Facebook
|
|
51
|
+
"msclkid", // Microsoft Ads
|
|
52
|
+
"yclid", // Yandex
|
|
53
|
+
"igshid", // Instagram
|
|
54
|
+
"ttclid", // TikTok
|
|
55
|
+
"twclid", // Twitter/X
|
|
56
|
+
"li_fat_id", // LinkedIn
|
|
57
|
+
"srsltid", // Google Shopping
|
|
58
|
+
"_ke", // Klaviyo
|
|
59
|
+
]);
|
|
60
|
+
// Prefix-matched families (all members are tracking-only)
|
|
61
|
+
const NOISE_PARAM_PREFIXES = [
|
|
62
|
+
"utm_", // Google Analytics UTM parameters
|
|
63
|
+
"mc_", // Mailchimp
|
|
64
|
+
];
|
|
65
|
+
function isNoiseParam(key) {
|
|
66
|
+
if (NOISE_PARAM_EXACT.has(key))
|
|
67
|
+
return true;
|
|
68
|
+
return NOISE_PARAM_PREFIXES.some((p) => key.startsWith(p));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clean a URL value to reduce token bloat without losing addressability:
|
|
72
|
+
* - returns null for javascript: and data: URLs so the caller drops the attribute entirely
|
|
73
|
+
* - strips a matching page origin → relative path
|
|
74
|
+
* - removes cross-site tracking query params (utm_*, gclid, fbclid, etc.)
|
|
75
|
+
*
|
|
76
|
+
* Preserves fragment, parameter order, and percent-encoding of remaining values.
|
|
77
|
+
*/
|
|
78
|
+
export function cleanUrl(url, origin) {
|
|
79
|
+
if (url.startsWith("javascript:") || url.startsWith("data:"))
|
|
80
|
+
return null;
|
|
81
|
+
let working = url;
|
|
82
|
+
if (origin && working.startsWith(origin)) {
|
|
83
|
+
working = working.slice(origin.length) || "/";
|
|
84
|
+
}
|
|
85
|
+
// Pull the fragment off first so query-param parsing can't accidentally consume it
|
|
86
|
+
let fragment = "";
|
|
87
|
+
const hashIdx = working.indexOf("#");
|
|
88
|
+
if (hashIdx >= 0) {
|
|
89
|
+
fragment = working.slice(hashIdx);
|
|
90
|
+
working = working.slice(0, hashIdx);
|
|
91
|
+
}
|
|
92
|
+
const qIdx = working.indexOf("?");
|
|
93
|
+
if (qIdx < 0)
|
|
94
|
+
return working + fragment;
|
|
95
|
+
const path = working.slice(0, qIdx);
|
|
96
|
+
const query = working.slice(qIdx + 1);
|
|
97
|
+
if (!query)
|
|
98
|
+
return path + fragment;
|
|
99
|
+
const kept = query.split("&").filter((part) => {
|
|
100
|
+
if (!part)
|
|
101
|
+
return false;
|
|
102
|
+
const eq = part.indexOf("=");
|
|
103
|
+
const key = eq < 0 ? part : part.slice(0, eq);
|
|
104
|
+
return !isNoiseParam(key);
|
|
105
|
+
});
|
|
106
|
+
if (kept.length === 0)
|
|
107
|
+
return path + fragment;
|
|
108
|
+
return `${path}?${kept.join("&")}${fragment}`;
|
|
109
|
+
}
|
|
110
|
+
/** Extract scheme://host from the root node's url= attribute, if present. */
|
|
111
|
+
export function extractPageOrigin(tree) {
|
|
112
|
+
const m = tree.match(/^\s*(?:uid=\S+|@\S+)\s+(?:RootWebArea|root)\b[^\n]*\burl="([^"]+)"/m);
|
|
113
|
+
if (!m)
|
|
114
|
+
return null;
|
|
115
|
+
try {
|
|
116
|
+
const u = new URL(m[1]);
|
|
117
|
+
return `${u.protocol}//${u.host}`;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Repeat a description value this many times before we treat it as boilerplate worth deduping.
|
|
124
|
+
// Below this, the bytes saved by dropping repeats don't beat the risk of hiding meaningful copy.
|
|
125
|
+
const DESCRIPTION_DEDUP_THRESHOLD = 3;
|
|
126
|
+
// Chrome a11y tree uses PascalCase for some internal role names; map them to compact lowercase.
|
|
127
|
+
const ROLE_RENAMES = {
|
|
128
|
+
RootWebArea: "root",
|
|
129
|
+
StaticText: "text",
|
|
130
|
+
DisclosureTriangle: "disclosure",
|
|
131
|
+
ColorWell: "color",
|
|
132
|
+
InputTime: "time",
|
|
133
|
+
Date: "date",
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Compact an accessibility snapshot tree to reduce token usage (~30% fewer tokens).
|
|
137
|
+
* Removes noise nodes, strips ARIA default attributes, normalises role names,
|
|
138
|
+
* de-quotes numeric attributes, converts headings to markdown, and rewrites
|
|
139
|
+
* refs to the @PAGE.ELEM display format.
|
|
140
|
+
*
|
|
141
|
+
* Operates on the raw tree text (after MCP preamble has been stripped).
|
|
142
|
+
*/
|
|
143
|
+
export function compactSnapshot(tree) {
|
|
144
|
+
const lines = tree.split("\n");
|
|
145
|
+
const out = [];
|
|
146
|
+
let dropDanglingQuote = false;
|
|
147
|
+
// Pre-pass: find page origin (for relative-URL rewriting) and count description values
|
|
148
|
+
// so we know which ones cross the dedup threshold.
|
|
149
|
+
const origin = extractPageOrigin(tree);
|
|
150
|
+
const descriptionCounts = new Map();
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
const re = / description="([^"]*)"/g;
|
|
153
|
+
let m;
|
|
154
|
+
while ((m = re.exec(line)) !== null) {
|
|
155
|
+
descriptionCounts.set(m[1], (descriptionCounts.get(m[1]) ?? 0) + 1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const seenDescription = new Set();
|
|
159
|
+
for (const raw of lines) {
|
|
160
|
+
let line = raw;
|
|
161
|
+
// <br> elements appear as LineBreak nodes; they're never useful in the a11y tree.
|
|
162
|
+
// Their label is a literal newline, so splitting on \n leaves a dangling `"` on the
|
|
163
|
+
// next line — skip that too.
|
|
164
|
+
if (/^\s*uid=\S+ LineBreak "/.test(line)) {
|
|
165
|
+
dropDanglingQuote = true;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (dropDanglingQuote) {
|
|
169
|
+
dropDanglingQuote = false;
|
|
170
|
+
if (/^\s*"\s*$/.test(line))
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
// Whitespace-only text nodes between elements are structural artifacts, not content
|
|
174
|
+
if (/^\s*uid=\S+ StaticText "\s*"\s*$/.test(line))
|
|
175
|
+
continue;
|
|
176
|
+
// StaticText children that just echo the parent's accessible name are redundant —
|
|
177
|
+
// links, headings, buttons etc. already carry the label on their own line
|
|
178
|
+
{
|
|
179
|
+
const m = line.match(/^(\s*)uid=\S+ StaticText "([^"]+)"\s*$/);
|
|
180
|
+
if (m) {
|
|
181
|
+
const childIndent = m[1].length;
|
|
182
|
+
const label = m[2];
|
|
183
|
+
let drop = false;
|
|
184
|
+
for (let i = out.length - 1; i >= 0; i--) {
|
|
185
|
+
if (!out[i].trim())
|
|
186
|
+
continue;
|
|
187
|
+
// Previous lines may already be in compact @X.Y form (B1 runs per-line before push)
|
|
188
|
+
const pm = out[i].match(/^(\s*)(?:uid=\S+|@\S+) \w+ "([^"]+)"/);
|
|
189
|
+
if (pm && pm[1].length === childIndent - 2 && pm[2] === label)
|
|
190
|
+
drop = true;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
if (drop)
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Empty valuetext is the same as having no valuetext
|
|
198
|
+
line = line.replace(/ valuetext=""/g, "");
|
|
199
|
+
// `disableable` is redundant when `disabled` is already present
|
|
200
|
+
if (/ disabled\b/.test(line))
|
|
201
|
+
line = line.replace(/ disableable\b/g, "");
|
|
202
|
+
// Every option and tab is selectable by definition; the attribute adds nothing
|
|
203
|
+
if (/ (?:option|tab) "/.test(line))
|
|
204
|
+
line = line.replace(/ selectable\b/g, "");
|
|
205
|
+
// `relevant="additions text"` is the ARIA default for live regions; omit it
|
|
206
|
+
line = line.replace(/ relevant="additions text"/g, "");
|
|
207
|
+
// `atomic` is implicit for alert and status by the ARIA spec
|
|
208
|
+
if (/ (?:alert|status) /.test(line))
|
|
209
|
+
line = line.replace(/ atomic\b/g, "");
|
|
210
|
+
// `live=` defaults are mandated by ARIA for these roles; no need to repeat them
|
|
211
|
+
if (/ status /.test(line))
|
|
212
|
+
line = line.replace(/ live="polite"/g, "");
|
|
213
|
+
if (/ alert /.test(line))
|
|
214
|
+
line = line.replace(/ live="assertive"/g, "");
|
|
215
|
+
// combobox is always expandable with a popup; both attributes are implied by the role
|
|
216
|
+
if (/ combobox /.test(line)) {
|
|
217
|
+
line = line.replace(/ haspopup="(?:menu|listbox)"/g, "");
|
|
218
|
+
line = line.replace(/ expandable\b/g, "");
|
|
219
|
+
}
|
|
220
|
+
// Horizontal is the default orientation for sliders and listboxes
|
|
221
|
+
line = line.replace(/ orientation="horizontal"/g, "");
|
|
222
|
+
// Autocomplete mode is an implementation detail rarely useful for navigation
|
|
223
|
+
line = line.replace(/ autocomplete="(?:both|list)"/g, "");
|
|
224
|
+
// Drop javascript: URLs entirely (no agent-actionable info), strip the page origin
|
|
225
|
+
// from same-site links, and remove tracking/encoding query params
|
|
226
|
+
line = line.replace(/ url="([^"]+)"/g, (_full, rawUrl) => {
|
|
227
|
+
const cleaned = cleanUrl(rawUrl, origin);
|
|
228
|
+
return cleaned == null ? "" : ` url="${cleaned}"`;
|
|
229
|
+
});
|
|
230
|
+
// Boilerplate descriptions (e.g. "use arrow keys to navigate" repeated on every link)
|
|
231
|
+
// are recoverable from the first occurrence; drop the rest
|
|
232
|
+
line = line.replace(/ description="([^"]*)"/g, (full, value) => {
|
|
233
|
+
if ((descriptionCounts.get(value) ?? 0) < DESCRIPTION_DEDUP_THRESHOLD)
|
|
234
|
+
return full;
|
|
235
|
+
if (seenDescription.has(value))
|
|
236
|
+
return "";
|
|
237
|
+
seenDescription.add(value);
|
|
238
|
+
return full;
|
|
239
|
+
});
|
|
240
|
+
// Normalise known PascalCase Chrome-internal role names to short lowercase forms.
|
|
241
|
+
// The uid= or @X.Y prefix is optional to handle simplified test fixtures.
|
|
242
|
+
line = line.replace(/^(\s*(?:(?:uid=|@)\S+\s+)?)([A-Za-z][a-zA-Z]*)( )/, (_, pre, role, post) => pre + (ROLE_RENAMES[role] ?? role) + post);
|
|
243
|
+
// Numeric attribute values don't need quotes — saves two tokens per attribute
|
|
244
|
+
line = line.replace(/(\w+)="(-?\d+)"/g, "$1=$2");
|
|
245
|
+
// `heading "Label" level=N` → `## Label` — markdown is shorter and familiar to models
|
|
246
|
+
{
|
|
247
|
+
const m = line.match(/^(\s*uid=\S+) heading "([^"]+)" level=(\d+)(.*)/);
|
|
248
|
+
if (m) {
|
|
249
|
+
const hashes = "#".repeat(parseInt(m[3], 10));
|
|
250
|
+
const extra = m[4].trim();
|
|
251
|
+
line = `${m[1]} ${hashes} ${m[2]}${extra ? " " + extra : ""}`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Rewrite refs last so all earlier transforms still match the uid= form;
|
|
255
|
+
// dot separator tokenises better than underscore in BPE encodings
|
|
256
|
+
line = line.replace(/\buid=(\d+)_(\d+)\b/g, (_, page, elem) => `@${page}.${elem}`);
|
|
257
|
+
out.push(line);
|
|
258
|
+
}
|
|
259
|
+
return collapseTextRuns(out).join("\n");
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Merge consecutive text nodes at the same indent into one, then re-apply
|
|
263
|
+
* the echo-dedup: if the merged label exactly matches the parent's label,
|
|
264
|
+
* the collapsed line is dropped entirely (parent already carries the content).
|
|
265
|
+
*
|
|
266
|
+
* Only runs when 2+ text nodes were actually merged; single text nodes that
|
|
267
|
+
* already survived the per-line echo-dedup are passed through unchanged.
|
|
268
|
+
*/
|
|
269
|
+
function collapseTextRuns(lines) {
|
|
270
|
+
const result = [];
|
|
271
|
+
for (let i = 0; i < lines.length; i++) {
|
|
272
|
+
const m = lines[i].match(/^(\s*)(@\S+) text "([^"]*)"\s*$/);
|
|
273
|
+
if (!m) {
|
|
274
|
+
result.push(lines[i]);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const [, indent, ref, firstLabel] = m;
|
|
278
|
+
let j = i + 1;
|
|
279
|
+
let merged = firstLabel;
|
|
280
|
+
while (j < lines.length) {
|
|
281
|
+
const next = lines[j].match(/^(\s*)@\S+ text "([^"]*)"\s*$/);
|
|
282
|
+
if (!next || next[1] !== indent)
|
|
283
|
+
break;
|
|
284
|
+
merged += next[2];
|
|
285
|
+
j++;
|
|
286
|
+
}
|
|
287
|
+
if (j === i + 1) {
|
|
288
|
+
// Only one text node — pass through (already echo-deduped in main loop)
|
|
289
|
+
result.push(lines[i]);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
// Multiple nodes merged — advance past consumed lines and echo-dedup the result
|
|
293
|
+
i = j - 1;
|
|
294
|
+
const childIndent = indent.length;
|
|
295
|
+
let drop = false;
|
|
296
|
+
for (let k = result.length - 1; k >= 0; k--) {
|
|
297
|
+
if (!result[k].trim())
|
|
298
|
+
continue;
|
|
299
|
+
const pm = result[k].match(/^(\s*)(?:uid=\S+|@\S+) \w+ "([^"]+)"/);
|
|
300
|
+
if (pm && pm[1].length === childIndent - 2 && pm[2] === merged)
|
|
301
|
+
drop = true;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
if (!drop)
|
|
305
|
+
result.push(`${indent}${ref} text "${merged}"`);
|
|
306
|
+
}
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
27
309
|
export function truncateSnapshot(snapshot, full, limit = 16000) {
|
|
28
310
|
const totalLength = snapshot.length;
|
|
29
311
|
if (full || totalLength <= limit) {
|
|
@@ -66,4 +348,91 @@ const INPUT_TYPES = ["textbox", "searchbox", "input", "combobox", "textarea"];
|
|
|
66
348
|
export function isInputType(type) {
|
|
67
349
|
return INPUT_TYPES.includes(type);
|
|
68
350
|
}
|
|
351
|
+
// --- URL LUT (Layer 2) ---
|
|
352
|
+
const MIN_DEDUP_LEN = 15;
|
|
353
|
+
const WHALE_THRESHOLD = 200;
|
|
354
|
+
const WHALE_PREVIEW_CAP = 60;
|
|
355
|
+
// Produce a short human-readable hint for a whale URL (no full value echoed).
|
|
356
|
+
// Relative paths are already concise; absolute URLs strip the scheme first.
|
|
357
|
+
function whalePreview(url) {
|
|
358
|
+
const target = url.startsWith("/") ? url : url.replace(/^https?:\/\//, "");
|
|
359
|
+
return target.length <= WHALE_PREVIEW_CAP
|
|
360
|
+
? target
|
|
361
|
+
: target.slice(0, WHALE_PREVIEW_CAP - 1) + "…";
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Apply a URL lookup table to a compacted, already-truncated snapshot.
|
|
365
|
+
*
|
|
366
|
+
* Two classes of URL are replaced with short $uN tokens:
|
|
367
|
+
* dedup — appears ≥2× and length ≥ MIN_DEDUP_LEN → full URL printed in trailer
|
|
368
|
+
* whale — length ≥ WHALE_THRESHOLD and not already a dedup URL
|
|
369
|
+
* → hidden in trailer with byte-size + path-stem preview only
|
|
370
|
+
*
|
|
371
|
+
* Must run AFTER truncation so the trailer only references URLs the agent can
|
|
372
|
+
* actually see in the body. Token IDs are assigned in tree-walk (top-down)
|
|
373
|
+
* order and are therefore deterministic for identical input.
|
|
374
|
+
*/
|
|
375
|
+
export function applyUrlLut(text) {
|
|
376
|
+
// Count occurrences of each URL value (Layer 1 has already cleaned them)
|
|
377
|
+
const urlCounts = new Map();
|
|
378
|
+
const scanRe = / url="([^"]+)"/g;
|
|
379
|
+
let m;
|
|
380
|
+
while ((m = scanRe.exec(text)) !== null) {
|
|
381
|
+
urlCounts.set(m[1], (urlCounts.get(m[1]) ?? 0) + 1);
|
|
382
|
+
}
|
|
383
|
+
const isDedup = (u) => (urlCounts.get(u) ?? 0) >= 2 && u.length >= MIN_DEDUP_LEN;
|
|
384
|
+
// Dedup wins when both conditions hold — URL gets full entry in trailer, not hidden.
|
|
385
|
+
const isWhale = (u) => u.length >= WHALE_THRESHOLD && !isDedup(u);
|
|
386
|
+
const urlToToken = new Map();
|
|
387
|
+
const urlMap = new Map();
|
|
388
|
+
let counter = 0;
|
|
389
|
+
const body = text.replace(/ url="([^"]+)"/g, (_full, url) => {
|
|
390
|
+
if (!isDedup(url) && !isWhale(url))
|
|
391
|
+
return _full;
|
|
392
|
+
if (!urlToToken.has(url)) {
|
|
393
|
+
const token = `$u${++counter}`;
|
|
394
|
+
urlToToken.set(url, token);
|
|
395
|
+
urlMap.set(token, url);
|
|
396
|
+
}
|
|
397
|
+
return ` url=${urlToToken.get(url)}`;
|
|
398
|
+
});
|
|
399
|
+
if (urlMap.size === 0)
|
|
400
|
+
return { body, trailer: "", urlMap };
|
|
401
|
+
const trailerLines = ["urls:"];
|
|
402
|
+
for (const [token, url] of urlMap) {
|
|
403
|
+
if (isWhale(url)) {
|
|
404
|
+
trailerLines.push(` ${token} [hidden ${url.length}b → ${whalePreview(url)}]`);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
trailerLines.push(` ${token} ${url}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return { body, trailer: trailerLines.join("\n"), urlMap };
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Resolve a URL from a LUT-applied snapshot body.
|
|
414
|
+
*
|
|
415
|
+
* target is either "$u3" (a LUT token) or "11.57" / "@11.57" (an element ref).
|
|
416
|
+
* For ref resolution the body is searched for the element's url= attribute;
|
|
417
|
+
* if it was tokenised, the token is further resolved via urlMap.
|
|
418
|
+
*
|
|
419
|
+
* Returns the full URL string, or null if not found.
|
|
420
|
+
*/
|
|
421
|
+
export function resolveUrl(body, urlMap, target) {
|
|
422
|
+
const normalised = target.replace(/^@/, "");
|
|
423
|
+
if (normalised.startsWith("$u")) {
|
|
424
|
+
return urlMap.get(normalised) ?? null;
|
|
425
|
+
}
|
|
426
|
+
// ref → find line and extract url= (quoted plain value or unquoted token)
|
|
427
|
+
const escaped = normalised.replace(/\./g, "\\.");
|
|
428
|
+
const re = new RegExp(`@${escaped}\\b[^\\n]*? url=(?:"([^"]+)"|(\\$u\\d+))`);
|
|
429
|
+
const hit = body.match(re);
|
|
430
|
+
if (!hit)
|
|
431
|
+
return null;
|
|
432
|
+
if (hit[1] !== undefined)
|
|
433
|
+
return hit[1];
|
|
434
|
+
if (hit[2] !== undefined)
|
|
435
|
+
return urlMap.get(hit[2]) ?? null;
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
69
438
|
//# sourceMappingURL=snapshot.js.map
|
package/dist/src/snapshot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/snapshot.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/snapshot.ts"],"names":[],"mappings":"AAMA,mEAAmE;AACnE,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,QAAQ,CAAC,GAAW;IAClC,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACnD,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAChE,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,MAAM,IAAI,GAAc,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,0DAA0D;QAC1D,oDAAoD;QACpD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;QACjF,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5B,qEAAqE;QACrE,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACjD,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACrE,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC;IACnC,kEAAkE;IAClE,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC/D,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC7D,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC;IACzC,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,uFAAuF;AACvF,+DAA+D;AAC/D,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,uBAAuB;IACvB,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY;IAClD,wCAAwC;IACxC,QAAQ,EAAK,gBAAgB;IAC7B,SAAS,EAAI,gBAAgB;IAC7B,OAAO,EAAM,SAAS;IACtB,QAAQ,EAAK,YAAY;IACzB,QAAQ,EAAK,SAAS;IACtB,QAAQ,EAAK,YAAY;IACzB,WAAW,EAAE,WAAW;IACxB,SAAS,EAAI,kBAAkB;IAC/B,KAAK,EAAQ,UAAU;CACxB,CAAC,CAAC;AACH,0DAA0D;AAC1D,MAAM,oBAAoB,GAAG;IAC3B,MAAM,EAAE,kCAAkC;IAC1C,KAAK,EAAG,YAAY;CACrB,CAAC;AAEF,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,MAAqB;IACzD,IAAI,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1E,IAAI,OAAO,GAAG,GAAG,CAAC;IAClB,IAAI,MAAM,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;IAChD,CAAC;IAED,mFAAmF;IACnF,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QACjB,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAClC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,IAAI,GAAG,CAAC;QAAE,OAAO,OAAO,GAAG,QAAQ,CAAC;IAExC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,GAAG,QAAQ,CAAC;IAEnC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QAC5C,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,MAAM,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9C,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,QAAQ,CAAC;IAC9C,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,EAAE,CAAC;AAChD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAClB,qEAAqE,CACtE,CAAC;IACF,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,OAAO,GAAG,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,+FAA+F;AAC/F,iGAAiG;AACjG,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAEtC,gGAAgG;AAChG,MAAM,YAAY,GAA2B;IAC3C,WAAW,EAAE,MAAM;IACnB,UAAU,EAAE,MAAM;IAClB,kBAAkB,EAAE,YAAY;IAChC,SAAS,EAAE,OAAO;IAClB,SAAS,EAAE,MAAM;IACjB,IAAI,EAAE,MAAM;CACb,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,iBAAiB,GAAG,KAAK,CAAC;IAE9B,uFAAuF;IACvF,mDAAmD;IACnD,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,EAAE,GAAG,yBAAyB,CAAC;QACrC,IAAI,CAAyB,CAAC;QAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACpC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IACD,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAE1C,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,IAAI,GAAG,GAAG,CAAC;QAEf,kFAAkF;QAClF,oFAAoF;QACpF,6BAA6B;QAC7B,IAAI,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,iBAAiB,GAAG,IAAI,CAAC;YACzB,SAAS;QACX,CAAC;QACD,IAAI,iBAAiB,EAAE,CAAC;YACtB,iBAAiB,GAAG,KAAK,CAAC;YAC1B,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;QACvC,CAAC;QAED,oFAAoF;QACpF,IAAI,kCAAkC,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAE5D,kFAAkF;QAClF,0EAA0E;QAC1E,CAAC;YACC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAC/D,IAAI,CAAC,EAAE,CAAC;gBACN,MAAM,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBAChC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBACnB,IAAI,IAAI,GAAG,KAAK,CAAC;gBACjB,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;oBACzC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;wBAAE,SAAS;oBAC7B,oFAAoF;oBACpF,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;oBAChE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK;wBAAE,IAAI,GAAG,IAAI,CAAC;oBAC3E,MAAM;gBACR,CAAC;gBACD,IAAI,IAAI;oBAAE,SAAS;YACrB,CAAC;QACH,CAAC;QAED,qDAAqD;QACrD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;QAE1C,gEAAgE;QAChE,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;QAEzE,+EAA+E;QAC/E,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;QAE9E,4EAA4E;QAC5E,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,6BAA6B,EAAE,EAAE,CAAC,CAAC;QAEvD,6DAA6D;QAC7D,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAE3E,gFAAgF;QAChF,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;QACtE,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;QAExE,sFAAsF;QACtF,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,+BAA+B,EAAE,EAAE,CAAC,CAAC;YACzD,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,kEAAkE;QAClE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAC;QAEtD,6EAA6E;QAC7E,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC,CAAC;QAE1D,mFAAmF;QACnF,kEAAkE;QAClE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACvD,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACzC,OAAO,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,OAAO,GAAG,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,sFAAsF;QACtF,2DAA2D;QAC3D,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,yBAAyB,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC7D,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,2BAA2B;gBAAE,OAAO,IAAI,CAAC;YACnF,IAAI,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAC1C,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;QAEH,kFAAkF;QAClF,0EAA0E;QAC1E,IAAI,GAAG,IAAI,CAAC,OAAO,CACjB,mDAAmD,EACnD,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,IAAI,CAClE,CAAC;QAEF,8EAA8E;QAC9E,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;QAEjD,sFAAsF;QACtF,CAAC;YACC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACxE,IAAI,CAAC,EAAE,CAAC;gBACN,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC9C,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC1B,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YAChE,CAAC;QACH,CAAC;QAED,yEAAyE;QACzE,kEAAkE;QAClE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;QAEnF,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CAAC,KAAe;IACvC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QAC5D,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACtB,SAAS;QACX,CAAC;QAED,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,IAAI,MAAM,GAAG,UAAU,CAAC;QACxB,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;YAC7D,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM;gBAAE,MAAM;YACvC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,EAAE,CAAC;QACN,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAChB,wEAAwE;YACxE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACtB,SAAS;QACX,CAAC;QAED,gFAAgF;QAChF,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACV,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;QAClC,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5C,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;gBAAE,SAAS;YAChC,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;YACnE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,MAAM;gBAAE,IAAI,GAAG,IAAI,CAAC;YAC5E,MAAM;QACR,CAAC;QACD,IAAI,CAAC,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,GAAG,UAAU,MAAM,GAAG,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAQD,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,IAAa,EACb,KAAK,GAAG,KAAK;IAEb,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC;IACpC,IAAI,IAAI,IAAI,WAAW,IAAI,KAAK,EAAE,CAAC;QACjC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAC3D,CAAC;IACD,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACzE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,KAAK,GAAG,IAAI;IACrD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC;IAChC,IAAI,WAAW,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IACjD,CAAC;IACD,0DAA0D;IAC1D,0EAA0E;IAC1E,IAAI,WAAW,IAAI,KAAK,GAAG,eAAe,EAAE,CAAC;QAC3C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IACjD,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,KAAK,GAAG,UAAU,CAAC;IACtC,uCAAuC;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC9E,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC,CAAC;IAC/D,MAAM,IAAI,GACR,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,WAAW;QACtC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;QAC3B,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,UAAU,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,WAAW,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACxD,MAAM,MAAM,GAAG,GAAG,IAAI,YAAY,OAAO,mBAAmB,WAAW,kBAAkB,IAAI,EAAE,CAAC;IAChG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,WAAW,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAE9E,kDAAkD;AAClD,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,4BAA4B;AAE5B,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,MAAM,eAAe,GAAG,GAAG,CAAC;AAC5B,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAQ7B,8EAA8E;AAC9E,4EAA4E;AAC5E,SAAS,YAAY,CAAC,GAAW;IAC/B,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IAC3E,OAAO,MAAM,CAAC,MAAM,IAAI,iBAAiB;QACvC,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AACnD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,yEAAyE;IACzE,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,MAAM,MAAM,GAAG,iBAAiB,CAAC;IACjC,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACxC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,aAAa,CAAC;IACzF,qFAAqF;IACrF,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,eAAe,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAE1E,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,GAAW,EAAE,EAAE;QAClE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACjD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,KAAK,EAAE,OAAO,EAAE,CAAC;YAC/B,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC3B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACzB,CAAC;QACD,OAAO,QAAQ,UAAU,CAAC,GAAG,CAAC,GAAG,CAAE,EAAE,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;IAE5D,MAAM,YAAY,GAAG,CAAC,OAAO,CAAC,CAAC;IAC/B,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,EAAE,CAAC;QAClC,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACjB,YAAY,CAAC,IAAI,CAAC,KAAK,KAAK,YAAY,GAAG,CAAC,MAAM,OAAO,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjF,CAAC;aAAM,CAAC;YACN,YAAY,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CACxB,IAAY,EACZ,MAA2B,EAC3B,MAAc;IAEd,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5C,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;IACxC,CAAC;IACD,0EAA0E;IAC1E,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACjD,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,IAAI,OAAO,0CAA0C,CAAC,CAAC;IAC7E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;IACxC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC5D,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copy to .env and fill in your values.
|
|
2
|
+
# docker-compose reads this file automatically when run from the openclaw/ directory.
|
|
3
|
+
|
|
4
|
+
# --- AI provider keys (pass whichever providers OpenClaw is configured to use) ---
|
|
5
|
+
# ANTHROPIC_API_KEY=sk-ant-...
|
|
6
|
+
# OPENAI_API_KEY=sk-...
|
|
7
|
+
|
|
8
|
+
# --- opera-browser-cli notes ---
|
|
9
|
+
# The compose file wires OPERA_CLI_BROWSER_URL=http://localhost:9222 automatically.
|
|
10
|
+
# In connect-to-existing-browser mode use `newpage <url>` instead of `open <url>`:
|
|
11
|
+
# docker compose exec openclaw opera-browser-cli newpage https://example.com
|
|
12
|
+
#
|
|
13
|
+
# Additional Chrome flags (space-separated):
|
|
14
|
+
# OPERA_CLI_CHROME_ARGS=--some-flag
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
FROM ghcr.io/openclaw/openclaw:latest
|
|
2
|
+
|
|
3
|
+
USER root
|
|
4
|
+
|
|
5
|
+
# Install opera-browser-cli globally so it is on PATH for OpenClaw agents.
|
|
6
|
+
# Browser is provided by the chromedp/headless-shell sidecar in docker-compose.yml.
|
|
7
|
+
# Also registers SKILL.md into ~/.agents/skills/ — a path OpenClaw scans for skills
|
|
8
|
+
# that is not covered by any named volume, so the file lives in the image layer.
|
|
9
|
+
RUN npm install -g opera-browser-cli && \
|
|
10
|
+
mkdir -p /home/node/.agents/skills/opera-browser-cli && \
|
|
11
|
+
cp "$(npm root -g)/opera-browser-cli/SKILL.md" /home/node/.agents/skills/opera-browser-cli/SKILL.md && \
|
|
12
|
+
chown -R node:node /home/node/.agents
|
|
13
|
+
|
|
14
|
+
USER node
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Adding opera-browser-cli to OpenClaw (Docker)
|
|
2
|
+
|
|
3
|
+
`opera-browser-cli` can be added to any Docker-based OpenClaw setup. Two approaches —
|
|
4
|
+
pick whichever suits your workflow:
|
|
5
|
+
|
|
6
|
+
- **[Option A: Runtime install](#option-a-runtime-install)** — install into the running
|
|
7
|
+
container, no image rebuild required
|
|
8
|
+
- **[Option B: Dockerfile](#option-b-dockerfile)** — extend the OpenClaw image so the
|
|
9
|
+
install is baked in and fully persistent
|
|
10
|
+
|
|
11
|
+
> **Browser is generic headless Chromium, not Opera.** The sidecar uses
|
|
12
|
+
> `chromedp/headless-shell` (upstream Chromium). Standard automation commands
|
|
13
|
+
> (`open`, `newpage`, `snapshot`, `click`, `fill`, `type`, `screenshot`, `eval`,
|
|
14
|
+
> `pages`, `network`, `console`, `lighthouse`, …) work fine. Opera-specific
|
|
15
|
+
> commands — `chat`, `invoke-do`, `make`, `research` — **will not work**; they
|
|
16
|
+
> need an Opera browser with a signed-in user session.
|
|
17
|
+
>
|
|
18
|
+
> Do **not** run `opera-browser-cli setup` or `doctor` inside the container.
|
|
19
|
+
> Both are interactive Opera-Neon detectors and will fail. All required env vars
|
|
20
|
+
> are injected by the compose file — no further config is needed.
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
- Docker Desktop (Mac/Windows) or Docker Engine + Compose plugin (Linux)
|
|
25
|
+
|
|
26
|
+
## Compose changes (required for both options)
|
|
27
|
+
|
|
28
|
+
Add a headless Chrome sidecar and wire OpenClaw's network namespace to it:
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
services:
|
|
32
|
+
chrome:
|
|
33
|
+
image: chromedp/headless-shell:latest
|
|
34
|
+
ports:
|
|
35
|
+
- "18789:18789" # expose OpenClaw's gateway here — openclaw shares this netns
|
|
36
|
+
restart: unless-stopped
|
|
37
|
+
|
|
38
|
+
openclaw:
|
|
39
|
+
# ... your existing config, with these additions:
|
|
40
|
+
network_mode: "service:chrome"
|
|
41
|
+
environment:
|
|
42
|
+
OPERA_CLI_BROWSER_URL: http://localhost:9222
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Note:** `network_mode: "service:chrome"` is incompatible with `networks:` and `ports:`
|
|
46
|
+
on the `openclaw` service. Move any ports you were exposing on `openclaw` to the `chrome`
|
|
47
|
+
service instead (as shown above).
|
|
48
|
+
|
|
49
|
+
Then start the stack:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
docker compose up -d
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Option A: Runtime install
|
|
56
|
+
|
|
57
|
+
No Dockerfile or image rebuild needed. Once the stack is up, run:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
docker compose exec openclaw sh -c '
|
|
61
|
+
npm install -g opera-browser-cli &&
|
|
62
|
+
mkdir -p ~/.openclaw/skills/opera-browser-cli &&
|
|
63
|
+
cp $(npm root -g)/opera-browser-cli/SKILL.md ~/.openclaw/skills/opera-browser-cli/SKILL.md
|
|
64
|
+
'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This installs the binary and registers the skill in one step. The SKILL.md is written
|
|
68
|
+
to the `openclaw-config` named volume and persists across restarts and `docker compose
|
|
69
|
+
down`. The binary lives in the container filesystem and is lost when the container is
|
|
70
|
+
recreated — re-run `npm install -g opera-browser-cli` after each `docker compose down`.
|
|
71
|
+
|
|
72
|
+
## Option B: Dockerfile
|
|
73
|
+
|
|
74
|
+
Extend the official OpenClaw image so the install is baked in and survives `docker
|
|
75
|
+
compose down`. Use the `Dockerfile` in this directory (the reference implementation
|
|
76
|
+
below), or create your own using it as a starting point.
|
|
77
|
+
|
|
78
|
+
Point your compose service at it:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
openclaw:
|
|
82
|
+
build: . # path to the directory containing the Dockerfile
|
|
83
|
+
# ... rest of your existing config
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Build once, then start normally:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
docker compose build
|
|
90
|
+
docker compose up -d
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Re-run `docker compose build` when a new version of `opera-browser-cli` is released.
|
|
94
|
+
|
|
95
|
+
## Reference implementation
|
|
96
|
+
|
|
97
|
+
This directory contains a complete working example for a standalone fresh install:
|
|
98
|
+
|
|
99
|
+
- `Dockerfile` — the Option B extension above
|
|
100
|
+
- `docker-compose.yml` — a full compose file including the Chrome sidecar
|
|
101
|
+
- `.env.example` — template for environment variables
|
|
102
|
+
|
|
103
|
+
For OpenClaw gateway configuration (mode, auth token, AI provider keys), see the
|
|
104
|
+
[OpenClaw docs](https://docs.openclaw.ai).
|
|
105
|
+
|
|
106
|
+
## Using opera-browser-cli
|
|
107
|
+
|
|
108
|
+
For the full command reference see [SKILL.md](../SKILL.md).
|
|
109
|
+
|
|
110
|
+
**From the host** (for manual testing or debugging):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
docker compose exec openclaw opera-browser-cli <command>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Opening pages
|
|
117
|
+
|
|
118
|
+
Because `OPERA_CLI_BROWSER_URL` is set, the CLI connects to the existing Chrome rather
|
|
119
|
+
than launching one. Use `newpage` to open a fresh tab:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
opera-browser-cli newpage https://example.com
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`open <url>` also works, but only after a `stop` first — which resets the bridge so it
|
|
126
|
+
reconnects cleanly:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
opera-browser-cli stop
|
|
130
|
+
opera-browser-cli open https://example.com
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Without `stop` first, `open` returns `No page selected` because no tab is pre-selected
|
|
134
|
+
when connecting to an already-running Chrome instance.
|
|
135
|
+
|
|
136
|
+
## Environment variables
|
|
137
|
+
|
|
138
|
+
`OPERA_CLI_BROWSER_URL` is the only variable required. Set it in your compose file as
|
|
139
|
+
shown above.
|
|
140
|
+
|
|
141
|
+
| Variable | Set to | Purpose |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `OPERA_CLI_BROWSER_URL` | `http://localhost:9222` | Connect to the headless-shell sidecar instead of launching a browser |
|
|
144
|
+
|
|
145
|
+
Other `opera-browser-cli` variables (not needed unless customising):
|
|
146
|
+
|
|
147
|
+
| Variable | Purpose |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `OPERA_CLI_EXECUTABLE_PATH` | Path to a browser binary (launch mode only — not relevant here) |
|
|
150
|
+
| `OPERA_CLI_CHROME_ARGS` | Extra Chrome flags (launch mode only) |
|
|
151
|
+
| `OPERA_CLI_HEADED` | `1` for headed mode — not useful in a headless container |
|
|
152
|
+
| `OPERA_CLI_USER_DATA_DIR` | Persistent Chrome profile path (launch mode only) |
|
|
153
|
+
| `OPERA_CLI_PORT` | Bridge HTTP port inside the container (default `9225`) |
|
|
154
|
+
|
|
155
|
+
## Troubleshooting
|
|
156
|
+
|
|
157
|
+
**`opera-browser-cli open` returns "No page selected"**
|
|
158
|
+
|
|
159
|
+
You are in connect mode. Use `newpage <url>` instead, or run `opera-browser-cli stop`
|
|
160
|
+
first and then `open`.
|
|
161
|
+
|
|
162
|
+
**Chrome connection errors (`Failed to fetch browser webSocket URL`)**
|
|
163
|
+
|
|
164
|
+
The reference `docker-compose.yml` uses a healthcheck on the `chrome` service so
|
|
165
|
+
OpenClaw only starts after Chrome's CDP port is confirmed open. If you see this error
|
|
166
|
+
with a custom compose file, add the healthcheck and `condition: service_healthy` to
|
|
167
|
+
your `depends_on` — see the reference `docker-compose.yml` for the exact config.
|
|
168
|
+
|
|
169
|
+
**Chromium SIGTRAP / crash if using a different base image or architecture**
|
|
170
|
+
|
|
171
|
+
Debian's system Chromium (`apt install chromium`) crashes on ARM64 Docker Desktop
|
|
172
|
+
with `Trace/breakpoint trap (core dumped)`. Do not substitute it for the
|
|
173
|
+
`chromedp/headless-shell` sidecar.
|