Skip to main content
Glama

Navvi uses a model to choose among controls and fields found by code, then saves a scraper with selectors, fingerprints and a navigation trace. Healthy repeat runs reuse the prompt interpretation and scraper without model calls. When a field or step changes, Navvi can ask the model for a targeted repair; some changes still require a person or recompilation.

Jev supplies fast typed decisions. Navvi adds persistence, structured extraction, replay and repair around those decisions. Jev also uses a text-capable fallback for prompt interpretation and values to type; those calls are included in usage.

See it work

1. Compile: Haiku versus Jev

Same task, separate empty caches, Haiku on the left and Jev on the right. The real-site comparison is pending owner review. Remote OK served unrelated roles on its Python-filter page, so that attempt is not a valid speed comparison. A Hacker News search comparison is being reviewed as an alternative.

2. Reuse: the saved scraper

Remote OK first run and saved replay

Watch the video: ten records from a real Remote OK search, then the same prompt in a fresh browser with zero model calls. The two sequential runs are aligned, with original clocks (42.6 s / 3.7 s). This historical capture demonstrates reuse, but its result relevance did not pass the launch review. It is not an accepted Python-job search demo. Browser execution still has a cost.

The controlled fixture below demonstrates compile, deliberately changed markup, and replay. Its answers are recorded fixtures: it shows behavior, not live model latency or a production-site guarantee. Video, npm run demo.

Controlled compile, healing and replay fixture

The older search-form comparison is a fixture recording, not Remote OK. Current capture instructions and the two-run real-site recorder are in docs/recording.md. Failed recordings retain evidence and do not export a success clip.

The historical measurement table and Jev question-bank hillclimb report a tuned scenario set. Their cell counts are harness checks, not independent semantic accuracy. They do not establish universal speedups, unseen-site accuracy or current prompt-to-output costs. A new live demo must retain its own rows, timings and revision.

Remote OK capture provenance · Separate source verification.

Related MCP server: puppeteer-real-browser-mcp-server

Install and run

Node 22+. The published compiler is 3.0.0. The separate --decider/--writer controls and highlighted-text extraction fix on main are newer than that release; use the source installation below to reproduce the proposed Hacker News demo.

Install the published compiler with Chromium:

NAVVI_BROWSER=chromium npm install -g navvi@3.0.0
npx playwright@1.60.0 install chromium
navvi "Search Remote OK for Python jobs and extract up to 10 results with job title, company, location and job link. Exclude ads." https://remoteok.com/ --browser chromium --max-pages 1 --max-items 10 --out jobs.json
# Repeat the identical command to reuse the saved prompt interpretation and scraper.

For the default Camoufox browser, use npm install -g navvi@3.0.0 without NAVVI_BROWSER; postinstall downloads Camoufox. Version 3 replaces the earlier 2.x product with the scraper compiler.

To work from source:

git clone https://github.com/fellowship-dev/navvi.git
cd navvi
NAVVI_SKIP_BROWSER_DOWNLOAD=1 npm ci
npx playwright install chromium
npm run build
node dist/bin/cli.js --help

Node 22+. Select a chooser below; an installed, signed-in Claude Code or Codex CLI can answer on your subscription. Jev needs a TypeSafe or AI Gateway key plus a text-capable fallback. A Gateway account must have access to the configured text model as well as Jev; access to Jev alone does not cover prompt interpretation or typed text. Browser time and subscription/API charges still apply. Agents: read SKILL.md; llms.txt indexes the docs.

What you get

  • JSON or CSV records with a _source URL.

  • A reusable scraper at storage/key_value_stores/scraper-cache/<cacheKey>.json. The rest of storage/ can contain live browser sessions; keep it private.

  • Cached prompt interpretation and healthy scraper replay with zero model calls. --force-recompile deliberately bypasses reuse.

  • A targeted repair path plus explicit failure statuses when automation cannot finish.

  • A stderr summary with pages, items, chooser usage and healing events.

  • Typed values when you ask for them: --fields name,price:money,stock:boolean (or type on a field in the JSON input) coerces the extracted text after the fingerprint check: money and number read $ 6.990 as 6990, 12.990,50 as 12990.5 and 12 990 as 12990, integer takes a whole number, boolean maps stock phrases in Spanish and English (En stock, Agotado, Out of stock, Disponible: No), url resolves to an absolute http(s) URL; a value that does not coerce is null. A bare 1.250 is 1250 under money and integer, where a three-decimal reading is impossible, and null under number, where it is an ordinary weight and nothing in the text settles it. The type is recorded in the compiled scraper, so a replay coerces the same way with no model call. Untyped fields stay strings.

  • A URL list as the start: --from-url <url> (or a { "requestsFromUrl": "<url>" } entry in startUrls) fetches a URL that answers the pages to scrape as newline text or JSON (an array of URLs or of { url } objects, or an object whose urls, data or items is one), so a backend endpoint can feed the daily target list directly.

