Skip to content

justrach/kuri

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

165 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Kuri

Kuri 🌰

Stable release License Zig node_modules status

Install

curl -fsSL https://raw.githubusercontent.com/justrach/kuri/main/install.sh | sh

macOS arm64/x86_64 and Linux x86_64/arm64. Single binary, no runtime deps.

Direct downloads: macOS arm64 Β· macOS x86_64 Β· Linux x86_64 Β· Linux arm64


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 Β· iOS + Android device control

Quick Start Β· Benchmarks Β· kuri-agent Β· Security Testing Β· API Β· Skills Β· Changelog

Why teams switch to Kuri: current Apple Silicon ReleaseFast builds stay sub-2 MB per binary, and a fresh Google Flights rerun on 2026-04-23 measured 3,392 tokens for a full kuri-agent loop (go→snap→click→snap→eval). Cross-tool deltas should be rerun in the same environment before quoting a percentage.


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.

  • 135 HTTP endpoints β€” full parity with agent-browser and browser-use, from React inspection to Core Web Vitals.
  • 7-12% fewer tokens than agent-browser on real pages thanks to @eN ref format and zero-prefix rendering.
  • 44x lighter observations with /page/state (48 tokens) vs full snapshot (2,124 tokens) for the same Google Flights page.
  • Batch execution β€” POST /batch sends N commands in one HTTP call, eliminating N-1 round-trips and N-1 LLM turns.
  • React-compatible β€” trusted CDP mouse events and per-character key events fire React 18/19 onClick and onChange.

Snapshot tokens: Google Flights SIN β†’ TPE

Fresh rerun on 2026-05-24 in this workspace, measured with wc -c and chars/4 approximation.

Tool / Mode Chars ~Tokens Note
kuri snap (full) 8,499 2,124 All nodes + interactive refs
kuri snap (interactive only) ~3,000 ~750 Best for agent loops
kuri /page/state 190 48 Lightweight observation (url, title, scroll%, counts)
agent-browser snap (estimated) ~9,183 ~2,295 [ref=e0] format overhead

Token efficiency: kuri vs agent-browser

Page kuri tokens agent-browser tokens Savings
example.com 40 35 -13% (trivial page, agent-browser skips root)
Hacker News 386 ~440 12% fewer
Google Flights SIN→TPE 2,124 ~2,295 7% fewer

The savings come from kuri's compact format:

  • @e0 refs (3 chars) vs [ref=e0] (9 chars)
  • No - prefix per line (saves 2 chars Γ— line count)
  • Same indentation, same node filtering

Full workflow cost: go β†’ snap β†’ click β†’ snap β†’ eval

Tool Tokens per cycle
kuri-agent ~3,400
With /page/state instead of second snap ~1,700
With POST /batch (all in one call) ~1,700 (same tokens, 1 HTTP call instead of 5)

Binary size and memory

Measured on Apple M4 Pro, macOS 26.4.1. Current binaries were built with -Doptimize=ReleaseFast.

Binary Current size
kuri 1,093,840 B (1.04 MiB)
kuri-agent 629,904 B (615 KiB)
kuri-browse 1,089,120 B (1.04 MiB)
kuri-fetch 2,063,488 B (1.97 MiB)

RSS stayed flat across the Zig 0.16 migration

Measured against the current v0.4.3 ReleaseFast build with /usr/bin/time -l.

Command v0.4.3 mean max RSS
kuri-fetch --version ~2.45 MiB
kuri-browse --version ~2.45 MiB
kuri-fetch --quiet --dump markdown http://example.com/ ~9.17 MiB

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/release-channel/stable/install.sh | sh

Detects your platform, downloads the right binary, installs to ~/.local/bin. Downloads come from Kuri's self-managed release-channel branch. macOS binaries are locally signed with a Developer ID certificate. GitHub Release assets mirror these same tarballs.

bun / npm

bun install -g kuri-agent
# or: npm install -g kuri-agent

Downloads the correct native binary for your platform at install time.

Release channel

Kuri's stable binaries live on the release-channel branch and are served directly from GitHub raw URLs.

  • Stable installer: https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.sh
  • Stable manifest: https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/latest.json
  • Branch view: https://github.com/justrach/kuri/tree/release-channel/stable
  • Direct download pattern: https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/<version>/kuri-<version>-<target>.tar.gz

Manual

Download the tarball for your platform from the stable release manifest or from the GitHub Releases page and unpack it to your $PATH.

