Skip to content
Back to skills

Browser

ASecurity

A headless browser of this session's own, driven from the shell: load pages, click, drag and scroll with trusted input, hold keys, type, run JavaScript in the page, read its text, take screenshots and PDFs, feed a fake microphone from a WAV. Use it to check a web UI in a real browser (how a change behaves, how a page renders) when the project has no browser tooling of its own. It is muted, shared with no other session, and closes when the session ends.

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 20, 2026
ai-agentsjavascriptrustgojavashellbashnode

Works with

  • cli

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned October 20, 2026

npx -y skills add sadilek/obeya --skill browser --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Browser?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Browser
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sadilek-browser/badge)](https://www.skillsdirectory.com/skills/sadilek-browser)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: browser
description: "A headless browser of this session's own, driven from the shell: load pages, click, drag and scroll with trusted input, hold keys, type, run JavaScript in the page, read its text, take screenshots and PDFs, feed a fake microphone from a WAV. Use it to check a web UI in a real browser (how a change behaves, how a page renders) when the project has no browser tooling of its own. It is muted, shared with no other session, and closes when the session ends."
---

# A headless browser for UI checks

`${CLAUDE_SKILL_DIR}/lib/browser.ts` starts a headless Chromium for this session and drives it over
the Chrome DevTools Protocol. It runs with plain Node (22.18 or newer) on macOS, Linux and Windows,
on the browser the demo skill records with: Chrome where it is installed, else Edge, else
Playwright's own Chromium (`DEMO_CHROME` names another).

The browser belongs to this session: other sessions on the machine have their own, and it closes by
itself when this session ends. It is always muted, so a page's videos and narration stay silent, and its `speechSynthesis` speaks
nothing (it goes on as if it had: `start`, then `end` after about as long as saying it takes).
Its profile is fresh at every `start`: no logins or cookies of the machine's user.

For what headless Chromium can't reproduce (on-screen keyboard, fullscreen, rotation), use
`bun scripts/android-phone.ts` in Obeya's repository: Chrome in the Android emulator, with real taps.

## Commands

All commands: `node ${CLAUDE_SKILL_DIR}/lib/browser.ts <command>`

```bash
start [--window-size 1280,900] [--fake-mic file.wav]   # launch (idempotent; prints CDP port)
stop                              # close it (it also closes when the session ends)
status | tabs                     # list open tabs: id, url, title
open <url> [--no-wait]            # new tab, waits for document.readyState === complete, prints tab id
nav <url> [--tab id] [--no-wait]  # navigate an existing tab
close <tabId>                     # close a tab
eval <js> [--tab id]              # run JS in the page (awaits promises), prints JSON result
text [--tab id]                   # document.body.innerText
html [--tab id]                   # full page HTML
screenshot <file.png> [--tab id] [--full]   # --full = full page height
pdf <file.pdf> [--tab id]
input <step>… [--tab id]          # trusted mouse/keyboard input, see below
```

`--tab` accepts an id prefix; without it, the first (most recently used) tab is targeted.

## Typical session

```bash
B=${CLAUDE_SKILL_DIR}/lib/browser.ts
node $B start
node $B open http://127.0.0.1:4480/          # prints tab id
node $B text                                 # page content as plain text
node $B screenshot /tmp/page.png --full      # then Read the PNG to view it
node $B stop
```

## Interacting with pages

`input` sends trusted input through CDP (real pointer and key events, which synthetic JS events are
not: pointer capture, `isTrusted`, key holds). Steps run in order on one connection, so a key
pressed with `down:` stays held until `up:`:

```bash
node $B input "click:@#search" "type:hello" "key:Enter" "wait:500" "shot:/tmp/after.png"
node $B input "dbl:700,400" "drag:@.card,900,500" "wheel:700,400,0,-300" "wheel:700,400,0,-100,ctrl"
node $B input "down:Space" "wait:3000" "up:Space" "eval:document.title"   # hold a key (push-to-talk)
```

Steps: `click:X,Y`, `dbl:X,Y`, `drag:X1,Y1,X2,Y2`, `wheel:X,Y,DX,DY[,ctrl]`, `key:NAME`,
`down:NAME`, `up:NAME` (a character, Enter, Escape, Tab, Space, Backspace, ArrowUp, ArrowDown,
ArrowLeft, ArrowRight), `type:TEXT`, `wait:MS`, `shot:FILE.png`, `eval:JS`. `click`, `dbl` and the
start of `drag` (`drag:@sel,X2,Y2`) also take `@<css selector>` (the centre of the first match,
scrolled into view); `wheel` and a drag's end take numbers only.

For pages that record audio, `start --fake-mic speech.wav` grants the microphone and plays the WAV
in a loop as its input. The page has to be a secure context (`https`, or `localhost` /
`127.0.0.1`). Make the WAV with any text-to-speech that writes one: on macOS
`say -o speech.wav --data-format=LEI16@48000 "…"`, on Linux `espeak-ng -w speech.wav "…"`.

Plain JS through `eval` covers the rest; it runs arbitrary JS in the page and awaits promises:

```bash
node $B eval "document.querySelector('button.submit').click()"
node $B eval "await new Promise(r => setTimeout(r, 1000)); document.title"   # wait, then read
node $B eval "[...document.querySelectorAll('a')].map(a => a.href)"          # extract data as JSON
```

After a click that triggers navigation, give the page a moment (the `eval` wait pattern above) or
re-run `tabs` to see the new URL.

## Notes

- `open`/`nav` wait up to 20 s for full load, then print a warning and continue; slow SPAs may need
  an extra `eval`-based wait for a specific element.
- Screenshots are PNG; view them with the Read tool.
- "No browser running in this session" or "stale state cleaned up" just mean: run `start` (again),
  as after a restart of the session.
- A headless page runs `requestAnimationFrame`, so camera flights and animations finish here,
  unlike in a hidden tab of a desktop browser.

Files in this skill

  • SKILL.md5.1 KB
  • lib/browser.test.ts8.9 KB
  • lib/browser.ts23.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.