Kuri – Zig based agent-browser alternative

Hacker News Top Tools

Summary

Kuri is a Zig-based browser-automation toolkit targeting AI agents, offering a 464 KB binary with ~3 ms cold start and 16% lower token usage per workflow cycle versus agent-browser.

No content available
Original Article
View Cached Full Text

Cached at: 04/22/26, 05:43 AM

justrach/kuri

Source: https://github.com/justrach/kuri

Kuri

Kuri 🌰

Release License Zig node_modules status

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 / ModeBytesTokensvs kuriNote
kuri snap (compact)13,4794,328baseline
kuri snap --interactive7,0241,9270.4xBest for agent loops
kuri snap --json102,12431,2807.2xOld default
agent-browser snapshot17,1034,6411.1x
agent-browser snapshot -i8,7042,4250.6x
lightpanda semantic_tree67,83026,2446.1x⚠ no JS — raw DOM
lightpanda semantic_tree_text1,9095070.1x⚠ no JS — empty shell

Full workflow cost: go → snap → click → snap → eval

ToolTokens per cycle
kuri-agent4,110
agent-browser4,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)

⚡ 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

PathDescription
GET /healthServer status, tab count, version
GET /tabsList all registered tabs
GET /discoverAuto-discover Chrome tabs via CDP
GET /browdie🌰 (easter egg)

Browser Control

PathParamsDescription
GET /navigatetab_id, urlNavigate tab to URL
GET /tab/newurlCreate a new tab
GET /window/newurlCreate a new window/tab target
GET /snapshottab_id, filter, formatA11y tree snapshot with @eN refs
GET /texttab_idExtract page text
GET /screenshottab_id, format, qualityCapture screenshot (base64)
GET /actiontab_id, ref, kindClick/type/scroll by ref
GET /evaluatetab_id, expressionExecute JavaScript
GET /closetab_idClose tab + cleanup

Content Extraction

PathDescription
GET /markdownConvert page to Markdown
GET /linksExtract all links
GET /dom/queryCSS selector query
GET /dom/htmlGet element HTML
GET /pdfPrint page to PDF

HAR Recording & API Replay

PathDescription
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=allAPI map with curl/fetch/python code snippets

Navigation & State

PathDescription
GET /backBrowser back
GET /forwardBrowser forward
GET /reloadReload page
GET /cookiesGet cookies
GET /cookies/deleteDelete cookies
GET /cookies/clearClear all cookies
GET /storage/localGet localStorage
GET /storage/sessionGet sessionStorage
GET /storage/local/clearClear localStorage
GET /storage/session/clearClear sessionStorage
GET /session/saveSave browser session
GET /session/loadRestore browser session
GET /session/listList saved browser sessions
GET /auth/profile/saveSave cookies + storage as a named auth profile
GET /auth/profile/loadRestore a named auth profile into a tab
GET /auth/profile/listList saved auth profiles
GET /auth/profile/deleteDelete a saved auth profile
GET /debug/enableEnable in-page debug HUD and optional freeze mode
GET /debug/disableDisable in-page debug HUD
GET /headersSet custom request headers
GET /perf/lcpCapture 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

PathDescription
GET /diff/snapshotDelta diff between snapshots
GET /emulateDevice emulation
GET /geolocationSet geolocation
POST /uploadFile upload
GET /script/injectInject JavaScript
GET /intercept/startStart request interception
GET /intercept/stopStop interception
GET /screenshot/annotatedScreenshot with element annotations
GET /screenshot/diffVisual diff between screenshots
GET /screencast/startStart screencast
GET /screencast/stopStop screencast
GET /video/startStart video recording
GET /video/stopStop video recording
GET /consoleGet console messages
GET /stopStop page loading
GET /getDirect HTTP fetch (server-side)
GET /scrollintoviewScroll a referenced element into view
GET /dragDrag from one ref to another
GET /keyboard/typeType text with key events
GET /keyboard/inserttextInsert text directly
GET /keydownDispatch a keydown event
GET /keyupDispatch a keyup event
GET /waitWait for ready state or element conditions
GET /tab/closeClose a tab
GET /highlightHighlight an element by ref or selector
GET /errorsGet page/runtime errors
GET /set/offlineToggle offline network emulation
GET /set/mediaSet emulated media features
GET /set/credentialsSet HTTP basic auth credentials
GET /findFind text matches in the current page
GET /trace/startStart Chrome tracing
GET /trace/stopStop tracing and return trace data
GET /profiler/startStart JS profiler
GET /profiler/stopStop JS profiler
GET /inspectInspect an element or page state
GET /set/viewportSet viewport size
GET /set/useragentOverride user agent
GET /dom/attributesGet element attributes
GET /framesList frame tree
GET /networkInspect 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.