Stable install URL:

curl -fsSL https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.sh | sh

The manifest includes exact asset URLs plus SHA-256 checksums for aarch64-linux, x86_64-linux, aarch64-macos, and x86_64-macos.

Platform support

Platform Status
macOS (aarch64, x86_64) Prebuilt binaries, signed + notarized
Linux (aarch64, x86_64) Prebuilt binaries
Windows (x86_64) Experimental β€” cross-compile only. zig build -Dtarget=x86_64-windows-gnu is CI-verified, but Chrome automation, daemonization, signal-based shutdown, HAR recording, and the file-backed auth store are all stubbed with error.UnsupportedOnWindows at runtime. Use WSL2 if you need the real feature set. Tracked at #153.

Kuri leans on POSIX primitives (fork, clock_gettime, raw sockets) in several spots, so a full native Windows port is real work. The compile-level baseline above lets the --version/--help paths and pure in-memory operations run; the gnarly bits (Chrome, sockets, daemonize) need real Win32 implementations before they come off the stub list. If you want to take any of those on, +1 #153 or open a PR.

Build from source

Requires Zig β‰₯ 0.16.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.16.0 Β· Chrome/Chromium (for CDP mode)

git clone https://github.com/justrach/kuri.git
cd kuri

zig build              # build everything
zig build test         # run 252+ 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

