browsirai 0.1.1 → 0.2.1
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/LICENSE +661 -78
- package/README.md +120 -6
- package/dist/bin.js +7 -17
- package/dist/bin.js.map +1 -1
- package/dist/cli/commands/act.js +1226 -0
- package/dist/cli/commands/act.js.map +1 -0
- package/dist/cli/commands/nav.js +739 -0
- package/dist/cli/commands/nav.js.map +1 -0
- package/dist/cli/commands/net.js +556 -0
- package/dist/cli/commands/net.js.map +1 -0
- package/dist/cli/commands/obs.js +1049 -0
- package/dist/cli/commands/obs.js.map +1 -0
- package/dist/cli/run.js +728 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli.js +7 -17
- package/dist/cli.js.map +1 -1
- package/dist/server.js +4 -2
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
// src/cli/commands/net.ts
|
|
2
|
+
import pc2 from "picocolors";
|
|
3
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
4
|
+
|
|
5
|
+
// src/cli/run.ts
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
|
|
8
|
+
// src/chrome-launcher.ts
|
|
9
|
+
import { execSync, spawn } from "child_process";
|
|
10
|
+
import { existsSync, readFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
11
|
+
import http from "http";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { homedir, tmpdir } from "os";
|
|
14
|
+
import { createConnection } from "net";
|
|
15
|
+
|
|
16
|
+
// src/cli/run.ts
|
|
17
|
+
function parseFlags(args) {
|
|
18
|
+
const flags = {};
|
|
19
|
+
let positionalIndex = 0;
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
if (arg.startsWith("--")) {
|
|
23
|
+
const eqIdx = arg.indexOf("=");
|
|
24
|
+
if (eqIdx !== -1) {
|
|
25
|
+
const key = arg.slice(2, eqIdx);
|
|
26
|
+
const value = arg.slice(eqIdx + 1);
|
|
27
|
+
flags[key] = value;
|
|
28
|
+
} else {
|
|
29
|
+
const key = arg.slice(2);
|
|
30
|
+
const next = args[i + 1];
|
|
31
|
+
if (next && !next.startsWith("-")) {
|
|
32
|
+
flags[key] = next;
|
|
33
|
+
i++;
|
|
34
|
+
} else {
|
|
35
|
+
flags[key] = "true";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} else if (arg.startsWith("-") && arg.length > 1 && !/^-\d/.test(arg)) {
|
|
39
|
+
const chars = arg.slice(1);
|
|
40
|
+
if (chars.length === 1) {
|
|
41
|
+
const next = args[i + 1];
|
|
42
|
+
if (next && !next.startsWith("-")) {
|
|
43
|
+
flags[chars] = next;
|
|
44
|
+
i++;
|
|
45
|
+
} else {
|
|
46
|
+
flags[chars] = "true";
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
for (const ch of chars) {
|
|
50
|
+
flags[ch] = "true";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
flags[`_${positionalIndex}`] = arg;
|
|
55
|
+
positionalIndex++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return flags;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/tools/browser-intercept.ts
|
|
62
|
+
var activeRoutes = /* @__PURE__ */ new Map();
|
|
63
|
+
var activeAborts = /* @__PURE__ */ new Map();
|
|
64
|
+
var fetchEnabled = false;
|
|
65
|
+
var handlerAttached = false;
|
|
66
|
+
function matchGlob(pattern, url) {
|
|
67
|
+
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "___DOUBLESTAR___").replace(/\*/g, "[^/]*").replace(/___DOUBLESTAR___/g, ".*");
|
|
68
|
+
return new RegExp(`^${regex}$`).test(url);
|
|
69
|
+
}
|
|
70
|
+
async function syncFetchPatterns(cdp) {
|
|
71
|
+
const patterns = [
|
|
72
|
+
...Array.from(activeRoutes.keys()),
|
|
73
|
+
...Array.from(activeAborts.keys())
|
|
74
|
+
].map((p) => ({ urlPattern: p }));
|
|
75
|
+
if (patterns.length === 0) {
|
|
76
|
+
if (fetchEnabled) {
|
|
77
|
+
await cdp.send("Fetch.disable");
|
|
78
|
+
fetchEnabled = false;
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
await cdp.send("Fetch.enable", { patterns });
|
|
83
|
+
fetchEnabled = true;
|
|
84
|
+
if (!handlerAttached) {
|
|
85
|
+
cdp.on("Fetch.requestPaused", async (params) => {
|
|
86
|
+
const url = params.request.url;
|
|
87
|
+
const requestId = params.requestId;
|
|
88
|
+
try {
|
|
89
|
+
for (const [pattern] of activeAborts) {
|
|
90
|
+
if (matchGlob(pattern, url)) {
|
|
91
|
+
await cdp.send("Fetch.failRequest", {
|
|
92
|
+
requestId,
|
|
93
|
+
reason: "BlockedByClient"
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const [pattern, rule] of activeRoutes) {
|
|
99
|
+
if (matchGlob(pattern, url)) {
|
|
100
|
+
const responseHeaders = Object.entries(rule.headers).map(
|
|
101
|
+
([name, value]) => ({ name, value })
|
|
102
|
+
);
|
|
103
|
+
await cdp.send("Fetch.fulfillRequest", {
|
|
104
|
+
requestId,
|
|
105
|
+
responseCode: rule.status,
|
|
106
|
+
body: btoa(rule.body),
|
|
107
|
+
responseHeaders
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
await cdp.send("Fetch.continueRequest", { requestId });
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
handlerAttached = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function browserRoute(cdp, params) {
|
|
120
|
+
const body = typeof params.body === "object" && params.body !== null ? JSON.stringify(params.body) : String(params.body);
|
|
121
|
+
const status = params.status ?? 200;
|
|
122
|
+
const headers = params.headers ?? { "Content-Type": "application/json" };
|
|
123
|
+
const rule = {
|
|
124
|
+
urlPattern: params.url,
|
|
125
|
+
body,
|
|
126
|
+
status,
|
|
127
|
+
headers
|
|
128
|
+
};
|
|
129
|
+
activeRoutes.set(params.url, rule);
|
|
130
|
+
await syncFetchPatterns(cdp);
|
|
131
|
+
return {
|
|
132
|
+
url: params.url,
|
|
133
|
+
status,
|
|
134
|
+
activeRoutes: activeRoutes.size
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function browserAbort(cdp, params) {
|
|
138
|
+
const rule = {
|
|
139
|
+
urlPattern: params.url
|
|
140
|
+
};
|
|
141
|
+
activeAborts.set(params.url, rule);
|
|
142
|
+
await syncFetchPatterns(cdp);
|
|
143
|
+
return {
|
|
144
|
+
url: params.url,
|
|
145
|
+
activeAborts: activeAborts.size
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async function browserUnroute(cdp, params) {
|
|
149
|
+
let removed = 0;
|
|
150
|
+
if (params.all) {
|
|
151
|
+
removed = activeRoutes.size + activeAborts.size;
|
|
152
|
+
activeRoutes.clear();
|
|
153
|
+
activeAborts.clear();
|
|
154
|
+
} else if (params.url) {
|
|
155
|
+
if (activeRoutes.delete(params.url)) removed++;
|
|
156
|
+
if (activeAborts.delete(params.url)) removed++;
|
|
157
|
+
}
|
|
158
|
+
await syncFetchPatterns(cdp);
|
|
159
|
+
return {
|
|
160
|
+
removed,
|
|
161
|
+
remaining: activeRoutes.size + activeAborts.size
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/tools/browser-session-state.ts
|
|
166
|
+
import { writeFileSync, readFileSync as readFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
167
|
+
import { join as join2 } from "path";
|
|
168
|
+
import { homedir as homedir2 } from "os";
|
|
169
|
+
function getStatesDir() {
|
|
170
|
+
return join2(homedir2(), ".browsirai", "states");
|
|
171
|
+
}
|
|
172
|
+
function ensureStatesDir() {
|
|
173
|
+
const dir = getStatesDir();
|
|
174
|
+
if (!existsSync2(dir)) {
|
|
175
|
+
mkdirSync2(dir, { recursive: true });
|
|
176
|
+
}
|
|
177
|
+
return dir;
|
|
178
|
+
}
|
|
179
|
+
function getStatePath(name) {
|
|
180
|
+
return join2(getStatesDir(), `${name}.json`);
|
|
181
|
+
}
|
|
182
|
+
async function browserSaveState(cdp, params) {
|
|
183
|
+
const { name } = params;
|
|
184
|
+
const cookieResponse = await cdp.send("Network.getAllCookies");
|
|
185
|
+
const cookies = cookieResponse.cookies ?? [];
|
|
186
|
+
const urlResponse = await cdp.send("Runtime.evaluate", {
|
|
187
|
+
expression: "window.location.href",
|
|
188
|
+
returnByValue: true
|
|
189
|
+
});
|
|
190
|
+
const url = urlResponse.result.value ?? "";
|
|
191
|
+
const localStorageResponse = await cdp.send("Runtime.evaluate", {
|
|
192
|
+
expression: "JSON.stringify(Object.entries(localStorage))",
|
|
193
|
+
returnByValue: true
|
|
194
|
+
});
|
|
195
|
+
const localStorageEntries = JSON.parse(
|
|
196
|
+
localStorageResponse.result.value ?? "[]"
|
|
197
|
+
);
|
|
198
|
+
const localStorage = Object.fromEntries(localStorageEntries);
|
|
199
|
+
const sessionStorageResponse = await cdp.send("Runtime.evaluate", {
|
|
200
|
+
expression: "JSON.stringify(Object.entries(sessionStorage))",
|
|
201
|
+
returnByValue: true
|
|
202
|
+
});
|
|
203
|
+
const sessionStorageEntries = JSON.parse(
|
|
204
|
+
sessionStorageResponse.result.value ?? "[]"
|
|
205
|
+
);
|
|
206
|
+
const sessionStorage = Object.fromEntries(sessionStorageEntries);
|
|
207
|
+
const dir = ensureStatesDir();
|
|
208
|
+
const filePath = join2(dir, `${name}.json`);
|
|
209
|
+
const stateFile = {
|
|
210
|
+
version: 1,
|
|
211
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
212
|
+
url,
|
|
213
|
+
cookies,
|
|
214
|
+
localStorage,
|
|
215
|
+
sessionStorage
|
|
216
|
+
};
|
|
217
|
+
writeFileSync(filePath, JSON.stringify(stateFile, null, 2), "utf-8");
|
|
218
|
+
return {
|
|
219
|
+
name,
|
|
220
|
+
path: filePath,
|
|
221
|
+
cookies: cookies.length,
|
|
222
|
+
localStorage: localStorageEntries.length,
|
|
223
|
+
sessionStorage: sessionStorageEntries.length
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async function browserLoadState(cdp, params) {
|
|
227
|
+
const { name, url: customUrl } = params;
|
|
228
|
+
const filePath = getStatePath(name);
|
|
229
|
+
if (!existsSync2(filePath)) {
|
|
230
|
+
throw new Error(`State file not found: ${filePath}`);
|
|
231
|
+
}
|
|
232
|
+
const stateFile = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
233
|
+
const targetUrl = customUrl ?? stateFile.url;
|
|
234
|
+
if (targetUrl) {
|
|
235
|
+
await cdp.send("Page.enable");
|
|
236
|
+
await cdp.send("Page.navigate", { url: targetUrl });
|
|
237
|
+
}
|
|
238
|
+
if (stateFile.cookies.length > 0) {
|
|
239
|
+
await cdp.send("Network.setCookies", { cookies: stateFile.cookies });
|
|
240
|
+
}
|
|
241
|
+
const localEntries = Object.entries(stateFile.localStorage);
|
|
242
|
+
if (localEntries.length > 0) {
|
|
243
|
+
const localScript = localEntries.map(([k, v]) => `localStorage.setItem(${JSON.stringify(k)}, ${JSON.stringify(v)})`).join(";");
|
|
244
|
+
await cdp.send("Runtime.evaluate", {
|
|
245
|
+
expression: localScript,
|
|
246
|
+
returnByValue: true
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const sessionEntries = Object.entries(stateFile.sessionStorage);
|
|
250
|
+
if (sessionEntries.length > 0) {
|
|
251
|
+
const sessionScript = sessionEntries.map(([k, v]) => `sessionStorage.setItem(${JSON.stringify(k)}, ${JSON.stringify(v)})`).join(";");
|
|
252
|
+
await cdp.send("Runtime.evaluate", {
|
|
253
|
+
expression: sessionScript,
|
|
254
|
+
returnByValue: true
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
await cdp.send("Page.reload");
|
|
258
|
+
return {
|
|
259
|
+
name,
|
|
260
|
+
cookies: stateFile.cookies.length,
|
|
261
|
+
localStorage: localEntries.length,
|
|
262
|
+
sessionStorage: sessionEntries.length
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/tools/browser-diff.ts
|
|
267
|
+
async function captureScreenshot(cdp, selector) {
|
|
268
|
+
const captureParams = { format: "png" };
|
|
269
|
+
if (selector) {
|
|
270
|
+
const doc = await cdp.send("DOM.getDocument", {});
|
|
271
|
+
const queryResult = await cdp.send("DOM.querySelector", {
|
|
272
|
+
nodeId: doc.root.nodeId,
|
|
273
|
+
selector
|
|
274
|
+
});
|
|
275
|
+
if (!queryResult.nodeId) {
|
|
276
|
+
throw new Error(`Element not found: ${selector}`);
|
|
277
|
+
}
|
|
278
|
+
const boxModel = await cdp.send("DOM.getBoxModel", {
|
|
279
|
+
nodeId: queryResult.nodeId
|
|
280
|
+
});
|
|
281
|
+
const content = boxModel.model.content;
|
|
282
|
+
captureParams.clip = {
|
|
283
|
+
x: content[0],
|
|
284
|
+
y: content[1],
|
|
285
|
+
width: content[2] - content[0],
|
|
286
|
+
height: content[5] - content[1],
|
|
287
|
+
scale: 1
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const screenshot = await cdp.send(
|
|
291
|
+
"Page.captureScreenshot",
|
|
292
|
+
captureParams
|
|
293
|
+
);
|
|
294
|
+
return screenshot.data;
|
|
295
|
+
}
|
|
296
|
+
function buildComparisonExpression(threshold) {
|
|
297
|
+
return [
|
|
298
|
+
"(async () => {",
|
|
299
|
+
" const beforeSrc = window._diffBefore;",
|
|
300
|
+
" const afterSrc = window._diffAfter;",
|
|
301
|
+
"",
|
|
302
|
+
" const loadImg = (src) => new Promise((res, rej) => {",
|
|
303
|
+
" const img = new Image();",
|
|
304
|
+
" img.onload = () => res(img);",
|
|
305
|
+
" img.onerror = (e) => rej(new Error('Failed to load image'));",
|
|
306
|
+
" img.src = src;",
|
|
307
|
+
" });",
|
|
308
|
+
"",
|
|
309
|
+
" const img1 = await loadImg('data:image/png;base64,' + beforeSrc);",
|
|
310
|
+
" const img2 = await loadImg('data:image/png;base64,' + afterSrc);",
|
|
311
|
+
"",
|
|
312
|
+
" const w = Math.max(img1.width, img2.width);",
|
|
313
|
+
" const h = Math.max(img1.height, img2.height);",
|
|
314
|
+
"",
|
|
315
|
+
" const c1 = document.createElement('canvas');",
|
|
316
|
+
" c1.width = w; c1.height = h;",
|
|
317
|
+
" const ctx1 = c1.getContext('2d');",
|
|
318
|
+
" ctx1.drawImage(img1, 0, 0);",
|
|
319
|
+
"",
|
|
320
|
+
" const c2 = document.createElement('canvas');",
|
|
321
|
+
" c2.width = w; c2.height = h;",
|
|
322
|
+
" const ctx2 = c2.getContext('2d');",
|
|
323
|
+
" ctx2.drawImage(img2, 0, 0);",
|
|
324
|
+
"",
|
|
325
|
+
" const d1 = ctx1.getImageData(0, 0, w, h).data;",
|
|
326
|
+
" const d2 = ctx2.getImageData(0, 0, w, h).data;",
|
|
327
|
+
"",
|
|
328
|
+
" const diff = document.createElement('canvas');",
|
|
329
|
+
" diff.width = w; diff.height = h;",
|
|
330
|
+
" const dCtx = diff.getContext('2d');",
|
|
331
|
+
" dCtx.drawImage(img2, 0, 0);",
|
|
332
|
+
" const dData = dCtx.getImageData(0, 0, w, h);",
|
|
333
|
+
"",
|
|
334
|
+
" let diffCount = 0;",
|
|
335
|
+
" const threshold = " + threshold + ";",
|
|
336
|
+
" for (let i = 0; i < d1.length; i += 4) {",
|
|
337
|
+
" const dr = Math.abs(d1[i] - d2[i]);",
|
|
338
|
+
" const dg = Math.abs(d1[i+1] - d2[i+1]);",
|
|
339
|
+
" const db = Math.abs(d1[i+2] - d2[i+2]);",
|
|
340
|
+
" if (dr > threshold || dg > threshold || db > threshold) {",
|
|
341
|
+
" diffCount++;",
|
|
342
|
+
" dData.data[i] = 255;",
|
|
343
|
+
" dData.data[i+1] = 0;",
|
|
344
|
+
" dData.data[i+2] = 0;",
|
|
345
|
+
" dData.data[i+3] = 200;",
|
|
346
|
+
" }",
|
|
347
|
+
" }",
|
|
348
|
+
"",
|
|
349
|
+
" dCtx.putImageData(dData, 0, 0);",
|
|
350
|
+
" const diffBase64 = diff.toDataURL('image/png').split(',')[1];",
|
|
351
|
+
"",
|
|
352
|
+
" const total = w * h;",
|
|
353
|
+
" return JSON.stringify({",
|
|
354
|
+
" diffPercentage: parseFloat((diffCount / total * 100).toFixed(4)),",
|
|
355
|
+
" totalPixels: total,",
|
|
356
|
+
" diffPixels: diffCount,",
|
|
357
|
+
" identical: (diffCount / total) < 0.001,",
|
|
358
|
+
" diffImage: diffBase64,",
|
|
359
|
+
" width: w,",
|
|
360
|
+
" height: h",
|
|
361
|
+
" });",
|
|
362
|
+
"})()"
|
|
363
|
+
].join("\n");
|
|
364
|
+
}
|
|
365
|
+
async function browserDiff(cdp, params) {
|
|
366
|
+
const threshold = params.threshold ?? 30;
|
|
367
|
+
let beforeBase64;
|
|
368
|
+
if (params.before === "current") {
|
|
369
|
+
beforeBase64 = await captureScreenshot(cdp, params.selector);
|
|
370
|
+
} else {
|
|
371
|
+
beforeBase64 = params.before;
|
|
372
|
+
}
|
|
373
|
+
let afterBase64;
|
|
374
|
+
if (!params.after || params.after === "current") {
|
|
375
|
+
afterBase64 = await captureScreenshot(cdp, params.selector);
|
|
376
|
+
} else {
|
|
377
|
+
afterBase64 = params.after;
|
|
378
|
+
}
|
|
379
|
+
await cdp.send("Runtime.evaluate", {
|
|
380
|
+
expression: "window._diffBefore = " + JSON.stringify(beforeBase64) + ";",
|
|
381
|
+
returnByValue: true
|
|
382
|
+
});
|
|
383
|
+
await cdp.send("Runtime.evaluate", {
|
|
384
|
+
expression: "window._diffAfter = " + JSON.stringify(afterBase64) + ";",
|
|
385
|
+
returnByValue: true
|
|
386
|
+
});
|
|
387
|
+
const result = await cdp.send("Runtime.evaluate", {
|
|
388
|
+
expression: buildComparisonExpression(threshold),
|
|
389
|
+
awaitPromise: true,
|
|
390
|
+
returnByValue: true
|
|
391
|
+
});
|
|
392
|
+
if (result.exceptionDetails) {
|
|
393
|
+
throw new Error(
|
|
394
|
+
"Diff comparison failed: " + result.exceptionDetails.text
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
await cdp.send("Runtime.evaluate", {
|
|
398
|
+
expression: "delete window._diffBefore; delete window._diffAfter;",
|
|
399
|
+
returnByValue: true
|
|
400
|
+
});
|
|
401
|
+
return JSON.parse(result.result.value);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/cli/commands/net.ts
|
|
405
|
+
var routeCommand = {
|
|
406
|
+
name: "route",
|
|
407
|
+
description: "Intercept matching requests with a custom response",
|
|
408
|
+
usage: 'browsirai route <urlPattern> [--status=200] [--body="..."] [--contentType=application/json]',
|
|
409
|
+
run: async (cdp, args) => {
|
|
410
|
+
const flags = parseFlags(args);
|
|
411
|
+
const urlPattern = flags._0;
|
|
412
|
+
if (!urlPattern) {
|
|
413
|
+
console.error(pc2.red("Error: URL pattern is required."));
|
|
414
|
+
console.log(pc2.dim(`Usage: ${routeCommand.usage}`));
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
const status = flags.status ? parseInt(flags.status, 10) : 200;
|
|
418
|
+
const body = flags.body ?? "{}";
|
|
419
|
+
const contentType = flags.contentType ?? "application/json";
|
|
420
|
+
const result = await browserRoute(cdp, {
|
|
421
|
+
url: urlPattern,
|
|
422
|
+
body,
|
|
423
|
+
status,
|
|
424
|
+
headers: { "Content-Type": contentType }
|
|
425
|
+
});
|
|
426
|
+
console.log(
|
|
427
|
+
pc2.green(`Routing ${pc2.bold(result.url)} \u2192 ${result.status} (custom response)`)
|
|
428
|
+
);
|
|
429
|
+
console.log(pc2.dim(`Active routes: ${result.activeRoutes}`));
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
var abortCommand = {
|
|
433
|
+
name: "abort",
|
|
434
|
+
description: "Block matching requests",
|
|
435
|
+
usage: "browsirai abort <urlPattern>",
|
|
436
|
+
run: async (cdp, args) => {
|
|
437
|
+
const flags = parseFlags(args);
|
|
438
|
+
const urlPattern = flags._0;
|
|
439
|
+
if (!urlPattern) {
|
|
440
|
+
console.error(pc2.red("Error: URL pattern is required."));
|
|
441
|
+
console.log(pc2.dim(`Usage: ${abortCommand.usage}`));
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
const result = await browserAbort(cdp, { url: urlPattern });
|
|
445
|
+
console.log(
|
|
446
|
+
pc2.green(`Blocking requests matching ${pc2.bold(result.url)}`)
|
|
447
|
+
);
|
|
448
|
+
console.log(pc2.dim(`Active abort rules: ${result.activeAborts}`));
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
var unrouteCommand = {
|
|
452
|
+
name: "unroute",
|
|
453
|
+
description: "Remove intercept rules",
|
|
454
|
+
usage: "browsirai unroute [urlPattern] [--all]",
|
|
455
|
+
run: async (cdp, args) => {
|
|
456
|
+
const flags = parseFlags(args);
|
|
457
|
+
const urlPattern = flags._0;
|
|
458
|
+
const removeAll = flags.all === "true";
|
|
459
|
+
if (!urlPattern && !removeAll) {
|
|
460
|
+
console.error(pc2.red("Error: Provide a URL pattern or --all."));
|
|
461
|
+
console.log(pc2.dim(`Usage: ${unrouteCommand.usage}`));
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
const result = await browserUnroute(cdp, {
|
|
465
|
+
url: urlPattern,
|
|
466
|
+
all: removeAll
|
|
467
|
+
});
|
|
468
|
+
if (removeAll) {
|
|
469
|
+
console.log(pc2.green(`Removed all routes (${result.removed} rules cleared)`));
|
|
470
|
+
} else {
|
|
471
|
+
console.log(pc2.green(`Removed route for ${pc2.bold(urlPattern)} (${result.removed} removed)`));
|
|
472
|
+
}
|
|
473
|
+
console.log(pc2.dim(`Remaining rules: ${result.remaining}`));
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
var saveCommand = {
|
|
477
|
+
name: "save",
|
|
478
|
+
description: "Save browser session state (cookies, storage)",
|
|
479
|
+
usage: "browsirai save <name>",
|
|
480
|
+
run: async (cdp, args) => {
|
|
481
|
+
const flags = parseFlags(args);
|
|
482
|
+
const name = flags._0;
|
|
483
|
+
if (!name) {
|
|
484
|
+
console.error(pc2.red("Error: State name is required."));
|
|
485
|
+
console.log(pc2.dim(`Usage: ${saveCommand.usage}`));
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
const result = await browserSaveState(cdp, { name });
|
|
489
|
+
console.log(pc2.green(`State saved as '${pc2.bold(result.name)}'`));
|
|
490
|
+
console.log(pc2.dim(` Path: ${result.path}`));
|
|
491
|
+
console.log(pc2.dim(` Cookies: ${result.cookies}`));
|
|
492
|
+
console.log(pc2.dim(` localStorage: ${result.localStorage}`));
|
|
493
|
+
console.log(pc2.dim(` sessionStorage: ${result.sessionStorage}`));
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
var loadCommand = {
|
|
497
|
+
name: "load",
|
|
498
|
+
description: "Load a saved browser session state",
|
|
499
|
+
usage: "browsirai load <name> [--url=https://...]",
|
|
500
|
+
run: async (cdp, args) => {
|
|
501
|
+
const flags = parseFlags(args);
|
|
502
|
+
const name = flags._0;
|
|
503
|
+
if (!name) {
|
|
504
|
+
console.error(pc2.red("Error: State name is required."));
|
|
505
|
+
console.log(pc2.dim(`Usage: ${loadCommand.usage}`));
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
const result = await browserLoadState(cdp, {
|
|
509
|
+
name,
|
|
510
|
+
url: flags.url
|
|
511
|
+
});
|
|
512
|
+
console.log(pc2.green(`State '${pc2.bold(result.name)}' loaded`));
|
|
513
|
+
console.log(pc2.dim(` Cookies: ${result.cookies}`));
|
|
514
|
+
console.log(pc2.dim(` localStorage: ${result.localStorage}`));
|
|
515
|
+
console.log(pc2.dim(` sessionStorage: ${result.sessionStorage}`));
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
var diffCommand = {
|
|
519
|
+
name: "diff",
|
|
520
|
+
description: "Pixel-by-pixel screenshot comparison",
|
|
521
|
+
usage: "browsirai diff [--selector=...] [--threshold=30] [--output=diff.png]",
|
|
522
|
+
run: async (cdp, args) => {
|
|
523
|
+
const flags = parseFlags(args);
|
|
524
|
+
const selector = flags.selector;
|
|
525
|
+
const threshold = flags.threshold ? parseInt(flags.threshold, 10) : 30;
|
|
526
|
+
const output = flags.output;
|
|
527
|
+
const result = await browserDiff(cdp, {
|
|
528
|
+
before: "current",
|
|
529
|
+
after: "current",
|
|
530
|
+
selector,
|
|
531
|
+
threshold
|
|
532
|
+
});
|
|
533
|
+
if (output) {
|
|
534
|
+
const imageBuffer = Buffer.from(result.diffImage, "base64");
|
|
535
|
+
writeFileSync2(output, imageBuffer);
|
|
536
|
+
console.log(pc2.dim(`Diff image saved to ${output}`));
|
|
537
|
+
}
|
|
538
|
+
const pct = result.diffPercentage.toFixed(2);
|
|
539
|
+
const status = result.identical ? pc2.green("identical") : pc2.yellow(`${pct}% changed`);
|
|
540
|
+
console.log(
|
|
541
|
+
`Diff: ${status} (${result.diffPixels.toLocaleString()} pixels, ${result.width}x${result.height})`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
var netCommands = [
|
|
546
|
+
routeCommand,
|
|
547
|
+
abortCommand,
|
|
548
|
+
unrouteCommand,
|
|
549
|
+
saveCommand,
|
|
550
|
+
loadCommand,
|
|
551
|
+
diffCommand
|
|
552
|
+
];
|
|
553
|
+
export {
|
|
554
|
+
netCommands
|
|
555
|
+
};
|
|
556
|
+
//# sourceMappingURL=net.js.map
|