Proxy support

KURI_PROXY=socks5://user:pass@residential-proxy:1080 ./zig-out/bin/kuri
KURI_PROXY=http://proxy:8080 ./zig-out/bin/kuri

Tested sites

SiteProtectionResult
Singapore AirlinesAkamai WAF✅ Bypassed (was blocked before v0.4)
Shopee SGCustom anti-fraud✅ Page loads, redirects to login
Google FlightsNone✅ Full interaction
Booking.comPerimeterX⚠️ Needs proxy

🔧 kuri-fetch

Standalone HTTP fetcher — no Chrome, no Playwright, no npm. Ships as a ~2 MB binary with built-in QuickJS for JS execution.

zig build fetch    # build + run

# Default: convert to Markdown
kuri-fetch https://example.com

# Extract links
kuri-fetch -d links https://news.ycombinator.com

# Structured JSON output
kuri-fetch --json https://example.com

# Execute inline scripts via QuickJS
kuri-fetch --js https://example.com

# Write to file, quiet mode
kuri-fetch -o page.md -q https://example.com

# Pipe-friendly: content → stdout, status → stderr
kuri-fetch -d text https://example.com | wc -w

Features

  • 5 output modes — markdown, html, links, text, json
  • QuickJS JS engine — --js executes inline <script> tags
  • DOM stubs — document.querySelector, getElementById, window.location, document.title, console.log, setTimeout (SSR-style)
  • SSRF defense — blocks private IPs, metadata endpoints, non-HTTP schemes
  • Colored output — respects NO_COLOR, TERM=dumb, --no-color, TTY detection
  • File output — -o / --output with byte count + timing summary
  • Custom UA — --user-agent flag
  • Quiet mode — -q suppresses stderr status

🌐 kuri-browse

Interactive terminal browser — browse the web from your terminal. No Chrome needed.

zig build browse   # build + run

kuri-browse https://example.com
🌰 kuri-browse — terminal browser
→ loading https://example.com

# Example Domain
This domain is for use in documentation examples...
Learn more [1]

───── Links ─────
  [1] https://iana.org/domains/example

✓ 528 bytes, 1 links (133ms)
[nav] https://example.com> 1     ← type 1 to follow the link

Commands

CommandAction
<number>Follow link [N]
<url>Navigate (if contains .)
:go <url>Navigate to URL
:back, :bGo back in history
:forward, :fGo forward
:reload, :rRe-fetch current page
:links, :lShow link index
/<term>Search in page (highlights matches)
:search <t>Search in page
:n, :nextRe-highlight search
:historyShow navigation history
:help, :hShow all commands
:quit, :qExit

Features

  • Colored markdown rendering — headings, links, code blocks, bold, blockquotes
  • Numbered links — every link gets [N], type the number to follow it
  • Navigation history — back/forward like a real browser
  • In-page search — /term highlights all matches
  • Relative URL resolution — follows links naturally across pages
  • Smart filtering — skips javascript: and mailto: hrefs

🤖 kuri-agent

Scriptable CLI for Chrome automation — drives the browser command-by-command from your terminal or shell scripts. Shares session state across invocations via ~/.kuri/session.json.

zig build agent   # build kuri-agent

# 1. Find a Chrome tab
kuri-agent tabs
# → ws://127.0.0.1:9222/devtools/page/ABC123  https://example.com

# 2. Attach to it
kuri-agent use ws://127.0.0.1:9222/devtools/page/ABC123

# 3. Navigate + interact
kuri-agent go https://example.com
kuri-agent snap --interactive        # → [{"ref":"e0","role":"link","name":"More info"}]
kuri-agent click e0
kuri-agent shot                      # saves ~/.kuri/screenshots/<ts>.png

Commands

CommandDescription
tabs [--port N]List Chrome tabs
use <ws_url>Attach to a tab (saves session)
statusShow current session
go <url>Navigate to URL
snap [--interactive] [--text] [--depth N]A11y snapshot, saves @eN refs
click <ref>Click element by ref
type <ref> <text>Type into element
fill <ref> <text>Fill input value
select <ref> <value>Select dropdown option
eval <js>Evaluate JavaScript
text [selector]Get page text
shot [--out file.png]Screenshot
cookiesList cookies with security flags
headersCheck security response headers
auditFull security audit