# Experimental standalone browser runtime β€” separate build, not production
(cd kuri-browser && zig build run -- render https://example.com)
(cd kuri-browser && zig build run -- bench --offline)

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

Session-first agent loop

For agent-style HTTP usage, prefer a session header plus /tab/new, /page/info, and /snapshot instead of repeating tab_id on every call.

SESSION=hn-demo
BASE=http://127.0.0.1:8080

curl -s -H "X-Kuri-Session: $SESSION" \
  "$BASE/tab/new?url=https%3A%2F%2Fnews.ycombinator.com"

curl -s -H "X-Kuri-Session: $SESSION" "$BASE/page/info"
SNAP=$(curl -s -H "X-Kuri-Session: $SESSION" "$BASE/snapshot?filter=interactive&format=compact")
MORE_REF=$(printf '%s' "$SNAP" | python3 -c 'import re,sys; print(re.search(r"\"More\" @(e\\d+)", sys.stdin.read()).group(1))')
curl -s -H "X-Kuri-Session: $SESSION" "$BASE/action?action=click&ref=$MORE_REF"
curl -s -H "X-Kuri-Session: $SESSION" "$BASE/page/info"

There is also a thin experimental wrapper at tools/kuri_harness.py if you want Python helpers on top of the same HTTP surface.

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. 135 endpoints β€” full parity with agent-browser and browser-use.

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 /tab/current Get or set the current tab for an X-Kuri-Session
GET /page/info Live URL/title/ready-state/viewport/scroll for the active tab
GET /page/state Compact page observation: url, title, scroll%, viewport, counts of forms/links/images/inputs
POST /batch Execute multiple commands in one HTTP call β€” returns array of results
GET /browdie 🌰 (easter egg)

Browser Control

Path Params Description
GET /navigate tab_id, url Navigate tab to URL
GET /tab/new url, activate, wait Create a new tab and optionally hydrate/set current tab
GET /tab/close tab_id Close a tab
GET /window/new url, activate, wait Create a new window/tab target
GET /snapshot tab_id, filter, format A11y tree snapshot with eN refs. Use filter=interactive&format=compact for low-token agent loops.
GET /text tab_id Extract page text
GET /screenshot tab_id, format, quality Capture screenshot (base64)
GET /screenshot/annotated tab_id Screenshot with numbered element labels
GET /screenshot/diff tab_id, baseline Visual diff between current and baseline screenshots
GET /action tab_id, ref, action, value Click/type/fill/select/scroll/hover/dblclick/check/uncheck/blur by ref
GET /evaluate tab_id, expression Execute JavaScript
GET /evalhandle tab_id, expression Execute JS, return objectId handle (not value)
GET /close tab_id Close tab + cleanup
GET /bringtofront tab_id Bring tab to front

Actions

Path Params Description
GET /clear ref Clear input field value
GET /selectall ref Select all text in input/contenteditable
GET /setvalue ref, value Set input value directly (bypasses key events)
GET /dispatch ref, type Dispatch custom DOM event on element
GET /boundingbox ref Get element bounding rect (x, y, width, height, centerX, centerY)
GET /getattribute ref, name Get element attribute by name
GET /inputvalue ref Get current input element value
GET /element/state ref, check Quick boolean: exists, visible, enabled, checked
GET /find-element text/role/label/placeholder/testid Semantic locator β€” find element without snap
GET /highlight ref or selector Highlight element with overlay

Mouse & Touch

Path Params Description
GET /mouse/move x, y Move mouse to coordinates
GET /mouse/down x, y, button Mouse button down
GET /mouse/up x, y, button Mouse button up
GET /mouse/wheel x, y, deltaX, deltaY Mouse wheel scroll
GET /tap x, y Touch tap (touchStart + touchEnd)
GET /swipe startX, startY, endX, endY Touch swipe gesture
GET /drag src_ref, tgt_ref Drag element to target

Keyboard

Path Params Description
GET /keyboard/type tab_id, text Type text via key events
GET /keyboard/inserttext tab_id, text Insert text directly
GET /keydown tab_id, key Key down event
GET /keyup tab_id, key Key up event

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 /dom/attributes Get element attributes
GET /pdf Print page to PDF
GET /find In-page text search

Waiting

Path Params Description
GET /wait selector, text, url, state, visible, timeout Wait for selector/text/URL pattern/networkidle/load state
GET /wait/function expression, timeout Wait for arbitrary JS expression to be truthy
GET /wait/download timeout Wait for file download to complete

Dialog Handling

Path Description
GET /dialog/auto Auto-handle all JS dialogs (accept or dismiss)
GET /dialog/accept Accept current dialog (with optional prompt text)
GET /dialog/dismiss Dismiss current dialog

Network & HAR

Path Description
GET /har/start Start recording network traffic
GET /har/stop Stop + return HAR 1.2 JSON
GET /har/status Recording state + entry count
GET /har/replay API map with curl/fetch/python code snippets
GET /cookies Get cookies
GET /cookies/set Set cookies
GET /cookies/delete Delete cookies
GET /cookies/clear Clear all cookies
GET /headers Set custom request headers
GET /intercept/start Start request interception
GET /intercept/stop Stop request interception
GET /intercept/requests List intercepted requests
GET /request/detail Get response body for a request ID
GET /response/body Fetch URL and return response body
GET /network Network traffic stats
GET /download Trigger file download

Navigation & State

Path Description
GET /back Browser back
GET /forward Browser forward
GET /reload Reload page
GET /stop Stop page loading
GET /pushstate SPA navigation via history.pushState
GET /storage/local Get/set localStorage
GET /storage/session Get/set 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 sessions
GET /setcontent Set page HTML directly (POST)

Auth Profiles

Path Description
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 /auth/extract Extract auth tokens (JWT, cookies, headers)
GET /set/credentials Set HTTP basic auth credentials

On macOS, auth profile secrets are stored in the user Keychain.

Emulation

Path Params Description
GET /emulate device type, screen size Device emulation
GET /set/viewport width, height Set viewport size
GET /set/useragent ua Set user agent
GET /set/media media Emulate media type
GET /set/offline offline Toggle offline mode
GET /geolocation lat, lng Override geolocation
GET /timezone timezone Override timezone (e.g. America/New_York)
GET /locale locale Override locale (e.g. en-US)
GET /permissions name, state Grant/deny permissions (geolocation, notifications, clipboard)

Scripts & Injection

Path Description
GET /script/inject Inject JavaScript into page (persists across nav)
GET /initscript/remove Remove a previously injected init script
GET /addstyle Inject CSS stylesheet
GET /expose Expose a named function to page JS context

React Inspection

Path Description
GET /react/tree React component tree via DevTools hook
GET /react/inspect React component props and state
GET /react/renders React render tracking (start/stop)
GET /react/suspense React Suspense boundary status

Recording & Performance

Path Description
GET /recording/start Record user actions (click, input, navigate)
GET /recording/stop Stop recording + return action log
GET /vitals Core Web Vitals (LCP, CLS, FID, TTFB, FCP, domInteractive)
GET /perf/lcp Largest Contentful Paint timing
GET /trace/start Start performance trace
GET /trace/stop Stop trace
GET /profiler/start Start JS profiler
GET /profiler/stop Stop profiler

Debugging

Path Description
GET /debug/enable Enable in-page debug HUD and optional freeze mode
GET /debug/disable Disable in-page debug HUD
GET /inspect Element inspection
GET /errors Collect JS errors
GET /console Read console logs
GET /frames List page frames
GET /frame Switch to iframe context by name or URL
GET /mainframe Switch back to main frame
GET /diff/snapshot Diff accessibility tree snapshots
GET /diff/url Compare two URLs side by side (navigate, snapshot, diff)

Streaming

Path Description
GET /screencast/start Start screen recording
GET /screencast/stop Stop screen recording
GET /video/start Start video capture
GET /video/stop Stop video capture
GET /ws/start WebSocket tunnel start
GET /ws/stop WebSocket tunnel stop

Agent-friendly loop

The lowest-friction server loop is:

  1. GET /tab/new?url=...
  2. GET /page/state (lightweight) or GET /snapshot?filter=interactive&format=compact (full)
  3. GET /action?action=click&ref=eN
  4. Repeat β€” or use POST /batch for multi-step operations in one call

url and expression query params are percent-decoded. Send X-Kuri-Session: my-agent to persist tab context server-side.

🧠 Skills

The repo includes a user-extensible skill area:

  • skills/kuri-skill.md is the base Kuri HTTP-agent skill
  • skills/custom/ is reserved for your own project-specific skills
  • skills/custom/hackernews-page-2.md is a concrete example custom skill
  • .claude/skills/kuri-server/SKILL.md stays in sync for Claude-style repo skills

The base skill now also explains which browser path to use:

  • kuri HTTP API: production Chrome/CDP automation with sessions, snapshots, actions, HAR, cookies, and screenshots
  • kuri-fetch: standalone no-Chrome fetch/text extraction
  • kuri-browse: interactive terminal browsing
  • kuri-agent: scriptable CLI automation against the Kuri server
  • kuri-browser/: experimental separate Zig-native browser runtime for parity work

For the experimental browser CLI:

cd kuri-browser
zig build run -- render https://news.ycombinator.com --selector ".titleline a" --dump text
zig build run -- render https://todomvc.com/examples/react/dist/ --js --wait-eval "document.querySelectorAll('.todo-list li').length >= 1"
zig build run -- parity --offline
zig build run -- bench --offline
zig build run -- serve-cdp --port 9333

kuri-browser serve-cdp exposes Chrome-style HTTP discovery plus a minimal WebSocket JSON-RPC router for protocol smoke tests. Runtime eval returns V8-shaped CDP remote objects backed by QuickJS; this does not add a V8 dependency and is not full Playwright/Puppeteer compatibility yet.

Screenshots in kuri-browser currently delegate to the main Kuri/CDP renderer. Start ./zig-out/bin/kuri first, then:

cd kuri-browser
zig build run -- screenshot https://example.com --out example.jpg --compress --kuri-base http://127.0.0.1:8080

--compress captures a PNG baseline and JPEG candidate, writes the smaller file, and reports byte savings. Current local measurement on https://example.com: 20,523 bytes PNG to 18,183 bytes JPEG quality 50, saving 2,340 bytes or 11%.

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.

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

Site Protection Result
Singapore Airlines Akamai WAF βœ… Bypassed (was blocked before v0.4.0)
Shopee SG Custom anti-fraud βœ… Page loads, redirects to login
Google Flights None βœ… Full interaction
Booking.com PerimeterX ⚠️ 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

Command Action
<number> Follow link [N]
<url> Navigate (if contains .)
:go <url> Navigate to URL
:back, :b Go back in history
:forward, :f Go forward
:reload, :r Re-fetch current page
:links, :l Show link index
/<term> Search in page (highlights matches)
:search <t> Search in page
:n, :next Re-highlight search
:history Show navigation history
:help, :h Show all commands
:quit, :q Exit

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

Command Description
tabs [--port N] List Chrome tabs
use <ws_url> Attach to a tab (saves session)
open [url] [--port N] Open a new tab (optionally navigating to url)
status Show current session
go <url> Navigate to URL
snap [--interactive] [--json] [--text] [--depth N] A11y snapshot, saves eN refs
click <ref> Click element by ref (CDP mouse events, React-compatible)
type <ref> <text> Type into element (per-character key events, React-compatible)
fill <ref> <text> Fill input value
select <ref> <value> Select dropdown option
hover <ref> Hover over element
focus <ref> Focus element
scroll Scroll the page
viewport [width height] Get or set viewport dimensions
eval <js> Evaluate JavaScript
text [selector] Get page text
shot [--out file.png] Screenshot
back Navigate back
forward Navigate forward
reload Reload current page
cookies List cookies with security flags
headers Check security response headers
audit Full security audit
storage [local|session|all] Dump localStorage / sessionStorage
jwt Extract and decode JWTs from cookies and storage
fetch <method> <url> [--data <json>] Authenticated fetch using page cookies
probe <url-template> <start> <end> IDOR probe: iterate numeric IDs in URL
grab <ref> Click ref, intercept window.open, follow redirect in-tab
wait-for-tab [--port N] Poll for a new tab, auto-switch session
stealth Apply anti-detection patches
set-header <name> <value> Add a custom header to all requests
show-headers Show stored extra headers
clear-headers Remove all extra headers

πŸ“± kuri-mobile (iOS + Android)

Native Zig CLI for driving iOS Simulators, real iPhones (listing + launch/terminate), and Android devices/emulators β€” inspired by mobile-device-mcp, reimplemented in Zig with no Bun/Node/Gradle/Xcode in the build path.

cd kuri-mobile && zig build && cp zig-out/bin/kuri-mobile ../zig-out/bin/

# The main `kuri` binary forwards android/ios subcommands to kuri-mobile:
kuri ios list-devices                              # sims + real devices (usbmuxd, native)
kuri ios openurl https://example.com               # navigate Safari
kuri ios screenshot out.png                        # auto-picks booted sim
kuri ios launch com.apple.Preferences

kuri android list-devices                          # native Zig adb wire-protocol client
kuri android tap 540 1200
kuri android swipe 100 1500 100 500
kuri android screenshot phone.png
kuri android uitree                                # flat element list via uiautomator dump

What's native Zig: adb host protocol (libc sockets, 4-hex framing over host:transport:/shell:/exec:), Android XML UI tree parser, usbmuxd ListDevices plist client. What shells out: xcrun simctl (iOS Simulator), xcrun devicectl (iOS real-device launch/terminate). Driverless by design: no on-device app is installed, so run_code sandboxes and XCUITest-backed tap/uitree on real iOS devices are intentionally not available. See kuri-mobile/README.md for the full parity matrix vs upstream.


πŸ”’ 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

Mode Behavior
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.16.0)
β”œβ”€β”€ 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
β”œβ”€β”€ kuri-browser/              # Native Zig rendering experiments
└── kuri-mobile/               # iOS + Android device control (Zig-native adb + usbmuxd)
    β”œβ”€β”€ src/
    β”‚   β”œβ”€β”€ common/            # io helpers, unified UI tree parser
    β”‚   β”œβ”€β”€ android/           # adb wire protocol client, driver, CLI
    β”‚   └── ios/               # simctl, usbmuxd, devicectl, CLI
    └── README.md              # Full parity matrix vs mobile-device-mcp

