Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
kuri — 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. | Kitploit
Tools/GitHubGitHub/justrach/kuri
Information GatheringWeb SecurityPenetration TestingMobile SecurityCrawler
GitHubjustrach/kuri

kuri

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.

View Repository
350184411 days agoReviewed by Kitploit

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Website
Share
<p align="center">
  <img src="https://assets.kitploit.com/production/public/readmes/13639/7915bba5fb25229c11f59482cfcd4a0eaf2945ee066dbf5cec395df880b53268.png" alt="Kuri" width="200" />
</p>

<h1 align="center">Kuri 🌰</h1>

<p align="center">
  <a href="https://kuri.trilok.ai/download/latest.json"><img src="https://img.shields.io/badge/stable-v0.6.1-brightgreen?style=flat-square" alt="Stable release"></a>
  <a href="https://github.com/justrach/kuri/blob/main/LICENSE"><img src="https://img.shields.io/github/license/justrach/kuri?style=flat-square" alt="License"></a>
  <img src="https://img.shields.io/badge/zig-0.16.0-f7a41d?style=flat-square" alt="Zig">
  <img src="https://img.shields.io/badge/node__modules-0_files-brightgreen?style=flat-square" alt="node_modules">
  <img src="https://img.shields.io/badge/status-stable-brightgreen?style=flat-square" alt="status">
</p>

## Install

```sh
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](https://kuri.trilok.ai/download/v0.6.1/kuri-v0.6.1-aarch64-macos.tar.gz) · [macOS x86_64](https://kuri.trilok.ai/download/v0.6.1/kuri-v0.6.1-x86_64-macos.tar.gz) · [Linux x86_64](https://kuri.trilok.ai/download/v0.6.1/kuri-v0.6.1-x86_64-linux.tar.gz) · [Linux arm64](https://kuri.trilok.ai/download/v0.6.1/kuri-v0.6.1-aarch64-linux.tar.gz)

---

**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](#-quick-start) · [Benchmarks](#-benchmarks) · [kuri-agent](#-kuri-agent) · [Security Testing](#-security-testing) · [API](#-http-api) · [Skills](#-skills) · [Changelog](https://github.com/justrach/kuri/blob/main/CHANGELOG.md)

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

---

## Why Kuri Wins for Agents

Most browser tooling was built for QA engineers. Kuri is built for agent loops: read the page, keep token cost low, act on stable refs, and move on.

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

### Snapshot tokens: Google Flights `SIN → TPE`

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

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

### Token efficiency: kuri vs agent-browser

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

The savings come from kuri's compact format:
- `@e0` refs (3 chars) vs `[ref=e0]` (9 chars)
- No `- ` prefix per line (saves 2 chars × line count)
- Same indentation, same node filtering

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

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

### kuri vs libretto

[libretto](https://github.com/saffron-health/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](https://github.com/justrach/kuri/blob/main/benchmarks/libretto_comparison.md)**). The honest split:

| Axis | Winner | Detail |
|---|---|---|
| Latency per call | **kuri** | 4–117 ms vs 1,344–1,500 ms (**13–376× faster** — persistent server vs Node-per-command) |
| Snapshot tokens, typical page | **kuri** | simple 61 vs 151 (2.5×), article 265 vs 363 (1.37×) — tighter grammar |
| Snapshot tokens, large list | split | kuri 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**, barely | 898 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 runs | **libretto** | compiles 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`.

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

### RSS stayed flat across the Zig 0.16 migration

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

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

## The Problem

Every browser automation tool drags in Playwright (~300 MB), a Node.js runtime, and a cascade of npm dependencies. Your AI agent just wants to read a page, click a button, and move on.
**Kuri is a single Zig binary.** Four modes, zero runtime:

```
kuri           →  CDP server (Chrome automation, a11y snapshots, HAR)
kuri-fetch     →  standalone fetcher (no Chrome, QuickJS for JS, ~2 MB)
kuri-browse    →  interactive terminal browser (navigate, follow links, search)
kuri-agent     →  agentic CLI (scriptable Chrome automation + security testing)
```

---
## 📦 Installation

### One-line install (macOS / Linux)

```sh
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

```sh
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](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`.

Stable install URL:

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

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

### Platform support

| Platform | Status |
|---|---|
| macOS (`aarch64`, `x86_64`) | Prebuilt binaries, signed + notarized |
| Linux (`aarch64`, `x86_64`) | Prebuilt binaries |
| Windows (`x86_64`) | **Experimental — cross-compile only.** `zig build -Dtarget=x86_64-windows-gnu` is CI-verified, but Chrome automation, daemonization, signal-based shutdown, HAR recording, and the file-backed auth store are all stubbed with `error.UnsupportedOnWindows` at runtime. Use **WSL2** if you need the real feature set. Tracked at [#153](https://github.com/justrach/kuri/issues/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](https://github.com/justrach/kuri/issues/153) or open a PR.

### Build from source

Requires [Zig ≥ 0.16.0](https://ziglang.org/download/).

```bash
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](https://ziglang.org/download/) · Chrome/Chromium (for CDP mode)

```bash
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

```bash
# 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.

```bash
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:

```bash
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

```bash
# 1. Discover Chrome tabs
curl -s http://localhost:8080/discover
# → {"discovered":1,"total_tabs":1}

# 2. Get tab ID
curl -s http://localhost:8080/tabs
# → [{"id":"ABC123","url":"chrome://newtab/","title":"New Tab"}]

# 3. Navigate
curl -s "http://localhost:8080/navigate?tab_id=ABC123&url=https://vercel.com"

# 4. Get accessibility snapshot (token-optimized for LLMs)
curl -s "http://localhost:8080/snapshot?tab_id=ABC123&filter=interactive"
# → [{"ref":"e0","role":"link","name":"VercelLogotype"},
#    {"ref":"e1","role":"button","name":"Ask AI"}, ...]
```

---

## 🌐 HTTP API

All endpoints return JSON. Optional auth via `KURI_SECRET` env var. **135 endpoints** — full parity with agent-browser and browser-use.

### Core

| Path | Description |
|------|-------------|
| `GET /health` | Server status, tab count, version |
| `GET /tabs` | List all registered tabs |
| `GET /discover` | Auto-discover Chrome tabs via CDP |
| `GET /tab/current` | Get or set the current tab for an `X-Kuri-Session` |
| `GET /page/info` | Live URL/title/ready-state/viewport/scroll for the active tab |
| `GET /page/state` | Compact page observation: url, title, scroll%, viewport, counts of forms/links/images/inputs |
| `POST /batch` | Execute multiple commands in one HTTP call — returns array of results |
| `GET /browdie` | 🌰 (easter egg) |

### Browser Control

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

### Actions

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

### Mouse & Touch

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

### Keyboard

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

### Content Extraction

| Path | Description |
|------|-------------|
| `GET /markdown` | Convert page to Markdown |
| `GET /links` | Extract all links |
| `GET /dom/query` | CSS selector query |
| `GET /dom/html` | Get element HTML |
| `GET /dom/attributes` | Get element attributes |
| `GET /pdf` | Print page to PDF |
| `GET /find` | In-page text search |

### Waiting

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

### Dialog Handling

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

### Network & HAR

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

### Navigation & State

| Path | Description |
|------|-------------|
| `GET /back` | Browser back |
| `GET /forward` | Browser forward |
| `GET /reload` | Reload page |
| `GET /stop` | Stop page loading |
| `GET /pushstate` | SPA navigation via history.pushState |
| `GET /storage/local` | Get/set localStorage |
| `GET /storage/session` | Get/set sessionStorage |
| `GET /storage/local/clear` | Clear localStorage |
| `GET /storage/session/clear` | Clear sessionStorage |
| `GET /session/save` | Save browser session |
| `GET /session/load` | Restore browser session |
| `GET /session/list` | List saved sessions |
| `GET /setcontent` | Set page HTML directly (POST) |

### Auth Profiles

| Path | Description |
|------|-------------|
| `GET /auth/profile/save` | Save cookies + storage as a named auth profile |
| `GET /auth/profile/load` | Restore a named auth profile into a tab |
| `GET /auth/profile/list` | List saved auth profiles |
| `GET /auth/profile/delete` | Delete a saved auth profile |
| `GET /auth/extract` | Extract auth tokens (JWT, cookies, headers) |
| `GET /set/credentials` | Set HTTP basic auth credentials |

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

### Emulation

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

### Scripts & Injection

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

### React Inspection

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

### Recording & Performance

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

### Debugging

| Path | Description |
|------|-------------|
| `GET /debug/enable` | Enable in-page debug HUD and optional freeze mode |
| `GET /debug/disable` | Disable in-page debug HUD |
| `GET /inspect` | Element inspection |
| `GET /errors` | Collect JS errors |
| `GET /console` | Read console logs |
| `GET /frames` | List page frames |
| `GET /frame` | Switch to iframe context by name or URL |
| `GET /mainframe` | Switch back to main frame |
| `GET /diff/snapshot` | Compact `+`/`~`/`-` 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/url` | Compare two URLs side by side (navigate, snapshot, diff) |

### Streaming

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

### Agent-friendly loop

The lowest-friction server loop is:

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

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

## 🧠 Skills

The repo includes a user-extensible skill area:

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

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

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

For the experimental browser CLI:

```bash
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:

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

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

### Advanced

| Path | Description |
|------|-------------|
| `GET /diff/snapshot` | Compact `+`/`~`/`-` delta vs the previous snapshot (agent action loop) |
| `GET /emulate` | Device emulation |
| `GET /geolocation` | Set geolocation |
| `POST /upload` | File upload |
| `GET /script/inject` | Inject JavaScript |
| `GET /intercept/start` | Start request interception |
| `GET /intercept/stop` | Stop interception |
| `GET /screenshot/annotated` | Screenshot with element annotations |
| `GET /screenshot/diff` | Visual diff between screenshots |
| `GET /screencast/start` | Start screencast |
| `GET /screencast/stop` | Stop screencast |
| `GET /video/start` | Start video recording |
| `GET /video/stop` | Stop video recording |
| `GET /console` | Get console messages |
| `GET /stop` | Stop page loading |
| `GET /get` | Direct HTTP fetch (server-side) |
| `GET /scrollintoview` | Scroll a referenced element into view |
| `GET /drag` | Drag from one ref to another |
| `GET /keyboard/type` | Type text with key events |
| `GET /keyboard/inserttext` | Insert text directly |
| `GET /keydown` | Dispatch a keydown event |
| `GET /keyup` | Dispatch a keyup event |
| `GET /wait` | Wait for ready state or element conditions |
| `GET /tab/close` | Close a tab |
| `GET /highlight` | Highlight an element by ref or selector |
| `GET /errors` | Get page/runtime errors |
| `GET /set/offline` | Toggle offline network emulation |
| `GET /set/media` | Set emulated media features |
| `GET /set/credentials` | Set HTTP basic auth credentials |
| `GET /find` | Find text matches in the current page |
| `GET /trace/start` | Start Chrome tracing |
| `GET /trace/stop` | Stop tracing and return trace data |
| `GET /profiler/start` | Start JS profiler |
| `GET /profiler/stop` | Stop JS profiler |
| `GET /inspect` | Inspect an element or page state |
| `GET /set/viewport` | Set viewport size |
| `GET /set/useragent` | Override user agent |
| `GET /dom/attributes` | Get element attributes |
| `GET /frames` | List frame tree |
| `GET /network` | Inspect network state/requests |

---

## 🛡️ Stealth & Bot Evasion

Kuri applies anti-detection patches automatically on startup — no manual config needed.

### What's applied

- **`Page.addScriptToEvaluateOnNewDocument`** — stealth patches run before any page JS
- **navigator.webdriver = false** — hides automation flag at Chromium level (`--disable-blink-features=AutomationControlled`)
- **WebGL/Canvas/AudioContext spoofing** — defeats fingerprint-based detection
- **UA rotation** — 5 realistic Chrome/Safari/Firefox user agents
- **chrome.csi/chrome.loadTimes** — stubs for Akamai-specific checks

### Bot block detection

Navigate auto-detects blocks and returns structured fallback:

```bash
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

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

### Tested sites

| Site | Protection | Result |
|------|-----------|--------|
| Singapore Airlines | Akamai WAF | ✅ Bypassed (was blocked before v0.4.0) |
| Shopee SG | Custom anti-fraud | ✅ Page loads, redirects to login |
| Google Flights | None | ✅ Full interaction |
| Booking.com | PerimeterX | ⚠️ Needs proxy |

---

## 🔧 kuri-fetch

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

```bash
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.

```bash
zig build browse   # build + run

kuri-browse https://example.com
```

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

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

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

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

### Commands

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

### Features

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

---

## 🤖 kuri-agent

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

```bash
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
```

---

[Read more](https://github.com/justrach/kuri)
Download Tool