🔒 Security Testing

kuri-agent supports browser-native security trajectories — log in once, then run reconnaissance and header/cookie audits without leaving the terminal.

Trajectories

Enumerate → Inspect — after authenticating, dump auth cookies and check security flags:

kuri-agent go https://target.example.com/login
kuri-agent snap --interactive
kuri-agent fill e2 myuser
kuri-agent fill e3 mypassword
kuri-agent click e4                  # submit login

kuri-agent cookies
# cookies (3):
#   session_id  domain=.example.com path=/  [Secure] [HttpOnly] [SameSite=Strict]
#   csrf_token  domain=.example.com path=/  [Secure] [!HttpOnly]
#   tracking    domain=.example.com path=/  [!Secure] [!HttpOnly]

Header audit — check what security headers the target sends:

kuri-agent go https://target.example.com
kuri-agent headers
# → {"url":"https://...","status":200,"headers":{
#     "content-security-policy":"default-src 'self'",
#     "strict-transport-security":"max-age=31536000",
#     "x-frame-options":"(missing)",
#     "x-content-type-options":"nosniff", ...}}

Full audit — HTTPS, missing headers, JS-visible cookies in one shot:

kuri-agent audit
# → {"protocol":"https:","url":"https://...","score":6,
#     "issues":["MISSING:x-frame-options","COOKIES_EXPOSED_TO_JS:2"],
#     "headers":{"content-security-policy":"default-src 'self'", ...}}

Cross-account trajectory — use eval to replay API calls with different tokens:

# After login, grab the auth token from localStorage
kuri-agent eval "localStorage.getItem('token')"

# Probe a resource ID with the current session
kuri-agent eval "fetch('/api/assessments/42').then(r=>r.status)"

# Check for IDOR: does a different user's resource return 200 or 403?
kuri-agent eval "fetch('/api/assessments/99').then(r=>r.status)"

Trajectory Report Format

kuri-agent outputs JSON suitable for pipeline integration. Each security command emits a single JSON line — pipe through jq for triage:

kuri-agent audit | jq '.issues[]'
kuri-agent cookies | head -20
kuri-agent headers | jq '.headers | to_entries[] | select(.value == "(missing)") | .key'

🏗 Architecture

┌──────────────────────────────────────────────────────────┐
│                     HTTP API Layer                        │
│         (std.http.Server, thread-per-connection)          │
├──────────────┬──────────────────┬────────────────────────┤
│   Browser    │  Crawler Engine  │   kuri-fetch / browse   │
│   Bridge     │                  │   (standalone CLIs)     │
├──────────────┼──────────────────┼────────────────────────┤
│ CDP Client   │ URL Validator    │ std.http.Client         │
│ Tab Registry │ HTML→Markdown    │ QuickJS JS Engine       │
│ A11y Snapshot│ Link Extractor   │ DOM Stubs (Layer 3)     │
│ Ref Cache    │ Text Extractor   │ SSRF Validator          │
│ HAR Recorder │                  │ Colored Renderer        │
│ Stealth JS   │                  │ History + REPL          │
├──────────────┴──────────────────┴────────────────────────┤
│  Chrome Lifecycle Manager                                 │
│  (launch, health-check, auto-restart, port detection)     │
└──────────────────────────────────────────────────────────┘

Memory Model

  • Arena-per-request — all per-request memory freed in one deinit() call
  • No GC — GeneralPurposeAllocator in debug mode catches every leak
  • Proper cleanup chains — Launcher → Bridge → CdpClients → HarRecorders → Snapshots → Tabs
  • errdefer guards — partial failures roll back cleanly

Chrome Lifecycle

ModeBehavior
Managed (no CDP_URL)Launches Chrome headless, finds free CDP port, supervises, auto-restarts on crash (max 3 retries), kills on shutdown
External (CDP_URL set)Connects to existing Chrome, health-checks via /json/version, does NOT kill on shutdown

📁 Structure