Choosers

Navvi never lets a model write a selector or a script. It enumerates the candidates itself and asks a chooser to pick. A run picks two of them, independently: the decider (--decider) answers the structured questions — which of these candidates, yes or no, score this — and the writer (--writer) answers the occasional free-text one, the search query to type into a box. Five backends, one contract, and only Jev is limited to one role:

name

Who answers

Needs

Decider

Writer

claude

Claude Code (claude -p) on your subscription

claude installed and signed in

yes

yes

codex

Codex (codex exec) on your subscription

codex installed and signed in

yes

yes

jev

Jev by TypeSafe, through Vercel AI Gateway or direct

AI_GATEWAY_API_KEY or TYPESAFE_API_KEY

yes

no

model

Any AI SDK model

ANTHROPIC_API_KEY, or the Gateway

yes

yes

agent

The coding agent running the command, over stdio

Nothing

yes

yes

Without --decider the order is: a key (jev, then model); else Claude Code, then Codex, when installed and signed in (an installed CLI that is not signed in never wins; the run says so and names claude or codex login); else agent. The choice and its reason print on stderr unless --quiet. NAVVI_CLAUDE_MODEL (default haiku) and NAVVI_CODEX_MODEL pick the CLI model. CLI usage reports $0.0000 with billing subscription; Claude Code's own cost figure is kept as reportedCostUsd in the usage.

Without --writer the decider writes its own text — every backend but Jev can. Jev answers choices, booleans and scores but cannot write, so its text questions go to a second backend, and there the order is the other way round, subscription before metering: claude, then codex when on PATH, and only then a metered API key (ANTHROPIC_API_KEY first, since a dedicated key is a deliberate choice, then AI_GATEWAY_API_KEY). So AI_GATEWAY_API_KEY routes Jev's structured questions over Vercel, which is free for Jev, while the text questions still prefer a signed-in CLI on your subscription; the Gateway text model is used only when no CLI is installed. Sign-in cannot be checked without running the CLI, so an installed but signed-out CLI is tried once, says so, and the run moves to the next backend instead of failing. An explicit --writer model (or --chooser model) always means the API model, whatever is installed.

--decider-transport gateway|typesafe says which API Jev is reached over. Unset, it is inferred from the keys, the Gateway first when both are present; --decider-transport typesafe forces the official TypeSafe API (api.typesafe.ai) even with AI_GATEWAY_API_KEY in the environment, and refuses the run rather than routing the other way when the matching key is missing.

--chooser is the one flag those three replace, and it keeps working exactly as before: it names the decider and leaves the writer derived. --decider and --writer win over it, so --chooser jev --decider claude runs Claude Code. The stderr summary reports the run under chooser, plus a writer line naming the second source and its share of the tokens and cost when one answered the text, so a run is attributable per role.

A locally-run open-source model is a designed seam, not a shipped backend. Either role would take an OpenAI-compatible base URL plus a model id — the two values llama.cpp, Ollama, vLLM and LM Studio all expose — as a local writer and a third --decider-transport. Nothing accepts local today: it is documented rather than enumerated, so no flag can select a backend that would throw mid-run. The interface it must satisfy is Chooser in src/chooser/chooser.ts (name, ask(batch), usage()); the comment above textFallbackFor in src/chooser/index.ts spells out the rest.

With the agent chooser, the CLI prints each question batch on stdout between ---NAVVI-QUESTIONS--- and ---END--- and reads one JSON answer line from stdin. When stdin cannot stay open, --agent-mode file parks the batch in storage/questions/<token>.json, exits 3, and --answers answers.json --resume <token> continues. Full protocol in SKILL.md.

How it differs

The reusable scraper is the product. A model-backed browser interaction produces an artifact that code can execute again, with fingerprints and alternatives to help detect and repair changes. This is useful for recurring extraction rather than asking an agent to rediscover the same workflow every time.

The side-by-side recorder compares Navvi + Jev with Navvi + Haiku under the same task. It does not measure Navvi against Jev Ultra Fast as a separate product. A healthy replay recording also does not prove live healing; demonstrate drift separately before making that claim.

Local and Apify

