GitHub - justrach/kuri: Browser automation and web crawling for AI agents. Zig-native, token-efficient CDP snapshots, HAR recording, and a standalone fetcher.
Pangram verdict · v3.3
We believe that this document is a mix of AI-generated, and human-written content
AI likelihood · overall
MixedArticle text · 1,475 words · 7 segments analyzed
Browser automation & web crawling for AI agents. Written in Zig. Zero Node.js. CDP automation · A11y snapshots · HAR recording · Standalone fetcher · Interactive terminal browser · Agentic CLI · Security testing Quick Start · Benchmarks · kuri-agent · Security Testing · API · Changelog
Why teams switch to Kuri: 464 KB binary, ~3 ms cold start. On Google Flights, a full agent loop (go→snap→click→snap→eval) costs 4,110 tokens vs 4,880 for agent-browser — 16% less per cycle, compounding across multi-step tasks.
Why Kuri Wins for Agents Most browser tooling was built for QA engineers. Kuri is built for agent loops: read the page, keep token cost low, act on stable refs, and move on.
The product story is not "most commands." It is "useful state from real pages at the lowest model cost." A tiny output only counts if the page actually rendered. Empty-shell output is a failure mode, not a win. The best proof is same-page, same-session, same-tokenizer comparisons.
Snapshot tokens: Google Flights SIN → TPE Same Chrome session, measured with tiktoken cl100k_base. Run ./bench/token_benchmark.sh to reproduce.
Tool / Mode Bytes Tokens vs kuri Note
kuri snap (compact) 13,479 4,328 baseline
kuri snap --interactive 7,024 1,927 0.4x Best for agent loops
kuri snap --json 102,124 31,280 7.2x Old default
agent-browser snapshot 17,103 4,641 1.1x
agent-browser snapshot -i 8,704 2,425 0.6x
lightpanda semantic_tree
67,830 26,244 6.1x ⚠ no JS — raw DOM
lightpanda semantic_tree_text 1,909 507 0.1x ⚠ no JS — empty shell
Full workflow cost: go → snap → click → snap → eval
Tool Tokens per cycle
kuri-agent 4,110
agent-browser 4,880
kuri saves 16% tokens per workflow cycle — compounding across multi-step tasks. Action responses are flat JSON ({"ok":true}) instead of nested CDP, which adds up: click = 9 tokens, back = 5 tokens, scroll = 5 tokens.
Why lightpanda scores low: Lightpanda can't execute JS-heavy SPAs. Google Flights renders via client-side fetch() — lightpanda returns a 507-token empty nav shell with zero flight data. The low token count is a failed render, not efficiency.
Small binary, fast start Measured on Apple M3 Pro, macOS 15.3. kuri built with -Doptimize=ReleaseFast. agent-browser v0.20.0. agent-browser kuri delta (v0.20) (v0.2) ───────────────────────────────────────────────────────────────────── CLI binary 6.0 MB 464 KB 13× smaller Cold start (--version) 3.4 ms 3.0 ms ~same Install (npm) 33 MB 3.3 MB (3 bins) 10× smaller Commands 140+ 40+ endpoints different focus Standalone fetcher ❌ ✅ kuri-fetch no Chrome needed Terminal browser ❌ ✅ kuri-browse interactive REPL JS engine (no Chrome) ❌ ✅ QuickJS SSR-style DOM HTTP API server ❌ (CLI only) ✅ kuri thread-per-conn
agent-browser exposes a broader browser-control surface. Kuri is intentionally narrower: a lightweight HTTP API and CLI stack optimized for agent integration, token economy, and deployment simplicity.
The Problem Every browser automation tool drags in Playwright (~300 MB), a Node.js runtime, and a cascade of npm dependencies. Your AI agent just wants to read a page, click a button, and move on. Kuri is a single Zig binary. Four modes, zero runtime: kuri → CDP server (Chrome automation, a11y snapshots, HAR) kuri-fetch → standalone fetcher (no Chrome, QuickJS for JS, ~2 MB) kuri-browse → interactive terminal browser (navigate, follow links, search) kuri-agent → agentic CLI (scriptable Chrome automation + security testing)
📦 Installation One-line install (macOS / Linux) curl -fsSL https://raw.githubusercontent.com/justrach/kuri/main/install.sh | sh Detects your platform, downloads the right binary, installs to ~/.local/bin. macOS binaries are notarized — no Gatekeeper prompt. bun / npm bun install -g kuri-agent # or: npm install -g kuri-agent Downloads the correct native binary for your platform at install time. Manual Download the tarball for your platform from GitHub Releases and unpack it to your $PATH. Build from source Requires Zig ≥ 0.15.0. git clone https://github.com/justrach/kuri.git cd kuri zig build -Doptimize=ReleaseFast # Binaries in zig-out/bin/: kuri kuri-agent kuri-fetch kuri-browse
⚡ Quick Start Requirements: Zig ≥ 0.15.1 · Chrome/Chromium (for CDP mode) git clone https://github.com/justrach/kuri.git cd kuri
zig build # build everything zig build test # run 230+ tests
# CDP mode — launches Chrome automatically ./zig-out/bin/kuri
# Standalone mode — no Chrome needed ./zig-out/bin/kuri-fetch https://example.com
# Interactive browser — browse from your terminal
./zig-out/bin/kuri-browse https://example.com First run, shortest path # start the server; if CDP_URL is unset, kuri launches managed Chrome for you ./zig-out/bin/kuri
# discover tabs from that managed browser curl -s http://127.0.0.1:8080/discover
# inspect the discovered tab list curl -s http://127.0.0.1:8080/tabs If you already have Chrome running with remote debugging, set CDP_URL to either the WebSocket or HTTP endpoint: CDP_URL=ws://127.0.0.1:9222/devtools/browser/... ./zig-out/bin/kuri # or CDP_URL=http://127.0.0.1:9222 ./zig-out/bin/kuri Browse vercel.com in 4 commands # 1. Discover Chrome tabs curl -s http://localhost:8080/discover # → {"discovered":1,"total_tabs":1}
# 2. Get tab ID curl -s http://localhost:8080/tabs # → [{"id":"ABC123","url":"chrome://newtab/","title":"New Tab"}]
# 3. Navigate curl -s "http://localhost:8080/navigate?tab_id=ABC123&url=https://vercel.com"
# 4. Get accessibility snapshot (token-optimized for LLMs) curl -s "http://localhost:8080/snapshot?tab_id=ABC123&filter=interactive" # → [{"ref":"e0","role":"link","name":"VercelLogotype"}, # {"ref":"e1","role":"button","name":"Ask AI"}, ...]
🌐 HTTP API All endpoints return JSON. Optional auth via KURI_SECRET env var. Core
Path Description
GET /health Server status, tab count, version
GET /tabs List all registered tabs
GET /discover Auto-discover Chrome tabs via CDP
GET /browdie 🌰 (easter egg)
Browser Control
Path Params Description
GET /navigate
tab_id, url Navigate tab to URL
GET /tab/new url Create a new tab
GET /window/new url Create a new window/tab target
GET /snapshot tab_id, filter, format A11y tree snapshot with @eN refs
GET /text tab_id Extract page text
GET /screenshot tab_id, format, quality Capture screenshot (base64)
GET /action tab_id, ref, kind Click/type/scroll by ref
GET /evaluate tab_id, expression Execute JavaScript
GET /close tab_id Close tab + cleanup
Content Extraction
Path Description
GET /markdown Convert page to Markdown
GET /links Extract all links
GET /dom/query CSS selector query
GET /dom/html Get element HTML
GET /pdf Print page to PDF
HAR Recording & API Replay
Path Description
GET /har/start?tab_id= Start recording network traffic
GET /har/stop?tab_id= Stop + return HAR 1.2 JSON
GET /har/status?tab_id= Recording state + entry count
GET /har/replay?tab_id=&filter=api&format=all API map with curl/fetch/python code snippets
Navigation & State
Path Description
GET /back Browser back
GET /forward Browser forward
GET /reload Reload page
GET /cookies Get cookies
GET /cookies/delete Delete cookies
GET /cookies/clear Clear all cookies
GET /storage/local Get localStorage
GET /storage/session Get sessionStorage
GET /storage/local/clear Clear localStorage
GET /storage/session/clear Clear sessionStorage
GET /session/save Save browser session
GET /session/load Restore browser session
GET
/session/list List saved browser sessions
GET /auth/profile/save Save cookies + storage as a named auth profile
GET /auth/profile/load Restore a named auth profile into a tab
GET /auth/profile/list List saved auth profiles
GET /auth/profile/delete Delete a saved auth profile
GET /debug/enable Enable in-page debug HUD and optional freeze mode
GET /debug/disable Disable in-page debug HUD
GET /headers Set custom request headers
GET /perf/lcp Capture Largest Contentful Paint timing, optionally after navigation
On macOS, auth profile secrets are stored in the user Keychain. On other platforms, Kuri falls back to .kuri/auth-profiles/. url and expression query params are percent-decoded by the server, so encoded values like https%3A%2F%2Fexample.com are accepted. Advanced
Path Description
GET /diff/snapshot Delta diff between snapshots
GET /emulate Device emulation
GET /geolocation Set geolocation
POST /upload File upload
GET /script/inject Inject JavaScript
GET /intercept/start Start request interception
GET /intercept/stop Stop interception
GET /screenshot/annotated Screenshot with element annotations
GET /screenshot/diff Visual diff between screenshots
GET /screencast/start Start screencast
GET /screencast/stop Stop screencast
GET /video/start Start video recording
GET /video/stop Stop video recording
GET /console Get console messages
GET /stop Stop page loading
GET /get Direct HTTP fetch (server-side)
GET /scrollintoview Scroll a referenced element into view
GET /drag Drag from one ref to another
GET /keyboard/type Type text with key events
GET /keyboard/inserttext Insert text directly
GET /keydown Dispatch a keydown event
GET /keyup
Dispatch a keyup event
GET /wait Wait for ready state or element conditions
GET /tab/close Close a tab
GET /highlight Highlight an element by ref or selector
GET /errors Get page/runtime errors
GET /set/offline Toggle offline network emulation
GET /set/media Set emulated media features
GET /set/credentials Set HTTP basic auth credentials
GET /find Find text matches in the current page
GET /trace/start Start Chrome tracing
GET /trace/stop Stop tracing and return trace data
GET /profiler/start Start JS profiler
GET /profiler/stop Stop JS profiler
GET /inspect Inspect an element or page state
GET /set/viewport Set viewport size
GET /set/useragent Override user agent
GET /dom/attributes Get element attributes
GET /frames List frame tree
GET /network Inspect network state/requests
🛡️ Stealth & Bot Evasion Kuri applies anti-detection patches automatically on startup — no manual config needed. What's applied
Page.addScriptToEvaluateOnNewDocument — stealth patches run before any page JS navigator.webdriver = false — hides automation flag at Chromium level (--disable-blink-features=AutomationControlled) WebGL/Canvas/AudioContext spoofing — defeats fingerprint-based detection UA rotation — 5 realistic Chrome/Safari/Firefox user agents chrome.csi/chrome.loadTimes — stubs for Akamai-specific checks
Bot block detection Navigate auto-detects blocks and returns structured fallback: curl -s "http://localhost:8080/navigate?tab_id=ABC&url=https://protected-site.com" # If blocked: # {"blocked":true,"blocker":"akamai","ref_code":"0.7d...", # "fallback":{"suggestions":["Open URL directly in browser","Use KURI_PROXY"]}} # If ok: normal CDP response Detects: Akamai, Cloudflare, PerimeterX, DataDome, generic captcha.