kuri/
├── build.zig                  # Build system (Zig 0.15.2)
├── build.zig.zon              # Package manifest + QuickJS dep
├── src/
│   ├── main.zig               # CDP server entry point
│   ├── fetch_main.zig         # kuri-fetch CLI entry point
│   ├── browse_main.zig        # kuri-browse CLI entry point
│   ├── js_engine.zig          # QuickJS wrapper + DOM stubs
│   ├── bench.zig              # Benchmark harness
│   ├── chrome/
│   │   └── launcher.zig       # Chrome lifecycle manager
│   ├── server/
│   │   ├── router.zig         # HTTP route dispatch (40+ endpoints)
│   │   ├── middleware.zig     # Auth (constant-time comparison)
│   │   └── response.zig      # JSON response helpers
│   ├── bridge/
│   │   ├── bridge.zig         # Central state (tabs, CDP, HAR, snapshots)
│   │   └── config.zig         # Env var configuration
│   ├── cdp/
│   │   ├── client.zig         # CDP WebSocket client
│   │   ├── websocket.zig      # WebSocket frame codec
│   │   ├── protocol.zig       # CDP method constants
│   │   ├── actions.zig        # High-level CDP actions
│   │   ├── stealth.zig        # Bot detection bypass
│   │   └── har.zig            # HAR 1.2 recorder
│   ├── snapshot/
│   │   ├── a11y.zig           # A11y tree with interactive filter
│   │   ├── diff.zig           # Snapshot delta diffing
│   │   └── ref_cache.zig      # @eN ref → node ID cache
│   ├── crawler/
│   │   ├── validator.zig      # SSRF defense, URL validation
│   │   ├── markdown.zig       # HTML → Markdown (SIMD tag counting)
│   │   ├── fetcher.zig        # Page fetching
│   │   ├── extractor.zig      # Readability extraction
│   │   └── pipeline.zig       # Parallel crawl pipeline
│   ├── storage/
│   │   ├── local.zig          # Local file writer
│   │   └── r2.zig             # R2/S3 uploader
│   ├── util/
│   │   └── json.zig           # JSON helpers
│   └── test/
│       ├── harness.zig        # Test HTTP client
│       ├── integration.zig    # Integration tests
│       └── merjs_e2e.zig      # E2E tests
└── js/
    ├── stealth.js             # Bot detection bypass
    └── readability.js         # Content extraction

⚙️ Configuration

Env VarDefaultDescription
HOST127.0.0.1Server bind address
PORT8080Server port
CDP_URL(none)Connect to existing Chrome (ws://... or http://127.0.0.1:9222)
KURI_SECRET(none)Auth secret for API requests
STATE_DIR.kuriSession state directory
REQUEST_TIMEOUT_MS30000HTTP request timeout
NAVIGATE_TIMEOUT_MS30000Navigation timeout
STALE_TAB_INTERVAL_S30Stale tab cleanup interval
NO_COLOR(none)Disable colored CLI output

💰 Token Cost

For a 50-page monitoring task (from Pinchtab benchmarks):

MethodTokensCost ($)Best For
/text~40,000$0.20Read-heavy (13× cheaper than screenshots)
/snapshot?filter=interactive~180,000$0.90Element interaction
/snapshot (full)~525,000$2.63Full page understanding
/screenshot~100,000$1.00Visual verification

🤝 Contributing

Open an issue before submitting a large PR so we can align on the approach.

git clone https://github.com/justrach/kuri.git
cd kuri
zig build test         # 230+ tests must pass
zig build test-fetch   # kuri-fetch tests (66 tests)
zig build test-browse  # kuri-browse tests

See CONTRIBUTORS.md for guidelines.


Credits

ProjectWhat we borrowed
agent-browser@eN ref system, snapshot diffing, HAR recording patterns
PinchtabBrowser control architecture for AI agents
PathikHigh-performance crawling patterns
QuickJS-ng via mitchellh/zig-quickjs-ngJS engine for kuri-fetch
LightpandaZig-native headless browser pioneer, CDP compatibility patterns
Zig 0.15.2The whole stack

License

Apache-2.0

Similar Articles

Cloudflare launches Kitesurf, a browser built for AI agents

TechCrunch AI

Cloudflare launches Kitesurf, a cloud-hosted browser built specifically for AI agents, enabling developers to automate web navigation and tasks without building browser software—available free in beta on its Browser Run platform.

Kit by Speakeasy

Product Hunt

Speakeasy launched Kit, a coding agent runtime that combines a terminal client, Agent Client Protocol server, A2A endpoint, and subagent orchestrator into a single static binary aimed at faster, cheaper AI coding workflows.