kuri

justrach活跃维护

用 Zig 写的浏览器自动化与移动端控制工具,给 AI Agent 提供省 token 的 CDP 快照、HAR 录制和原生 adb 客户端,统一搞定网页抓取和 iOS/Android 设备操作。

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.

v0.6.1发布于 2026-10-09 09:25 UTC368star21fork同步于 2026-11-08 15:03 UTC

怎么上手

由 README 提炼,执行前请确认
curl -fsSL https://kuri.trilok.ai/download | sh
硬件要求按 README 说明

README 未说明显存要求;CDP 模式需本机安装 Chrome/Chromium,独立抓取模式无需浏览器;预编译包支持 macOS arm64/x86_64 与 Linux x86_64/arm64。

支持 Apple Silicon
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

用 Zig 写的浏览器自动化与设备控制工具,为 AI agent 提供低 token 的 CDP 快照和 HTTP 接口

用 Zig 写的浏览器自动化与移动端控制工具,给 AI Agent 提供省 token 的 CDP 快照、HAR 录制和原生 adb 客户端,统一搞定网页抓取和 iOS/Android 设备操作。

核心能力

  • 用 @eN 紧凑引用格式输出无障碍树快照,降低 token 消耗
  • 提供 135 个 HTTP 端点,覆盖导航、点击、截图、HAR 录制等
  • POST /batch 单次调用批量执行多条命令,减少往返
  • 内置原生 adb 协议客户端,可控制 iOS 与 Android 设备
  • kuri-fetch 独立抓取网页,无需 Chrome 运行时

容易踩的坑