βš™οΈ Configuration

Env Var Default Description
HOST 127.0.0.1 Server bind address
PORT 8080 Server 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 .kuri Session state directory
REQUEST_TIMEOUT_MS 30000 HTTP request timeout
NAVIGATE_TIMEOUT_MS 30000 Navigation timeout
STALE_TAB_INTERVAL_S 30 Stale tab cleanup interval
NO_COLOR (none) Disable colored CLI output

πŸ’° Token Cost

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

Method Tokens Cost ($) Best For
/text ~40,000 $0.20 Read-heavy (13Γ— cheaper than screenshots)
/snapshot?filter=interactive&format=compact ~40,000 $0.20 Low-token element interaction
/snapshot (full) ~525,000 $2.63 Full page understanding
/screenshot ~100,000 $1.00 Visual 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         # 252+ tests must pass
zig build test-fetch   # kuri-fetch tests (69 tests)
zig build test-browse  # kuri-browse tests (22 tests)

See CONTRIBUTORS.md for guidelines.


Credits

Project What we borrowed
agent-browser @eN ref system, snapshot diffing, HAR recording patterns
Pinchtab Browser control architecture for AI agents
Pathik High-performance crawling patterns
QuickJS-ng via mitchellh/zig-quickjs-ng JS engine for kuri-fetch
Lightpanda Zig-native headless browser pioneer, CDP compatibility patterns
Zig 0.16.0 The whole stack

License

Apache-2.0

About

Browser automation, web crawling, and iOS + Android device control for AI agents. Zig-native, token-efficient CDP snapshots, HAR recording, native adb wire-protocol client, and a standalone fetcher.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors