Browser automation where an LLM plans and Jev (Typesafe System One) decides. Library, CLI and MCP server.
# Add to your Claude Code skills
git clone https://github.com/Ying-Kai-Liao/jev-browserLast scanned: 9/21/2026
{
"issues": [],
"status": "PASSED",
"scannedAt": "2026-09-21T09:37:56.042Z",
"npmAuditRan": true,
"pipAuditRan": true,
"promptInjectionRan": true
}See how jev-browser compares with popular alternatives.
jev-browser is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by Ying-Kai-Liao. Browser automation where an LLM plans and Jev (Typesafe System One) decides. Library, CLI and MCP server. It has 95 GitHub stars.
Yes. jev-browser passed SkillsLLM's automated security scan — a dependency vulnerability audit plus prompt-injection heuristics — with no high-severity issues. You can read the full report in the Security Report section on this page.
Clone the repository with "git clone https://github.com/Ying-Kai-Liao/jev-browser" and add it to your Claude Code skills directory (see the Installation section above).
jev-browser is primarily written in JavaScript. It is open-source under Ying-Kai-Liao on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh jev-browser against similar tools.
No comments yet. Be the first to share your thoughts!
⚠️ Third-Party Software Notice
This skill is third-party open-source software developed and hosted independently on GitHub. SkillsLLM is an informational directory and does not control or maintain the underlying repository.
Any security checks, ratings, or warnings displayed by SkillsLLM are automated and limited in scope. They do not constitute a security certification or guarantee that the software is safe, error-free, or free from malicious code, vulnerabilities, compromised dependencies, or prompt-injection risks.
Review the source code, permissions, dependencies, and configuration before installing or running any third-party skill. Use is at your own risk. To the maximum extent permitted by applicable law, SkillsLLM is not liable for losses arising from third-party software.
Browser automation where an LLM plans and Jev decides.
Unofficial project, not affiliated with TypeSafe. It calls the TypeSafe System One API with your own API key.
The calling LLM (Claude, via MCP) says what outcome it wants, one step at a time, and hands over any text to type. For each round of a step, code describes the page. Then one ~300 ms Typesafe System One request asks Jev several questions at once: which element, which action, which value, and is the step done / blocked / showing an error / about to do something irreversible. Playwright performs the action. The LLM never reads page snapshots unless it chooses to take over.
Claude ── browser_do("Log in", {email, password}) ──▶ jev-browser
│ loop until done / stuck / needs confirmation
│ 1. settle (network + DOM quiet)
│ 2. describe (elements, labels, state, visible text, diff, counts)
│ 3. Jev (done? error? irreversible? tool? target? value?)
│ 4. act (Playwright)
Claude ◀── { status: "done", url, actions[], done_score } ─┘
https://github.com/user-attachments/assets/2e688df9-4985-4854-8ebe-ba97c9d13d68
Jev only answers with probability distributions: yes/no (noul), pick one option (choice)
or a rating (score). It never writes text. So everything free-form comes from the caller as
candidates, and code turns disagreement or low confidence into a status the LLM can act on.
42 tasks in 16 categories on live sites (see RESULTS.md):
likely_done) and verifying a sort.Works on: forms, native and custom dropdowns, checkboxes and radios (including styled replacements), dynamic loading, modals, JS dialogs, hover, right-click, drag and drop, key presses, file upload, iframes, shadow DOM, new tabs, pages with 2,000+ elements (two-stage selection), and non-English UIs.
Known limits, so write steps around them:
browser_check or browser_snapshot.npx playwright install chromium # once
claude mcp add jev-browser -e TYPESAFE_API_KEY=your-key -- npx -y -p jev-browser jev-browser-mcp
Any MCP client works the same way: command npx, args -y -p jev-browser jev-browser-mcp,
env TYPESAFE_API_KEY. Add JEV_BROWSER_HEADED=1 to watch it work.
git clone https://github.com/Ying-Kai-Liao/jev-browser && cd jev-browser
npm install
npm run setup # downloads Chromium for Playwright
cp .env.example .env # add TYPESAFE_API_KEY
npm test # offline tests (no network, no key)
npm run test:e2e # MCP server end to end (network + key)
claude mcp add jev-browser -- node /absolute/path/to/jev-browser/bin/jev-browser-mcp.mjs
From a source checkout the server reads TYPESAFE_API_KEY from the repo's .env.
| tool | purpose |
|---|---|
browser_open(url) |
navigate and wait for the page to settle |
browser_do(goal, values?, max_actions?, allow_irreversible?, explain?) |
work toward one outcome; returns a status |
browser_check(question) |
yes/no about the page → p_yes |
browser_choose(question, options) |
pick among given options → distribution |
browser_snapshot(diff?, interactive?, filter?, urls?, max_chars?) |
compact numbered element list, for taking over; the options below narrow it |
browser_act(action, element, value?, key?, destination?, accept_dialog?) |
act on an element directly, no model; confirm/prompt dialogs are dismissed unless accept_dialog |
browser_read(question, max_chars?) |
the page text that helps answer a question; Jev drops the rest |
browser_screenshot(full_page?) |
PNG image |
browser_close() |
end the session |
browser_readbrowser_snapshot is the take-over-and-act surface: numbered elements, plus the text in the
viewport. That is the right trade for acting, and it is already small (99–6,900 tokens on
every page measured, because elements are capped at 400 and the text at 2,500 chars). It is a
poor way to read, though — a Wikipedia article is ~179k chars of text, so a snapshot shows
about 1.4% of it and the rest costs a scroll-and-snapshot loop.
browser_read(question) is the understand-the-content surface. Code extracts the whole
document's text and cuts it at headings and sections; Jev rates every block against the
question in batches of 40; code returns the blocks that help, in document order, inside a
character budget. Jev ranks, it never writes — the text you get back is the page's own.
browser_snapshot |
browser_read |
|
|---|---|---|
| gives you | numbered elements + viewport text | document text relevant to a question |
| use it to | act (with browser_act) |
read, quote, answer |
| model calls | none | 1 per ~50k chars, ~1 s each |
| lossless | yes | no — drops blocks, and says which |
When it pays. Long articles, documentation and reference pages. On the WW2 article, "When and why did Japan surrender?" returned 11.8k of 178.5k chars — 15× less — in 3.4 s over 5 Jev calls, keeping the atomic-bomb and surrender paragraphs.
When it doesn't. Short pages, apps and forms. Below a 8,000-char floor (and below your own
max_chars) it returns the page whole and never calls Jev at all, because paying ~1 s to prune
a 2k-token page is a loss. Hacker News, a BBC front page and saucedemo all land under the floor.
It is biased to keep: a block Jev cannot rate — or a whole batch whose request failed — is
kept, and whatever is left out is counted and marked in place with [… N blocks dropped …].
Nothing is ever silently cut. Use browser_snapshot if you need the page as the caller
normally sees it.
node examples/read-demo.mjs # WW2 article, default question
node examples/read-demo.mjs "https://…" "what do you want?" # needs a key
browser_do statuses:
| status | meaning / what the caller should do |
|---|---|
done |
goal reached |
likely_done |
the page looks done but Jev is unsure: verify before moving on |
needs_login |
a sign-in wall and no credentials in values; log in yourself (headed + JEV_BROWSER_PROFILE) or pass credentials |
needs_confirmation |
next action, or a confirm dialog it opened (then dismissed), looks irreversible (order, pay, send, delete); see pending, re-call with allow_irreversible: true only if the user wants it |
error |
the page shows an error after the last action (e.g. wrong password); see page_text |
stuck / max_actions |
no progress; see info, page_text, candidates |
ambiguous |
low confidence in the target; pick from candidates with browser_act |
blocked |
captcha, access denied, error page |
browser_snapshot options. A snapshot is the one page the calling LLM reads in full, so every
option narrows what gets rendered. All of them are pure formatting: nothing is collected
differently and none of them costs a Jev call.
| option | default | effect |
|---|---|---|
diff |
false |
only what changed since the previous snapshot, using the same diff the browser_do loop feeds Jev. With no previous snapshot you get a full one and a note saying so. A diff carries no element numbers: take a full snapshot to act. |
interactive |
false |
drop the visible text: block; element lines and dialog/status text stay. The element list is interactive-only already, so this narrows the prose, not the elements. |
filter |
— | keep only elements whose label, text, placeholder, name or href contains the substring, case-insensitive; the header says how many were hidden |
urls |
false |
print href= on element lines. Off by default — on a link-heavy page link targets are the largest single share of the output, and browser_act takes element numbers, not URLs. A link with nothing else to identify it keeps its href. |
max_chars |
10000 |
size of the rendered snapshot, hard cap 20000. When it truncates, the last line says how many element lines and characters were dropped and which option would narrow it. |
A typed password is never printed: input:password values render as [redacted], in a snapshot
and in a diff. Measured on a Wikipedia article (1,840 elements), a default snapshot went from
26,919 to 9,926 characters (~6,700 → ~2,500 estimated tokens); filter brings it to ~800 and
diff after a scroll to ~50.
Env: JEV_BROWSER_HEADED=1 shows the browser, JEV_BROWSER_PROFILE=/dir keeps a persistent
profile (logins survive restarts), JEV_BROWSER_LOG=1 prints per-round decisions to stderr.
// npm install jev-browser &&