agent-browser-loop 0.1.0
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/.claude/skills/agent-browser-loop/REFERENCE.md +374 -0
- package/.claude/skills/agent-browser-loop/SKILL.md +211 -0
- package/LICENSE +9 -0
- package/README.md +168 -0
- package/package.json +73 -0
- package/src/actions.ts +267 -0
- package/src/browser.ts +564 -0
- package/src/chrome.ts +45 -0
- package/src/cli.ts +795 -0
- package/src/commands.ts +455 -0
- package/src/config.ts +59 -0
- package/src/context.ts +20 -0
- package/src/daemon-entry.ts +4 -0
- package/src/daemon.ts +626 -0
- package/src/id.ts +109 -0
- package/src/index.ts +58 -0
- package/src/log.ts +42 -0
- package/src/server.ts +927 -0
- package/src/state.ts +602 -0
- package/src/types.ts +229 -0
package/src/commands.ts
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { AgentBrowser } from "./browser";
|
|
3
|
+
import { formatStateText } from "./state";
|
|
4
|
+
import type { BrowserState, GetStateOptions } from "./types";
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Command Schemas
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
// Base option schemas
|
|
11
|
+
const navigateOptionsSchema = z.object({
|
|
12
|
+
waitUntil: z.enum(["load", "domcontentloaded", "networkidle"]).optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const clickOptionsSchema = z.object({
|
|
16
|
+
ref: z.string().optional(),
|
|
17
|
+
index: z.number().int().optional(),
|
|
18
|
+
double: z.boolean().optional(),
|
|
19
|
+
button: z.enum(["left", "right", "middle"]).optional(),
|
|
20
|
+
modifiers: z.array(z.enum(["Alt", "Control", "Meta", "Shift"])).optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const typeOptionsSchema = z.object({
|
|
24
|
+
ref: z.string().optional(),
|
|
25
|
+
index: z.number().int().optional(),
|
|
26
|
+
text: z.string(),
|
|
27
|
+
submit: z.boolean().optional(),
|
|
28
|
+
clear: z.boolean().optional(),
|
|
29
|
+
delay: z.number().int().optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const waitForNavigationOptionsSchema = z.object({
|
|
33
|
+
timeoutMs: z.number().int().optional(),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const waitForElementOptionsSchema = z.object({
|
|
37
|
+
timeoutMs: z.number().int().optional(),
|
|
38
|
+
state: z.enum(["attached", "visible"]).optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const getStateOptionsSchema = z.object({
|
|
42
|
+
includeScreenshot: z.boolean().optional(),
|
|
43
|
+
viewportOnly: z.boolean().optional(),
|
|
44
|
+
includeElements: z.boolean().optional(),
|
|
45
|
+
includeTree: z.boolean().optional(),
|
|
46
|
+
elementsLimit: z.number().int().optional(),
|
|
47
|
+
elementsHead: z.number().int().optional(),
|
|
48
|
+
elementsTail: z.number().int().optional(),
|
|
49
|
+
treeLimit: z.number().int().optional(),
|
|
50
|
+
treeHead: z.number().int().optional(),
|
|
51
|
+
treeTail: z.number().int().optional(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const dumpStateOptionsSchema = z.object({
|
|
55
|
+
path: z.string(),
|
|
56
|
+
pretty: z.boolean().optional(),
|
|
57
|
+
state: getStateOptionsSchema.optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const dumpStateTextOptionsSchema = z.object({
|
|
61
|
+
path: z.string(),
|
|
62
|
+
state: getStateOptionsSchema.optional(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const dumpNetworkOptionsSchema = z.object({
|
|
66
|
+
path: z.string(),
|
|
67
|
+
pretty: z.boolean().optional(),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Command schemas
|
|
71
|
+
const navigateCommandSchema = z
|
|
72
|
+
.object({ type: z.literal("navigate"), url: z.string() })
|
|
73
|
+
.extend({ options: navigateOptionsSchema.optional() });
|
|
74
|
+
|
|
75
|
+
const clickCommandSchema = z
|
|
76
|
+
.object({ type: z.literal("click") })
|
|
77
|
+
.extend(clickOptionsSchema.shape);
|
|
78
|
+
|
|
79
|
+
const typeCommandSchema = z
|
|
80
|
+
.object({ type: z.literal("type") })
|
|
81
|
+
.extend(typeOptionsSchema.shape);
|
|
82
|
+
|
|
83
|
+
const pressCommandSchema = z.object({
|
|
84
|
+
type: z.literal("press"),
|
|
85
|
+
key: z.string(),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const scrollCommandSchema = z.object({
|
|
89
|
+
type: z.literal("scroll"),
|
|
90
|
+
direction: z.enum(["up", "down"]),
|
|
91
|
+
amount: z.number().int().optional(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const hoverCommandSchema = z.object({
|
|
95
|
+
type: z.literal("hover"),
|
|
96
|
+
ref: z.string().optional(),
|
|
97
|
+
index: z.number().int().optional(),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const selectCommandSchema = z.object({
|
|
101
|
+
type: z.literal("select"),
|
|
102
|
+
ref: z.string().optional(),
|
|
103
|
+
index: z.number().int().optional(),
|
|
104
|
+
value: z.union([z.string(), z.array(z.string())]),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const waitForNavigationCommandSchema = z.object({
|
|
108
|
+
type: z.literal("waitForNavigation"),
|
|
109
|
+
options: waitForNavigationOptionsSchema.optional(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const waitForElementCommandSchema = z.object({
|
|
113
|
+
type: z.literal("waitForElement"),
|
|
114
|
+
selector: z.string(),
|
|
115
|
+
options: waitForElementOptionsSchema.optional(),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const screenshotCommandSchema = z.object({
|
|
119
|
+
type: z.literal("screenshot"),
|
|
120
|
+
fullPage: z.boolean().optional(),
|
|
121
|
+
path: z.string().optional(),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const saveStorageStateCommandSchema = z.object({
|
|
125
|
+
type: z.literal("saveStorageState"),
|
|
126
|
+
path: z.string().optional(),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const getStateCommandSchema = z.object({
|
|
130
|
+
type: z.literal("getState"),
|
|
131
|
+
options: getStateOptionsSchema.optional(),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const dumpStateCommandSchema = z
|
|
135
|
+
.object({ type: z.literal("dumpState") })
|
|
136
|
+
.extend(dumpStateOptionsSchema.shape);
|
|
137
|
+
|
|
138
|
+
const dumpStateTextCommandSchema = z
|
|
139
|
+
.object({ type: z.literal("dumpStateText") })
|
|
140
|
+
.extend(dumpStateTextOptionsSchema.shape);
|
|
141
|
+
|
|
142
|
+
const dumpNetworkLogsCommandSchema = z
|
|
143
|
+
.object({ type: z.literal("dumpNetworkLogs") })
|
|
144
|
+
.extend(dumpNetworkOptionsSchema.shape);
|
|
145
|
+
|
|
146
|
+
const getConsoleLogsCommandSchema = z.object({
|
|
147
|
+
type: z.literal("getConsoleLogs"),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const clearConsoleLogsCommandSchema = z.object({
|
|
151
|
+
type: z.literal("clearConsoleLogs"),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const getNetworkLogsCommandSchema = z.object({
|
|
155
|
+
type: z.literal("getNetworkLogs"),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const clearNetworkLogsCommandSchema = z.object({
|
|
159
|
+
type: z.literal("clearNetworkLogs"),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const enableNetworkCaptureCommandSchema = z.object({
|
|
163
|
+
type: z.literal("enableNetworkCapture"),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const closeCommandSchema = z.object({ type: z.literal("close") });
|
|
167
|
+
|
|
168
|
+
// Step actions - subset that can be batched
|
|
169
|
+
export const stepActionSchema = z.discriminatedUnion("type", [
|
|
170
|
+
navigateCommandSchema,
|
|
171
|
+
clickCommandSchema,
|
|
172
|
+
typeCommandSchema,
|
|
173
|
+
pressCommandSchema,
|
|
174
|
+
scrollCommandSchema,
|
|
175
|
+
hoverCommandSchema,
|
|
176
|
+
selectCommandSchema,
|
|
177
|
+
waitForNavigationCommandSchema,
|
|
178
|
+
waitForElementCommandSchema,
|
|
179
|
+
screenshotCommandSchema,
|
|
180
|
+
saveStorageStateCommandSchema,
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
// All commands
|
|
184
|
+
export const commandSchema = z.discriminatedUnion("type", [
|
|
185
|
+
navigateCommandSchema,
|
|
186
|
+
clickCommandSchema,
|
|
187
|
+
typeCommandSchema,
|
|
188
|
+
pressCommandSchema,
|
|
189
|
+
scrollCommandSchema,
|
|
190
|
+
hoverCommandSchema,
|
|
191
|
+
selectCommandSchema,
|
|
192
|
+
waitForNavigationCommandSchema,
|
|
193
|
+
waitForElementCommandSchema,
|
|
194
|
+
getStateCommandSchema,
|
|
195
|
+
dumpStateCommandSchema,
|
|
196
|
+
dumpStateTextCommandSchema,
|
|
197
|
+
dumpNetworkLogsCommandSchema,
|
|
198
|
+
screenshotCommandSchema,
|
|
199
|
+
getConsoleLogsCommandSchema,
|
|
200
|
+
clearConsoleLogsCommandSchema,
|
|
201
|
+
getNetworkLogsCommandSchema,
|
|
202
|
+
clearNetworkLogsCommandSchema,
|
|
203
|
+
enableNetworkCaptureCommandSchema,
|
|
204
|
+
saveStorageStateCommandSchema,
|
|
205
|
+
closeCommandSchema,
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
// Wait condition schema
|
|
209
|
+
export const waitConditionSchema = z.object({
|
|
210
|
+
selector: z.string().min(1).optional(),
|
|
211
|
+
text: z.string().min(1).optional(),
|
|
212
|
+
url: z.string().min(1).optional(),
|
|
213
|
+
notSelector: z.string().min(1).optional(),
|
|
214
|
+
notText: z.string().min(1).optional(),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Derive types
|
|
218
|
+
export type StepAction = z.infer<typeof stepActionSchema>;
|
|
219
|
+
export type Command = z.infer<typeof commandSchema>;
|
|
220
|
+
export type WaitCondition = z.infer<typeof waitConditionSchema>;
|
|
221
|
+
|
|
222
|
+
// ============================================================================
|
|
223
|
+
// Command Execution
|
|
224
|
+
// ============================================================================
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Execute a single command against an AgentBrowser.
|
|
228
|
+
* Returns data for query commands, undefined for action commands.
|
|
229
|
+
*/
|
|
230
|
+
export async function executeCommand(
|
|
231
|
+
browser: AgentBrowser,
|
|
232
|
+
command: Command,
|
|
233
|
+
): Promise<unknown | undefined> {
|
|
234
|
+
switch (command.type) {
|
|
235
|
+
case "navigate":
|
|
236
|
+
await browser.navigate(command.url, command.options);
|
|
237
|
+
return;
|
|
238
|
+
case "click":
|
|
239
|
+
await browser.click(command);
|
|
240
|
+
return;
|
|
241
|
+
case "type":
|
|
242
|
+
await browser.type(command);
|
|
243
|
+
return;
|
|
244
|
+
case "press":
|
|
245
|
+
await browser.press(command.key);
|
|
246
|
+
return;
|
|
247
|
+
case "scroll":
|
|
248
|
+
await browser.scroll(command.direction, command.amount);
|
|
249
|
+
return;
|
|
250
|
+
case "hover":
|
|
251
|
+
await browser.hover(command);
|
|
252
|
+
return;
|
|
253
|
+
case "select":
|
|
254
|
+
await browser.select(command);
|
|
255
|
+
return;
|
|
256
|
+
case "waitForNavigation":
|
|
257
|
+
await browser.waitForNavigation(command.options);
|
|
258
|
+
return;
|
|
259
|
+
case "waitForElement":
|
|
260
|
+
await browser.waitForElement(command.selector, command.options);
|
|
261
|
+
return;
|
|
262
|
+
case "dumpState":
|
|
263
|
+
await browser.dumpState(command);
|
|
264
|
+
return;
|
|
265
|
+
case "dumpStateText":
|
|
266
|
+
await browser.dumpStateText(command);
|
|
267
|
+
return;
|
|
268
|
+
case "dumpNetworkLogs":
|
|
269
|
+
await browser.dumpNetworkLogs(command);
|
|
270
|
+
return;
|
|
271
|
+
case "clearConsoleLogs":
|
|
272
|
+
browser.clearConsoleLogs();
|
|
273
|
+
return;
|
|
274
|
+
case "clearNetworkLogs":
|
|
275
|
+
browser.clearNetworkLogs();
|
|
276
|
+
return;
|
|
277
|
+
case "enableNetworkCapture":
|
|
278
|
+
browser.enableNetworkCapture();
|
|
279
|
+
return;
|
|
280
|
+
case "close":
|
|
281
|
+
await browser.stop();
|
|
282
|
+
return;
|
|
283
|
+
// Commands that return data
|
|
284
|
+
case "getState":
|
|
285
|
+
return browser.getState(command.options);
|
|
286
|
+
case "screenshot":
|
|
287
|
+
return browser.screenshot(command);
|
|
288
|
+
case "getConsoleLogs":
|
|
289
|
+
return browser.getConsoleLogs();
|
|
290
|
+
case "getNetworkLogs":
|
|
291
|
+
return browser.getNetworkLogs();
|
|
292
|
+
case "saveStorageState":
|
|
293
|
+
return browser.saveStorageState(command.path);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Result from executing a single action
|
|
299
|
+
*/
|
|
300
|
+
export interface ActionResult {
|
|
301
|
+
action: StepAction;
|
|
302
|
+
result?: unknown;
|
|
303
|
+
error?: string;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Execute multiple actions sequentially
|
|
308
|
+
*/
|
|
309
|
+
export async function executeActions(
|
|
310
|
+
browser: AgentBrowser,
|
|
311
|
+
actions: StepAction[],
|
|
312
|
+
options: { haltOnError?: boolean } = {},
|
|
313
|
+
): Promise<ActionResult[]> {
|
|
314
|
+
const { haltOnError = true } = options;
|
|
315
|
+
const results: ActionResult[] = [];
|
|
316
|
+
|
|
317
|
+
for (const action of actions) {
|
|
318
|
+
try {
|
|
319
|
+
const result = await executeCommand(browser, action);
|
|
320
|
+
results.push({ action, result: result ?? undefined });
|
|
321
|
+
} catch (error) {
|
|
322
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
323
|
+
results.push({ action, error: message });
|
|
324
|
+
if (haltOnError) {
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return results;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Execute a wait condition
|
|
335
|
+
*/
|
|
336
|
+
export async function executeWait(
|
|
337
|
+
browser: AgentBrowser,
|
|
338
|
+
condition: WaitCondition,
|
|
339
|
+
options: { timeoutMs?: number; signal?: AbortSignal } = {},
|
|
340
|
+
): Promise<void> {
|
|
341
|
+
const { selector, text, url, notSelector, notText } = condition;
|
|
342
|
+
|
|
343
|
+
if (!selector && !text && !url && !notSelector && !notText) {
|
|
344
|
+
throw new Error("Wait condition required");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
await browser.waitFor({
|
|
348
|
+
selector,
|
|
349
|
+
text,
|
|
350
|
+
url,
|
|
351
|
+
notSelector,
|
|
352
|
+
notText,
|
|
353
|
+
timeoutMs: options.timeoutMs,
|
|
354
|
+
signal: options.signal,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ============================================================================
|
|
359
|
+
// Response Formatting
|
|
360
|
+
// ============================================================================
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Format step results as human-readable text
|
|
364
|
+
*/
|
|
365
|
+
export function formatStepText(params: {
|
|
366
|
+
results: ActionResult[];
|
|
367
|
+
stateText?: string;
|
|
368
|
+
}): string {
|
|
369
|
+
const lines: string[] = [];
|
|
370
|
+
lines.push("Step results:");
|
|
371
|
+
for (const result of params.results) {
|
|
372
|
+
const hasError = result.error != null;
|
|
373
|
+
const status = hasError ? "error" : "ok";
|
|
374
|
+
const action = JSON.stringify(result.action);
|
|
375
|
+
lines.push(`- ${status} ${action}`);
|
|
376
|
+
if (hasError) {
|
|
377
|
+
lines.push(` ${result.error}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (params.stateText) {
|
|
382
|
+
lines.push("");
|
|
383
|
+
lines.push("State:");
|
|
384
|
+
lines.push(params.stateText);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return lines.join("\n");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Format wait result as human-readable text
|
|
392
|
+
*/
|
|
393
|
+
export function formatWaitText(params: {
|
|
394
|
+
condition: WaitCondition;
|
|
395
|
+
stateText?: string;
|
|
396
|
+
}): string {
|
|
397
|
+
const lines: string[] = [];
|
|
398
|
+
const conditions: string[] = [];
|
|
399
|
+
if (params.condition.selector) {
|
|
400
|
+
conditions.push(`selector=${params.condition.selector}`);
|
|
401
|
+
}
|
|
402
|
+
if (params.condition.text) {
|
|
403
|
+
conditions.push(`text=${params.condition.text}`);
|
|
404
|
+
}
|
|
405
|
+
if (params.condition.url) {
|
|
406
|
+
conditions.push(`url=${params.condition.url}`);
|
|
407
|
+
}
|
|
408
|
+
if (params.condition.notSelector) {
|
|
409
|
+
conditions.push(`notSelector=${params.condition.notSelector}`);
|
|
410
|
+
}
|
|
411
|
+
if (params.condition.notText) {
|
|
412
|
+
conditions.push(`notText=${params.condition.notText}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
lines.push(
|
|
416
|
+
`Wait: ok${conditions.length > 0 ? ` (${conditions.join(", ")})` : ""}`,
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
if (params.stateText) {
|
|
420
|
+
lines.push("");
|
|
421
|
+
lines.push("State:");
|
|
422
|
+
lines.push(params.stateText);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return lines.join("\n");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Get state and optionally format as text
|
|
430
|
+
*/
|
|
431
|
+
export async function getStateWithFormat(
|
|
432
|
+
browser: AgentBrowser,
|
|
433
|
+
options: {
|
|
434
|
+
stateOptions?: GetStateOptions;
|
|
435
|
+
includeState?: boolean;
|
|
436
|
+
includeStateText?: boolean;
|
|
437
|
+
} = {},
|
|
438
|
+
): Promise<{ state?: BrowserState; stateText?: string }> {
|
|
439
|
+
const {
|
|
440
|
+
stateOptions,
|
|
441
|
+
includeState = false,
|
|
442
|
+
includeStateText = true,
|
|
443
|
+
} = options;
|
|
444
|
+
|
|
445
|
+
if (!includeState && !includeStateText) {
|
|
446
|
+
return {};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const state = await browser.getState(stateOptions);
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
state: includeState ? state : undefined,
|
|
453
|
+
stateText: includeStateText ? formatStateText(state) : undefined,
|
|
454
|
+
};
|
|
455
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { BrowserCliConfig } from "./types";
|
|
3
|
+
|
|
4
|
+
const storageStateSchema = z.object({
|
|
5
|
+
cookies: z
|
|
6
|
+
.array(
|
|
7
|
+
z.object({
|
|
8
|
+
name: z.string(),
|
|
9
|
+
value: z.string(),
|
|
10
|
+
domain: z.string(),
|
|
11
|
+
path: z.string(),
|
|
12
|
+
expires: z.number(),
|
|
13
|
+
httpOnly: z.boolean(),
|
|
14
|
+
secure: z.boolean(),
|
|
15
|
+
sameSite: z.enum(["Strict", "Lax", "None"]),
|
|
16
|
+
}),
|
|
17
|
+
)
|
|
18
|
+
.default([]),
|
|
19
|
+
origins: z
|
|
20
|
+
.array(
|
|
21
|
+
z.object({
|
|
22
|
+
origin: z.string(),
|
|
23
|
+
localStorage: z
|
|
24
|
+
.array(z.object({ name: z.string(), value: z.string() }))
|
|
25
|
+
.default([]),
|
|
26
|
+
}),
|
|
27
|
+
)
|
|
28
|
+
.default([]),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const browserCliConfigSchema = z.looseObject({
|
|
32
|
+
headless: z.boolean().optional(),
|
|
33
|
+
executablePath: z.string().optional(),
|
|
34
|
+
useSystemChrome: z.boolean().optional(),
|
|
35
|
+
viewportWidth: z.number().int().optional(),
|
|
36
|
+
viewportHeight: z.number().int().optional(),
|
|
37
|
+
userDataDir: z.string().optional(),
|
|
38
|
+
timeout: z.number().int().optional(),
|
|
39
|
+
captureNetwork: z.boolean().optional(),
|
|
40
|
+
networkLogLimit: z.number().int().optional(),
|
|
41
|
+
storageState: z.union([z.string(), storageStateSchema]).optional(),
|
|
42
|
+
storageStatePath: z.string().optional(),
|
|
43
|
+
saveStorageStatePath: z.string().optional(),
|
|
44
|
+
serverHost: z.string().optional(),
|
|
45
|
+
serverPort: z.number().int().optional(),
|
|
46
|
+
serverSessionTtlMs: z.number().int().optional(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export function defineBrowserConfig<T extends BrowserCliConfig>(config: T): T {
|
|
50
|
+
return config;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parseBrowserConfig(input: unknown): BrowserCliConfig {
|
|
54
|
+
const parsed = browserCliConfigSchema.safeParse(input);
|
|
55
|
+
if (!parsed.success) {
|
|
56
|
+
throw new Error(`Invalid browser config: ${parsed.error.message}`);
|
|
57
|
+
}
|
|
58
|
+
return parsed.data;
|
|
59
|
+
}
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
export function createContext<T>(name: string) {
|
|
4
|
+
const storage = new AsyncLocalStorage<T>();
|
|
5
|
+
return {
|
|
6
|
+
use() {
|
|
7
|
+
const result = storage.getStore();
|
|
8
|
+
if (!result) {
|
|
9
|
+
throw new Error(`No context available for ${name}`);
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
},
|
|
13
|
+
useSafe() {
|
|
14
|
+
return storage.getStore();
|
|
15
|
+
},
|
|
16
|
+
with<R>(value: T, fn: () => R) {
|
|
17
|
+
return storage.run<R>(value, fn);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|