从源码构建需 Zig ≥ 0.16.0;CDP 模式需本机有 Chrome/Chromium,否则用 kuri-fetch 独立模式;默认监听 127.0.0.1:8080,可用 KURI_SECRET 开启鉴权。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.6.1
  1. v0.6.1最新正式版2026-10-09 · 2026-10-09 09:25 UTC

    v0.6.1

    Fixes

    • Telemetry skips /health — container liveness probes (Docker/k8s httpGet, curl /health) call /health every few seconds, so they made up almost every recorded request. They are no longer recorded; every other route is recorded as before.

    Note — macOS assets in this release are not signed or notarized. Gatekeeper will quarantine them; clear it with xattr -d com.apple.quarantine <binary>.

  2. v0.6.02026-08-04 · 2026-08-04 02:20 UTC
    查看更新说明

    v0.6.0

    Features

    • Xcode build commands in kuri-mobile — ios list-schemes, build, build-run, test, product, and clean delegate to xcodebuild through the existing toolchain resolution, so they work even when xcode-select points at CommandLineTools. build-run goes from source to a running simulator app in one command: build, resolve the product, install on the booted simulator, launch.
    • Session defaults — ios defaults set|show|clear persists project, scheme, configuration, and udid so the build-family commands can be invoked with no arguments. Explicit flags and positionals always win, and no other command consults the stored values.
    • MCP server — kuri-mobile mcp serves every iOS and Android command plus doctor (79 tools) over MCP stdio, built on the mcp-zig library. The tool registry is generated at comptime from the same tables that render the CLI help, so new commands appear as MCP tools with no extra work. Measured against XcodeBuildMCP 2.7.0 driving the same project: ready to serve in ~9 ms vs ~414 ms, tools/list in ~0.4 ms vs ~10–25 ms, warm incremental build-run ~2.2 s vs ~2.9–3.1 s.

    Removed

    • connect feature and nanostore dependency — the /connect/* routes, kuri-agent connect, kuri-connect-broker, and relay-fetch mode shipped in v0.5.1 are removed from main. They depended on the private justrach/nanostore sibling library, which made every build of kuri require a private checkout plus a NANOSTORE_PAT secret in CI, and its pinned commit did not compile on the project's Zig 0.17.0-dev toolchain. Building kuri now needs only this repo. KURI_VAULT_PASSPHRASE, KURI_RELAY, and KURI_BROKER* no longer have any effect.

    macOS assets in this release are signed and notarized.

  3. v0.5.12026-07-31 · 2026-07-31 09:28 UTC
    查看更新说明

    v0.5.1

    Features

    • Diff-first agent observations — /diff/snapshot and MCP take_snapshot_diff return only page changes after the first observation, with a full-snapshot fallback when navigation replaces the page.
    • Richer snapshot targeting — stable refs, scoped re-capture, optional hierarchy output, and list truncation are available through the HTTP and MCP snapshot surfaces; get_page_state provides a compact orientation payload.
    • HAR capture and interaction replay — API-shaped response capture, CDP action dispatch, and accessibility-signature replay are available through /har/*, /dispatch, and /replay.
    • Anonymous usage telemetry (opt-out) — kuri records aggregate, non-identifying usage signal (route name, method, status, latency, response byte count, platform, version, and a random per-install id) and ships it as an OpenTelemetry-aligned kuri.telemetry.v1 payload. On by default; disable with KURI_NO_TELEMETRY=1 or --no-telemetry. Override the endpoint with KURI_TELEMETRY_URL. Never collects URLs, page content, cookies, selectors, or anything that identifies a user — the query string is stripped before telemetry sees a request. See docs/telemetry.md.

    Fixes

    • Single-sourced versions — kuri, kuri-fetch, kuri-browse, and kuri-mcp now read their version from build.zig.zon through build_options.
    • Background-safe iOS input — tap, swipe, and type operations now require explicit --host-input/--allow-host-input consent (or KURI_ALLOW_HOST_INPUT=1) and simulator window geometry failures report actionable diagnostics.
    • Resilient Chrome/CDP launch — failed DevTools startup retries with a fresh port and stale-lock cleanup, and connection failures surface actionable errors instead of terminating the server abruptly.

    Release verification

    • Built with Zig 0.16.0 on Darwin arm64 from commit a48d24f3f49e75619d5f69d0dde574ad8f7ed46d.
    • macOS arm64 notarization was accepted by Apple as submission b758aa90-b7b2-4e87-af61-4687763d7acf.
    • macOS x86_64 notarization was accepted by Apple as submission 7894cfd0-cfdd-4c45-8cd4-dd617288dee3.
    • The macOS deliverables contain signed bare executables. xcrun stapler returned Error 73 for those standalone binaries, so the archives are notarized but not described as stapled.
  4. v0.4.142026-07-26 · 2026-07-26 04:33 UTC
    查看更新说明

    v0.4.14

    Android element extraction, rebuilt. uitree reported what was in the XML; what an agent needs is what can be acted on, and named the way it reads on screen. Prompted by a comparison against CursorTouch/Android-MCP, whose element handling was better than ours even though kuri's command surface is roughly twice the size.

    Fixes

    • android tap --label never worked. The tool table advertised the flag and android tools printed it, but the dispatcher only ever read positional coordinates — every documented invocation failed with missing argument: x y. It now resolves selectors, and the same selectors work on find
    • No cli.zig test had ever run. main.zig aggregates tests through an explicit _ = @import(...) block and both the Android and iOS dispatchers were missing from it, so their test blocks compiled but never executed and a deliberately failing assertion still reported success. That is how a flag could be advertised and unimplemented at the same time. Both are now in the list
    • uiautomator attribute values were never XML-decoded. A settings row labelled "Network & internet" was reported as Network &amp; internet, so the text an agent read differed from the text on screen and --label "Network & internet" could not match it. Named and numeric references are both decoded now, and an ampersand that is not part of an entity is preserved rather than swallowed
    • Elements with no label were dropped entirely. The filter kept anything clickable or carrying text/id/content-desc, which discards a scrollable RecyclerView or an unlabelled Switch — there was no way to discover the list could be scrolled

    Features — Android

    • android state (alias snapshot). Foreground activity, screen size and every actionable element with tap-ready centroids, in one call instead of three (current-activity, screen-info, uitree) plus manual filtering
    • Interactive-element detection. focusable, clickable, long-clickable, checkable, scrollable, selected and password, plus a narrow widget-class allowlist for apps that ship a Button without setting clickable. uitree --interactive restricts output to these: on a Settings screen that is 72 elements down to 14
    • Names are synthesized from descendants. A clickable row whose label lives in child TextViews — the most common Android list layout there is — used to list as nameless and could not be addressed by label at all. The hierarchy is now parsed with its nesting intact, and a row takes its name from its children, stopping at children that are themselves actionable so their text stays attributed to them
    • Selectors on find and tap: --id, --class, --desc and --index alongside --label, AND-ed so they narrow. --id accepts either btn_login or com.example.app:id/btn_login, and listings print the short form, so what you read is what you can paste back
    • State is reported, not just capability: *checked/*unchecked, *password, *focused, *selected, *scrollable. Whether a switch is on is not something a label can tell you
    • android type --clear replaces a field's contents rather than appending to them. input text only appends, and Android has no select-all keyevent that holds across versions, so the character count comes from the focused element and the deletes go in one round trip
    • android notifications reads posted notifications as package/title/text; --open pulls the shade down instead. Needs no device driver
    • android wait <ms> for the settle wait-for-ui cannot express. Runs without a device attached, so it cannot fail a script with "no device attached"

    Tests

    59 in kuri-mobile, up from 44 — and 8 of the previous 44 were never being executed. Verified live against a headless Android 16 emulator: state, uitree --interactive, the selectors, tap --label (launcher → Messages), type --clear (replaces

  5. v0.4.132026-07-26 · 2026-07-26 03:27 UTC
    查看更新说明

    v0.4.13

    Seven HTTP routes answered with results they never obtained from the browser. Each returned 200 with a well-formed body, so no caller could distinguish "Chrome reported nothing" from "kuri never asked Chrome" — the failure mode that costs the most, because it looks exactly like success.

    Fixes — routes that fabricated their answers

    • /react/tree reported {"react":false} on every React page. React double-buffers fibers, and the __reactContainer$… property is stamped on the host node at mount and keeps pointing at whichever fiber existed then. After the first commit that is the off-screen side of the pair, whose .child is null — so detection concluded there was no React at all. It now follows whichever side actually rendered, via .alternate. Confirmed against React 19.2.5, where the mount-time fiber has hasChild:false while its alternate has children; the route now returns a real tree with FunctionComponent, Context.Consumer and HostComponent nodes at their true depths
    • /trace/stop returned an empty acknowledgement. Tracing.start never asked for transferMode: ReturnAsStream, so Chrome kept the trace to itself and there was no stream handle to drain — the route reported success and wrote nothing, every time. IO.read/IO.close now drain the stream; a routine page load yields ~400 KB of real trace on disk
    • /network returned a canned {"status":"ok"}. No request was ever recorded. Now a bounded ring of real requests carrying url, method and MIME type
    • /mainframe returned the same hard-coded string to every caller, regardless of tab or page. Now the actual frameId, loaderId and url from Page.getFrameTree
    • /expose bindings fired into the void. The binding was registered with Chrome but its invocations were never captured, so /expose/calls stayed empty no matter how many times the page called it. Payloads are now recorded with the call
    • /screencast/stop returned a canned ok without ever reporting what it had captured. Now reports the real frame_count
    • /console and /errors had regressed to returning empty on any page, having lost their collector

    Fixes — script injection

    • /evaluate and /evalhandle JSON-escaped the expression twice. The expression arrives already escaped; escaping it again turned a newline into \n and then \\n, which decodes back to a literal backslash-n in the JavaScript source. Chrome answered every multi-line or quote-containing expression with SyntaxError: Invalid or unexpected token — the two routes whose entire purpose is running arbitrary JS could not run any non-trivial JS. Escaped exactly once now
    • stealth.js polluted every page's error stream. The file was a top-level script containing const originalQuery, but it is injected twice by design — once via Page.addScriptToEvaluateOnNewDocument, once evaluated into the already-loaded page. Re-running it in the same global, which happens whenever tab discovery re-runs against a live tab, re-declared the const. The resulting SyntaxError aborted the whole re-injection at parse time, so none of the stealth measures after it applied, and it surfaced in the page's own error stream, meaning /errors reported a kuri-internal failure as if it were the page's. Now an IIFE behind an idempotence guard, which also stops the permissions wrapper being wrapped around itself. /errors on a clean page is []

    Fixes — memory safety

    • The event rings handed out pointers into storage they were about to overwrite. A snapshot borrowed the ring's own buffers, so the next event that wrapped the ring rewrote memory the caller was still reading — a use-after-free reachable from ordinary traffic. Every ring (RequestRing, ScreencastRing, BindingCallRing, NetworkRing) now dupes into a caller-supplied per-request arena, and the contract is stated once and referenced from each
    • **extractObject/extractField were blind to JSON escap

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Install

    curl -fsSL https://kuri.trilok.ai/download | 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 / ModeChars~TokensNote
    kuri snap (full)8,4992,124All nodes + interactive refs
    kuri snap (interactive only)~3,000~750Best for agent loops
    kuri /page/state19048Lightweight observation (url, title, scroll%, counts)
    agent-browser snap (estimated)~9,183~2,295[ref=e0] format overhead

    Token efficiency: kuri vs agent-browser

    Pagekuri tokensagent-browser tokensSavings
    example.com4035-13% (trivial page, agent-browser skips root)
    Hacker News386~44012% fewer
    Google Flights SIN→TPE2,124~2,2957% 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

    ToolTokens 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)

    kuri vs libretto

    libretto (Playwright + Node) is the closest competitor on per-step token cost. Measured head-to-head on 2026-07-04 — same Chrome, same tab, real tiktoken o200k_base counts (full methodology and reproduction: benchmarks/libretto_comparison.md). The honest split:

    AxisWinnerDetail
    Latency per callkuri4–117 ms vs 1,344–1,500 ms (13–376× faster — persistent server vs Node-per-command)
    Snapshot tokens, typical pagekurisimple 61 vs 151 (2.5×), article 265 vs 363 (1.37×) — tighter grammar
    Snapshot tokens, large listsplitkuri default 4,424 vs 813 — kuri emits all 259 refs, libretto truncates by default. With limit=5 kuri renders 555 tokens (1.46× under libretto), 34 refs + … +45 more markers
    Trajectory (feed, 9 clicks)kuri, barely898 vs 939 tokens (limit=5 base + diff loop vs exec loop) — parity-to-slight-edge; the morning's 5.1× loss was the untruncated base
    Repeat runslibrettocompiles trajectories to a Playwright script → 0-token replays; kuri re-pays the loop every run

    What kuri gained from studying libretto (all shipped this release): a diff-first loop (take_snapshot_diff, ~38 tokens/step); an adaptive diff that falls back to a full snapshot with a ! page replaced header on navigation; identity-only removal lines; screenshots written to disk (path returned, bytes never enter context); get_page_state over MCP; and — after rewriting parseA11yNodes as a real DFS tree walk — opt-in list truncation (/snapshot?limit=N, one … +K more line per capped run), scoped re-capture (scope=@ref), and hierarchy indentation, also exposed as uid/limit on MCP take_snapshot. The 9-click feed trajectory that cost 44,285 tokens with naive full re-snapshots costs 898 with a truncated base + diffs — 49× cheaper, and past libretto's 939.

    The older tables above use a chars/4 token approximation; the libretto comparison uses real tiktoken counts. Rerun cross-tool numbers in your own environment before quoting a percentage.

    Binary size and memory

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

    BinaryCurrent size
    kuri1,093,840 B (1.04 MiB)
    kuri-agent629,904 B (615 KiB)
    kuri-browse1,089,120 B (1.04 MiB)
    kuri-fetch2,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.

    Commandv0.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

    PlatformStatus
    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

    PathDescription
    GET /healthServer status, tab count, version
    GET /tabsList all registered tabs
    GET /discoverAuto-discover Chrome tabs via CDP
    GET /tab/currentGet or set the current tab for an X-Kuri-Session
    GET /page/infoLive URL/title/ready-state/viewport/scroll for the active tab
    GET /page/stateCompact page observation: url, title, scroll%, viewport, counts of forms/links/images/inputs
    POST /batchExecute multiple commands in one HTTP call — returns array of results
    GET /browdie🌰 (easter egg)

    Browser Control

    PathParamsDescription
    GET /navigatetab_id, urlNavigate tab to URL
    GET /tab/newurl, activate, waitCreate a new tab and optionally hydrate/set current tab
    GET /tab/closetab_idClose a tab
    GET /window/newurl, activate, waitCreate a new window/tab target
    GET /snapshottab_id, filter, formatA11y tree snapshot with eN refs. Use filter=interactive&format=compact for low-token agent loops.
    GET /texttab_idExtract page text
    GET /screenshottab_id, format, quality, saveCapture screenshot (base64); save=true writes the PNG to STATE_DIR/screenshots and returns {path,bytes} instead
    GET /screenshot/annotatedtab_idScreenshot with numbered element labels
    GET /screenshot/difftab_id, baselineVisual diff between current and baseline screenshots
    GET /actiontab_id, ref, action, valueClick/type/fill/select/scroll/hover/dblclick/check/uncheck/blur by ref
    GET /evaluatetab_id, expressionExecute JavaScript
    GET /evalhandletab_id, expressionExecute JS, return objectId handle (not value)
    GET /closetab_idClose tab + cleanup
    GET /bringtofronttab_idBring tab to front

    Actions

    PathParamsDescription
    GET /clearrefClear input field value
    GET /selectallrefSelect all text in input/contenteditable
    GET /setvalueref, valueSet input value directly (bypasses key events)
    GET /dispatchref, typeDispatch custom DOM event on element
    GET /boundingboxrefGet element bounding rect (x, y, width, height, centerX, centerY)
    GET /getattributeref, nameGet element attribute by name
    GET /inputvaluerefGet current input element value
    GET /element/stateref, checkQuick boolean: exists, visible, enabled, checked
    GET /find-elementtext/role/label/placeholder/testidSemantic locator — find element without snap
    GET /highlightref or selectorHighlight element with overlay

    Mouse & Touch

    PathParamsDescription
    GET /mouse/movex, yMove mouse to coordinates
    GET /mouse/downx, y, buttonMouse button down
    GET /mouse/upx, y, buttonMouse button up
    GET /mouse/wheelx, y, deltaX, deltaYMouse wheel scroll
    GET /tapx, yTouch tap (touchStart + touchEnd)
    GET /swipestartX, startY, endX, endYTouch swipe gesture
    GET /dragsrc_ref, tgt_refDrag element to target

    Keyboard

    PathParamsDescription
    GET /keyboard/typetab_id, textType text via key events
    GET /keyboard/inserttexttab_id, textInsert text directly
    GET /keydowntab_id, keyKey down event
    GET /keyuptab_id, keyKey up event

    Content Extraction

    PathDescription
    GET /markdownConvert page to Markdown
    GET /linksExtract all links
    GET /dom/queryCSS selector query
    GET /dom/htmlGet element HTML
    GET /dom/attributesGet element attributes
    GET /pdfPrint page to PDF
    GET /findIn-page text search

    Waiting

    PathParamsDescription
    GET /waitselector, text, url, state, visible, timeoutWait for selector/text/URL pattern/networkidle/load state
    GET /wait/functionexpression, timeoutWait for arbitrary JS expression to be truthy
    GET /wait/downloadtimeoutWait for file download to complete

    Dialog Handling

    PathDescription
    GET /dialog/autoAuto-handle all JS dialogs (accept or dismiss)
    GET /dialog/acceptAccept current dialog (with optional prompt text)
    GET /dialog/dismissDismiss current dialog

    Network & HAR

    PathDescription
    GET /har/startStart recording network traffic
    GET /har/stopStop + return HAR 1.2 JSON
    GET /har/statusRecording state + entry count
    GET /har/replayAPI map with curl/fetch/python code snippets
    GET /cookiesGet cookies
    GET /cookies/setSet cookies
    GET /cookies/deleteDelete cookies
    GET /cookies/clearClear all cookies
    GET /headersSet custom request headers
    GET /intercept/startStart request interception
    GET /intercept/stopStop request interception
    GET /intercept/requestsList intercepted requests
    GET /request/detailGet response body for a request ID
    GET /response/bodyFetch URL and return response body
    GET /networkNetwork traffic stats
    GET /downloadTrigger file download

    Navigation & State

    PathDescription
    GET /backBrowser back
    GET /forwardBrowser forward
    GET /reloadReload page
    GET /stopStop page loading
    GET /pushstateSPA navigation via history.pushState
    GET /storage/localGet/set localStorage
    GET /storage/sessionGet/set 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 sessions
    GET /setcontentSet page HTML directly (POST)

    Auth Profiles

    PathDescription
    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 /auth/extractExtract auth tokens (JWT, cookies, headers)
    GET /set/credentialsSet HTTP basic auth credentials

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

    Emulation

    PathParamsDescription
    GET /emulatedevice type, screen sizeDevice emulation
    GET /set/viewportwidth, heightSet viewport size
    GET /set/useragentuaSet user agent
    GET /set/mediamediaEmulate media type
    GET /set/offlineofflineToggle offline mode
    GET /geolocationlat, lngOverride geolocation
    GET /timezonetimezoneOverride timezone (e.g. America/New_York)
    GET /localelocaleOverride locale (e.g. en-US)
    GET /permissionsname, stateGrant/deny permissions (geolocation, notifications, clipboard)

    Scripts & Injection

    PathDescription
    GET /script/injectInject JavaScript into page (persists across nav)
    GET /initscript/removeRemove a previously injected init script
    GET /addstyleInject CSS stylesheet
    GET /exposeExpose a named function to page JS context

    React Inspection

    PathDescription
    GET /react/treeReact component tree via DevTools hook
    GET /react/inspectReact component props and state
    GET /react/rendersReact render tracking (start/stop)
    GET /react/suspenseReact Suspense boundary status

    Recording & Performance

    PathDescription
    GET /recording/startRecord user actions (click, input, navigate)
    GET /recording/stopStop recording + return action log
    GET /vitalsCore Web Vitals (LCP, CLS, FID, TTFB, FCP, domInteractive)
    GET /perf/lcpLargest Contentful Paint timing
    GET /trace/startStart performance trace
    GET /trace/stopStop trace
    GET /profiler/startStart JS profiler
    GET /profiler/stopStop profiler

    Debugging

    PathDescription
    GET /debug/enableEnable in-page debug HUD and optional freeze mode
    GET /debug/disableDisable in-page debug HUD
    GET /inspectElement inspection
    GET /errorsCollect JS errors
    GET /consoleRead console logs
    GET /framesList page frames
    GET /frameSwitch to iframe context by name or URL
    GET /mainframeSwitch back to main frame
    GET /diff/snapshotCompact +/~/- diff vs the previous call for this tab — the token-efficient action loop (aliased by /snapshot/changes). Falls back to a full snapshot with a ! page replaced header on mass change.
    GET /diff/urlCompare two URLs side by side (navigate, snapshot, diff)

    Streaming

    PathDescription
    GET /screencast/startStart screen recording
    GET /screencast/stopStop screen recording
    GET /video/startStart video capture
    GET /video/stopStop video capture
    GET /ws/startWebSocket tunnel start
    GET /ws/stopWebSocket 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

    PathDescription
    GET /diff/snapshotCompact +/~/- delta vs the previous snapshot (agent action loop)
    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.0)
    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)
    open [url] [--port N]Open a new tab (optionally navigating to url)
    statusShow 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
    scrollScroll the page
    viewport [width height]Get or set viewport dimensions
    eval <js>Evaluate JavaScript
    text [selector]Get page text
    shot [--out file.png]Screenshot
    backNavigate back
    forwardNavigate forward
    reloadReload current page
    cookiesList cookies with security flags
    headersCheck security response headers
    auditFull security audit
    storage [local|session|all]Dump localStorage / sessionStorage
    jwtExtract 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
    stealthApply anti-detection patches
    set-header <name> <value>Add a custom header to all requests
    show-headersShow stored extra headers
    clear-headersRemove 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

    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.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 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&format=compact~40,000$0.20Low-token element 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         # 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

    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.16.0The whole stack

    License

    Apache-2.0