@ulpi/browse 0.4.0 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulpi/browse",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/ulpi-io/browse"
package/skill/SKILL.md CHANGED
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  name: browse
3
- version: 2.0.0
3
+ version: 2.3.0
4
4
  description: |
5
- Fast web browsing for Claude Code via persistent headless Chromium daemon. Navigate to any URL,
5
+ Fast web browsing for AI coding agents via persistent headless Chromium daemon. Navigate to any URL,
6
6
  read page content, click elements, fill forms, run JavaScript, take screenshots,
7
7
  inspect CSS/DOM, capture console/network logs, and more. ~100ms per command after
8
- first call. Use when you need to check a website, verify a deployment, read docs,
9
- or interact with any web page. No MCP, no Chrome extension — just fast CLI.
8
+ first call. Works with Claude Code, Cursor, Cline, Windsurf, and any agent that can run Bash.
9
+ No MCP, no Chrome extension — just fast CLI.
10
10
  allowed-tools:
11
11
  - Bash
12
12
  - Read
13
13
 
14
14
  ---
15
15
 
16
- # browse: Persistent Browser for Claude Code
16
+ # browse: Persistent Browser for AI Coding Agents
17
17
 
18
18
  Persistent headless Chromium daemon. First call auto-starts the server (~3s).
19
19
  Every subsequent call: ~100-200ms. Auto-shuts down after 30 min idle.
@@ -74,7 +74,7 @@ If the file is missing or does not contain browse permission rules in `permissio
74
74
  "Bash(browse newtab:*)", "Bash(browse closetab:*)",
75
75
  "Bash(browse frame:*)",
76
76
  "Bash(browse sessions:*)", "Bash(browse session-close:*)",
77
- "Bash(browse state:*)", "Bash(browse auth:*)", "Bash(browse har:*)",
77
+ "Bash(browse state:*)", "Bash(browse auth:*)", "Bash(browse har:*)", "Bash(browse video:*)",
78
78
  "Bash(browse route:*)", "Bash(browse offline:*)",
79
79
  "Bash(browse status:*)", "Bash(browse stop:*)", "Bash(browse restart:*)",
80
80
  "Bash(browse cookie:*)", "Bash(browse header:*)",
@@ -197,6 +197,12 @@ browse har start
197
197
  browse goto https://example.com
198
198
  browse har stop ./recording.har
199
199
 
200
+ # Video recording
201
+ browse video start ./videos
202
+ browse goto https://example.com
203
+ browse click @e3
204
+ browse video stop
205
+
200
206
  # Device emulation
201
207
  browse emulate iphone
202
208
  browse emulate reset
@@ -325,7 +331,8 @@ browse clipboard write <text> Write text to system clipboard
325
331
 
326
332
  ### Visual
327
333
  ```
328
- browse screenshot [path] Screenshot (default: .browse/sessions/{id}/screenshot.png)
334
+ browse screenshot [path] Viewport screenshot (default: .browse/sessions/{id}/screenshot.png)
335
+ browse screenshot --full [path] Full-page screenshot (entire scrollable page)
329
336
  browse screenshot --annotate [path] Screenshot with numbered badges + legend
330
337
  browse pdf [path] Save as PDF
331
338
  browse responsive [prefix] Screenshots at mobile/tablet/desktop
@@ -394,6 +401,13 @@ browse har start Start recording network traffic
394
401
  browse har stop [path] Stop and save HAR file
395
402
  ```
396
403
 
404
+ ### Video recording
405
+ ```
406
+ browse video start [dir] Start recording video (WebM, compositor-level)
407
+ browse video stop Stop recording and save video files
408
+ browse video status Check if recording is active
409
+ ```
410
+
397
411
  ### Server management
398
412
  ```
399
413
  browse status Server health, uptime, session count
@@ -454,6 +468,7 @@ browse inspect Open DevTools (requires BROWSE_DEBUG_PORT)
454
468
  | Save/restore session | `state save mysite` / `state load mysite` |
455
469
  | Auto-login | `auth save gh https://github.com/login user pass` → `auth login gh` |
456
470
  | Record network | `har start` → browse around → `har stop ./out.har` |
471
+ | Record video | `video start ./vids` → browse around → `video stop` |
457
472
  | Parallel agents | `--session agent-a <cmd>` / `--session agent-b <cmd>` |
458
473
  | Multi-step flow | `echo '[...]' \| browse chain` |
459
474
  | Secure browsing | `--allowed-domains example.com goto https://example.com` |
package/src/cli.ts CHANGED
@@ -605,7 +605,7 @@ Inspection: js <expr> | eval <file> | css <sel> <prop> | attrs <sel>
605
605
  element-state <sel> | console [--clear] | network [--clear]
606
606
  cookies | storage [set <k> <v>] | perf
