
AI 에이전트를 위한 브라우저 자동화, 웹 크롤링, iOS + Android 기기 제어. Zig 네이티브, 토큰 효율적인 CDP 스냅샷, HAR 기록, 네이티브 adb 와이어 프로토콜 클라이언트, 그리고 독립형 페처.
<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>
## 설치
```sh
curl -fsSL https://kuri.trilok.ai/download | sh
```
macOS arm64/x86_64 및 Linux x86_64/arm64. 단일 바이너리, 런타임 의존성 없음.
직접 다운로드: [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)
---
**AI 에이전트를 위한 브라우저 자동화 및 웹 크롤링. Zig로 작성됨. Node.js 제로.**
CDP 자동화 · A11y 스냅샷 · HAR 녹화 · 독립형 페처 · 대화형 터미널 브라우저 · 에이전틱 CLI · 보안 테스트 · iOS + Android 기기 제어
[빠른 시작](#-quick-start) · [벤치마크](#-benchmarks) · [kuri-agent](#-kuri-agent) · [보안 테스트](#-security-testing) · [API](#-http-api) · [스킬](#-skills) · [변경 로그](https://github.com/justrach/kuri/blob/main/CHANGELOG.md)
> **팀들이 Kuri로 전환하는 이유:** 현재 Apple Silicon `ReleaseFast` 빌드는 바이너리당 2 MB 미만을 유지하며, 2026-04-23에 새로 실행한 Google Flights 재실행에서 전체 `kuri-agent` 루프(`go→snap→click→snap→eval`)에 대해 **3,392 토큰**이 측정되었습니다. 백분율을 인용하기 전에 도구 간 차이는 동일한 환경에서 다시 실행해야 합니다.
---
## 에이전트에게 Kuri가 뛰어난 이유
대부분의 브라우저 도구는 QA 엔지니어를 위해 만들어졌습니다. Kuri는 에이전트 루프를 위해 만들어졌습니다: 페이지를 읽고, 토큰 비용을 낮게 유지하고, 안정적인 ref에 대해 동작하고, 다음으로 넘어갑니다.
- **135개의 HTTP 엔드포인트** — React 검사부터 Core Web Vitals까지 agent-browser 및 browser-use와 완전한 동등성.
- 실제 페이지에서 agent-browser보다 **7-12% 더 적은 토큰** — `@eN` ref 형식과 제로 프리픽스 렌더링 덕분.
- 동일한 Google Flights 페이지에 대해 전체 스냅샷(2,124 토큰) 대비 `/page/state`(48 토큰)로 **44배 더 가벼운 관찰**.
- **배치 실행** — `POST /batch`는 하나의 HTTP 호출로 N개의 명령을 보내 N-1번의 왕복과 N-1번의 LLM 턴을 제거합니다.
- **React 호환** — 신뢰할 수 있는 CDP 마우스 이벤트와 문자별 키 이벤트가 React 18/19의 `onClick`과 `onChange`를 발생시킵니다.
### 스냅샷 토큰: Google Flights `SIN → TPE`
이 워크스페이스에서 2026-05-24에 새로 재실행하여 `wc -c`와 `chars/4` 근사치로 측정.
| 도구 / 모드 | 문자 수 | ~토큰 | 비고 |
|---|---:|---:|---|
| `kuri snap` (전체) | 8,499 | **2,124** | 모든 노드 + 대화형 ref |
| `kuri snap` (대화형만) | ~3,000 | **~750** | 에이전트 루프에 최적 |
| `kuri /page/state` | 190 | **48** | 경량 관찰 (url, title, scroll%, counts) |
| agent-browser snap (추정) | ~9,183 | **~2,295** | `[ref=e0]` 형식 오버헤드 |
### 토큰 효율성: kuri vs agent-browser
| 페이지 | kuri 토큰 | agent-browser 토큰 | 절감 |
|---|---:|---:|---|
| example.com | 40 | 35 | -13% (사소한 페이지, agent-browser는 루트를 건너뜀) |
| Hacker News | 386 | ~440 | **12% 더 적음** |
| Google Flights SIN→TPE | 2,124 | ~2,295 | **7% 더 적음** |
절감은 kuri의 간결한 형식에서 비롯됩니다:
- `@e0` ref (3자) vs `[ref=e0]` (9자)
- 줄당 `- ` 프리픽스 없음 (줄 수 × 2자 절약)
- 동일한 들여쓰기, 동일한 노드 필터링
### 전체 워크플로 비용: `go → snap → click → snap → eval`
| 도구 | 사이클당 토큰 |
|---|---:|
| **kuri-agent** | **~3,400** |
| 두 번째 snap 대신 `/page/state` 사용 | **~1,700** |
| `POST /batch` 사용 (모두 한 번의 호출로) | **~1,700** (동일한 토큰, 5번 대신 1번의 HTTP 호출) |
### kuri vs libretto
[libretto](https://github.com/saffron-health/libretto) (Playwright + Node)는 단계별 토큰 비용에서 가장 가까운 경쟁자입니다. 2026-07-04에 직접 비교 측정 — 동일한 Chrome, 동일한 탭, 실제 `tiktoken` `o200k_base` 카운트 (전체 방법론 및 재현: **[benchmarks/libretto_comparison.md](https://github.com/justrach/kuri/blob/main/benchmarks/libretto_comparison.md)**). 솔직한 비교:
| 축 | 승자 | 세부 사항 |
|---|---|---|
| 호출당 지연 시간 | **kuri** | 4–117 ms vs 1,344–1,500 ms (**13–376배 빠름** — 영구 서버 vs 명령당 Node) |
| 스냅샷 토큰, 일반 페이지 | **kuri** | 단순 61 vs 151 (2.5배), 기사 265 vs 363 (1.37배) — 더 간결한 문법 |
| 스냅샷 토큰, 대형 목록 | 분할 | kuri 기본 4,424 vs 813 — kuri는 259개 ref를 모두 출력, libretto는 기본적으로 잘라냄. `limit=5`로 kuri는 555 토큰 렌더링 (**libretto보다 1.46배 적음**), 34개 ref + `… +45 more` 마커 |
| 궤적 (피드, 9회 클릭) | **kuri**, 근소하게 | 898 vs 939 토큰 (`limit=5` 기반 + diff 루프 vs exec 루프) — 동등하거나 약간 우세; 아침의 5.1배 손실은 잘리지 않은 기반이었음 |
| 반복 실행 | **libretto** | 궤적을 Playwright 스크립트로 컴파일 → 0-토큰 재생; kuri는 매 실행마다 루프 비용을 다시 지불 |
**kuri가 libretto를 연구하며 얻은 것** (이번 릴리스에 모두 포함): diff 우선 루프 (`take_snapshot_diff`, 단계당 ~38 토큰); 탐색 시 `! page replaced` 헤더와 함께 전체 스냅샷으로 폴백하는 적응형 diff; 아이덴티티 전용 제거 줄; 디스크에 기록되는 스크린샷 (경로 반환, 바이트는 컨텍스트에 들어가지 않음); MCP를 통한 `get_page_state`; 그리고 `parseA11yNodes`를 실제 DFS 트리 순회로 재작성한 후 — **선택적 목록 자르기** (`/snapshot?limit=N`, 잘린 실행당 하나의 `… +K more` 줄), **범위 지정 재캡처** (`scope=@ref`), **계층 들여쓰기**, MCP `take_snapshot`의 `uid`/`limit`으로도 노출됨. 순진한 전체 재스냅샷으로 44,285 토큰이 들었던 9회 클릭 피드 궤적은 잘린 기반 + diff로 **898**이 듦 — 49배 저렴하며, libretto의 939를 넘어섰음.
> 위의 이전 표는 `chars/4` 토큰 근사치를 사용하며, libretto 비교는 실제 `tiktoken` 카운트를 사용합니다. 백분율을 인용하기 전에 자신의 환경에서 도구 간 수치를 다시 실행하세요.
### 바이너리 크기 및 메모리
Apple M4 Pro, macOS 26.4.1에서 측정. 현재 바이너리는 `-Doptimize=ReleaseFast`로 빌드되었습니다.
| 바이너리 | 현재 크기 |
|---|---:|
| `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) |
### Zig 0.16 마이그레이션 전반에 걸쳐 RSS는 평탄하게 유지됨
현재 `v0.4.3` `ReleaseFast` 빌드에 대해 `/usr/bin/time -l`로 측정.
| 명령 | `v0.4.3` 평균 최대 RSS |
|---|---:|
| `kuri-fetch --version` | ~2.45 MiB |
| `kuri-browse --version` | ~2.45 MiB |
| `kuri-fetch --quiet --dump markdown http://example.com/` | ~9.17 MiB |
## 문제
모든 브라우저 자동화 도구는 Playwright(~300 MB), Node.js 런타임, 그리고 수많은 npm 의존성을 끌어들입니다. 당신의 AI 에이전트는 그저 페이지를 읽고, 버튼을 클릭하고, 다음으로 넘어가길 원할 뿐입니다.
**Kuri는 단일 Zig 바이너리입니다.** 네 가지 모드, 런타임 제로:
```
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)
```
---
## 📦 설치
### 원라인 설치 (macOS / Linux)
```sh
curl -fsSL https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.sh | sh
```
플랫폼을 감지하고, 올바른 바이너리를 다운로드하여 `~/.local/bin`에 설치합니다.
다운로드는 Kuri가 자체 관리하는 `release-channel` 브랜치에서 이루어집니다. macOS 바이너리는 Developer ID 인증서로 로컬 서명됩니다. GitHub Release 에셋은 이와 동일한 tarball을 미러링합니다.
### bun / npm
```sh
bun install -g kuri-agent
# or: npm install -g kuri-agent
```
설치 시 플랫폼에 맞는 네이티브 바이너리를 다운로드합니다.
### 릴리스 채널
Kuri의 안정 바이너리는 `release-channel` 브랜치에 있으며 GitHub raw URL에서 직접 제공됩니다.
- 안정 설치 프로그램: `https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.sh`
- 안정 매니페스트: `https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/latest.json`
- 브랜치 보기: `https://github.com/justrach/kuri/tree/release-channel/stable`
- 직접 다운로드 패턴: `https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/<version>/kuri-<version>-<target>.tar.gz`
### 수동 설치
[안정 릴리스 매니페스트](https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/latest.json) 또는 [GitHub Releases 페이지](https://github.com/justrach/kuri/releases)에서 플랫폼에 맞는 tarball을 다운로드한 후 `$PATH`에 압축을 풀어주세요.
안정 설치 URL:
```sh
curl -fsSL https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.sh | sh
```
매니페스트에는 `aarch64-linux`, `x86_64-linux`, `aarch64-macos`, `x86_64-macos`에 대한 정확한 에셋 URL과 SHA-256 체크섬이 포함되어 있습니다.
### 플랫폼 지원
| 플랫폼 | 상태 |
|---|---|
| macOS (`aarch64`, `x86_64`) | 사전 빌드된 바이너리, 서명 + 공증됨 |
| Linux (`aarch64`, `x86_64`) | 사전 빌드된 바이너리 |
| Windows (`x86_64`) | **실험적 — 크로스 컴파일 전용.** `zig build -Dtarget=x86_64-windows-gnu`는 CI에서 검증되지만, Chrome 자동화, 데몬화, 시그널 기반 종료, HAR 기록, 파일 기반 인증 저장소는 모두 런타임에 `error.UnsupportedOnWindows`로 스텁 처리되어 있습니다. 실제 기능 세트가 필요하면 **WSL2**를 사용하세요. [#153](https://github.com/justrach/kuri/issues/153)에서 추적 중입니다. |
Kuri는 여러 부분에서 POSIX 기본 요소(`fork`, `clock_gettime`, raw 소켓)에 의존하므로 완전한 네이티브 Windows 포팅은 상당한 작업입니다. 위의 컴파일 수준 기준선은 `--version`/`--help` 경로와 순수 인메모리 작업이 실행되도록 해주지만, 까다로운 부분(Chrome, 소켓, 데몬화)은 스텁 목록에서 벗어나려면 실제 Win32 구현이 필요합니다. 이 중 하나라도 맡고 싶다면 [#153](https://github.com/justrach/kuri/issues/153)에 +1을 남기거나 PR을 열어주세요.
### 소스에서 빌드
[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
```
---
## ⚡ 빠른 시작
**요구 사항:** [Zig ≥ 0.16.0](https://ziglang.org/download/) · Chrome/Chromium (CDP 모드용)
```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)
```
### 첫 실행, 최단 경로
```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
```
### 세션 우선 에이전트 루프
에이전트 스타일의 HTTP 사용에서는 매 호출마다 `tab_id`를 반복하는 대신 세션 헤더와 `/tab/new`, `/page/info`, `/snapshot`을 사용하는 것이 좋습니다.
```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"
```
`tools/kuri_harness.py`에는 동일한 HTTP 표면 위에 Python 헬퍼를 사용하고 싶을 때 쓸 수 있는 얇은 실험적 래퍼도 있습니다.
이미 원격 디버깅이 활성화된 Chrome이 실행 중이라면, `CDP_URL`을 WebSocket 또는 HTTP 엔드포인트 중 하나로 설정하세요:
```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
```
### 4개의 명령어로 vercel.com 둘러보기
```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
모든 엔드포인트는 JSON을 반환합니다. `KURI_SECRET` 환경 변수를 통한 선택적 인증을 지원합니다. **135개의 엔드포인트** — agent-browser 및 browser-use와 완전한 동등성을 제공합니다.
### Core
| Path | Description |
|------|-------------|
| `GET /health` | 서버 상태, 탭 수, 버전 |
| `GET /tabs` | 등록된 모든 탭 목록 |
| `GET /discover` | CDP를 통해 Chrome 탭 자동 검색 |
| `GET /tab/current` | `X-Kuri-Session`의 현재 탭 조회 또는 설정 |
| `GET /page/info` | 활성 탭의 실시간 URL/제목/준비 상태/뷰포트/스크롤 |
| `GET /page/state` | 간결한 페이지 관찰: url, title, scroll%, viewport, forms/links/images/inputs 개수 |
| `POST /batch` | 단일 HTTP 호출로 여러 명령 실행 — 결과 배열 반환 |
| `GET /browdie` | 🌰 (이스터 에그) |
### Browser Control
| Path | Params | Description |
|------|--------|-------------|
| `GET /navigate` | `tab_id`, `url` | 탭을 URL로 이동 |
| `GET /tab/new` | `url`, `activate`, `wait` | 새 탭 생성 및 선택적으로 hydrate/현재 탭 설정 |
| `GET /tab/close` | `tab_id` | 탭 닫기 |
| `GET /window/new` | `url`, `activate`, `wait` | 새 창/탭 대상 생성 |
| `GET /snapshot` | `tab_id`, `filter`, `format` | `eN` 참조가 포함된 A11y 트리 스냅샷. 저토큰 에이전트 루프에는 `filter=interactive&format=compact` 사용. |
| `GET /text` | `tab_id` | 페이지 텍스트 추출 |
| `GET /screenshot` | `tab_id`, `format`, `quality`, `save` | 스크린샷 캡처 (base64); `save=true`는 PNG를 `STATE_DIR/screenshots`에 저장하고 대신 `{path,bytes}`를 반환 |
| `GET /screenshot/annotated` | `tab_id` | 번호가 매겨진 요소 레이블이 있는 스크린샷 |
| `GET /screenshot/diff` | `tab_id`, `baseline` | 현재와 기준 스크린샷 간의 시각적 차이 |
| `GET /action` | `tab_id`, `ref`, `action`, `value` | ref로 클릭/입력/채우기/선택/스크롤/호버/더블클릭/체크/체크해제/블러 |
| `GET /evaluate` | `tab_id`, `expression` | JavaScript 실행 |
| `GET /evalhandle` | `tab_id`, `expression` | JS 실행, objectId 핸들 반환 (값 아님) |
| `GET /close` | `tab_id` | 탭 닫기 + 정리 |
| `GET /bringtofront` | `tab_id` | 탭을 앞으로 가져오기 |
### Actions
| Path | Params | Description |
|------|--------|-------------|
| `GET /clear` | `ref` | 입력 필드 값 지우기 |
| `GET /selectall` | `ref` | input/contenteditable의 모든 텍스트 선택 |
| `GET /setvalue` | `ref`, `value` | 입력 값 직접 설정 (키 이벤트 우회) |
| `GET /dispatch` | `ref`, `type` | 요소에 사용자 정의 DOM 이벤트 디스패치 |
| `GET /boundingbox` | `ref` | 요소 바운딩 렉트 가져오기 (x, y, width, height, centerX, centerY) |
| `GET /getattribute` | `ref`, `name` | 이름으로 요소 속성 가져오기 |
| `GET /inputvalue` | `ref` | 현재 입력 요소 값 가져오기 |
| `GET /element/state` | `ref`, `check` | 빠른 불리언: `exists`, `visible`, `enabled`, `checked` |
| `GET /find-element` | `text`/`role`/`label`/`placeholder`/`testid` | 시맨틱 로케이터 — 스냅 없이 요소 찾기 |
| `GET /highlight` | `ref` 또는 `selector` | 오버레이로 요소 강조 표시 |
### Mouse & Touch
| Path | Params | Description |
|------|--------|-------------|
| `GET /mouse/move` | `x`, `y` | 마우스를 좌표로 이동 |
| `GET /mouse/down` | `x`, `y`, `button` | 마우스 버튼 누름 |
| `GET /mouse/up` | `x`, `y`, `button` | 마우스 버튼 뗌 |
| `GET /mouse/wheel` | `x`, `y`, `deltaX`, `deltaY` | 마우스 휠 스크롤 |
| `GET /tap` | `x`, `y` | 터치 탭 (touchStart + touchEnd) |
| `GET /swipe` | `startX`, `startY`, `endX`, `endY` | 터치 스와이프 제스처 |
| `GET /drag` | `src_ref`, `tgt_ref` | 요소를 대상으로 드래그 |
### Keyboard
| Path | Params | Description |
|------|--------|-------------|
| `GET /keyboard/type` | `tab_id`, `text` | 키 이벤트를 통해 텍스트 입력 |
| `GET /keyboard/inserttext` | `tab_id`, `text` | 텍스트 직접 삽입 |
| `GET /keydown` | `tab_id`, `key` | 키 다운 이벤트 |
| `GET /keyup` | `tab_id`, `key` | 키 업 이벤트 |
### Content Extraction
| Path | Description |
|------|-------------|
| `GET /markdown` | 페이지를 Markdown으로 변환 |
| `GET /links` | 모든 링크 추출 |
| `GET /dom/query` | CSS 선택자 쿼리 |
| `GET /dom/html` | 요소 HTML 가져오기 |
| `GET /dom/attributes` | 요소 속성 가져오기 |
| `GET /pdf` | 페이지를 PDF로 인쇄 |
| `GET /find` | 페이지 내 텍스트 검색 |
### Waiting
| Path | Params | Description |
|------|--------|-------------|
| `GET /wait` | `selector`, `text`, `url`, `state`, `visible`, `timeout` | 선택자/텍스트/URL 패턴/networkidle/로드 상태 대기 |
| `GET /wait/function` | `expression`, `timeout` | 임의의 JS 표현식이 truthy가 될 때까지 대기 |
| `GET /wait/download` | `timeout` | 파일 다운로드 완료 대기 |
### Dialog Handling
| Path | Description |
|------|-------------|
| `GET /dialog/auto` | 모든 JS 다이얼로그 자동 처리 (수락 또는 거부) |
| `GET /dialog/accept` | 현재 다이얼로그 수락 (선택적 프롬프트 텍스트 포함) |
| `GET /dialog/dismiss` | 현재 다이얼로그 거부 |
### Network & HAR
| Path | Description |
|------|-------------|
| `GET /har/start` | 네트워크 트래픽 기록 시작 |
| `GET /har/stop` | 중지 + HAR 1.2 JSON 반환 |
| `GET /har/status` | 기록 상태 + 항목 수 |
| `GET /har/replay` | curl/fetch/python 코드 스니펫이 포함된 API 맵 |
| `GET /cookies` | 쿠키 가져오기 |
| `GET /cookies/set` | 쿠키 설정 |
| `GET /cookies/delete` | 쿠키 삭제 |
| `GET /cookies/clear` | 모든 쿠키 지우기 |
| `GET /headers` | 사용자 정의 요청 헤더 설정 |
| `GET /intercept/start` | 요청 인터셉트 시작 |
| `GET /intercept/stop` | 요청 인터셉트 중지 |
| `GET /intercept/requests` | 인터셉트된 요청 목록 |
| `GET /request/detail` | 요청 ID에 대한 응답 본문 가져오기 |
| `GET /response/body` | URL을 가져와 응답 본문 반환 |
| `GET /network` | 네트워크 트래픽 통계 |
| `GET /download` | 파일 다운로드 트리거 |
### Navigation & State
| Path | Description |
|------|-------------|
| `GET /back` | 브라우저 뒤로 |
| `GET /forward` | 브라우저 앞으로 |
| `GET /reload` | 페이지 새로고침 |
| `GET /stop` | 페이지 로딩 중지 |
| `GET /pushstate` | history.pushState를 통한 SPA 탐색 |
| `GET /storage/local` | localStorage 가져오기/설정 |
| `GET /storage/session` | sessionStorage 가져오기/설정 |
| `GET /storage/local/clear` | localStorage 지우기 |
| `GET /storage/session/clear` | sessionStorage 지우기 |
| `GET /session/save` | 브라우저 세션 저장 |
| `GET /session/load` | 브라우저 세션 복원 |
| `GET /session/list` | 저장된 세션 목록 |
| `GET /setcontent` | 페이지 HTML 직접 설정 (POST) |
### Auth Profiles
| Path | Description |
|------|-------------|
| `GET /auth/profile/save` | 쿠키 + 스토리지를 명명된 인증 프로필로 저장 |
| `GET /auth/profile/load` | 명명된 인증 프로필을 탭으로 복원 |
| `GET /auth/profile/list` | 저장된 인증 프로필 목록 |
| `GET /auth/profile/delete` | 저장된 인증 프로필 삭제 |
| `GET /auth/extract` | 인증 토큰 추출 (JWT, 쿠키, 헤더) |
| `GET /set/credentials` | HTTP 기본 인증 자격 증명 설정 |
macOS에서 인증 프로필 비밀은 사용자 Keychain에 저장됩니다.
### Emulation
| Path | Params | Description |
|------|--------|-------------|
| `GET /emulate` | device type, screen size | 기기 에뮬레이션 |
| `GET /set/viewport` | `width`, `height` | 뷰포트 크기 설정 |
| `GET /set/useragent` | `ua` | 사용자 에이전트 설정 |
| `GET /set/media` | `media` | 미디어 유형 에뮬레이션 |
| `GET /set/offline` | `offline` | 오프라인 모드 전환 |
| `GET /geolocation` | `lat`, `lng` | 지리적 위치 재정의 |
| `GET /timezone` | `timezone` | 시간대 재정의 (예: `America/New_York`) |
| `GET /locale` | `locale` | 로케일 재정의 (예: `en-US`) |
| `GET /permissions` | `name`, `state` | 권한 부여/거부 (geolocation, notifications, clipboard) |
### Scripts & Injection
| Path | Description |
|------|-------------|
| `GET /script/inject` | 페이지에 JavaScript 주입 (탐색 간 유지) |
| `GET /initscript/remove` | 이전에 주입된 init 스크립트 제거 |
| `GET /addstyle` | CSS 스타일시트 주입 |
| `GET /expose` | 페이지 JS 컨텍스트에 명명된 함수 노출 |
### React Inspection
| Path | Description |
|------|-------------|
| `GET /react/tree` | DevTools 훅을 통한 React 컴포넌트 트리 |
| `GET /react/inspect` | React 컴포넌트 props 및 state |
| `GET /react/renders` | React 렌더 추적 (시작/중지) |
| `GET /react/suspense` | React Suspense 경계 상태 |
### Recording & Performance
| Path | Description |
|------|-------------|
| `GET /recording/start` | 사용자 작업 기록 (클릭, 입력, 탐색) |
| `GET /recording/stop` | 기록 중지 + 작업 로그 반환 |
| `GET /vitals` | Core Web Vitals (LCP, CLS, FID, TTFB, FCP, domInteractive) |
| `GET /perf/lcp` | Largest Contentful Paint 타이밍 |
| `GET /trace/start` | 성능 트레이스 시작 |
| `GET /trace/stop` | 트레이스 중지 |
| `GET /profiler/start` | JS 프로파일러 시작 |
| `GET /profiler/stop` | 프로파일러 중지 |
### Debugging
| Path | Description |
|------|-------------|
| `GET /debug/enable` | 페이지 내 디버그 HUD 및 선택적 프리즈 모드 활성화 |
| `GET /debug/disable` | 페이지 내 디버그 HUD 비활성화 |
| `GET /inspect` | 요소 검사 |
| `GET /errors` | JS 오류 수집 |
| `GET /console` | 콘솔 로그 읽기 |
| `GET /frames` | 페이지 프레임 목록 |
| `GET /frame` | 이름 또는 URL로 iframe 컨텍스트로 전환 |
| `GET /mainframe` | 메인 프레임으로 다시 전환 |
| `GET /diff/snapshot` | 이 탭의 이전 호출 대비 간결한 `+`/`~`/`-` diff — 토큰 효율적인 액션 루프 (`/snapshot/changes`로 별칭 지정됨). 대량 변경 시 `! page replaced` 헤더와 함께 전체 스냅샷으로 폴백. |
| `GET /diff/url` | 두 URL을 나란히 비교 (탐색, 스냅샷, diff) |
### Streaming
| Path | Description |
|------|-------------|
| `GET /screencast/start` | 화면 녹화 시작 |
| `GET /screencast/stop` | 화면 녹화 중지 |
| `GET /video/start` | 비디오 캡처 시작 |
| `GET /video/stop` | 비디오 캡처 중지 |
| `GET /ws/start` | WebSocket 터널 시작 |
| `GET /ws/stop` | WebSocket 터널 중지 |
### Agent-friendly loop
가장 마찰이 적은 서버 루프는 다음과 같습니다:
1. `GET /tab/new?url=...`
2. `GET /page/state` (경량) 또는 `GET /snapshot?filter=interactive&format=compact` (전체)
3. `GET /action?action=click&ref=eN`
4. 반복 — 또는 단일 호출로 다단계 작업을 수행하려면 `POST /batch` 사용
`url` 및 `expression` 쿼리 파라미터는 퍼센트 디코딩됩니다. 서버 측에서 탭 컨텍스트를 유지하려면 `X-Kuri-Session: my-agent`를 전송하세요.
---
## 🧠 Skills
이 저장소에는 사용자 확장 가능한 스킬 영역이 포함되어 있습니다:
- `skills/kuri-skill.md`는 기본 Kuri HTTP 에이전트 스킬입니다
- `skills/custom/`은 자체 프로젝트별 스킬을 위해 예약되어 있습니다
- `skills/custom/hackernews-page-2.md`는 구체적인 예제 사용자 정의 스킬입니다
- `.claude/skills/kuri-server/SKILL.md`는 Claude 스타일 저장소 스킬과 동기화 상태를 유지합니다
기본 스킬은 이제 어떤 브라우저 경로를 사용할지도 설명합니다:
- `kuri` HTTP API: 세션, 스냅샷, 액션, HAR, 쿠키, 스크린샷을 포함한 프로덕션 Chrome/CDP 자동화
- `kuri-fetch`: Chrome 없이 독립 실행형 fetch/텍스트 추출
- `kuri-browse`: 대화형 터미널 브라우징
- `kuri-agent`: Kuri 서버에 대한 스크립트 가능한 CLI 자동화
- `kuri-browser/`: 동등성 작업을 위한 실험적 별도 Zig 네이티브 브라우저 런타임
실험적 브라우저 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`는 Chrome 스타일 HTTP 디스커버리와 프로토콜 스모크 테스트를 위한 최소한의 WebSocket JSON-RPC 라우터를 노출합니다. 런타임 eval은 QuickJS를 기반으로 하는 V8 형태의 CDP 원격 객체를 반환합니다. 이는 V8 의존성을 추가하지 않으며 아직 완전한 Playwright/Puppeteer 호환성은 아닙니다.
`kuri-browser`의 스크린샷은 현재 메인 Kuri/CDP 렌더러에 위임됩니다. 먼저 `./zig-out/bin/kuri`를 시작한 다음:
```bash
cd kuri-browser
zig build run -- screenshot https://example.com --out example.jpg --compress --kuri-base http://127.0.0.1:8080
```
`--compress`는 PNG 기준선과 JPEG 후보를 캡처하고, 더 작은 파일을 기록하며, 바이트 절감량을 보고합니다. `https://example.com`에 대한 현재 로컬 측정값: PNG `20,523`바이트에서 JPEG 품질 50으로 `18,183`바이트, `2,340`바이트 또는 `11%` 절감.
### 고급
| 경로 | 설명 |
|------|-------------|
| `GET /diff/snapshot` | 이전 스냅샷 대비 간결한 `+`/`~`/`-` 델타 (에이전트 액션 루프) |
| `GET /emulate` | 디바이스 에뮬레이션 |
| `GET /geolocation` | 지리적 위치 설정 |
| `POST /upload` | 파일 업로드 |
| `GET /script/inject` | JavaScript 주입 |
| `GET /intercept/start` | 요청 가로채기 시작 |
| `GET /intercept/stop` | 가로채기 중지 |
| `GET /screenshot/annotated` | 요소 주석이 포함된 스크린샷 |
| `GET /screenshot/diff` | 스크린샷 간 시각적 차이 |
| `GET /screencast/start` | 스크린캐스트 시작 |
| `GET /screencast/stop` | 스크린캐스트 중지 |
| `GET /video/start` | 비디오 녹화 시작 |
| `GET /video/stop` | 비디오 녹화 중지 |
| `GET /console` | 콘솔 메시지 가져오기 |
| `GET /stop` | 페이지 로딩 중지 |
| `GET /get` | 직접 HTTP 가져오기 (서버 측) |
| `GET /scrollintoview` | 참조된 요소를 뷰로 스크롤 |
| `GET /drag` | 한 ref에서 다른 ref로 드래그 |
| `GET /keyboard/type` | 키 이벤트로 텍스트 입력 |
| `GET /keyboard/inserttext` | 텍스트 직접 삽입 |
| `GET /keydown` | keydown 이벤트 디스패치 |
| `GET /keyup` | keyup 이벤트 디스패치 |
| `GET /wait` | 준비 상태 또는 요소 조건 대기 |
| `GET /tab/close` | 탭 닫기 |
| `GET /highlight` | ref 또는 선택자로 요소 강조 표시 |
| `GET /errors` | 페이지/런타임 오류 가져오기 |
| `GET /set/offline` | 오프라인 네트워크 에뮬레이션 토글 |
| `GET /set/media` | 에뮬레이션된 미디어 기능 설정 |
| `GET /set/credentials` | HTTP 기본 인증 자격 증명 설정 |
| `GET /find` | 현재 페이지에서 텍스트 일치 항목 찾기 |
| `GET /trace/start` | Chrome 추적 시작 |
| `GET /trace/stop` | 추적 중지 및 추적 데이터 반환 |
| `GET /profiler/start` | JS 프로파일러 시작 |
| `GET /profiler/stop` | JS 프로파일러 중지 |
| `GET /inspect` | 요소 또는 페이지 상태 검사 |
| `GET /set/viewport` | 뷰포트 크기 설정 |
| `GET /set/useragent` | 사용자 에이전트 재정의 |
| `GET /dom/attributes` | 요소 속성 가져오기 |
| `GET /frames` | 프레임 트리 나열 |
| `GET /network` | 네트워크 상태/요청 검사 |
---
## 🛡️ 스텔스 및 봇 회피
Kuri는 시작 시 자동으로 안티 탐지 패치를 적용합니다 — 수동 구성이 필요하지 않습니다.
### 적용되는 항목
- **`Page.addScriptToEvaluateOnNewDocument`** — 스텔스 패치가 모든 페이지 JS보다 먼저 실행됨
- **navigator.webdriver = false** — Chromium 수준에서 자동화 플래그 숨김 (`--disable-blink-features=AutomationControlled`)
- **WebGL/Canvas/AudioContext 스푸핑** — 지문 기반 탐지 무력화
- **UA 로테이션** — 5개의 현실적인 Chrome/Safari/Firefox 사용자 에이전트
- **chrome.csi/chrome.loadTimes** — Akamai 특정 검사용 스텁
### 봇 차단 탐지
Navigate는 차단을 자동 감지하고 구조화된 폴백을 반환합니다:
```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
```
감지: **Akamai**, **Cloudflare**, **PerimeterX**, **DataDome**, 일반 캡차.
### 프록시 지원
```bash
KURI_PROXY=socks5://user:pass@residential-proxy:1080 ./zig-out/bin/kuri
KURI_PROXY=http://proxy:8080 ./zig-out/bin/kuri
```
### 테스트한 사이트
| 사이트 | 보호 | 결과 |
|------|-----------|--------|
| Singapore Airlines | Akamai WAF | ✅ 우회됨 (v0.4.0 이전에는 차단됨) |
---
[Read more](https://github.com/justrach/kuri)