Locally Navvi runs through the CLI: Camoufox by default, Chromium with --browser chromium, profiles and compiled scrapers under --storage (default ./storage).

On Apify the same code is the actor under .actor/: the manifest, the input schema (the CLI flags as fields, with section captions and descriptions written for a model reading them through Apify's MCP server), the dataset schema and two Dockerfiles. Dockerfile is the default build on apify/actor-node-playwright-chrome; Dockerfile.camoufox builds on apify/actor-node-playwright-camoufox and is the switch for a site that challenges Chromium. Both image tags carry the Playwright version and must equal the playwright pin in package.json; node scripts/check-image-pins.mjs fails CI when they disagree. CI pushes every green main to the beta build tag through apify/push-actor-action when the APIFY_TOKEN repository secret is present; latest is a manual promote. node scripts/push-beta.mjs pushes the Chromium beta from a signed-in CLI and --camoufox pushes the Camoufox build under the beta-camoufox tag of the same version.

The actor input differs from the CLI in three places: startUrls takes { url } and { requestsFromUrl } entries (Apify's request-list editor); profile is store only, chooser and decider are jev or model and writer is model (the image has no CLI to run on a subscription); a caller key comes as a secret input (typesafeApiKey, gatewayApiKey, anthropicApiKey) and is used for that run only. scriptId pins a compiled scraper by key, with scraperStore naming the key-value store when the key is bare (default scraper-cache in your account). Locally,

npx apify run --input-file input.json   # runs dist/src/main.js with local storage

runs the actor entry with the same input.

Proxies and residential IPs

Yes: everything Apify Proxy offers is selectable in the Console's proxy editor and reaches Crawlee. The proxy input is Apify's own proxy object, so the groups and the country you pick survive validation and are handed to Actor.createProxyConfiguration, which Crawlee then rotates per session for every browser it launches.

{
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "CL"
  }
}

In the Console: open Proxy, choose Apify Proxy, then select the RESIDENTIAL group and, optionally, a country (two-letter code, e.g. US, CL). Leaving the groups empty lets Apify pick datacenter proxies automatically.

  • Residential costs money, by the gigabyte, billed to your Apify account on top of the actor's events — datacenter proxies are the cheap default and are included in most plans. Turn residential on for the sites that need it, not for every run.

  • It is the fix for blocked_bot_detection. A run that ends there is usually being blocked on the IP, not on the page: re-run it with apifyProxyGroups: ["RESIDENTIAL"], and on a stubborn site with the Camoufox build as well.

  • Residential is not on every plan. If your account has no access to the group you selected, the platform run stops before the browser opens with a configuration error that names the group and the country it asked for, so the fix is legible instead of a stream of 407s. Locally, where the Apify SDK only warns, the run says on stderr that no proxy was created and continues without one.

  • Your own proxies go in proxyUrls instead, and are rotated the same way. Apify Proxy and your own proxies are one choice, not two layers: asking for both is refused at validation (Apify's own ProxyConfiguration also refuses to combine them) rather than one silently shadowing the other.

Pay-per-event

On Apify the actor charges four events, priced in the Apify Console, never in code. Every run ends with a SUMMARY record in the run's key-value store carrying the status, counts, chooser usage, healing events, the scriptId to pin next time, the charged event counts and the zero-data-retention state.

Event

Charged

actor-start

Once, first thing; covers navigation model spend when the operator key is used

scraper-compiled

Once per template, the first time a page passes the fingerprint check with a scraper compiled this run; a cache hit charges nothing

page-scraped

Per scraped page (listing, paginated page, detail page); the limit is checked before every page

result-item

Per dataset item

When the run's charge limit is reached the items pushed so far stay in the dataset and the run ends charge_limit. Off the platform nothing is charged and every count in the summary is zero; a local run with ACTOR_TEST_PAY_PER_EVENT=1 ACTOR_USE_CHARGING_LOG_DATASET=1 charges at $1 per event against ACTOR_MAX_TOTAL_CHARGE_USD and writes the charging log to the charging_log dataset instead.

Who pays what under pay-per-event, per Apify's pricing docs: the caller pays the events; the actor's platform usage (compute, residential proxy, storage) is the operator's cost, which is why the event prices carry a compute margin. The first platform run under this pricing confirms the split and this paragraph is updated with the observed numbers.

Exit codes

Exit

Status

Meaning

0

succeeded

Records written

1

no_items_found, drift, blocked_bot_detection, blocked_login_required, blocked_no_progress

The run stopped short; stderr says why

2

configuration or validation error

Bad flags, missing key (the message names AI_GATEWAY_API_KEY / TYPESAFE_API_KEY / ANTHROPIC_API_KEY and reminds you --chooser agent needs none), a CLI chooser that is not signed in (claude, codex login), a private host without --allow-private-host

3

needs_human

Questions parked in storage/questions/<token>.json; answer and --resume

4

budget_exhausted, model_unavailable, charge_limit

Retry later, raise the cap, or switch chooser

Limits

  • Models can mistake a filled form for a completed search. Validate the target results and output meaning, not just status or non-empty fields.

  • Repairs supported field/step changes, but cannot guarantee repair of a redesign. An empty listing may report no_items_found; it is not always distinguishable from a changed item selector. Missing compiled rows get a bounded five-second wait.

  • Extraction reflects the source. A selected search filter does not guarantee every returned job matches its meaning; verify relevance separately.

  • No captcha solving. On a headed run (--headed) a challenge is handed to the person at the keyboard and their step is recorded; unattended runs report blocked_bot_detection — see Proxies and residential IPs for the usual fix.

  • Logins use --profile local. Secrets come from NAVVI_SECRET_<NAME> (--secret name), a --secrets-file, or a hidden TTY prompt; never the command line, never a question, a log or the scraper JSON.

  • The run stays on the start URLs' domains unless --allow-domain widens it; private hosts need --allow-private-host; destructive-looking actions need --allow-mutation.

  • Pagination and item caps default to 10 pages and 1000 items (--max-pages, --max-items).

Measurements

With and without Jev on the same pages: questions, wait time, cost and heal rate live in docs/measurements.md.

Development and tests

npm run typecheck
npm test
npm run build

CI runs the same offline suite with Chromium and recorded model answers; it needs no model key. Live model tests are separate: npm run test:live. Testing and CI explained · Recording guide. Report a failing URL and a redacted summary in an issue. Never include browser profiles, cookies or API keys.

License

MIT.

Available Tools

31 tools

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 31 tool updatesv3.21.0
    • First observednavvi_account
    • First observednavvi_atomic
    • First observednavvi_browse
    • First observednavvi_click
    • First observednavvi_context
    • First observednavvi_creds
    • First observednavvi_drag
    • First observednavvi_fill
    • First observednavvi_find
    • First observednavvi_flow
    • First observednavvi_hold
    • First observednavvi_list
    • First observednavvi_login
    • First observednavvi_milestone
    • First observednavvi_mousedown
    • First observednavvi_mousemove
    • First observednavvi_mouseup
    • First observednavvi_open
    • First observednavvi_persona
    • First observednavvi_press
    • First observednavvi_record_gif
    • First observednavvi_record_start
    • First observednavvi_record_stop
    • First observednavvi_screenshot
    • First observednavvi_scroll
    • First observednavvi_start
    • First observednavvi_status
    • First observednavvi_stop
    • First observednavvi_tab
    • First observednavvi_url
    • First observednavvi_vnc

TDQS

A3.7/5.0

Scored across 31 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: high-level browsing (navvi_browse), low-level actions (navvi_click, navvi_fill), credential management (navvi_creds), persona lifecycle (navvi_persona, navvi_account, navvi_milestone), etc. Even related tools like mouse actions are differentiated by action type. No overlapping ambiguity.

Naming Consistency5/5

All tools follow the consistent snake_case pattern 'navvi_<verb_or_noun>'. The naming is predictable: verbs for actions (browse, click, find) and nouns for entities (context, flow, persona). No mixed conventions or irregular patterns.

Tool Count4/5

31 tools is higher than typical (3-15) but well-justified given the comprehensive scope: browser automation (atomic & high-level), credential management, persona profiles, knowledge context, recording, and infrastructure. Each tool has a defined role without redundancy.

Completeness4/5

The tool set covers the full lifecycle of persona-driven web automation: browser control (open, find, click, type, scroll), credential handling, knowledge storage, flow recipes, and recording. Minor gaps exist (e.g., file uploads, extension management) but do not hinder primary workflows.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables AI agents to authenticate with websites using a real Chromium browser with anti-detection measures and human-in-the-loop support for captchas and 2FA. Features stealth browsing, human-like interactions, and persistent session storage to automate and resume login workflows.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides AI assistants with powerful, detection-resistant browser automation capabilities, allowing them to control a real web browser to navigate, extract content, and perform tasks while avoiding bot detection.
    6 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides a persistent browser profile for AI agents, enabling them to log in once and maintain sessions across restarts. Supports 20 tools for browsing, navigation, text extraction, and screenshot.
    1
    MIT