607
607
  value <sel> | count <sel> | clipboard [write <text>]
608
- Visual: screenshot [path] | pdf [path] | responsive [prefix]
608
+ Visual: screenshot [path] [--full] [--annotate] | pdf [path] | responsive [prefix]
609
609
  Snapshot: snapshot [-i] [-c] [-C] [-d N] [-s sel]
610
610
  Find: find role|text|label|placeholder|testid <query> [name]
611
611
  Compare: diff <url1> <url2> | screenshot-diff <baseline> [current]
@@ -218,7 +218,8 @@ export async function handleMetaCommand(
218
218
  case 'screenshot': {
219
219
  const page = bm.getPage();
220
220
  const annotate = args.includes('--annotate');
221
- const filteredArgs = args.filter(a => a !== '--annotate');
221
+ const isFullPage = args.includes('--full');
222
+ const filteredArgs = args.filter(a => a !== '--annotate' && a !== '--full');
222
223
  const screenshotPath = filteredArgs[0] || (currentSession ? `${currentSession.outputDir}/screenshot.png` : `${LOCAL_DIR}/browse-screenshot.png`);
223
224
 
224
225
  if (annotate) {
@@ -278,7 +279,7 @@ export async function handleMetaCommand(
278
279
  document.body.appendChild(container);
279
280
  }, badges);
280
281
 
281
- await page.screenshot({ path: screenshotPath, fullPage: true });
282
+ await page.screenshot({ path: screenshotPath, fullPage: isFullPage });
282
283
  } finally {
283
284
  await page.evaluate(() => {
284
285
  document.getElementById('__browse_annotate__')?.remove();
@@ -288,7 +289,7 @@ export async function handleMetaCommand(
288
289
  return `Screenshot saved: ${screenshotPath}\n\nLegend:\n${legend.join('\n')}`;
289
290
  }
290
291
 
291
- await page.screenshot({ path: screenshotPath, fullPage: true });
292
+ await page.screenshot({ path: screenshotPath, fullPage: isFullPage });
292
293
  return `Screenshot saved: ${screenshotPath}`;
293
294
  }
294
295
 
@@ -481,14 +482,16 @@ export async function handleMetaCommand(
481
482
 
482
483
  // ─── Screenshot Diff ──────────────────────────────
483
484
  case 'screenshot-diff': {
484
- const baseline = args[0];
485
- if (!baseline) throw new Error('Usage: browse screenshot-diff <baseline> [current] [--threshold 0.1]');
485
+ const isFullPageDiff = args.includes('--full');
486
+ const diffArgs = args.filter(a => a !== '--full');
487
+ const baseline = diffArgs[0];
488
+ if (!baseline) throw new Error('Usage: browse screenshot-diff <baseline> [current] [--threshold 0.1] [--full]');
486
489
  if (!fs.existsSync(baseline)) throw new Error(`Baseline file not found: ${baseline}`);
487
490
 
488
491
  let thresholdPct = 0.1;
489
- const threshIdx = args.indexOf('--threshold');
490
- if (threshIdx !== -1 && args[threshIdx + 1]) {
491
- thresholdPct = parseFloat(args[threshIdx + 1]);
492
+ const threshIdx = diffArgs.indexOf('--threshold');
493
+ if (threshIdx !== -1 && diffArgs[threshIdx + 1]) {
494
+ thresholdPct = parseFloat(diffArgs[threshIdx + 1]);
492
495
  }
493
496
 
494
497
  const baselineBuffer = fs.readFileSync(baseline);
@@ -496,16 +499,16 @@ export async function handleMetaCommand(
496
499
  // Find optional current image path: any non-flag arg after baseline
497
500
  let currentBuffer: Buffer;
498
501
  let currentPath: string | undefined;
499
- for (let i = 1; i < args.length; i++) {
500
- if (args[i] === '--threshold') { i++; continue; }
501
- if (!args[i].startsWith('--')) { currentPath = args[i]; break; }
502
+ for (let i = 1; i < diffArgs.length; i++) {
503
+ if (diffArgs[i] === '--threshold') { i++; continue; }
504
+ if (!diffArgs[i].startsWith('--')) { currentPath = diffArgs[i]; break; }
502
505
  }
503
506
  if (currentPath) {
504
507
  if (!fs.existsSync(currentPath)) throw new Error(`Current screenshot not found: ${currentPath}`);
505
508
  currentBuffer = fs.readFileSync(currentPath);
506
509
  } else {
507
510
  const page = bm.getPage();
508
- currentBuffer = await page.screenshot({ fullPage: true }) as Buffer;
511
+ currentBuffer = await page.screenshot({ fullPage: isFullPageDiff }) as Buffer;
509
512
  }
510
513
 
511
514
  const { compareScreenshots } = await import('../png-compare');