Skip to content
Back to skills

Headless Browser

ASecurity

Use when an agent needs to visit a page it can drive itself -- fetch rendered HTML or visible text, screenshot a site, run a page through JS, or browse the open web -- rather than read a page the operator already has open. A real headless Chromium on this device, driven by Playwright over CDP. Ships the launcher and the CLI that drive it. For a page already open in the operator's Firefox, use firefox-remote-debug instead.

  • 3 stars
  • 0 votes
  • 0 copies
  • 6 views
  • Added September 25, 2026
ai-agentsjavascriptgojavanodeapi

Works with

  • terminal
  • cli
  • api

Security analysis

A100/100

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

Scanned September 25, 2026

npx -y skills add nev3rfail/harness.apk --skill headless-browser --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Headless Browser?

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

Security grade badge for Headless Browser
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nev3rfail-headless-browser/badge)](https://www.skillsdirectory.com/skills/nev3rfail-headless-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: headless-browser
description: Use when an agent needs to visit a page it can drive itself -- fetch rendered HTML or visible text, screenshot a site, run a page through JS, or browse the open web -- rather than read a page the operator already has open. A real headless Chromium on this device, driven by Playwright over CDP. Ships the launcher and the CLI that drive it. For a page already open in the operator's Firefox, use firefox-remote-debug instead.
---

# Browsing the web from this device

A native headless Chromium runs here and a Node client drives it with Playwright.
It renders JavaScript, so it sees what a browser sees, not what `curl` gets. The
working tree is `$HOME/browsing`; the launcher and the CLI ship beside this skill
in `browsing/` and are copied there by the install.

This is for pages *you* go to. For a value out of a page the operator already has
open in Firefox, that is `firefox-remote-debug`.

## Why it is built this way

- Chromium comes from Termux's TUR (`tur-repo` + `x11-repo`); its command is
  **`chromium-browser`**, not `chromium`. It is a bionic aarch64 build, so no
  proot is involved.
- There is no root and no user namespaces, so it only starts with
  `--no-sandbox --headless=new`.
- Playwright's own bundled Chromium is a glibc build and will not run here. So
  `playwright-core` is installed **without** its browser download and used only
  to *drive* the native Chromium over CDP (`connectOverCDP`).
- Termux's Node reports `process.platform === "android"`, which playwright-core's
  registry refuses with `Unsupported platform`. The client spoofs it to `linux`
  before importing playwright. Keep that line first in any new script.

## The route

Chromium runs as a long-lived process exposing CDP on `127.0.0.1:9222`; the
client connects to that. Start it once, then drive it as often as you like.

```sh
# 1. Ensure the browser is up (idempotent: only starts if the port is down)
curl -s --max-time 2 http://127.0.0.1:9222/json/version >/dev/null \
  || { $HOME/browsing/chromium-headless.sh >$HOME/browsing/chromium.log 2>&1 & \
       until curl -s --max-time 2 http://127.0.0.1:9222/json/version >/dev/null; do sleep 1; done; }

# 2. Drive it
node $HOME/browsing/browse.mjs <url> [--text | --html | --shot [file.png]] [--wait <ms>]
```

- `--text` prints `document.body.innerText`; `--html` prints the rendered DOM;
  `--shot` writes a full-page PNG (default `shot.png`); no flag prints the title.
- `--wait <ms>` pauses after load for slow client-rendered pages.
- A screenshot is a file, not something the terminal shows. Hand it to the
  operator with `show_document_panel` (embed it) or `open_in_phone_app`.

Chromium does not survive a reboot. The ensure snippet starts it again, and only
when the port is down.

## Driving it directly

For anything past the CLI -- clicking, typing, waiting on a selector, reading a
specific node -- write a short script. The first line is not optional.

```js
Object.defineProperty(process, 'platform', { value: 'linux' }); // Termux quirk
const { chromium } = await import('playwright-core');
const b = await chromium.connectOverCDP('http://127.0.0.1:9222');
const ctx = b.contexts()[0] || await b.newContext();
const page = await ctx.newPage();
await page.goto(url, { waitUntil: 'networkidle', timeout: 45000 });
// ... page.click / page.fill / page.waitForSelector / page.evaluate ...
await b.close();   // closes YOUR connection, leaves Chromium running
```

Run such a script from `$HOME/browsing` so it resolves `playwright-core` out of
the tree's `node_modules`. The full Playwright page API is available; only the
launch/registry side is off limits, which is why you connect instead of
`launch()`.

## If Chromium is not installed

If `chromium-browser` is missing or `$HOME/browsing` is empty, it has not been
set up on this device yet. It is a one-time, ~300 MB install -- TUR + x11 repos,
the native Chromium, Termux Node, the two shipped scripts, and `playwright-core`
**without** its bundled (glibc) browser. The full sequence, and why each piece is
needed, is in [references/install.md](references/install.md). Ask the operator
before pulling it, then follow that file.

## Common mistakes

| Mistake | What is true |
|---|---|
| `chromium ...` | The command is `chromium-browser`; `chromium` is not found |
| `chromium.launch()` | The registry refuses `android`/glibc. Start the process, then `connectOverCDP` |
| Importing playwright before the platform spoof | Throws `Unsupported platform: android`. Spoof `process.platform` first |
| Running without `--no-sandbox` | No root here; it exits at startup |
| Typing `/data/data/<channel>/files` | Three channels run this skill. Spell paths `$HOME` and `$PREFIX` |
| `chromium-headless.sh: Permission denied` | The copy out of the skill carries content, not the execute bit. `chmod +x` it |
| Writing scratch to `/tmp` | Not writable. Use `$TMPDIR` |
| Treating dbus/GCM lines in `chromium.log` as failures | Harmless -- there is no dbus and no Play Services |
| `curl`-ing the page instead | You lose JS rendering; that is the whole point of this |
| Printing a screenshot path and stopping | The operator cannot see a file path. Show or share the PNG |

Files in this skill

  • SKILL.md5.1 KB
  • browsing/browse.mjs1.5 KB
  • browsing/chromium-headless.sh670 B
  • browsing/package-lock.json697 B
  • browsing/package.json252 B
  • references/install.md4.1 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.