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.
@@ -0,0 +1,739 @@
1
+ // src/cli/run.ts
2
+ import pc from "picocolors";
3
+
4
+ // src/chrome-launcher.ts
5
+ import { execSync, spawn } from "child_process";
6
+ import { existsSync, readFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
7
+ import http from "http";
8
+ import { join } from "path";
9
+ import { homedir, tmpdir } from "os";
10
+ import { createConnection } from "net";
11
+
12
+ // src/cli/run.ts
13
+ function parseFlags(args) {
14
+ const flags = {};
15
+ let positionalIndex = 0;
16
+ for (let i = 0; i < args.length; i++) {
17
+ const arg = args[i];
18
+ if (arg.startsWith("--")) {
19
+ const eqIdx = arg.indexOf("=");
20
+ if (eqIdx !== -1) {
21
+ const key = arg.slice(2, eqIdx);
22
+ const value = arg.slice(eqIdx + 1);
23
+ flags[key] = value;
24
+ } else {
25
+ const key = arg.slice(2);
26
+ const next = args[i + 1];
27
+ if (next && !next.startsWith("-")) {
28
+ flags[key] = next;
29
+ i++;
30
+ } else {
31
+ flags[key] = "true";
32
+ }
33
+ }
34
+ } else if (arg.startsWith("-") && arg.length > 1 && !/^-\d/.test(arg)) {
35
+ const chars = arg.slice(1);
36
+ if (chars.length === 1) {
37
+ const next = args[i + 1];
38
+ if (next && !next.startsWith("-")) {
39
+ flags[chars] = next;
40
+ i++;
41
+ } else {
42
+ flags[chars] = "true";
43
+ }
44
+ } else {
45
+ for (const ch of chars) {
46
+ flags[ch] = "true";
47
+ }
48
+ }
49
+ } else {
50
+ flags[`_${positionalIndex}`] = arg;
51
+ positionalIndex++;
52
+ }
53
+ }
54
+ return flags;
55
+ }
56
+ function printResult(data) {
57
+ if (data === void 0 || data === null) return;
58
+ if (typeof data === "string") {
59
+ console.log(data);
60
+ } else if (typeof data === "object") {
61
+ console.log(JSON.stringify(data, null, 2));
62
+ } else {
63
+ console.log(String(data));
64
+ }
65
+ }
66
+
67
+ // src/tools/browser-navigate.ts
68
+ var POLL_INTERVAL_MS = 100;
69
+ async function browserNavigate(cdp, params) {
70
+ const { url, timeout = 8 } = params;
71
+ const timeoutMs = timeout * 1e3;
72
+ await cdp.send("Page.enable");
73
+ const result = await Promise.race([
74
+ performNavigation(cdp, url, params.waitUntil),
75
+ createTimeout(timeoutMs)
76
+ ]);
77
+ return result;
78
+ }
79
+ async function performNavigation(cdp, url, waitUntil) {
80
+ const navResponse = await cdp.send("Page.navigate", { url });
81
+ if (navResponse.errorText) {
82
+ throw new Error(`Navigation failed: ${navResponse.errorText}`);
83
+ }
84
+ const hasCrossDocNavigation = Boolean(navResponse.loaderId);
85
+ if (hasCrossDocNavigation) {
86
+ await waitForLoadCompletion(cdp, waitUntil);
87
+ }
88
+ return getPageInfo(cdp);
89
+ }
90
+ function waitForLoadCompletion(cdp, waitUntil) {
91
+ const eventName = waitUntil === "domcontentloaded" ? "Page.domContentEventFired" : "Page.loadEventFired";
92
+ return new Promise((resolve) => {
93
+ let settled = false;
94
+ const handler = () => {
95
+ if (settled) return;
96
+ settled = true;
97
+ cdp.off(eventName, handler);
98
+ resolve();
99
+ };
100
+ cdp.on(eventName, handler);
101
+ const poll = async () => {
102
+ while (!settled) {
103
+ try {
104
+ const response = await cdp.send("Runtime.evaluate", {
105
+ expression: "document.readyState",
106
+ returnByValue: true
107
+ });
108
+ const readyState = response.result.value;
109
+ const isLoadingState = readyState === "loading" || readyState === "interactive";
110
+ if (readyState === "complete" || !isLoadingState) {
111
+ if (!settled) {
112
+ settled = true;
113
+ cdp.off(eventName, handler);
114
+ resolve();
115
+ }
116
+ return;
117
+ }
118
+ } catch {
119
+ }
120
+ if (!settled) {
121
+ await delay(POLL_INTERVAL_MS);
122
+ }
123
+ }
124
+ };
125
+ poll();
126
+ });
127
+ }
128
+ async function getPageInfo(cdp) {
129
+ const [titleResponse, urlResponse] = await Promise.all([
130
+ cdp.send("Runtime.evaluate", {
131
+ expression: "document.title"
132
+ }),
133
+ cdp.send("Runtime.evaluate", {
134
+ expression: "location.href"
135
+ })
136
+ ]);
137
+ return {
138
+ title: titleResponse.result.value ?? "",
139
+ url: urlResponse.result.value ?? ""
140
+ };
141
+ }
142
+ function createTimeout(ms) {
143
+ return new Promise((_resolve, reject) => {
144
+ setTimeout(() => {
145
+ reject(new Error(`Navigation timeout after ${ms}ms`));
146
+ }, ms);
147
+ });
148
+ }
149
+ function delay(ms) {
150
+ return new Promise((resolve) => setTimeout(resolve, ms));
151
+ }
152
+
153
+ // src/tools/browser-navigate-back.ts
154
+ async function browserNavigateBack(cdp, params) {
155
+ const direction = params.direction ?? "back";
156
+ const history = await cdp.send("Page.getNavigationHistory");
157
+ const targetIndex = direction === "back" ? history.currentIndex - 1 : history.currentIndex + 1;
158
+ if (targetIndex < 0 || targetIndex >= history.entries.length) {
159
+ return { success: false, url: history.entries[history.currentIndex]?.url };
160
+ }
161
+ const entry = history.entries[targetIndex];
162
+ await cdp.send("Page.navigateToHistoryEntry", { entryId: entry.id });
163
+ return { success: true, url: entry.url };
164
+ }
165
+
166
+ // src/tools/browser-scroll.ts
167
+ var DEFAULT_SCROLL_AMOUNT = 300;
168
+ async function resolveSelector(cdp, selector) {
169
+ const evalResult = await cdp.send("Runtime.evaluate", {
170
+ expression: `document.querySelector(${JSON.stringify(selector)})`,
171
+ returnByValue: false
172
+ });
173
+ if (evalResult.result.objectId && evalResult.result.subtype !== "null") {
174
+ return { objectId: evalResult.result.objectId };
175
+ }
176
+ if (evalResult.result.subtype === "null" || evalResult.result.value === null) {
177
+ throw new Error(`Element not found: ${selector}`);
178
+ }
179
+ try {
180
+ const resolveResponse = await cdp.send("DOM.resolveNode", {
181
+ backendNodeId: void 0
182
+ });
183
+ if (resolveResponse.object?.objectId) {
184
+ return { objectId: resolveResponse.object.objectId };
185
+ }
186
+ } catch {
187
+ }
188
+ throw new Error(`Could not find element: ${selector}`);
189
+ }
190
+ async function browserScroll(cdp, params) {
191
+ const { direction, selector } = params;
192
+ const amount = params.amount ?? DEFAULT_SCROLL_AMOUNT;
193
+ if (selector && !direction) {
194
+ const { objectId } = await resolveSelector(cdp, selector);
195
+ await cdp.send("Runtime.callFunctionOn", {
196
+ objectId,
197
+ functionDeclaration: `function() { this.scrollIntoView({ behavior: "smooth", block: "center" }); }`,
198
+ returnByValue: true
199
+ });
200
+ return { success: true };
201
+ }
202
+ if (selector && direction) {
203
+ const { objectId } = await resolveSelector(cdp, selector);
204
+ const scrollX2 = direction === "left" ? -amount : direction === "right" ? amount : 0;
205
+ const scrollY2 = direction === "up" ? -amount : direction === "down" ? amount : 0;
206
+ await cdp.send("Runtime.callFunctionOn", {
207
+ objectId,
208
+ functionDeclaration: `function() { this.scrollBy(${scrollX2}, ${scrollY2}); }`,
209
+ returnByValue: true
210
+ });
211
+ return { success: true };
212
+ }
213
+ const scrollX = direction === "left" ? -amount : direction === "right" ? amount : 0;
214
+ const scrollY = direction === "up" ? -amount : direction === "down" ? amount : 0;
215
+ await cdp.send("Runtime.evaluate", {
216
+ expression: `window.scrollBy(${scrollX}, ${scrollY})`,
217
+ returnByValue: true
218
+ });
219
+ return { success: true };
220
+ }
221
+
222
+ // src/tools/browser-wait-for.ts
223
+ var DEFAULT_TIMEOUT_S = 30;
224
+ var POLL_INTERVAL_MS2 = 100;
225
+ function normalizeTimeoutMs(timeout) {
226
+ if (timeout > 60) {
227
+ return timeout;
228
+ }
229
+ return timeout * 1e3;
230
+ }
231
+ async function browserWaitFor(cdp, params) {
232
+ const timeoutMs = normalizeTimeoutMs(params.timeout ?? DEFAULT_TIMEOUT_S);
233
+ const start = Date.now();
234
+ if (params.time !== void 0) {
235
+ const delayMs = params.time * 1e3;
236
+ await delay2(delayMs);
237
+ return { success: true, elapsed: Date.now() - start };
238
+ }
239
+ const condition = buildCondition(params);
240
+ while (true) {
241
+ const elapsed = Date.now() - start;
242
+ if (elapsed >= timeoutMs) {
243
+ throw new Error(
244
+ `Timeout after ${timeoutMs}ms waiting for condition: ${describeCondition(params)}`
245
+ );
246
+ }
247
+ let met = false;
248
+ try {
249
+ met = await evaluateCondition(cdp, condition);
250
+ } catch {
251
+ }
252
+ if (met) {
253
+ return { success: true, elapsed: Date.now() - start };
254
+ }
255
+ await delay2(POLL_INTERVAL_MS2);
256
+ }
257
+ }
258
+ function buildCondition(params) {
259
+ if (params.url !== void 0) {
260
+ return { kind: "url", expression: params.url };
261
+ }
262
+ if (params.fn !== void 0) {
263
+ return {
264
+ kind: "fn",
265
+ expression: `Boolean(${params.fn})`
266
+ };
267
+ }
268
+ if (params.selector !== void 0 && params.state === "hidden") {
269
+ return {
270
+ kind: "selectorHidden",
271
+ expression: buildVisibilityCheck(params.selector)
272
+ };
273
+ }
274
+ if (params.selector !== void 0 && params.visible) {
275
+ return {
276
+ kind: "selectorVisible",
277
+ expression: buildVisibilityCheck(params.selector)
278
+ };
279
+ }
280
+ if (params.selector !== void 0) {
281
+ return {
282
+ kind: "selector",
283
+ expression: `document.querySelector(${JSON.stringify(params.selector)})`
284
+ };
285
+ }
286
+ if (params.text !== void 0) {
287
+ return {
288
+ kind: "text",
289
+ expression: `document.body && document.body.innerText.includes(${JSON.stringify(params.text)})`
290
+ };
291
+ }
292
+ if (params.textGone !== void 0) {
293
+ return {
294
+ kind: "textGone",
295
+ expression: `document.body && !document.body.innerText.includes(${JSON.stringify(params.textGone)})`
296
+ };
297
+ }
298
+ if (params.networkIdle) {
299
+ return {
300
+ kind: "networkIdle",
301
+ expression: "true"
302
+ // Simplified: check via Runtime.evaluate
303
+ };
304
+ }
305
+ if (params.loadState !== void 0) {
306
+ return {
307
+ kind: "loadState",
308
+ expression: params.loadState
309
+ };
310
+ }
311
+ if (params.load) {
312
+ return {
313
+ kind: "load",
314
+ expression: "document.readyState"
315
+ };
316
+ }
317
+ throw new Error("browserWaitFor: no wait condition specified");
318
+ }
319
+ function buildVisibilityCheck(selector) {
320
+ const sel = JSON.stringify(selector);
321
+ return `(function() {
322
+ var el = document.querySelector(${sel});
323
+ if (!el) return false;
324
+ var style = window.getComputedStyle(el);
325
+ return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetParent !== null;
326
+ })()`;
327
+ }
328
+ async function evaluateCondition(cdp, condition) {
329
+ if (condition.kind === "url") {
330
+ return evaluateUrlCondition(cdp, condition.expression);
331
+ }
332
+ if (condition.kind === "load") {
333
+ return evaluateLoadCondition(cdp, condition.expression);
334
+ }
335
+ if (condition.kind === "loadState") {
336
+ return evaluateLoadStateCondition(cdp, condition.expression);
337
+ }
338
+ if (condition.kind === "selectorHidden") {
339
+ const response2 = await cdp.send("Runtime.evaluate", {
340
+ expression: condition.expression,
341
+ returnByValue: true
342
+ });
343
+ return response2.result.value === false;
344
+ }
345
+ if (condition.kind === "selector") {
346
+ const response2 = await cdp.send("Runtime.evaluate", {
347
+ expression: condition.expression,
348
+ returnByValue: true
349
+ });
350
+ return response2.result.value !== null && response2.result.subtype !== "null";
351
+ }
352
+ const response = await cdp.send("Runtime.evaluate", {
353
+ expression: condition.expression,
354
+ returnByValue: true
355
+ });
356
+ return response.result.value === true;
357
+ }
358
+ async function evaluateLoadCondition(cdp, expression) {
359
+ const response = await cdp.send("Runtime.evaluate", {
360
+ expression,
361
+ returnByValue: true
362
+ });
363
+ if (response.result.type === "string") {
364
+ return response.result.value === "complete";
365
+ }
366
+ return response.result.value === true;
367
+ }
368
+ async function evaluateLoadStateCondition(cdp, targetState) {
369
+ const response = await cdp.send("Runtime.evaluate", {
370
+ expression: "document.readyState",
371
+ returnByValue: true
372
+ });
373
+ const current = response.result.value;
374
+ if (targetState === "complete") {
375
+ return current === "complete";
376
+ }
377
+ if (targetState === "interactive") {
378
+ return current === "interactive" || current === "complete";
379
+ }
380
+ return current === targetState;
381
+ }
382
+ async function evaluateUrlCondition(cdp, pattern) {
383
+ const response = await cdp.send("Runtime.evaluate", {
384
+ expression: "location.href",
385
+ returnByValue: true
386
+ });
387
+ const currentUrl = response.result.value;
388
+ return globMatch(pattern, currentUrl);
389
+ }
390
+ function globMatch(pattern, value) {
391
+ const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\u0000/g, ".*").replace(/\?/g, ".");
392
+ const regex = new RegExp(regexStr);
393
+ return regex.test(value);
394
+ }
395
+ function describeCondition(params) {
396
+ if (params.text) return `text "${params.text}" to appear`;
397
+ if (params.textGone) return `text "${params.textGone}" to disappear`;
398
+ if (params.selector && params.state === "hidden")
399
+ return `selector "${params.selector}" to become hidden`;
400
+ if (params.selector && params.visible)
401
+ return `selector "${params.selector}" to become visible`;
402
+ if (params.selector) return `selector "${params.selector}" to appear`;
403
+ if (params.url) return `URL matching "${params.url}"`;
404
+ if (params.fn) return `JS condition: ${params.fn}`;
405
+ if (params.loadState) return `document.readyState === "${params.loadState}"`;
406
+ if (params.networkIdle) return "network idle";
407
+ if (params.load) return "page load complete";
408
+ return "unknown condition";
409
+ }
410
+ function delay2(ms) {
411
+ return new Promise((resolve) => setTimeout(resolve, ms));
412
+ }
413
+
414
+ // src/tools/browser-close.ts
415
+ async function browserClose(cdp, params) {
416
+ let closedCount = 0;
417
+ if (params.closeAll) {
418
+ const targetsResponse = await cdp.send("Target.getTargets");
419
+ const pageTargets = targetsResponse.targetInfos.filter(
420
+ (t) => t.type === "page"
421
+ );
422
+ for (const target of pageTargets) {
423
+ try {
424
+ await cdp.send("Target.closeTarget", {
425
+ targetId: target.targetId
426
+ });
427
+ closedCount++;
428
+ } catch {
429
+ }
430
+ }
431
+ } else if (params.targetId) {
432
+ await cdp.send("Target.closeTarget", {
433
+ targetId: params.targetId
434
+ });
435
+ closedCount = 1;
436
+ } else {
437
+ const targetsResponse = await cdp.send("Target.getTargets");
438
+ const pageTargets = targetsResponse.targetInfos.filter(
439
+ (t) => t.type === "page"
440
+ );
441
+ if (pageTargets.length === 0) {
442
+ throw new Error("No page targets found to close");
443
+ }
444
+ const activeTarget = pageTargets.find((t) => t.attached) ?? pageTargets[0];
445
+ await cdp.send("Target.closeTarget", {
446
+ targetId: activeTarget.targetId
447
+ });
448
+ closedCount = 1;
449
+ }
450
+ return {
451
+ success: true,
452
+ closedTargets: closedCount
453
+ };
454
+ }
455
+
456
+ // src/tools/browser-tabs.ts
457
+ function globToRegExp(pattern) {
458
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
459
+ const regexStr = escaped.replace(/\*/g, ".*");
460
+ return new RegExp(`^${regexStr}$`, "i");
461
+ }
462
+ async function browserTabs(cdp, params = {}) {
463
+ const response = await cdp.send("Target.getTargets");
464
+ let tabs2 = response.targetInfos.filter(
465
+ (target) => target.type === "page"
466
+ );
467
+ if (params.filter) {
468
+ const regex = globToRegExp(params.filter);
469
+ tabs2 = tabs2.filter((target) => regex.test(target.url));
470
+ }
471
+ return {
472
+ tabs: tabs2.map((target) => ({
473
+ id: target.targetId,
474
+ title: target.title,
475
+ url: target.url
476
+ }))
477
+ };
478
+ }
479
+
480
+ // src/tools/browser-resize.ts
481
+ var PRESETS = {
482
+ mobile: { width: 375, height: 667 },
483
+ tablet: { width: 768, height: 1024 },
484
+ desktop: { width: 1280, height: 720 },
485
+ fullhd: { width: 1920, height: 1080 }
486
+ };
487
+ async function browserResize(cdp, params) {
488
+ let width;
489
+ let height;
490
+ if (params.preset?.toLowerCase() === "reset") {
491
+ await cdp.send("Emulation.clearDeviceMetricsOverride");
492
+ return { success: true, width: 0, height: 0 };
493
+ }
494
+ if (params.preset) {
495
+ const preset = PRESETS[params.preset.toLowerCase()];
496
+ if (!preset) {
497
+ throw new Error(
498
+ `Unknown preset "${params.preset}". Available: ${Object.keys(PRESETS).join(", ")}, reset`
499
+ );
500
+ }
501
+ width = preset.width;
502
+ height = preset.height;
503
+ } else {
504
+ width = params.width ?? 1280;
505
+ height = params.height ?? 720;
506
+ }
507
+ if (params.width !== void 0) width = params.width;
508
+ if (params.height !== void 0) height = params.height;
509
+ const deviceScaleFactor = params.deviceScaleFactor ?? 0;
510
+ const mobile = width < 768;
511
+ await cdp.send("Emulation.setDeviceMetricsOverride", {
512
+ width,
513
+ height,
514
+ deviceScaleFactor,
515
+ mobile
516
+ });
517
+ return {
518
+ success: true,
519
+ width,
520
+ height
521
+ };
522
+ }
523
+
524
+ // src/cli/commands/nav.ts
525
+ var navigate = {
526
+ name: "navigate",
527
+ aliases: ["open", "goto"],
528
+ description: "Navigate the browser to a URL",
529
+ usage: "browsirai open <url> [--waitUntil=load]",
530
+ async run(cdp, args) {
531
+ const flags = parseFlags(args);
532
+ const url = flags._0 ?? flags.url;
533
+ if (!url) {
534
+ console.error("Usage: browsirai open <url> [--waitUntil=load]");
535
+ console.error(" Provide a URL as the first argument or via --url=...");
536
+ process.exit(1);
537
+ }
538
+ const fullUrl = /^https?:\/\//i.test(url) ? url : `https://${url}`;
539
+ try {
540
+ const result = await browserNavigate(cdp, {
541
+ url: fullUrl,
542
+ waitUntil: flags.waitUntil,
543
+ timeout: flags.timeout ? Number(flags.timeout) : void 0
544
+ });
545
+ console.log(`Navigated to ${result.url}`);
546
+ if (result.title) {
547
+ console.log(` Title: ${result.title}`);
548
+ }
549
+ } catch (err) {
550
+ const msg = err instanceof Error ? err.message : String(err);
551
+ console.error(`Navigate failed: ${msg}`);
552
+ process.exit(1);
553
+ }
554
+ }
555
+ };
556
+ var back = {
557
+ name: "back",
558
+ description: "Navigate back or forward in browser history",
559
+ usage: "browsirai back [--direction=back]",
560
+ async run(cdp, args) {
561
+ const flags = parseFlags(args);
562
+ const direction = flags._0 ?? flags.direction ?? "back";
563
+ try {
564
+ const result = await browserNavigateBack(cdp, { direction });
565
+ if (result.success) {
566
+ console.log(`Navigated ${direction}`);
567
+ if (result.url) {
568
+ console.log(` URL: ${result.url}`);
569
+ }
570
+ } else {
571
+ console.log(`Cannot navigate ${direction} \u2014 no history entry`);
572
+ if (result.url) {
573
+ console.log(` Current URL: ${result.url}`);
574
+ }
575
+ }
576
+ } catch (err) {
577
+ const msg = err instanceof Error ? err.message : String(err);
578
+ console.error(`Navigate ${direction} failed: ${msg}`);
579
+ process.exit(1);
580
+ }
581
+ }
582
+ };
583
+ var scroll = {
584
+ name: "scroll",
585
+ description: "Scroll the page in a direction",
586
+ usage: "browsirai scroll <direction> [--pixels=300] [--selector=...]",
587
+ async run(cdp, args) {
588
+ const flags = parseFlags(args);
589
+ const direction = flags._0 ?? flags.direction ?? "down";
590
+ const amount = flags.pixels ? Number(flags.pixels) : flags.amount ? Number(flags.amount) : 300;
591
+ const selector = flags.selector;
592
+ try {
593
+ await browserScroll(cdp, {
594
+ direction,
595
+ amount,
596
+ selector
597
+ });
598
+ console.log(`Scrolled ${direction} ${amount}px`);
599
+ } catch (err) {
600
+ const msg = err instanceof Error ? err.message : String(err);
601
+ console.error(`Scroll failed: ${msg}`);
602
+ process.exit(1);
603
+ }
604
+ }
605
+ };
606
+ var wait = {
607
+ name: "wait",
608
+ description: "Wait for a condition on the page",
609
+ usage: "browsirai wait [--text=...] [--selector=...] [--url=...] [--fn=...] [--time=N] [--timeout=30]",
610
+ async run(cdp, args) {
611
+ const flags = parseFlags(args);
612
+ const timeout = flags.timeout ? Number(flags.timeout) : void 0;
613
+ const params = {};
614
+ if (flags.text !== void 0) params.text = flags.text;
615
+ if (flags.selector !== void 0) params.selector = flags.selector;
616
+ if (flags.url !== void 0) params.url = flags.url;
617
+ if (flags.fn !== void 0) params.fn = flags.fn;
618
+ if (flags.time !== void 0) params.time = Number(flags.time);
619
+ if (flags.visible !== void 0) params.visible = flags.visible === "true";
620
+ if (flags.state !== void 0) params.state = flags.state;
621
+ if (flags.networkIdle !== void 0) params.networkIdle = flags.networkIdle === "true";
622
+ if (flags.load !== void 0) params.load = flags.load === "true";
623
+ if (timeout !== void 0) params.timeout = timeout;
624
+ const hasCondition = Object.keys(params).some((k) => k !== "timeout");
625
+ if (!hasCondition && flags._0) {
626
+ params.text = flags._0;
627
+ }
628
+ if (!hasCondition && !flags._0) {
629
+ console.error("Usage: browsirai wait [--text=...] [--selector=...] [--url=...] [--fn=...] [--time=N]");
630
+ console.error(" Provide at least one condition to wait for.");
631
+ process.exit(1);
632
+ }
633
+ try {
634
+ const result = await browserWaitFor(cdp, params);
635
+ console.log(`Condition met (${result.elapsed}ms)`);
636
+ } catch (err) {
637
+ const msg = err instanceof Error ? err.message : String(err);
638
+ console.error(`Wait failed: ${msg}`);
639
+ process.exit(1);
640
+ }
641
+ }
642
+ };
643
+ var tabs = {
644
+ name: "tab",
645
+ aliases: ["tabs"],
646
+ description: "List open browser tabs",
647
+ usage: "browsirai tab [--filter=*github*]",
648
+ async run(cdp, args) {
649
+ const flags = parseFlags(args);
650
+ const filter = flags._0 ?? flags.filter;
651
+ try {
652
+ const result = await browserTabs(cdp, { filter });
653
+ if (result.tabs.length === 0) {
654
+ console.log("No tabs found");
655
+ return;
656
+ }
657
+ const lines = result.tabs.map(
658
+ (t) => `[${t.id}] ${t.title}
659
+ ${t.url}`
660
+ );
661
+ printResult(lines.join("\n\n"));
662
+ } catch (err) {
663
+ const msg = err instanceof Error ? err.message : String(err);
664
+ console.error(`Tabs failed: ${msg}`);
665
+ process.exit(1);
666
+ }
667
+ }
668
+ };
669
+ var close = {
670
+ name: "close",
671
+ description: "Close browser tab(s)",
672
+ usage: "browsirai close [--force] [--targetId=...] [--closeAll]",
673
+ async run(cdp, args) {
674
+ const flags = parseFlags(args);
675
+ try {
676
+ const result = await browserClose(cdp, {
677
+ force: flags.force === "true",
678
+ targetId: flags.targetId,
679
+ closeAll: flags.closeAll === "true"
680
+ });
681
+ if (result.success) {
682
+ console.log(`Closed ${result.closedTargets} tab(s)`);
683
+ } else {
684
+ console.log("Close failed \u2014 no targets matched");
685
+ }
686
+ } catch (err) {
687
+ const msg = err instanceof Error ? err.message : String(err);
688
+ console.error(`Close failed: ${msg}`);
689
+ process.exit(1);
690
+ }
691
+ }
692
+ };
693
+ var resize = {
694
+ name: "resize",
695
+ description: "Resize the browser viewport",
696
+ usage: "browsirai resize <width> <height> [--preset=mobile]",
697
+ async run(cdp, args) {
698
+ const flags = parseFlags(args);
699
+ const preset = flags.preset;
700
+ const width = flags._0 ? Number(flags._0) : flags.width ? Number(flags.width) : void 0;
701
+ const height = flags._1 ? Number(flags._1) : flags.height ? Number(flags.height) : void 0;
702
+ const deviceScaleFactor = flags.deviceScaleFactor ? Number(flags.deviceScaleFactor) : void 0;
703
+ if (!preset && width === void 0) {
704
+ console.error("Usage: browsirai resize <width> <height> [--preset=mobile]");
705
+ console.error(" Provide dimensions or a preset (mobile, tablet, desktop, fullhd, reset).");
706
+ process.exit(1);
707
+ }
708
+ try {
709
+ const result = await browserResize(cdp, {
710
+ width,
711
+ height,
712
+ preset,
713
+ deviceScaleFactor
714
+ });
715
+ if (preset?.toLowerCase() === "reset") {
716
+ console.log("Viewport reset to browser defaults");
717
+ } else {
718
+ console.log(`Viewport resized to ${result.width}x${result.height}`);
719
+ }
720
+ } catch (err) {
721
+ const msg = err instanceof Error ? err.message : String(err);
722
+ console.error(`Resize failed: ${msg}`);
723
+ process.exit(1);
724
+ }
725
+ }
726
+ };
727
+ var navCommands = [
728
+ navigate,
729
+ back,
730
+ scroll,
731
+ wait,
732
+ tabs,
733
+ close,
734
+ resize
735
+ ];
736
+ export {
737
+ navCommands
738
+ };
739
+ //# sourceMappingURL=nav.js.map