{"id":49579135,"url":"https://github.com/justrach/kuri","last_synced_at":"2026-05-24T08:02:19.153Z","repository":{"id":341991227,"uuid":"1172319662","full_name":"justrach/kuri","owner":"justrach","description":"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.","archived":false,"fork":false,"pushed_at":"2026-05-20T18:02:06.000Z","size":78302,"stargazers_count":317,"open_issues_count":5,"forks_count":16,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-20T22:46:04.249Z","etag":null,"topics":["ai-agents","android","browser-automation","chrome-cdp","ios","mobile-automation","web-scraping","zig"],"latest_commit_sha":null,"homepage":"https://kuri.trilok.ai","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/justrach.png","metadata":{"files":{"readme":"readme.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-04T07:09:00.000Z","updated_at":"2026-05-20T21:12:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/justrach/kuri","commit_stats":null,"previous_names":["justrach/agentic-browdie"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/justrach/kuri","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justrach%2Fkuri","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justrach%2Fkuri/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justrach%2Fkuri/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justrach%2Fkuri/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/justrach","download_url":"https://codeload.github.com/justrach/kuri/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justrach%2Fkuri/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33426013,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"online","status_checked_at":"2026-05-24T02:00:06.296Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","android","browser-automation","chrome-cdp","ios","mobile-automation","web-scraping","zig"],"created_at":"2026-05-03T18:12:22.886Z","updated_at":"2026-05-24T08:02:19.137Z","avatar_url":"https://github.com/justrach.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"kuri.png\" alt=\"Kuri\" width=\"200\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eKuri 🌰\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/latest.json\"\u003e\u003cimg src=\"https://img.shields.io/badge/stable-v0.4.3-brightgreen?style=flat-square\" alt=\"Stable release\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/justrach/kuri/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/justrach/kuri?style=flat-square\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/zig-0.16.0-f7a41d?style=flat-square\" alt=\"Zig\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/node__modules-0_files-brightgreen?style=flat-square\" alt=\"node_modules\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/status-stable-brightgreen?style=flat-square\" alt=\"status\"\u003e\n\u003c/p\u003e\n\n## Install\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/justrach/kuri/main/install.sh | sh\n```\n\nmacOS arm64/x86_64 and Linux x86_64/arm64. Single binary, no runtime deps.\n\nDirect downloads: [macOS arm64](https://github.com/justrach/kuri/releases/download/v0.4.3/kuri-v0.4.3-aarch64-macos.tar.gz) · [macOS x86_64](https://github.com/justrach/kuri/releases/download/v0.4.3/kuri-v0.4.3-x86_64-macos.tar.gz) · [Linux x86_64](https://github.com/justrach/kuri/releases/download/v0.4.3/kuri-v0.4.3-x86_64-linux.tar.gz) · [Linux arm64](https://github.com/justrach/kuri/releases/download/v0.4.3/kuri-v0.4.3-aarch64-linux.tar.gz)\n\n---\n\n**Browser automation \u0026 web crawling for AI agents. Written in Zig. Zero Node.js.**\n\nCDP automation · A11y snapshots · HAR recording · Standalone fetcher · Interactive terminal browser · Agentic CLI · Security testing · iOS + Android device control\n\n[Quick Start](#-quick-start) · [Benchmarks](#-benchmarks) · [kuri-agent](#-kuri-agent) · [Security Testing](#-security-testing) · [API](#-http-api) · [Skills](#-skills) · [Changelog](CHANGELOG.md)\n\n\u003e **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.\n\n---\n\n## Why Kuri Wins for Agents\n\nMost 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.\n\n- **135 HTTP endpoints** — full parity with agent-browser and browser-use, from React inspection to Core Web Vitals.\n- **7-12% fewer tokens** than agent-browser on real pages thanks to `@eN` ref format and zero-prefix rendering.\n- **44x lighter observations** with `/page/state` (48 tokens) vs full snapshot (2,124 tokens) for the same Google Flights page.\n- **Batch execution** — `POST /batch` sends N commands in one HTTP call, eliminating N-1 round-trips and N-1 LLM turns.\n- **React-compatible** — trusted CDP mouse events and per-character key events fire React 18/19 `onClick` and `onChange`.\n\n### Snapshot tokens: Google Flights `SIN → TPE`\n\nFresh rerun on 2026-05-24 in this workspace, measured with `wc -c` and `chars/4` approximation.\n\n| Tool / Mode | Chars | ~Tokens | Note |\n|---|---:|---:|---|\n| `kuri snap` (full) | 8,499 | **2,124** | All nodes + interactive refs |\n| `kuri snap` (interactive only) | ~3,000 | **~750** | Best for agent loops |\n| `kuri /page/state` | 190 | **48** | Lightweight observation (url, title, scroll%, counts) |\n| agent-browser snap (estimated) | ~9,183 | **~2,295** | `[ref=e0]` format overhead |\n\n### Token efficiency: kuri vs agent-browser\n\n| Page | kuri tokens | agent-browser tokens | Savings |\n|---|---:|---:|---|\n| example.com | 40 | 35 | -13% (trivial page, agent-browser skips root) |\n| Hacker News | 386 | ~440 | **12% fewer** |\n| Google Flights SIN→TPE | 2,124 | ~2,295 | **7% fewer** |\n\nThe savings come from kuri's compact format:\n- `@e0` refs (3 chars) vs `[ref=e0]` (9 chars)\n- No `- ` prefix per line (saves 2 chars × line count)\n- Same indentation, same node filtering\n\n### Full workflow cost: `go → snap → click → snap → eval`\n\n| Tool | Tokens per cycle |\n|---|---:|\n| **kuri-agent** | **~3,400** |\n| With `/page/state` instead of second snap | **~1,700** |\n| With `POST /batch` (all in one call) | **~1,700** (same tokens, 1 HTTP call instead of 5) |\n\n### Binary size and memory\n\nMeasured on Apple M4 Pro, macOS 26.4.1. Current binaries were built with `-Doptimize=ReleaseFast`.\n\n| Binary | Current size |\n|---|---:|\n| `kuri` | 1,093,840 B (1.04 MiB) |\n| `kuri-agent` | 629,904 B (615 KiB) |\n| `kuri-browse` | 1,089,120 B (1.04 MiB) |\n| `kuri-fetch` | 2,063,488 B (1.97 MiB) |\n\n### RSS stayed flat across the Zig 0.16 migration\n\nMeasured against the current `v0.4.3` `ReleaseFast` build with `/usr/bin/time -l`.\n\n| Command | `v0.4.3` mean max RSS |\n|---|---:|\n| `kuri-fetch --version` | ~2.45 MiB |\n| `kuri-browse --version` | ~2.45 MiB |\n| `kuri-fetch --quiet --dump markdown http://example.com/` | ~9.17 MiB |\n\n## The Problem\n\nEvery 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.\n**Kuri is a single Zig binary.** Four modes, zero runtime:\n\n```\nkuri           →  CDP server (Chrome automation, a11y snapshots, HAR)\nkuri-fetch     →  standalone fetcher (no Chrome, QuickJS for JS, ~2 MB)\nkuri-browse    →  interactive terminal browser (navigate, follow links, search)\nkuri-agent     →  agentic CLI (scriptable Chrome automation + security testing)\n```\n\n---\n## 📦 Installation\n\n### One-line install (macOS / Linux)\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.sh | sh\n```\n\nDetects your platform, downloads the right binary, installs to `~/.local/bin`.\nDownloads 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.\n\n### bun / npm\n\n```sh\nbun install -g kuri-agent\n# or: npm install -g kuri-agent\n```\n\nDownloads the correct native binary for your platform at install time.\n\n### Release channel\n\nKuri's stable binaries live on the `release-channel` branch and are served directly from GitHub raw URLs.\n\n- Stable installer: `https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.sh`\n- Stable manifest: `https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/latest.json`\n- Branch view: `https://github.com/justrach/kuri/tree/release-channel/stable`\n- Direct download pattern: `https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/\u003cversion\u003e/kuri-\u003cversion\u003e-\u003ctarget\u003e.tar.gz`\n\n### Manual\n\nDownload the tarball for your platform from the [stable release manifest](https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/latest.json) or from the [GitHub Releases page](https://github.com/justrach/kuri/releases) and unpack it to your `$PATH`.\n\nStable install URL:\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.sh | sh\n```\n\nThe manifest includes exact asset URLs plus SHA-256 checksums for `aarch64-linux`, `x86_64-linux`, `aarch64-macos`, and `x86_64-macos`.\n\n### Platform support\n\n| Platform | Status |\n|---|---|\n| macOS (`aarch64`, `x86_64`) | Prebuilt binaries, signed + notarized |\n| Linux (`aarch64`, `x86_64`) | Prebuilt binaries |\n| 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](https://github.com/justrach/kuri/issues/153). |\n\nKuri 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](https://github.com/justrach/kuri/issues/153) or open a PR.\n\n### Build from source\n\nRequires [Zig ≥ 0.16.0](https://ziglang.org/download/).\n\n```bash\ngit clone https://github.com/justrach/kuri.git\ncd kuri\nzig build -Doptimize=ReleaseFast\n# Binaries in zig-out/bin/: kuri  kuri-agent  kuri-fetch  kuri-browse\n```\n\n---\n\n## ⚡ Quick Start\n\n**Requirements:** [Zig ≥ 0.16.0](https://ziglang.org/download/) · Chrome/Chromium (for CDP mode)\n\n```bash\ngit clone https://github.com/justrach/kuri.git\ncd kuri\n\nzig build              # build everything\nzig build test         # run 252+ tests\n\n# CDP mode — launches Chrome automatically\n./zig-out/bin/kuri\n\n# Standalone mode — no Chrome needed\n./zig-out/bin/kuri-fetch https://example.com\n\n# Interactive browser — browse from your terminal\n./zig-out/bin/kuri-browse https://example.com\n\n# Experimental standalone browser runtime — separate build, not production\n(cd kuri-browser \u0026\u0026 zig build run -- render https://example.com)\n(cd kuri-browser \u0026\u0026 zig build run -- bench --offline)\n```\n\n### First run, shortest path\n\n```bash\n# start the server; if CDP_URL is unset, kuri launches managed Chrome for you\n./zig-out/bin/kuri\n\n# discover tabs from that managed browser\ncurl -s http://127.0.0.1:8080/discover\n\n# inspect the discovered tab list\ncurl -s http://127.0.0.1:8080/tabs\n```\n\n### Session-first agent loop\n\nFor agent-style HTTP usage, prefer a session header plus `/tab/new`, `/page/info`, and `/snapshot` instead of repeating `tab_id` on every call.\n\n```bash\nSESSION=hn-demo\nBASE=http://127.0.0.1:8080\n\ncurl -s -H \"X-Kuri-Session: $SESSION\" \\\n  \"$BASE/tab/new?url=https%3A%2F%2Fnews.ycombinator.com\"\n\ncurl -s -H \"X-Kuri-Session: $SESSION\" \"$BASE/page/info\"\nSNAP=$(curl -s -H \"X-Kuri-Session: $SESSION\" \"$BASE/snapshot?filter=interactive\u0026format=compact\")\nMORE_REF=$(printf '%s' \"$SNAP\" | python3 -c 'import re,sys; print(re.search(r\"\\\"More\\\" @(e\\\\d+)\", sys.stdin.read()).group(1))')\ncurl -s -H \"X-Kuri-Session: $SESSION\" \"$BASE/action?action=click\u0026ref=$MORE_REF\"\ncurl -s -H \"X-Kuri-Session: $SESSION\" \"$BASE/page/info\"\n```\n\nThere is also a thin experimental wrapper at `tools/kuri_harness.py` if you want Python helpers on top of the same HTTP surface.\n\nIf you already have Chrome running with remote debugging, set `CDP_URL` to either the WebSocket or HTTP endpoint:\n\n```bash\nCDP_URL=ws://127.0.0.1:9222/devtools/browser/... ./zig-out/bin/kuri\n# or\nCDP_URL=http://127.0.0.1:9222 ./zig-out/bin/kuri\n```\n\n### Browse vercel.com in 4 commands\n\n```bash\n# 1. Discover Chrome tabs\ncurl -s http://localhost:8080/discover\n# → {\"discovered\":1,\"total_tabs\":1}\n\n# 2. Get tab ID\ncurl -s http://localhost:8080/tabs\n# → [{\"id\":\"ABC123\",\"url\":\"chrome://newtab/\",\"title\":\"New Tab\"}]\n\n# 3. Navigate\ncurl -s \"http://localhost:8080/navigate?tab_id=ABC123\u0026url=https://vercel.com\"\n\n# 4. Get accessibility snapshot (token-optimized for LLMs)\ncurl -s \"http://localhost:8080/snapshot?tab_id=ABC123\u0026filter=interactive\"\n# → [{\"ref\":\"e0\",\"role\":\"link\",\"name\":\"VercelLogotype\"},\n#    {\"ref\":\"e1\",\"role\":\"button\",\"name\":\"Ask AI\"}, ...]\n```\n\n---\n\n## 🌐 HTTP API\n\nAll endpoints return JSON. Optional auth via `KURI_SECRET` env var. **135 endpoints** — full parity with agent-browser and browser-use.\n\n### Core\n\n| Path | Description |\n|------|-------------|\n| `GET /health` | Server status, tab count, version |\n| `GET /tabs` | List all registered tabs |\n| `GET /discover` | Auto-discover Chrome tabs via CDP |\n| `GET /tab/current` | Get or set the current tab for an `X-Kuri-Session` |\n| `GET /page/info` | Live URL/title/ready-state/viewport/scroll for the active tab |\n| `GET /page/state` | Compact page observation: url, title, scroll%, viewport, counts of forms/links/images/inputs |\n| `POST /batch` | Execute multiple commands in one HTTP call — returns array of results |\n| `GET /browdie` | 🌰 (easter egg) |\n\n### Browser Control\n\n| Path | Params | Description |\n|------|--------|-------------|\n| `GET /navigate` | `tab_id`, `url` | Navigate tab to URL |\n| `GET /tab/new` | `url`, `activate`, `wait` | Create a new tab and optionally hydrate/set current tab |\n| `GET /tab/close` | `tab_id` | Close a tab |\n| `GET /window/new` | `url`, `activate`, `wait` | Create a new window/tab target |\n| `GET /snapshot` | `tab_id`, `filter`, `format` | A11y tree snapshot with `eN` refs. Use `filter=interactive\u0026format=compact` for low-token agent loops. |\n| `GET /text` | `tab_id` | Extract page text |\n| `GET /screenshot` | `tab_id`, `format`, `quality` | Capture screenshot (base64) |\n| `GET /screenshot/annotated` | `tab_id` | Screenshot with numbered element labels |\n| `GET /screenshot/diff` | `tab_id`, `baseline` | Visual diff between current and baseline screenshots |\n| `GET /action` | `tab_id`, `ref`, `action`, `value` | Click/type/fill/select/scroll/hover/dblclick/check/uncheck/blur by ref |\n| `GET /evaluate` | `tab_id`, `expression` | Execute JavaScript |\n| `GET /evalhandle` | `tab_id`, `expression` | Execute JS, return objectId handle (not value) |\n| `GET /close` | `tab_id` | Close tab + cleanup |\n| `GET /bringtofront` | `tab_id` | Bring tab to front |\n\n### Actions\n\n| Path | Params | Description |\n|------|--------|-------------|\n| `GET /clear` | `ref` | Clear input field value |\n| `GET /selectall` | `ref` | Select all text in input/contenteditable |\n| `GET /setvalue` | `ref`, `value` | Set input value directly (bypasses key events) |\n| `GET /dispatch` | `ref`, `type` | Dispatch custom DOM event on element |\n| `GET /boundingbox` | `ref` | Get element bounding rect (x, y, width, height, centerX, centerY) |\n| `GET /getattribute` | `ref`, `name` | Get element attribute by name |\n| `GET /inputvalue` | `ref` | Get current input element value |\n| `GET /element/state` | `ref`, `check` | Quick boolean: `exists`, `visible`, `enabled`, `checked` |\n| `GET /find-element` | `text`/`role`/`label`/`placeholder`/`testid` | Semantic locator — find element without snap |\n| `GET /highlight` | `ref` or `selector` | Highlight element with overlay |\n\n### Mouse \u0026 Touch\n\n| Path | Params | Description |\n|------|--------|-------------|\n| `GET /mouse/move` | `x`, `y` | Move mouse to coordinates |\n| `GET /mouse/down` | `x`, `y`, `button` | Mouse button down |\n| `GET /mouse/up` | `x`, `y`, `button` | Mouse button up |\n| `GET /mouse/wheel` | `x`, `y`, `deltaX`, `deltaY` | Mouse wheel scroll |\n| `GET /tap` | `x`, `y` | Touch tap (touchStart + touchEnd) |\n| `GET /swipe` | `startX`, `startY`, `endX`, `endY` | Touch swipe gesture |\n| `GET /drag` | `src_ref`, `tgt_ref` | Drag element to target |\n\n### Keyboard\n\n| Path | Params | Description |\n|------|--------|-------------|\n| `GET /keyboard/type` | `tab_id`, `text` | Type text via key events |\n| `GET /keyboard/inserttext` | `tab_id`, `text` | Insert text directly |\n| `GET /keydown` | `tab_id`, `key` | Key down event |\n| `GET /keyup` | `tab_id`, `key` | Key up event |\n\n### Content Extraction\n\n| Path | Description |\n|------|-------------|\n| `GET /markdown` | Convert page to Markdown |\n| `GET /links` | Extract all links |\n| `GET /dom/query` | CSS selector query |\n| `GET /dom/html` | Get element HTML |\n| `GET /dom/attributes` | Get element attributes |\n| `GET /pdf` | Print page to PDF |\n| `GET /find` | In-page text search |\n\n### Waiting\n\n| Path | Params | Description |\n|------|--------|-------------|\n| `GET /wait` | `selector`, `text`, `url`, `state`, `visible`, `timeout` | Wait for selector/text/URL pattern/networkidle/load state |\n| `GET /wait/function` | `expression`, `timeout` | Wait for arbitrary JS expression to be truthy |\n| `GET /wait/download` | `timeout` | Wait for file download to complete |\n\n### Dialog Handling\n\n| Path | Description |\n|------|-------------|\n| `GET /dialog/auto` | Auto-handle all JS dialogs (accept or dismiss) |\n| `GET /dialog/accept` | Accept current dialog (with optional prompt text) |\n| `GET /dialog/dismiss` | Dismiss current dialog |\n\n### Network \u0026 HAR\n\n| Path | Description |\n|------|-------------|\n| `GET /har/start` | Start recording network traffic |\n| `GET /har/stop` | Stop + return HAR 1.2 JSON |\n| `GET /har/status` | Recording state + entry count |\n| `GET /har/replay` | API map with curl/fetch/python code snippets |\n| `GET /cookies` | Get cookies |\n| `GET /cookies/set` | Set cookies |\n| `GET /cookies/delete` | Delete cookies |\n| `GET /cookies/clear` | Clear all cookies |\n| `GET /headers` | Set custom request headers |\n| `GET /intercept/start` | Start request interception |\n| `GET /intercept/stop` | Stop request interception |\n| `GET /intercept/requests` | List intercepted requests |\n| `GET /request/detail` | Get response body for a request ID |\n| `GET /response/body` | Fetch URL and return response body |\n| `GET /network` | Network traffic stats |\n| `GET /download` | Trigger file download |\n\n### Navigation \u0026 State\n\n| Path | Description |\n|------|-------------|\n| `GET /back` | Browser back |\n| `GET /forward` | Browser forward |\n| `GET /reload` | Reload page |\n| `GET /stop` | Stop page loading |\n| `GET /pushstate` | SPA navigation via history.pushState |\n| `GET /storage/local` | Get/set localStorage |\n| `GET /storage/session` | Get/set sessionStorage |\n| `GET /storage/local/clear` | Clear localStorage |\n| `GET /storage/session/clear` | Clear sessionStorage |\n| `GET /session/save` | Save browser session |\n| `GET /session/load` | Restore browser session |\n| `GET /session/list` | List saved sessions |\n| `GET /setcontent` | Set page HTML directly (POST) |\n\n### Auth Profiles\n\n| Path | Description |\n|------|-------------|\n| `GET /auth/profile/save` | Save cookies + storage as a named auth profile |\n| `GET /auth/profile/load` | Restore a named auth profile into a tab |\n| `GET /auth/profile/list` | List saved auth profiles |\n| `GET /auth/profile/delete` | Delete a saved auth profile |\n| `GET /auth/extract` | Extract auth tokens (JWT, cookies, headers) |\n| `GET /set/credentials` | Set HTTP basic auth credentials |\n\nOn macOS, auth profile secrets are stored in the user Keychain.\n\n### Emulation\n\n| Path | Params | Description |\n|------|--------|-------------|\n| `GET /emulate` | device type, screen size | Device emulation |\n| `GET /set/viewport` | `width`, `height` | Set viewport size |\n| `GET /set/useragent` | `ua` | Set user agent |\n| `GET /set/media` | `media` | Emulate media type |\n| `GET /set/offline` | `offline` | Toggle offline mode |\n| `GET /geolocation` | `lat`, `lng` | Override geolocation |\n| `GET /timezone` | `timezone` | Override timezone (e.g. `America/New_York`) |\n| `GET /locale` | `locale` | Override locale (e.g. `en-US`) |\n| `GET /permissions` | `name`, `state` | Grant/deny permissions (geolocation, notifications, clipboard) |\n\n### Scripts \u0026 Injection\n\n| Path | Description |\n|------|-------------|\n| `GET /script/inject` | Inject JavaScript into page (persists across nav) |\n| `GET /initscript/remove` | Remove a previously injected init script |\n| `GET /addstyle` | Inject CSS stylesheet |\n| `GET /expose` | Expose a named function to page JS context |\n\n### React Inspection\n\n| Path | Description |\n|------|-------------|\n| `GET /react/tree` | React component tree via DevTools hook |\n| `GET /react/inspect` | React component props and state |\n| `GET /react/renders` | React render tracking (start/stop) |\n| `GET /react/suspense` | React Suspense boundary status |\n\n### Recording \u0026 Performance\n\n| Path | Description |\n|------|-------------|\n| `GET /recording/start` | Record user actions (click, input, navigate) |\n| `GET /recording/stop` | Stop recording + return action log |\n| `GET /vitals` | Core Web Vitals (LCP, CLS, FID, TTFB, FCP, domInteractive) |\n| `GET /perf/lcp` | Largest Contentful Paint timing |\n| `GET /trace/start` | Start performance trace |\n| `GET /trace/stop` | Stop trace |\n| `GET /profiler/start` | Start JS profiler |\n| `GET /profiler/stop` | Stop profiler |\n\n### Debugging\n\n| Path | Description |\n|------|-------------|\n| `GET /debug/enable` | Enable in-page debug HUD and optional freeze mode |\n| `GET /debug/disable` | Disable in-page debug HUD |\n| `GET /inspect` | Element inspection |\n| `GET /errors` | Collect JS errors |\n| `GET /console` | Read console logs |\n| `GET /frames` | List page frames |\n| `GET /frame` | Switch to iframe context by name or URL |\n| `GET /mainframe` | Switch back to main frame |\n| `GET /diff/snapshot` | Diff accessibility tree snapshots |\n| `GET /diff/url` | Compare two URLs side by side (navigate, snapshot, diff) |\n\n### Streaming\n\n| Path | Description |\n|------|-------------|\n| `GET /screencast/start` | Start screen recording |\n| `GET /screencast/stop` | Stop screen recording |\n| `GET /video/start` | Start video capture |\n| `GET /video/stop` | Stop video capture |\n| `GET /ws/start` | WebSocket tunnel start |\n| `GET /ws/stop` | WebSocket tunnel stop |\n\n### Agent-friendly loop\n\nThe lowest-friction server loop is:\n\n1. `GET /tab/new?url=...`\n2. `GET /page/state` (lightweight) or `GET /snapshot?filter=interactive\u0026format=compact` (full)\n3. `GET /action?action=click\u0026ref=eN`\n4. Repeat — or use `POST /batch` for multi-step operations in one call\n\n`url` and `expression` query params are percent-decoded. Send `X-Kuri-Session: my-agent` to persist tab context server-side.\n---\n\n## 🧠 Skills\n\nThe repo includes a user-extensible skill area:\n\n- `skills/kuri-skill.md` is the base Kuri HTTP-agent skill\n- `skills/custom/` is reserved for your own project-specific skills\n- `skills/custom/hackernews-page-2.md` is a concrete example custom skill\n- `.claude/skills/kuri-server/SKILL.md` stays in sync for Claude-style repo skills\n\nThe base skill now also explains which browser path to use:\n\n- `kuri` HTTP API: production Chrome/CDP automation with sessions, snapshots, actions, HAR, cookies, and screenshots\n- `kuri-fetch`: standalone no-Chrome fetch/text extraction\n- `kuri-browse`: interactive terminal browsing\n- `kuri-agent`: scriptable CLI automation against the Kuri server\n- `kuri-browser/`: experimental separate Zig-native browser runtime for parity work\n\nFor the experimental browser CLI:\n\n```bash\ncd kuri-browser\nzig build run -- render https://news.ycombinator.com --selector \".titleline a\" --dump text\nzig build run -- render https://todomvc.com/examples/react/dist/ --js --wait-eval \"document.querySelectorAll('.todo-list li').length \u003e= 1\"\nzig build run -- parity --offline\nzig build run -- bench --offline\nzig build run -- serve-cdp --port 9333\n```\n\n`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.\n\nScreenshots in `kuri-browser` currently delegate to the main Kuri/CDP renderer. Start `./zig-out/bin/kuri` first, then:\n\n```bash\ncd kuri-browser\nzig build run -- screenshot https://example.com --out example.jpg --compress --kuri-base http://127.0.0.1:8080\n```\n\n`--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%`.\n\n### Advanced\n\n| Path | Description |\n|------|-------------|\n| `GET /diff/snapshot` | Delta diff between snapshots |\n| `GET /emulate` | Device emulation |\n| `GET /geolocation` | Set geolocation |\n| `POST /upload` | File upload |\n| `GET /script/inject` | Inject JavaScript |\n| `GET /intercept/start` | Start request interception |\n| `GET /intercept/stop` | Stop interception |\n| `GET /screenshot/annotated` | Screenshot with element annotations |\n| `GET /screenshot/diff` | Visual diff between screenshots |\n| `GET /screencast/start` | Start screencast |\n| `GET /screencast/stop` | Stop screencast |\n| `GET /video/start` | Start video recording |\n| `GET /video/stop` | Stop video recording |\n| `GET /console` | Get console messages |\n| `GET /stop` | Stop page loading |\n| `GET /get` | Direct HTTP fetch (server-side) |\n| `GET /scrollintoview` | Scroll a referenced element into view |\n| `GET /drag` | Drag from one ref to another |\n| `GET /keyboard/type` | Type text with key events |\n| `GET /keyboard/inserttext` | Insert text directly |\n| `GET /keydown` | Dispatch a keydown event |\n| `GET /keyup` | Dispatch a keyup event |\n| `GET /wait` | Wait for ready state or element conditions |\n| `GET /tab/close` | Close a tab |\n| `GET /highlight` | Highlight an element by ref or selector |\n| `GET /errors` | Get page/runtime errors |\n| `GET /set/offline` | Toggle offline network emulation |\n| `GET /set/media` | Set emulated media features |\n| `GET /set/credentials` | Set HTTP basic auth credentials |\n| `GET /find` | Find text matches in the current page |\n| `GET /trace/start` | Start Chrome tracing |\n| `GET /trace/stop` | Stop tracing and return trace data |\n| `GET /profiler/start` | Start JS profiler |\n| `GET /profiler/stop` | Stop JS profiler |\n| `GET /inspect` | Inspect an element or page state |\n| `GET /set/viewport` | Set viewport size |\n| `GET /set/useragent` | Override user agent |\n| `GET /dom/attributes` | Get element attributes |\n| `GET /frames` | List frame tree |\n| `GET /network` | Inspect network state/requests |\n\n---\n\n## 🛡️ Stealth \u0026 Bot Evasion\n\nKuri applies anti-detection patches automatically on startup — no manual config needed.\n\n### What's applied\n\n- **`Page.addScriptToEvaluateOnNewDocument`** — stealth patches run before any page JS\n- **navigator.webdriver = false** — hides automation flag at Chromium level (`--disable-blink-features=AutomationControlled`)\n- **WebGL/Canvas/AudioContext spoofing** — defeats fingerprint-based detection\n- **UA rotation** — 5 realistic Chrome/Safari/Firefox user agents\n- **chrome.csi/chrome.loadTimes** — stubs for Akamai-specific checks\n\n### Bot block detection\n\nNavigate auto-detects blocks and returns structured fallback:\n\n```bash\ncurl -s \"http://localhost:8080/navigate?tab_id=ABC\u0026url=https://protected-site.com\"\n# If blocked:\n# {\"blocked\":true,\"blocker\":\"akamai\",\"ref_code\":\"0.7d...\",\n#  \"fallback\":{\"suggestions\":[\"Open URL directly in browser\",\"Use KURI_PROXY\"]}}\n# If ok: normal CDP response\n```\n\nDetects: **Akamai**, **Cloudflare**, **PerimeterX**, **DataDome**, generic captcha.\n\n### Proxy support\n\n```bash\nKURI_PROXY=socks5://user:pass@residential-proxy:1080 ./zig-out/bin/kuri\nKURI_PROXY=http://proxy:8080 ./zig-out/bin/kuri\n```\n\n### Tested sites\n\n| Site | Protection | Result |\n|------|-----------|--------|\n| Singapore Airlines | Akamai WAF | ✅ Bypassed (was blocked before v0.4.0) |\n| Shopee SG | Custom anti-fraud | ✅ Page loads, redirects to login |\n| Google Flights | None | ✅ Full interaction |\n| Booking.com | PerimeterX | ⚠️ Needs proxy |\n\n---\n\n## 🔧 kuri-fetch\n\nStandalone HTTP fetcher — no Chrome, no Playwright, no npm. Ships as a ~2 MB binary with built-in QuickJS for JS execution.\n\n```bash\nzig build fetch    # build + run\n\n# Default: convert to Markdown\nkuri-fetch https://example.com\n\n# Extract links\nkuri-fetch -d links https://news.ycombinator.com\n\n# Structured JSON output\nkuri-fetch --json https://example.com\n\n# Execute inline scripts via QuickJS\nkuri-fetch --js https://example.com\n\n# Write to file, quiet mode\nkuri-fetch -o page.md -q https://example.com\n\n# Pipe-friendly: content → stdout, status → stderr\nkuri-fetch -d text https://example.com | wc -w\n```\n\n### Features\n\n- **5 output modes** — `markdown`, `html`, `links`, `text`, `json`\n- **QuickJS JS engine** — `--js` executes inline `\u003cscript\u003e` tags\n- **DOM stubs** — `document.querySelector`, `getElementById`, `window.location`, `document.title`, `console.log`, `setTimeout` (SSR-style)\n- **SSRF defense** — blocks private IPs, metadata endpoints, non-HTTP schemes\n- **Colored output** — respects `NO_COLOR`, `TERM=dumb`, `--no-color`, TTY detection\n- **File output** — `-o` / `--output` with byte count + timing summary\n- **Custom UA** — `--user-agent` flag\n- **Quiet mode** — `-q` suppresses stderr status\n\n---\n\n## 🌐 kuri-browse\n\nInteractive terminal browser — browse the web from your terminal. No Chrome needed.\n\n```bash\nzig build browse   # build + run\n\nkuri-browse https://example.com\n```\n\n```\n🌰 kuri-browse — terminal browser\n→ loading https://example.com\n\n# Example Domain\nThis domain is for use in documentation examples...\nLearn more [1]\n\n───── Links ─────\n  [1] https://iana.org/domains/example\n\n✓ 528 bytes, 1 links (133ms)\n[nav] https://example.com\u003e 1     ← type 1 to follow the link\n```\n\n### Commands\n\n| Command | Action |\n|---------|--------|\n| `\u003cnumber\u003e` | Follow link [N] |\n| `\u003curl\u003e` | Navigate (if contains `.`) |\n| `:go \u003curl\u003e` | Navigate to URL |\n| `:back`, `:b` | Go back in history |\n| `:forward`, `:f` | Go forward |\n| `:reload`, `:r` | Re-fetch current page |\n| `:links`, `:l` | Show link index |\n| `/\u003cterm\u003e` | Search in page (highlights matches) |\n| `:search \u003ct\u003e` | Search in page |\n| `:n`, `:next` | Re-highlight search |\n| `:history` | Show navigation history |\n| `:help`, `:h` | Show all commands |\n| `:quit`, `:q` | Exit |\n\n### Features\n\n- **Colored markdown rendering** — headings, links, code blocks, bold, blockquotes\n- **Numbered links** — every link gets `[N]`, type the number to follow it\n- **Navigation history** — back/forward like a real browser\n- **In-page search** — `/term` highlights all matches\n- **Relative URL resolution** — follows links naturally across pages\n- **Smart filtering** — skips `javascript:` and `mailto:` hrefs\n\n---\n\n## 🤖 kuri-agent\n\nScriptable 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`.\n\n```bash\nzig build agent   # build kuri-agent\n\n# 1. Find a Chrome tab\nkuri-agent tabs\n# → ws://127.0.0.1:9222/devtools/page/ABC123  https://example.com\n\n# 2. Attach to it\nkuri-agent use ws://127.0.0.1:9222/devtools/page/ABC123\n\n# 3. Navigate + interact\nkuri-agent go https://example.com\nkuri-agent snap --interactive        # → [{\"ref\":\"e0\",\"role\":\"link\",\"name\":\"More info\"}]\nkuri-agent click e0\nkuri-agent shot                      # saves ~/.kuri/screenshots/\u003cts\u003e.png\n```\n\n### Commands\n\n| Command | Description |\n|---------|-------------|\n| `tabs [--port N]` | List Chrome tabs |\n| `use \u003cws_url\u003e` | Attach to a tab (saves session) |\n| `open [url] [--port N]` | Open a new tab (optionally navigating to url) |\n| `status` | Show current session |\n| `go \u003curl\u003e` | Navigate to URL |\n| `snap [--interactive] [--json] [--text] [--depth N]` | A11y snapshot, saves `eN` refs |\n| `click \u003cref\u003e` | Click element by ref (CDP mouse events, React-compatible) |\n| `type \u003cref\u003e \u003ctext\u003e` | Type into element (per-character key events, React-compatible) |\n| `fill \u003cref\u003e \u003ctext\u003e` | Fill input value |\n| `select \u003cref\u003e \u003cvalue\u003e` | Select dropdown option |\n| `hover \u003cref\u003e` | Hover over element |\n| `focus \u003cref\u003e` | Focus element |\n| `scroll` | Scroll the page |\n| `viewport [width height]` | Get or set viewport dimensions |\n| `eval \u003cjs\u003e` | Evaluate JavaScript |\n| `text [selector]` | Get page text |\n| `shot [--out file.png]` | Screenshot |\n| `back` | Navigate back |\n| `forward` | Navigate forward |\n| `reload` | Reload current page |\n| `cookies` | List cookies with security flags |\n| `headers` | Check security response headers |\n| `audit` | Full security audit |\n| `storage [local\\|session\\|all]` | Dump localStorage / sessionStorage |\n| `jwt` | Extract and decode JWTs from cookies and storage |\n| `fetch \u003cmethod\u003e \u003curl\u003e [--data \u003cjson\u003e]` | Authenticated fetch using page cookies |\n| `probe \u003curl-template\u003e \u003cstart\u003e \u003cend\u003e` | IDOR probe: iterate numeric IDs in URL |\n| `grab \u003cref\u003e` | Click ref, intercept `window.open`, follow redirect in-tab |\n| `wait-for-tab [--port N]` | Poll for a new tab, auto-switch session |\n| `stealth` | Apply anti-detection patches |\n| `set-header \u003cname\u003e \u003cvalue\u003e` | Add a custom header to all requests |\n| `show-headers` | Show stored extra headers |\n| `clear-headers` | Remove all extra headers |\n---\n\n## 📱 kuri-mobile (iOS + Android)\n\nNative Zig CLI for driving iOS Simulators, real iPhones (listing + launch/terminate), and Android devices/emulators — inspired by [`mobile-device-mcp`](https://github.com/srmorete/mobile-device-mcp), reimplemented in Zig with no Bun/Node/Gradle/Xcode in the build path.\n\n```bash\ncd kuri-mobile \u0026\u0026 zig build \u0026\u0026 cp zig-out/bin/kuri-mobile ../zig-out/bin/\n\n# The main `kuri` binary forwards android/ios subcommands to kuri-mobile:\nkuri ios list-devices                              # sims + real devices (usbmuxd, native)\nkuri ios openurl https://example.com               # navigate Safari\nkuri ios screenshot out.png                        # auto-picks booted sim\nkuri ios launch com.apple.Preferences\n\nkuri android list-devices                          # native Zig adb wire-protocol client\nkuri android tap 540 1200\nkuri android swipe 100 1500 100 500\nkuri android screenshot phone.png\nkuri android uitree                                # flat element list via uiautomator dump\n```\n\n**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.\n**What shells out:** `xcrun simctl` (iOS Simulator), `xcrun devicectl` (iOS real-device launch/terminate).\n**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`](kuri-mobile/README.md) for the full parity matrix vs upstream.\n\n---\n\n## 🔒 Security Testing\n\n`kuri-agent` supports browser-native security trajectories — log in once, then run reconnaissance and header/cookie audits without leaving the terminal.\n\n### Trajectories\n\n**Enumerate → Inspect** — after authenticating, dump auth cookies and check security flags:\n\n```bash\nkuri-agent go https://target.example.com/login\nkuri-agent snap --interactive\nkuri-agent fill e2 myuser\nkuri-agent fill e3 mypassword\nkuri-agent click e4                  # submit login\n\nkuri-agent cookies\n# cookies (3):\n#   session_id  domain=.example.com path=/  [Secure] [HttpOnly] [SameSite=Strict]\n#   csrf_token  domain=.example.com path=/  [Secure] [!HttpOnly]\n#   tracking    domain=.example.com path=/  [!Secure] [!HttpOnly]\n```\n\n**Header audit** — check what security headers the target sends:\n\n```bash\nkuri-agent go https://target.example.com\nkuri-agent headers\n# → {\"url\":\"https://...\",\"status\":200,\"headers\":{\n#     \"content-security-policy\":\"default-src 'self'\",\n#     \"strict-transport-security\":\"max-age=31536000\",\n#     \"x-frame-options\":\"(missing)\",\n#     \"x-content-type-options\":\"nosniff\", ...}}\n```\n\n**Full audit** — HTTPS, missing headers, JS-visible cookies in one shot:\n\n```bash\nkuri-agent audit\n# → {\"protocol\":\"https:\",\"url\":\"https://...\",\"score\":6,\n#     \"issues\":[\"MISSING:x-frame-options\",\"COOKIES_EXPOSED_TO_JS:2\"],\n#     \"headers\":{\"content-security-policy\":\"default-src 'self'\", ...}}\n```\n\n**Cross-account trajectory** — use `eval` to replay API calls with different tokens:\n\n```bash\n# After login, grab the auth token from localStorage\nkuri-agent eval \"localStorage.getItem('token')\"\n\n# Probe a resource ID with the current session\nkuri-agent eval \"fetch('/api/assessments/42').then(r=\u003er.status)\"\n\n# Check for IDOR: does a different user's resource return 200 or 403?\nkuri-agent eval \"fetch('/api/assessments/99').then(r=\u003er.status)\"\n```\n\n### Trajectory Report Format\n\nkuri-agent outputs JSON suitable for pipeline integration. Each security command emits a single JSON line — pipe through `jq` for triage:\n\n```bash\nkuri-agent audit | jq '.issues[]'\nkuri-agent cookies | head -20\nkuri-agent headers | jq '.headers | to_entries[] | select(.value == \"(missing)\") | .key'\n```\n\n---\n\n\n## 🏗 Architecture\n\n```\n┌──────────────────────────────────────────────────────────┐\n│                     HTTP API Layer                        │\n│         (std.http.Server, thread-per-connection)          │\n├──────────────┬──────────────────┬────────────────────────┤\n│   Browser    │  Crawler Engine  │   kuri-fetch / browse   │\n│   Bridge     │                  │   (standalone CLIs)     │\n├──────────────┼──────────────────┼────────────────────────┤\n│ CDP Client   │ URL Validator    │ std.http.Client         │\n│ Tab Registry │ HTML→Markdown    │ QuickJS JS Engine       │\n│ A11y Snapshot│ Link Extractor   │ DOM Stubs (Layer 3)     │\n│ Ref Cache    │ Text Extractor   │ SSRF Validator          │\n│ HAR Recorder │                  │ Colored Renderer        │\n│ Stealth JS   │                  │ History + REPL          │\n├──────────────┴──────────────────┴────────────────────────┤\n│  Chrome Lifecycle Manager                                 │\n│  (launch, health-check, auto-restart, port detection)     │\n└──────────────────────────────────────────────────────────┘\n```\n\n### Memory Model\n\n- **Arena-per-request** — all per-request memory freed in one `deinit()` call\n- **No GC** — `GeneralPurposeAllocator` in debug mode catches every leak\n- **Proper cleanup chains** — `Launcher → Bridge → CdpClients → HarRecorders → Snapshots → Tabs`\n- **`errdefer` guards** — partial failures roll back cleanly\n\n### Chrome Lifecycle\n\n| Mode | Behavior |\n|------|----------|\n| **Managed** (no `CDP_URL`) | Launches Chrome headless, finds free CDP port, supervises, auto-restarts on crash (max 3 retries), kills on shutdown |\n| **External** (`CDP_URL` set) | Connects to existing Chrome, health-checks via `/json/version`, does NOT kill on shutdown |\n\n---\n\n## 📁 Structure\n\n```\nkuri/\n├── build.zig                  # Build system (Zig 0.16.0)\n├── build.zig.zon              # Package manifest + QuickJS dep\n├── src/\n│   ├── main.zig               # CDP server entry point\n│   ├── fetch_main.zig         # kuri-fetch CLI entry point\n│   ├── browse_main.zig        # kuri-browse CLI entry point\n│   ├── js_engine.zig          # QuickJS wrapper + DOM stubs\n│   ├── bench.zig              # Benchmark harness\n│   ├── chrome/\n│   │   └── launcher.zig       # Chrome lifecycle manager\n│   ├── server/\n│   │   ├── router.zig         # HTTP route dispatch (40+ endpoints)\n│   │   ├── middleware.zig     # Auth (constant-time comparison)\n│   │   └── response.zig      # JSON response helpers\n│   ├── bridge/\n│   │   ├── bridge.zig         # Central state (tabs, CDP, HAR, snapshots)\n│   │   └── config.zig         # Env var configuration\n│   ├── cdp/\n│   │   ├── client.zig         # CDP WebSocket client\n│   │   ├── websocket.zig      # WebSocket frame codec\n│   │   ├── protocol.zig       # CDP method constants\n│   │   ├── actions.zig        # High-level CDP actions\n│   │   ├── stealth.zig        # Bot detection bypass\n│   │   └── har.zig            # HAR 1.2 recorder\n│   ├── snapshot/\n│   │   ├── a11y.zig           # A11y tree with interactive filter\n│   │   ├── diff.zig           # Snapshot delta diffing\n│   │   └── ref_cache.zig      # eN ref → node ID cache\n│   ├── crawler/\n│   │   ├── validator.zig      # SSRF defense, URL validation\n│   │   ├── markdown.zig       # HTML → Markdown (SIMD tag counting)\n│   │   ├── fetcher.zig        # Page fetching\n│   │   ├── extractor.zig      # Readability extraction\n│   │   └── pipeline.zig       # Parallel crawl pipeline\n│   ├── storage/\n│   │   ├── local.zig          # Local file writer\n│   │   └── r2.zig             # R2/S3 uploader\n│   ├── util/\n│   │   └── json.zig           # JSON helpers\n│   └── test/\n│       ├── harness.zig        # Test HTTP client\n│       ├── integration.zig    # Integration tests\n│       └── merjs_e2e.zig      # E2E tests\n├── js/\n│   ├── stealth.js             # Bot detection bypass\n│   └── readability.js         # Content extraction\n├── kuri-browser/              # Native Zig rendering experiments\n└── kuri-mobile/               # iOS + Android device control (Zig-native adb + usbmuxd)\n    ├── src/\n    │   ├── common/            # io helpers, unified UI tree parser\n    │   ├── android/           # adb wire protocol client, driver, CLI\n    │   └── ios/               # simctl, usbmuxd, devicectl, CLI\n    └── README.md              # Full parity matrix vs mobile-device-mcp\n```\n\n---\n\n## ⚙️ Configuration\n\n| Env Var | Default | Description |\n|---------|---------|-------------|\n| `HOST` | `127.0.0.1` | Server bind address |\n| `PORT` | `8080` | Server port |\n| `CDP_URL` | *(none)* | Connect to existing Chrome (`ws://...` or `http://127.0.0.1:9222`) |\n| `KURI_SECRET` | *(none)* | Auth secret for API requests |\n| `STATE_DIR` | `.kuri` | Session state directory |\n| `REQUEST_TIMEOUT_MS` | `30000` | HTTP request timeout |\n| `NAVIGATE_TIMEOUT_MS` | `30000` | Navigation timeout |\n| `STALE_TAB_INTERVAL_S` | `30` | Stale tab cleanup interval |\n| `NO_COLOR` | *(none)* | Disable colored CLI output |\n\n---\n\n## 💰 Token Cost\n\nFor a 50-page monitoring task (from Pinchtab benchmarks):\n\n| Method | Tokens | Cost ($) | Best For |\n|--------|--------|----------|----------|\n| `/text` | ~40,000 | $0.20 | Read-heavy (13× cheaper than screenshots) |\n| `/snapshot?filter=interactive\u0026format=compact` | ~40,000 | $0.20 | Low-token element interaction |\n| `/snapshot` (full) | ~525,000 | $2.63 | Full page understanding |\n| `/screenshot` | ~100,000 | $1.00 | Visual verification |\n\n---\n\n## 🤝 Contributing\n\nOpen an issue before submitting a large PR so we can align on the approach.\n\n```bash\ngit clone https://github.com/justrach/kuri.git\ncd kuri\nzig build test         # 252+ tests must pass\nzig build test-fetch   # kuri-fetch tests (69 tests)\nzig build test-browse  # kuri-browse tests (22 tests)\n```\n\nSee [CONTRIBUTORS.md](CONTRIBUTORS.md) for guidelines.\n\n---\n\n## Credits\n\n| Project | What we borrowed |\n|---------|-----------------|\n| [agent-browser](https://github.com/vercel-labs/agent-browser) | `@eN` ref system, snapshot diffing, HAR recording patterns |\n| [Pinchtab](https://github.com/pinchtab/pinchtab) | Browser control architecture for AI agents |\n| [Pathik](https://github.com/justrach/pathik) | High-performance crawling patterns |\n| [QuickJS-ng](https://github.com/nicklausw/quickjs-ng) via [mitchellh/zig-quickjs-ng](https://github.com/mitchellh/zig-quickjs-ng) | JS engine for `kuri-fetch` |\n| [Lightpanda](https://github.com/lightpanda-io/browser) | Zig-native headless browser pioneer, CDP compatibility patterns |\n| [Zig 0.16.0](https://ziglang.org) | The whole stack |\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjustrach%2Fkuri","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjustrach%2Fkuri","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjustrach%2Fkuri/lists"}