Skip to content

Repository files navigation

jev-browser logo

jev-browser

Browser use & computer use for coding agents, powered by TypeSafe Jev.
Calibrated judgments from a System One model. Control loop in code. No vision model, no prompt-and-parse.

test node 22+ zero dependencies TypeSafe Jev MCP server MIT

English · 简体中文

jev-browser banner


Demo

Real runs in ego lite, recorded by the tool itself (run --step-screenshots). Left: the page exactly as Jev saw it before each step. Right: Jev's calibrated judgment for that step and the action the code controller executed. Every demo is a single jev-browser run command.

Wikipedia, multi-hop on a real site — type into the search box and press Enter, pick the right result, follow links across three articles (a 500-link page: goal-aware candidate ordering keeps the relevant links in the list). 6 steps · $0.0029 · 21 s. ▶ MP4 1080p

jev-browser on Wikipedia in ego lite

Sign in with a secret, then fill a form with a dropdown — the password is typed but shown to Jev only as inputs.password; the topic is chosen from the dropdown's options in a second question. 10 steps · $0.0013 · 18 s. ▶ MP4 1080p

jev-browser signing in and filling a contact form in ego lite

Three identical "Start free trial" buttons — the goal names the Team plan; Jev picks the right button from page structure alone at probability 1.00. 3 steps · $0.0003 · 3 s. ▶ MP4 1080p · Bonus: GitHub repository navigation (MP4)

jev-browser choosing the Team plan among identical buttons

GitHub does not play repository MP4s inline, so the GIFs above are previews; the MP4s are the full-quality recordings (1080p, crossfades). Reproduce any of them with node scripts/make-demo.mjs <run.json> out.mp4 --gif out.gif after a run made with --step-screenshots <dir>.

Why

Works in the user's browser Default backend is ego lite: the agent reuses your signed-in sessions, and hands the browser to you when it hits a login, CAPTCHA or consent wall. Resume from the same tab afterwards.
Calibrated, not chatty Jev returns probabilities, not prose. Every decision is a number you can threshold, journal and tune. A step costs about $0.0003 and takes about a second.
Never invents text Values that must be typed come from inputs; Jev only selects among them. secrets are typed but never sent to the model or written to disk.
Code stays in control Legal actions are filtered in code, tried/blocked edges are remembered, loops are detected, step/cost/time budgets are enforced, and the final page is verified before reporting success.
Fits every agent One self-contained skill directory. CLI for Claude Code, Codex, Cursor and any shell-capable agent; a dependency-free MCP server for Claude Desktop, Cursor and Codex.
Nothing to install Node 22+ and a TYPESAFE_API_KEY. Zero npm dependencies.

How it works

flowchart LR
  O[observe page<br/>url · text · elements] --> Q[one Jev request<br/>goal_done · blocker · action<br/>click_target · type_target · type_value …]
  Q --> C{code controller<br/>memory · budgets · loop detection}
  C -- success / needs_user / stuck --> R[result + journal]
  C -- act --> A[click · type · select · scroll · back · navigate]
  A --> O
Loading

Per step, one POST /v1/systemone carries the page state and the questions below. They are independent and evaluated in parallel, so speculative ones are asked up front and consumed only when relevant.

question type used by code as
goal_done noul success when ≥ 0.85 (≥ 0.7 on the final verification pass)
blocker choice: none · login_required · verification_challenge · consent_or_permission_dialog · error_page · missing_information needs_user when a non-none option ≥ 0.6
action choice over the legal actions only (click · type · select · scroll · go_back · navigate · wait · stop) preference order
click_target / type_target / select_target choice over element ids + none which element
type_value choice over input keys + none which provided value
submit_after_type noul press Enter after typing
progress score: moved away · no change · closer · accomplished go back on regression

Full details: references/questions.md.

Quick start

git clone https://github.com/ChenYCL/jev-browser-skill.git && cd jev-browser-skill
export TYPESAFE_API_KEY=...        # https://console.typesafe.ai
node skills/jev-browser/bin/jev-browser.mjs doctor      # key, API, ego lite, Chrome, Safari, install status
node skills/jev-browser/bin/jev-browser.mjs install     # link into every agent + register Claude Desktop MCP

Optional: npm i -g . puts jev-browser on your PATH (the examples below assume it).

Install into your agents

install is idempotent, previews with --dry-run, backs up every file it edits and reverts with --uninstall.

target what it does default
claude-code symlink ~/.claude/skills/jev-browser ✔
codex symlink ~/.codex/skills/jev-browser ✔
agents symlink ~/.agents/skills/jev-browser (skills.sh convention: Codex, opencode, Gemini CLI, …) ✔
cursor symlink ~/.cursor/skills/jev-browser ✔
claude-desktop mcpServers.jev-browser in claude_desktop_config.json ✔
cursor-mcp mcpServers.jev-browser in ~/.cursor/mcp.json
codex-mcp [mcp_servers.jev-browser] in ~/.codex/config.toml
jev-browser install --targets claude-code,claude-desktop --dry-run

Other routes:

  • Claude Code plugin: claude plugin marketplace add ChenYCL/jev-browser-skill then claude plugin install jev-browser@jev-browser-skill
  • skills.sh: npx skills add ChenYCL/jev-browser-skill --skill jev-browser
  • Manual: copy skills/jev-browser/ anywhere your agent reads skills from.
  • Windows: prefer install --copy (symlinks need Developer Mode or elevation).

MCP hosts start servers without your shell environment, so the installer stores the key in ~/.config/jev-browser/config.json (mode 0600) when TYPESAFE_API_KEY is exported. Restart the host afterwards.

Usage

# navigate
jev-browser run --goal "Open the pricing page" --url https://example.com

# type a provided value, then press Enter (Jev decides when Enter is the natural submit)
jev-browser run --goal 'Search the catalog for "blue widget" and open its product page' \
  --url https://shop.example.com --input query="blue widget"

# sign in: the email is an input, the password is a secret (typed, never sent to the model)
jev-browser run --goal "Sign in and reach the dashboard" --url https://app.example.com/login \
  --input email=ada@example.com --secret password=hunter2

# dedicated headless Chrome, JSON result only
jev-browser run --goal "Add the Red Gadget to the cart" --url https://shop.example.com \
  --backend chrome --headless --json --screenshot /tmp/cart.png

# look before acting: the page exactly as Jev sees it / the first-step questions without spending a request
jev-browser observe --url https://example.com --json
jev-browser run --dry-run --goal "…" --url https://example.com

# raw Jev judgments, browser-independent
jev-browser judge --state '{"ticket":"My card was charged twice"}' \
  --questions '{"refund":{"type":"noul","instructions":"Does `ticket` ask for a refund?"}}'
jev-browser pick --question "Which link opens the plans page?" --candidate pricing="link 'Pricing'" --candidate docs="link 'Docs'"

Tips that matter: write goals in English describing the end state; put everything that must be typed in --input (quoted strings in the goal are added automatically); use --secret for credentials.

CLI reference

command purpose
run accomplish a goal (--goal, --url, --input k=v…, --secret k=v…)
observe print the page as Jev sees it (--url, --screenshot)
judge raw System One call (--state / --state-file, --questions / --questions-file, --model)
pick one Choice over named candidates (--question, --candidate id=desc…, --context, --no-none)
doctor environment check (--offline skips the live API probe, --json)
config show · path · set <key.path> <value> · unset <key.path> · set-key [<key> | --from-env]
install --targets a,b · --dry-run · --copy (copy instead of symlink; use on Windows) · --uninstall · --home <dir>
mcp MCP server over stdio

run options: -g/--goal · -u/--url · -i/--input · -s/--secret · -b/--backend ego\|chrome\|safari · --max-steps · --budget-usd · --max-ms · --model · --space-id and --page-label (ego: resume a task space) · --keep / --no-keep (leave the final page open; default keep on success) · --headless · --cdp-url (chrome: attach) · --screenshot <file> · --step-screenshots <dir> (one PNG per step, the page as Jev saw it) · --dry-run · --journal-dir <dir> · --no-journal · --json · -q/--quiet. jev-browser --help prints the same list.

Results

status meaning exit
success goal verified on the final page 0
needs_user blocker detected; on ego the tab was handed to you. Continue with --space-id <id> 3
stuck repeated no-effect actions, a loop, or Jev judged nothing listed helps 2
max_steps · budget_exhausted · timeout a limit was hit (--max-steps 25, --budget-usd 0.25, --max-ms 300000) 2
error backend or API failure 2

Every run writes <journalDir>/<runId>/steps.jsonl (state hash, compact answers, chosen action, whether the page changed, cost), requests.jsonl and run.json, with secrets redacted.

Backends

backend when notes
ego (default) you want the agent in your browser with your logins, and the option to take over keeps the result tab open on success; handOff on blockers; resume with --space-id
chrome unattended runs, CI, no window own profile dir, --headless, or --cdp-url http://127.0.0.1:9222 to attach
safari WebKit enable Develop → Allow Remote Automation once

Judging tiers

Three judging backends serve the same /v1/systemone contract, and all three are first-class options — but hosted Jev is the default: with no config file and no environment, a run uses https://api.typesafe.ai (and a loopback baseUrl — a local model — is priced at $0).

jev-browser tier list      # the three tiers: what each is, what it needs, how to start it, its port, its score, its bar
jev-browser tier status    # what a run would use right now: baseUrl, tier, resolved goal_done bar, endpoint
jev-browser tier use kev   # one tier's export line and start command (nothing is written unless you add --persist)

The GGUF readout (node skills/jev-browser/bin/jev-local.mjs, port 8092) needs no Python and serves an Apple Silicon llama.cpp server; Kev (node skills/jev-browser/bin/jev-kev.mjs, port 8008) trades a Python venv, a 9.34 GB base and tens of GB of GPU memory for the accuracy tier. The tier table with every measured number — scores, latency, disk, memory, each backend's goal_done bar and the known holes — lives in SKILL.md.

A local tier is one command: setup fetches whatever is missing through the launcher itself, starts the server in the background, writes baseUrl and the placeholder key into your config, then proves the endpoint works by asking it one real question.

jev-browser setup local-readout   # llama.cpp + the registry GGUF; prints the log path and the pid
jev-browser setup status          # installed / running / configured, per local tier
jev-browser setup stop local-readout

jev-browser setup kev is the same for the accuracy tier and needs uv as well (it clones the Kev checkout and runs uv sync --extra serve). Both write under ~/.jev-browser/, never into this repo. The manual route still exists — start bin/jev-local.mjs yourself and export the two variables it prints — and jev-browser tier use <tier> --persist still stores that line's values for you.

WebUI

node skills/jev-browser/bin/jev-webui.mjs          # prints http://127.0.0.1:8765/
node skills/jev-browser/bin/jev-webui.mjs --port 9000 --open

One page over the same skill, not a second implementation: the panels call the same lib/tiers.mjs, lib/doctor.mjs and lib/config.mjs the CLI does — so they cannot disagree with tier status, doctor or config show — and edits go to the same ~/.config/jev-browser/config.json. Every command it starts is one of this repo's own bin/*.mjs scripts, spawned with an argv array (never a shell), so no field on the page can become a command.

The server binds 127.0.0.1 only — never 0.0.0.0, so nothing on it is reachable from your network — and no route returns your API key: the config panel shows only whether one is set, and secrets typed into the Run panel are never echoed back into the page or the log pane.

panel what it does
Tiers the three judging tiers — what each is, what it needs, its port, its 20-item score and its goal_done bar, hosted marked as the default — plus what a run would use right now, start/stop for the two local servers with their output streaming into the page, and tier use's text (saving it to the user config is its own labelled button)
Config the effective configuration with its sources, editable, with the diff a save produced and an unset for every key the user file sets
Doctor doctor() live or offline: every check with its status, detail and hint
Models the local registry: which GGUF is downloaded, which is the default, which file is serving, and a one-click start with --model-name or an existing on-disk --model <path>
Judge a state and a question set against the configured endpoint, with each answer's choice, confidence and probability table
Run a real run (jev-browser run --json) with live progress, the final status / steps / cost and the per-step journal

MCP server

jev-browser mcp speaks MCP over stdio with zero dependencies. Tools: jev_browse, jev_observe, jev_judge, jev_pick, jev_doctor, jev_config. Results come back as JSON text and structuredContent. See references/mcp.md.

Configuration

Precedence: defaults → ~/.config/jev-browser/config.json → ./jev-browser.config.json (or $JEV_BROWSER_CONFIG) → environment → flags.

jev-browser config show
jev-browser config set model jev-1.13.0            # pin the model version
jev-browser config set thresholds.goalDone 0.9     # stricter success
jev-browser config set-key --from-env              # persist the key for MCP hosts

Environment: TYPESAFE_API_KEY TYPESAFE_BASE_URL TYPESAFE_DEFAULT_MODEL JEV_BROWSER_BACKEND JEV_BROWSER_MAX_STEPS JEV_BROWSER_BUDGET_USD JEV_BROWSER_JOURNAL_DIR JEV_BROWSER_CHROME_CDP_URL JEV_BROWSER_HEADLESS JEV_BROWSER_EGO_SERVER_NAME CHROME_PATH (Chrome executable override) JEV_BROWSER_CONFIG (explicit project config file). Every key is documented in references/config.md.

Tests and stability

npm test                      # unit + e2e; live Jev when TYPESAFE_API_KEY is set, else a local mock
npm run test:e2e:mock         # fully offline (needs Chrome)
JEV_BROWSER_TEST_SAFARI=1 npm run test:e2e   # also drive Safari

CI (.github/workflows/test.yml) runs the unit suite and the mock e2e suite on Ubuntu with headless Chrome; the live suite is meant to be run locally, so no API key is ever needed in the cloud.

The e2e suite serves a fixture site (catalog, search, login, pricing/trial, cart, contact form with a dropdown, long docs page, restricted area) and runs nine goal scenarios per backend: navigation, search with typed input, login with a secret, choosing the right one of three identical "Start free trial" buttons, add to cart, form + dropdown, scroll to reveal, hand-off on a blocker, limits on an impossible goal, plus observe/dry-run, the CLI round-trip and ego hand-off → resume. Tests never write outside the repo.

Measured on macOS with live Jev (2026-09-22):

full suite 54 passed, 0 failed, 5 skipped (Safari opt-in), ~40 s, three consecutive runs identical
per-scenario determinism same status every run; step counts identical on ego, ±1 on two Chrome scenarios
cost $0.00006–0.00094 per scenario run (always under $0.001), about $0.02 per full suite
line coverage 87 % overall (controller 92 %, questions/config/util 100 %, observe 99.6 %)

Real sites, first try, via ego lite: TypeSafe docs → the Choice page in 2 steps / 9 s / $0.0005; GitHub → docs/ATOMIC_PLANNING.md in NanoJev in 4 steps / 23 s / $0.0016.

What leaves your machine

Each step sends one request to api.typesafe.ai containing the goal, the non-secret inputs, and a compact view of the current page: URL, title, headings, up to observation.maxTextChars (3000) of visible text, the descriptions of listed interactive elements (role, name, href, placeholder, current value), the previous page's excerpt and the last action. Nothing else is sent: no screenshots, no cookies, no HTML, no secrets (their values are replaced by a fixed marker, and password fields report (hidden)). Journals stay local under journalDir with secrets redacted. TypeSafe states that API requests are not used for training; see their legal page. Pin a model version with config set model jev-1.13.0 if reproducibility matters.

Design notes

  • Dynamic legal moves. Like NanoJev's Snake controller, code filters the action set (no scroll_down at the bottom, no type without inputs) and the model breaks ties.
  • Edge memory. (page state, action) pairs that produced no change are blocked; pairs already tried are deprioritised, so A → B → back → A does not repeat forever.
  • Select, don't generate. Typed values, dropdown options and URLs are chosen from candidates supplied by code; Jev 1.13 reads literally and does not generate text.
  • Goal-aware candidates. Before truncating to observation.maxCandidates, code ranks elements whose name or href mention goal/input keywords first, then viewport, then position, so a relevant link far down a long page is still offered. Keywords matching most elements are ignored.
  • "Probably done" is not done. A model stop at 70–85 % goal probability first spends one more step on an untried action; only ≥ 85 % (or no alternatives) ends the run.
  • Speculative fan-out. All questions of a step go in one request; unused answers are free in latency and cheap in tokens.
  • Verification before success. Success needs goal_done ≥ 0.85 on the current page, and a final check runs when the step budget ends.

Limitations

  • Same-origin iframes are not enumerated; clicks that open a new tab are not followed.
  • Canvas apps, drag and drop, file uploads and hover-only menus are not handled.
  • Pages with more than observation.maxCandidates (100, API max 255) interactive elements are truncated; scrolling compensates.
  • Safari was implemented against the W3C WebDriver spec but not exercised on a machine with remote automation enabled.
  • Jev's primary training language is English; translate goals for best accuracy.

Layout

skills/jev-browser/          the skill (self-contained; this is what installers link)
  SKILL.md                   agent-facing instructions
  bin/jev-browser.mjs        CLI + MCP entry point
  lib/controller.mjs         code controller
  lib/questions.mjs          the atomic question set
  lib/observe.mjs            page enumerator shared by all backends
  lib/typesafe.mjs           HTTP client (retry, cost, cache)
  lib/backends/              ego · chrome · safari
  lib/mcp.mjs                MCP stdio server
  references/                questions · config · backends · mcp
tests/                       unit + e2e (fixture site, mock TypeSafe, per-backend scenarios)
.claude-plugin/ .mcp.json    Claude Code plugin packaging

Contributing

Issues and PRs are welcome. npm test must stay green in mock mode (no key needed); add a fixture page and a scenario in tests/e2e/scenarios.mjs for new behaviours. Question wording and thresholds live in lib/questions.mjs and lib/config.mjs; keep them in one place.

Credits

TypeSafe for Jev and the System One API · NanoJev for the atomic-judgments-plus-code-planning design · ego lite for a browser built for humans and agents together.

License

MIT

About

Browser use & computer use for coding agents, powered by TypeSafe Jev: calibrated judgments from a System One model, control loop in code. ego lite / Chrome / Safari · CLI + MCP

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages