
Automação de navegador, rastreamento web e controle de dispositivos iOS + Android para agentes de IA. Snapshots de CDP nativos em Zig e eficientes em tokens, gravação de HAR, cliente nativo do protocolo de rede adb e um fetcher independente.
curl -fsSL https://kuri.trilok.ai/download | sh
macOS arm64/x86_64 e Linux x86_64/arm64. Binário único, sem dependências de runtime.
Downloads diretos: [macOS arm64](https://kuri.trilok.ai/download/v0.6.0/kuri-v0.6.0-aarch64-macos.tar.gz) · [macOS x86_64](https://kuri.trilok.ai/download/v0.6.0/kuri-v0.6.0-x86_64-macos.tar.gz) · [Linux x86_64](https://kuri.trilok.ai/download/v0.6.0/kuri-v0.6.0-x86_64-linux.tar.gz) · [Linux arm64](https://kuri.trilok.ai/download/v0.6.0/kuri-v0.6.0-aarch64-linux.tar.gz)
---
**Automação de navegador e web crawling para agentes de IA. Escrito em Zig. Zero Node.js.**
Automação CDP · Snapshots A11y · Gravação de HAR · Fetcher autônomo · Navegador de terminal interativo · CLI agêntica · Testes de segurança · Controle de dispositivos iOS + Android
[Início Rápido](#-quick-start) · [Benchmarks](#-benchmarks) · [kuri-agent](#-kuri-agent) · [Testes de Segurança](#-security-testing) · [API](#-http-api) · [Habilidades](#-skills) · [Changelog](https://github.com/justrach/kuri/blob/HEAD/CHANGELOG.md)
> **Por que as equipes migram para o Kuri:** builds `ReleaseFast` atuais para Apple Silicon permanecem abaixo de 2 MB por binário, e uma nova execução no Google Flights em 2026-04-23 mediu **3,392 tokens** para um loop completo de `kuri-agent` (`go→snap→click→snap→eval`). Deltas entre ferramentas devem ser executados novamente no mesmo ambiente antes de citar uma porcentagem.
---
## Por que o Kuri vence para agentes
A maioria das ferramentas de navegador foi construída para engenheiros de QA. O Kuri é construído para loops de agentes: leia a página, mantenha o custo de tokens baixo, aja com base em refs estáveis e siga em frente.
- **135 endpoints HTTP** — paridade total com agent-browser e browser-use, da inspeção React ao Core Web Vitals.
- **7 a 12% menos tokens** do que agent-browser em páginas reais, graças ao formato de ref `@eN` e à renderização sem prefixo.
- **Observações 44x mais leves** com `/page/state` (48 tokens) vs snapshot completo (2,124 tokens) para a mesma página do Google Flights.
- **Execução em lote** — `POST /batch` envia N comandos em uma única chamada HTTP, eliminando N-1 round-trips e N-1 turnos de LLM.
- **Compatível com React** — eventos de mouse CDP confiáveis e eventos de teclado por caractere disparam `onClick` e `onChange` do React 18/19.
### Tokens de snapshot: Google Flights `SIN → TPE`
Nova execução em 2026-05-24 neste workspace, medida com `wc -c` e aproximação `chars/4`.
| Ferramenta / Modo | Chars | ~Tokens | Nota |
|---|---:|---:|---|
| `kuri snap` (completo) | 8,499 | **2,124** | Todos os nós + refs interativos |
| `kuri snap` (somente interativos) | ~3,000 | **~750** | Melhor para loops de agente |
| `kuri /page/state` | 190 | **48** | Observação leve (url, título, scroll%, contagens) |
| agent-browser snap (estimado) | ~9,183 | **~2,295** | Sobrecarga de formato `[ref=e0]` |
### Eficiência de tokens: kuri vs agent-browser
| Página | tokens kuri | tokens agent-browser | Economia |
|---|---:|---:|---|
| example.com | 40 | 35 | -13% (página trivial, agent-browser pula a raiz) |
| Hacker News | 386 | ~440 | **12% menos** |
| Google Flights SIN→TPE | 2,124 | ~2,295 | **7% menos** |
A economia vem do formato compacto do kuri:
- Refs `@e0` (3 caracteres) vs `[ref=e0]` (9 caracteres)
- Sem prefixo `- ` por linha (economiza 2 caracteres × número de linhas)
- Mesma indentação, mesma filtragem de nós
### Custo do fluxo completo: `go → snap → click → snap → eval`
| Ferramenta | Tokens por ciclo |
|---|---:|
| **kuri-agent** | **~3,400** |
| Com `/page/state` em vez do segundo snap | **~1,700** |
| Com `POST /batch` (tudo em uma chamada) | **~1,700** (mesmos tokens, 1 chamada HTTP em vez de 5) |
### kuri vs libretto
[libretto](https://github.com/saffron-health/libretto) (Playwright + Node) é o concorrente mais próximo em custo de tokens por etapa. Medido frente a frente em 2026-07-04 — mesmo Chrome, mesma aba, contagens reais de `tiktoken` `o200k_base` (metodologia completa e reprodução: **[benchmarks/libretto_comparison.md](https://github.com/justrach/kuri/blob/HEAD/benchmarks/libretto_comparison.md)**). A divisão honesta:
| Eixo | Vencedor | Detalhe |
|---|---|---|
| Latência por chamada | **kuri** | 4–117 ms vs 1,344–1,500 ms (**13–376× mais rápido** — servidor persistente vs Node por comando) |
| Tokens de snapshot, página típica | **kuri** | simples 61 vs 151 (2.5×), artigo 265 vs 363 (1.37×) — gramática mais enxuta |
| Tokens de snapshot, lista grande | dividido | padrão do kuri 4,424 vs 813 — o kuri emite todos os 259 refs, o libretto trunca por padrão. Com `limit=5`, o kuri renderiza 555 tokens (**1.46× abaixo do libretto**), 34 refs + marcadores `… +45 more` |
| Trajetória (feed, 9 cliques) | **kuri**, por pouco | 898 vs 939 tokens (base `limit=5` + loop de diff vs loop de exec) — paridade a leve vantagem; a perda de 5.1× da manhã foi devida à base não truncada |
| Execuções repetidas | **libretto** | compila trajetórias em um script Playwright → repetições de 0 tokens; o kuri volta a pagar o loop a cada execução |
**O que o kuri ganhou ao estudar o libretto** (tudo incluído nesta versão): um loop diff-first (`take_snapshot_diff`, ~38 tokens/etapa); um diff adaptativo que recorre a um snapshot completo com um cabeçalho `! page replaced` na navegação; linhas de remoção apenas com a identidade; capturas de tela gravadas em disco (o caminho é retornado, os bytes nunca entram no contexto); `get_page_state` via MCP; e — após reescrever `parseA11yNodes` como um verdadeiro caminhamento de árvore DFS — **truncamento de lista opt-in** (`/snapshot?limit=N`, uma linha `… +K more` por execução limitada), **recaptura com escopo** (`scope=@ref`) e **indentação hierárquica**, também expostos como `uid`/`limit` no `take_snapshot` do MCP. A trajetória de feed de 9 cliques que custava 44,285 tokens com re-snapshots completos ingênuos custa **898** com base truncada + diffs — 49× mais barata, ficando abaixo dos 939 do libretto.
> As tabelas mais antigas acima usam uma aproximação de tokens `chars/4`; a comparação com libretto usa contagens reais de `tiktoken`. Rode novamente os números entre ferramentas no seu próprio ambiente antes de citar uma porcentagem.
### Tamanho do binário e memória
Medido em Apple M4 Pro, macOS 26.4.1. Os binários atuais foram compilados com `-Doptimize=ReleaseFast`.
| Binário | Tamanho atual |
|---|---:|
| `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) |
### O RSS permaneceu estável durante a migração para o Zig 0.16
Medido em relação ao build `v0.4.3` `ReleaseFast` atual com `/usr/bin/time -l`.
| Comando | RSS máximo médio `v0.4.3` |
|---|---:|
| `kuri-fetch --version` | ~2.45 MiB |
| `kuri-browse --version` | ~2.45 MiB |
| `kuri-fetch --quiet --dump markdown http://example.com/` | ~9.17 MiB |
## O Problema
Toda ferramenta de automação de navegador arrasta consigo Playwright (~300 MB), um runtime Node.js e uma cascata de dependências npm. Seu agente de IA só quer ler uma página, clicar em um botão e seguir em frente.
**Kuri é um único binário Zig.** Quatro modos, 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)
curl -fsSL https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.sh | sh
Detecta sua plataforma, baixa o binário correto e instala em `~/.local/bin`.
Os downloads vêm do branch `release-channel` autogerenciado do Kuri. Os binários do macOS são assinados localmente com um certificado Developer ID. Os assets do GitHub Release espelham esses mesmos tarballs.
### bun / npm```sh
bun install -g kuri-agent
# or: npm install -g kuri-agent
Baixa o binário nativo correto para a sua plataforma no momento da instalação.
Os binários estáveis do Kuri ficam no branch release-channel e são servidos diretamente de URLs raw do GitHub.
https://raw.githubusercontent.com/justrach/kuri/release-channel/stable/install.shhttps://raw.githubusercontent.com/justrach/kuri/release-channel/stable/latest.jsonhttps://github.com/justrach/kuri/tree/release-channel/stablehttps://raw.githubusercontent.com/justrach/kuri/release-channel/stable/<version>/kuri-<version>-<target>.tar.gzBaixe o tarball para a sua plataforma a partir do manifesto de lançamento estável ou da página de lançamentos do GitHub e descompacte-o no seu $PATH.
URL de instalação estável:```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`) | Binários pré-compilados, assinados + notarizados |
| Linux (`aarch64`, `x86_64`) | Binários pré-compilados |
| Windows (`x86_64`) | **Experimental — apenas compilação cruzada.** `zig build -Dtarget=x86_64-windows-gnu` é verificado pelo CI, mas automação do Chrome, daemonização, desligamento baseado em sinais, gravação de HAR e o armazenamento de autenticação baseado em arquivo são todos stubs com `error.UnsupportedOnWindows` em tempo de execução. Use **WSL2** se você precisar do conjunto de recursos real. Rastreado em [#153](https://github.com/justrach/kuri/issues/153). |
Kuri depende de primitivas POSIX (`fork`, `clock_gettime`, sockets brutos) em vários pontos, portanto uma porta nativa completa para Windows é um trabalho considerável. A base em nível de compilação acima permite que os caminhos `--version`/`--help` e operações puramente em memória sejam executados; as partes complicadas (Chrome, sockets, daemonize) precisam de implementações Win32 reais antes de saírem da lista de stubs. Se você quiser assumir alguma delas, +1 [#153](https://github.com/justrach/kuri/issues/153) ou abra um PR.
### Compilar a partir do código-fonte
Requer [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
Requisitos: Zig ≥ 0.16.0 · Chrome/Chromium (para modo CDP)```bash git clone https://github.com/justrach/kuri.git cd kuri
zig build # build everything zig build test # run 252+ tests
./zig-out/bin/kuri
./zig-out/bin/kuri-fetch https://example.com
./zig-out/bin/kuri-browse https://example.com
(cd kuri-browser && zig build run -- render https://example.com) (cd kuri-browser && zig build run -- bench --offline)
### Primeira execução, caminho mais curto```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
Para uso de HTTP no estilo agente, prefira um cabeçalho de sessão mais /tab/new, /page/info e /snapshot em vez de repetir tab_id em cada chamada.```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"
Existe também um wrapper experimental leve em `tools/kuri_harness.py` se você quiser auxiliares em Python sobre a mesma superfície HTTP.
Se você já tem o Chrome em execução com depuração remota, defina `CDP_URL` para o endpoint WebSocket ou 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
curl -s http://localhost:8080/discover
curl -s http://localhost:8080/tabs
curl -s "http://localhost:8080/navigate?tab_id=ABC123&url=https://vercel.com"
curl -s "http://localhost:8080/snapshot?tab_id=ABC123&filter=interactive"
---
## 🌐 HTTP API
Todos os endpoints retornam JSON. Autenticação opcional via variável de ambiente `KURI_SECRET`. **135 endpoints** — paridade total com agent-browser e browser-use.
### Núcleo
| Caminho | Descrição |
|------|-------------|
| `GET /health` | Status do servidor, contagem de abas, versão |
| `GET /tabs` | Lista todas as abas registradas |
| `GET /discover` | Descobre automaticamente abas do Chrome via CDP |
| `GET /tab/current` | Obtém ou define a aba atual para uma `X-Kuri-Session` |
| `GET /page/info` | URL/título/ready-state/viewport/scroll em tempo real da aba ativa |
| `GET /page/state` | Observação compacta da página: url, título, scroll%, viewport, contagens de formulários/links/imagens/inputs |
| `POST /batch` | Executa vários comandos em uma única chamada HTTP — retorna uma matriz de resultados |
| `GET /browdie` | 🌰 (easter egg) |
### Controle do Navegador
| Caminho | Parâmetros | Descrição |
|------|--------|-------------|
| `GET /navigate` | `tab_id`, `url` | Navega a aba para a URL |
| `GET /tab/new` | `url`, `activate`, `wait` | Cria uma nova aba e opcionalmente hidrata/define a aba atual |
| `GET /tab/close` | `tab_id` | Fecha uma aba |
| `GET /window/new` | `url`, `activate`, `wait` | Cria um novo alvo de janela/aba |
| `GET /snapshot` | `tab_id`, `filter`, `format` | Snapshot da árvore a11y com refs `eN`. Use `filter=interactive&format=compact` para loops de agente com baixo consumo de tokens. |
| `GET /text` | `tab_id` | Extrai o texto da página |
| `GET /screenshot` | `tab_id`, `format`, `quality`, `save` | Captura screenshot (base64); `save=true` grava o PNG em `STATE_DIR/screenshots` e retorna `{path,bytes}` em vez disso |
| `GET /screenshot/annotated` | `tab_id` | Screenshot com rótulos numerados de elementos |
| `GET /screenshot/diff` | `tab_id`, `baseline` | Diff visual entre a screenshot atual e a de referência |
| `GET /action` | `tab_id`, `ref`, `action`, `value` | Click/type/fill/select/scroll/hover/dblclick/check/uncheck/blur por ref |
| `GET /evaluate` | `tab_id`, `expression` | Executa JavaScript |
| `GET /evalhandle` | `tab_id`, `expression` | Executa JS, retorna o handle objectId (não o valor) |
| `GET /close` | `tab_id` | Fecha aba + limpeza |
| `GET /bringtofront` | `tab_id` | Traz a aba para a frente |
### Ações
| Caminho | Parâmetros | Descrição |
|------|--------|-------------|
| `GET /clear` | `ref` | Limpa o valor do campo de entrada |
| `GET /selectall` | `ref` | Seleciona todo o texto em input/contenteditable |
| `GET /setvalue` | `ref`, `value` | Define o valor do campo de entrada diretamente (ignora eventos de teclado) |
| `GET /dispatch` | `ref`, `type` | Despacha evento DOM personalizado no elemento |
| `GET /boundingbox` | `ref` | Obtém o retângulo delimitador do elemento (x, y, width, height, centerX, centerY) |
| `GET /getattribute` | `ref`, `name` | Obtém o atributo do elemento pelo nome |
| `GET /inputvalue` | `ref` | Obtém o valor atual do elemento de entrada |
| `GET /element/state` | `ref`, `check` | Booleano rápido: `exists`, `visible`, `enabled`, `checked` |
| `GET /find-element` | `text`/`role`/`label`/`placeholder`/`testid` | Localizador semântico — encontra o elemento sem snapshot |
| `GET /highlight` | `ref` ou `selector` | Destaca o elemento com sobreposição |
### Mouse e Toque
| Caminho | Parâmetros | Descrição |
|------|--------|-------------|
| `GET /mouse/move` | `x`, `y` | Move o mouse para as coordenadas |
| `GET /mouse/down` | `x`, `y`, `button` | Botão do mouse pressionado |
| `GET /mouse/up` | `x`, `y`, `button` | Botão do mouse solto |
| `GET /mouse/wheel` | `x`, `y`, `deltaX`, `deltaY` | Rolagem da roda do mouse |
| `GET /tap` | `x`, `y` | Toque (touchStart + touchEnd) |
| `GET /swipe` | `startX`, `startY`, `endX`, `endY` | Gesto de deslize por toque |
| `GET /drag` | `src_ref`, `tgt_ref` | Arrasta o elemento até o alvo |
### Teclado
| Caminho | Parâmetros | Descrição |
|------|--------|-------------|
| `GET /keyboard/type` | `tab_id`, `text` | Digita texto via eventos de teclado |
| `GET /keyboard/inserttext` | `tab_id`, `text` | Insere texto diretamente |
| `GET /keydown` | `tab_id`, `key` | Evento de tecla pressionada |
| `GET /keyup` | `tab_id`, `key` | Evento de tecla solta |
### Extração de Conteúdo
| Caminho | Descrição |
|------|-------------|
| `GET /markdown` | Converte página para Markdown |
| `GET /links` | Extrai todos os links |
| `GET /dom/query` | Consulta de seletor CSS |
| `GET /dom/html` | Obtém o HTML do elemento |
| `GET /dom/attributes` | Obtém os atributos do elemento |
| `GET /pdf` | Imprime a página em PDF |
| `GET /find` | Pesquisa de texto na página |
### Aguardando
| Caminho | Parâmetros | Descrição |
|------|--------|-------------|
| `GET /wait` | `selector`, `text`, `url`, `state`, `visible`, `timeout` | Aguarda seletor/texto/padrão de URL/networkidle/estado de carregamento |
| `GET /wait/function` | `expression`, `timeout` | Aguarda até que uma expressão JS arbitrária seja truthy |
| `GET /wait/download` | `timeout` | Aguarda a conclusão do download do arquivo |
### Tratamento de Diálogos
| Caminho | Descrição |
|------|-------------|
| `GET /dialog/auto` | Lida automaticamente com todos os diálogos JS (aceitar ou dispensar) |
| `GET /dialog/accept` | Aceita o diálogo atual (com texto de prompt opcional) |
| `GET /dialog/dismiss` | Dispensa o diálogo atual |
### Rede e HAR
| Caminho | Descrição |
|------|-------------|
| `GET /har/start` | Inicia a gravação do tráfego de rede |
| `GET /har/stop` | Para e retorna o JSON HAR 1.2 |
| `GET /har/status` | Estado da gravação + contagem de entradas |
| `GET /har/replay` | Mapa da API com trechos de código curl/fetch/python |
| `GET /cookies` | Obtém cookies |
| `GET /cookies/set` | Define cookies |
| `GET /cookies/delete` | Exclui cookies |
| `GET /cookies/clear` | Limpa todos os cookies |
| `GET /headers` | Define cabeçalhos de requisição personalizados |
| `GET /intercept/start` | Inicia a interceptação de requisições |
| `GET /intercept/stop` | Para a interceptação de requisições |
| `GET /intercept/requests` | Lista requisições interceptadas |
| `GET /request/detail` | Obtém o corpo da resposta para um ID de requisição |
| `GET /response/body` | Busca a URL e retorna o corpo da resposta |
| `GET /network` | Estatísticas de tráfego de rede |
| `GET /download` | Dispara o download de arquivo |
### Navegação e Estado
| Caminho | Descrição |
|------|-------------|
| `GET /back` | Voltar no navegador |
| `GET /forward` | Avançar no navegador |
| `GET /reload` | Recarrega a página |
| `GET /stop` | Para o carregamento da página |
| `GET /pushstate` | Navegação SPA via history.pushState |
| `GET /storage/local` | Obtém/define localStorage |
| `GET /storage/session` | Obtém/define sessionStorage |
| `GET /storage/local/clear` | Limpa localStorage |
| `GET /storage/session/clear` | Limpa sessionStorage |
| `GET /session/save` | Salva a sessão do navegador |
| `GET /session/load` | Restaura a sessão do navegador |
| `GET /session/list` | Lista sessões salvas |
| `GET /setcontent` | Define o HTML da página diretamente (POST) |
### Perfis de Autenticação
| Caminho | Descrição |
|------|-------------|
| `GET /auth/profile/save` | Salva cookies + storage como um perfil de autenticação nomeado |
| `GET /auth/profile/load` | Restaura um perfil de autenticação nomeado em uma aba |
| `GET /auth/profile/list` | Lista perfis de autenticação salvos |
| `GET /auth/profile/delete` | Exclui um perfil de autenticação salvo |
| `GET /auth/extract` | Extrai tokens de autenticação (JWT, cookies, headers) |
| `GET /set/credentials` | Define credenciais de autenticação básica HTTP |
No macOS, os segredos do perfil de autenticação são armazenados no Keychain do usuário.
### Emulação
| Caminho | Parâmetros | Descrição |
|------|--------|-------------|
| `GET /emulate` | tipo de dispositivo, tamanho de tela | Emulação de dispositivo |
| `GET /set/viewport` | `width`, `height` | Define o tamanho do viewport |
| `GET /set/useragent` | `ua` | Define o user agent |
| `GET /set/media` | `media` | Emula o tipo de mídia |
| `GET /set/offline` | `offline` | Alterna o modo offline |
| `GET /geolocation` | `lat`, `lng` | Substitui a geolocalização |
| `GET /timezone` | `timezone` | Substitui o fuso horário (ex.: `America/New_York`) |
| `GET /locale` | `locale` | Substitui o locale (ex.: `en-US`) |
| `GET /permissions` | `name`, `state` | Concede/nega permissões (geolocalização, notificações, área de transferência) |
### Scripts e Injeção
| Caminho | Descrição |
|------|-------------|
| `GET /script/inject` | Injeta JavaScript na página (persiste entre navegações) |
| `GET /initscript/remove` | Remove um script init injetado anteriormente |
| `GET /addstyle` | Injeta folha de estilo CSS |
| `GET /expose` | Expõe uma função nomeada ao contexto JS da página |
### Inspeção React
| Caminho | Descrição |
|------|-------------|
| `GET /react/tree` | Árvore de componentes React via hook do DevTools |
| `GET /react/inspect` | Props e estado de componentes React |
| `GET /react/renders` | Rastreamento de renderização React (iniciar/parar) |
| `GET /react/suspense` | Status do limite Suspense do React |
### Gravação e Desempenho
| Caminho | Descrição |
|------|-------------|
| `GET /recording/start` | Grava ações do usuário (clique, entrada, navegação) |
| `GET /recording/stop` | Para a gravação + retorna o log de ações |
| `GET /vitals` | Core Web Vitals (LCP, CLS, FID, TTFB, FCP, domInteractive) |
| `GET /perf/lcp` | Tempo do Largest Contentful Paint |
| `GET /trace/start` | Inicia o trace de desempenho |
| `GET /trace/stop` | Para o trace |
| `GET /profiler/start` | Inicia o profiler JS |
| `GET /profiler/stop` | Para o profiler |
### Depuração
| Caminho | Descrição |
|------|-------------|
| `GET /debug/enable` | Ativa o HUD de depuração na página e o modo de congelamento opcional |
| `GET /debug/disable` | Desativa o HUD de depuração na página |
| `GET /inspect` | Inspeção de elemento |
| `GET /errors` | Coleta erros JS |
| `GET /console` | Lê os logs do console |
| `GET /frames` | Lista os frames da página |
| `GET /frame` | Alterna para o contexto do iframe por nome ou URL |
| `GET /mainframe` | Volta para o frame principal |
| `GET /diff/snapshot` | Diff compacto `+`/`~`/`-` em relação à chamada anterior para esta aba — o loop de ações eficiente em tokens (com alias `/snapshot/changes`). Em mudanças em massa, recorre a um snapshot completo com um cabeçalho `! page replaced`. |
| `GET /diff/url` | Compara duas URLs lado a lado (navegar, snapshot, diff) |
### Streaming
| Caminho | Descrição |
|------|-------------|
| `GET /screencast/start` | Inicia a gravação de tela |
| `GET /screencast/stop` | Para a gravação de tela |
| `GET /video/start` | Inicia a captura de vídeo |
| `GET /video/stop` | Para a captura de vídeo |
| `GET /ws/start` | Inicia o túnel WebSocket |
| `GET /ws/stop` | Para o túnel WebSocket |
### Loop amigável para agentes
O loop de servidor com menor atrito é:
1. `GET /tab/new?url=...`
2. `GET /page/state` (leve) ou `GET /snapshot?filter=interactive&format=compact` (completo)
3. `GET /action?action=click&ref=eN`
4. Repita — ou use `POST /batch` para operações de várias etapas em uma única chamada
Os parâmetros de consulta `url` e `expression` são decodificados via percent-encoding. Envie `X-Kuri-Session: my-agent` para persistir o contexto da aba no lado do servidor.
---
## 🧠 Habilidades
O repositório inclui uma área de habilidades extensível pelo usuário:
- `skills/kuri-skill.md` é a habilidade base do agente HTTP Kuri
- `skills/custom/` é reservado para suas próprias habilidades específicas do projeto
- `skills/custom/hackernews-page-2.md` é um exemplo concreto de habilidade personalizada
- `.claude/skills/kuri-server/SKILL.md` permanece sincronizado para habilidades de repositório no estilo Claude
A habilidade base agora também explica qual caminho de navegador usar:
- `kuri` API HTTP: automação de Chrome/CDP em produção com sessões, snapshots, ações, HAR, cookies e screenshots
- `kuri-fetch`: extração de fetch/texto independente, sem Chrome
- `kuri-browse`: navegação interativa via terminal
- `kuri-agent`: automação CLI via script contra o servidor Kuri
- `kuri-browser/`: runtime de navegador experimental separado, nativo em Zig, para trabalho de paridade
Para o CLI experimental do navegador:```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 expõe descoberta HTTP no estilo Chrome, além de um roteador mínimo de JSON-RPC WebSocket para testes de fumaça de protocolo.
O eval em tempo de execução retorna objetos remotos CDP no formato V8, suportados pelo QuickJS; isso não adiciona uma dependência do V8 e ainda não é totalmente compatível com Playwright/Puppeteer.
As capturas de tela em kuri-browser atualmente são delegadas ao renderizador principal Kuri/CDP. Inicie ./zig-out/bin/kuri primeiro e depois:```bash
cd kuri-browser
zig build run -- screenshot https://example.com --out example.jpg --compress --kuri-base http://127.0.0.1:8080
`--compress` captura uma linha de base PNG e um candidato JPEG, grava o arquivo menor e relata a economia de bytes. Medição local atual em `https://example.com`: `20.523` bytes PNG para `18.183` bytes JPEG qualidade 50, economizando `2.340` bytes ou `11%`.
### Avançado
| Path | Descrição |
|------|-------------|
| `GET /diff/snapshot` | Delta compacto `+`/`~`/`-` em relação ao snapshot anterior (loop de ação do agente) |
| `GET /emulate` | Emulação de dispositivo |
| `GET /geolocation` | Definir geolocalização |
| `POST /upload` | Upload de arquivo |
| `GET /script/inject` | Injetar JavaScript |
| `GET /intercept/start` | Iniciar interceptação de requisições |
| `GET /intercept/stop` | Parar interceptação |
| `GET /screenshot/annotated` | Captura de tela com anotações de elementos |
| `GET /screenshot/diff` | Diferença visual entre capturas de tela |
| `GET /screencast/start` | Iniciar screencast |
| `GET /screencast/stop` | Parar screencast |
| `GET /video/start` | Iniciar gravação de vídeo |
| `GET /video/stop` | Parar gravação de vídeo |
| `GET /console` | Obter mensagens do console |
| `GET /stop` | Parar carregamento da página |
| `GET /get` | Busca HTTP direta (lado do servidor) |
| `GET /scrollintoview` | Rolar até um elemento referenciado ficar visível |
| `GET /drag` | Arrastar de uma referência para outra |
| `GET /keyboard/type` | Digitar texto com eventos de tecla |
| `GET /keyboard/inserttext` | Inserir texto diretamente |
| `GET /keydown` | Disparar evento keydown |
| `GET /keyup` | Disparar evento keyup |
| `GET /wait` | Aguardar estado de prontidão ou condições do elemento |
| `GET /tab/close` | Fechar uma aba |
| `GET /highlight` | Destacar um elemento por referência ou seletor |
| `GET /errors` | Obter erros de página/execução |
| `GET /set/offline` | Alternar emulação de rede offline |
| `GET /set/media` | Definir recursos de mídia emulados |
| `GET /set/credentials` | Definir credenciais de autenticação básica HTTP |
| `GET /find` | Encontrar correspondências de texto na página atual |
| `GET /trace/start` | Iniciar tracing do Chrome |
| `GET /trace/stop` | Parar tracing e retornar dados de trace |
| `GET /profiler/start` | Iniciar profiler JS |
| `GET /profiler/stop` | Parar profiler JS |
| `GET /inspect` | Inspecionar um elemento ou estado da página |
| `GET /set/viewport` | Definir tamanho do viewport |
| `GET /set/useragent` | Substituir user agent |
| `GET /dom/attributes` | Obter atributos do elemento |
| `GET /frames` | Listar árvore de frames |
| `GET /network` | Inspecionar estado/requisições de rede |
---
## 🛡️ Furtividade e Evasão de Bots
A Kuri aplica patches anti-detecção automaticamente na inicialização — nenhuma configuração manual é necessária.
### O que é aplicado
- **`Page.addScriptToEvaluateOnNewDocument`** — patches de stealth são executados antes de qualquer JS da página
- **`navigator.webdriver = false`** — oculta o sinalizador de automação no nível do Chromium (`--disable-blink-features=AutomationControlled`)
- **WebGL/Canvas/AudioContext spoofing** — derrota a detecção baseada em fingerprint
- **UA rotation** — 5 user agents realistas do Chrome/Safari/Firefox
- **chrome.csi/chrome.loadTimes** — stubs para verificações específicas da Akamai
### Detecção de bloqueio de bot
Navigate detecta automaticamente bloqueios e retorna fallback estruturado:```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
Detecta: Akamai, Cloudflare, PerimeterX, DataDome, captcha genérico.
KURI_PROXY=socks5://user:pass@residential-proxy:1080 ./zig-out/bin/kuri KURI_PROXY=http://proxy:8080 ./zig-out/bin/kuri
### Sites testados
| Site | Proteção | Resultado |
|------|-----------|--------|
| Singapore Airlines | Akamai WAF | ✅ Contornado (era bloqueado antes da v0.4.0) |
| Shopee SG | Antifraude personalizado | ✅ Página carrega, redireciona para login |
| Google Flights | Nenhuma | ✅ Interação completa |
| Booking.com | PerimeterX | ⚠️ Precisa de proxy |
---
## 🔧 kuri-fetch
Buscador HTTP autônomo — sem Chrome, sem Playwright, sem npm. Vem como um binário de ~2 MB com QuickJS embutido para execução de JS.```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
markdown, html, links, text, json--js executa tags <script> inlinedocument.querySelector, getElementById, window.location, document.title, console.log, setTimeout (estilo SSR)NO_COLOR, , , detecção de TTYNavegador de terminal interativo — navegue na web a partir do seu terminal. Não é necessário Chrome.```bash zig build browse # build + run
kuri-browse https://example.com
I don't see any source text to translate — the INPUT section is empty. Please provide the chunk content to translate.```
🌰 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
[N], digite o número para segui-lo/term destaca todas as correspondênciasjavascript: e mailto:CLI com script para automação do Chrome — controla o navegador comando por comando a partir do seu terminal ou scripts de shell. Compartilha o estado da sessão entre invocações via ~/.kuri/session.json.```bash
zig build agent # build kuri-agent
kuri-agent tabs
kuri-agent use ws://127.0.0.1:9222/devtools/page/ABC123
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/.png
### Comandos
| Comando | Descrição |
|---------|-------------|
| `tabs [--port N]` | Listar abas do Chrome |
| `use <ws_url>` | Anexar a uma aba (salva a sessão) |
| `open [url] [--port N]` | Abrir uma nova aba (opcionalmente navegando para a URL) |
| `status` | Mostrar a sessão atual |
| `go <url>` | Navegar para URL |
| `snap [--interactive] [--json] [--text] [--depth N]` | Snapshot de acessibilidade, salva refs `eN` |
| `click <ref>` | Clicar no elemento por ref (eventos de mouse CDP, compatível com React) |
| `type <ref> <text>` | Digitar no elemento (eventos de tecla por caractere, compatível com React) |
| `fill <ref> <text>` | Preencher o valor do input |
| `select <ref> <value>` | Selecionar opção do dropdown |
| `hover <ref>` | Passar o mouse sobre o elemento |
| `focus <ref>` | Focar o elemento |
| `scroll` | Rolar a página |
| `viewport [width height]` | Obter ou definir dimensões da viewport |
| `eval <js>` | Avaliar JavaScript |
| `text [selector]` | Obter o texto da página |
| `shot [--out file.png]` | Captura de tela |
| `back` | Navegar de volta |
| `forward` | Navegar para frente |
| `reload` | Recarregar a página atual |
| `cookies` | Listar cookies com sinalizadores de segurança |
| `headers` | Verificar cabeçalhos de resposta de segurança |
| `audit` | Auditoria de segurança completa |
| `storage [local\|session\|all]` | Despejar localStorage / sessionStorage |
| `jwt` | Extrair e decodificar JWTs de cookies e do armazenamento |
| `fetch <method> <url> [--data <json>]` | Fetch autenticado usando cookies da página |
| `probe <url-template> <start> <end>` | Teste de IDOR: iterar IDs numéricos na URL |
| `grab <ref>` | Clicar na ref, interceptar `window.open`, seguir redirecionamento na mesma aba |
| `wait-for-tab [--port N]` | Verificar periodicamente uma nova aba, alternar a sessão automaticamente |
| `stealth` | Aplicar patches anti-detecção |
| `set-header <name> <value>` | Adicionar um cabeçalho personalizado a todas as requisições |
| `show-headers` | Mostrar cabeçalhos extras armazenados |
| `clear-headers` | Remover todos os cabeçalhos extras |
---
## 📱 kuri-mobile (iOS + Android)
CLI nativo em Zig para controlar simuladores iOS, iPhones reais (listagem + iniciar/encerrar) e dispositivos/emuladores Android — inspirado em [`mobile-device-mcp`](https://github.com/srmorete/mobile-device-mcp), reimplementado em Zig sem Bun/Node/Gradle/Xcode no caminho de build.```bash
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
O que é Zig nativo: protocolo host adb (sockets libc, framing de 4 hex sobre host:transport:/shell:/exec:), parser de árvore XML da UI Android, cliente plist ListDevices do usbmuxd.
O que usa subprocessos: xcrun simctl (Simulador iOS), xcrun devicectl (inicializar/encerrar em dispositivos iOS reais).
Sem driver por design: nenhum app no dispositivo é instalado, portanto o sandbox do run_code e o tap/uitree via XCUITest em dispositivos iOS reais estão intencionalmente não disponíveis. Consulte kuri-mobile/README.md para a matriz de paridade completa em relação ao upstream.
O kuri-agent suporta trajetórias de segurança nativas do navegador — faça login uma vez e então execute reconhecimento e auditorias de cabeçalhos/cookies sem sair do terminal.
Enumerar → Inspecionar — após autenticar, despeje os cookies de autenticação e verifique os sinalizadores de segurança:```bash 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
**Auditoria de cabeçalhos** — verifique quais cabeçalhos de segurança o alvo envia:```bash
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", ...}}
Auditoria completa — HTTPS, cabeçalhos ausentes, cookies visíveis em JS de uma só vez:```bash kuri-agent audit
**Trajetória cross-account** — use `eval` para reproduzir chamadas de API com tokens diferentes:```bash
# 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)"
kuri-agent emite JSON adequado para integração em pipeline. Cada comando de segurança emite uma única linha JSON — canalize via jq para triagem:```bash
kuri-agent audit | jq '.issues[]'
kuri-agent cookies | head -20
kuri-agent headers | jq '.headers | to_entries[] | select(.value == "(missing)") | .key'
---
## 🏗 Arquitetura```
┌──────────────────────────────────────────────────────────┐
│ 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) │
└──────────────────────────────────────────────────────────┘
deinit()GeneralPurposeAllocator em modo de depuração detecta todos os vazamentosLauncher → Bridge → CdpClients → HarRecorders → Snapshots → Tabserrdefer — falhas parciais são revertidas de forma limpa| Modo | Comportamento |
|---|---|
Gerenciado (sem CDP_URL) | Inicia Chrome headless, encontra porta CDP livre, supervisiona, reinicia automaticamente em caso de falha (máx. 3 tentativas), encerra no desligamento |
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
---
## ⚙️ Configuração
| Env Var | Padrão | Descrição |
|---------|--------|-----------|
| `HOST` | `127.0.0.1` | Endereço de bind do servidor |
| `PORT` | `8080` | Porta do servidor |
| `CDP_URL` | *(nenhum)* | Conectar a um Chrome existente (`ws://...` ou `http://127.0.0.1:9222`) |
| `KURI_SECRET` | *(nenhum)* | Segredo de autenticação para requisições de API |
| `STATE_DIR` | `.kuri` | Diretório de estado da sessão |
| `REQUEST_TIMEOUT_MS` | `30000` | Timeout de requisição HTTP |
| `NAVIGATE_TIMEOUT_MS` | `30000` | Timeout de navegação |
| `STALE_TAB_INTERVAL_S` | `30` | Intervalo de limpeza de abas obsoletas |
| `NO_COLOR` | *(nenhum)* | Desativar saída CLI colorida |
---
## 💰 Custo de Tokens
Para uma tarefa de monitoramento de 50 páginas (dos benchmarks do Pinchtab):
| Método | Tokens | Custo ($) | Melhor para |
|--------|--------|-----------|-------------|
| `/text` | ~40.000 | $0,20 | Leitura intensiva (13× mais barato que capturas de tela) |
| `/snapshot?filter=interactive&format=compact` | ~40.000 | $0,20 | Interação de elementos com baixo uso de tokens |
| `/snapshot` (completo) | ~525.000 | $2,63 | Compreensão completa da página |
| `/screenshot` | ~100.000 | $1,00 | Verificação visual |
---
## 🤝 Contribuindo
Abra uma issue antes de enviar um PR grande para podermos alinhar a abordagem.
---```bash
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)
Consulte CONTRIBUTORS.md para obter diretrizes.
Apache-2.0
TERM=dumb--no-color-o / --output com contagem de bytes + resumo de tempo--user-agent-q suprime o status do stderr| Command | Action |
|---|
<number> | Seguir o link [N] |
<url> | Navegar (se contiver .) |
:go <url> | Navegar para URL |
:back, :b | Voltar no histórico |
:forward, :f | Avançar |
:reload, :r | Recarregar a página atual |
:links, :l | Mostrar índice de links |
/<term> | Buscar na página (destaca correspondências) |
:search <t> | Buscar na página |
:n, :next | Re-destacar a busca |
:history | Mostrar histórico de navegação |
:help, :h | Mostrar todos os comandos |
:quit, :q | Sair |
Externo (CDP_URL definido) |
Conecta-se ao Chrome existente, verifica a saúde via /json/version, NÃO encerra no desligamento |
| Projeto | O que aproveitamos |
|---|
| agent-browser | sistema de referência @eN, diffing de snapshots, padrões de gravação HAR |
| Pinchtab | Arquitetura de controle de navegador para agentes de IA |
| Pathik | Padrões de rastreamento de alta performance |
| QuickJS-ng via mitchellh/zig-quickjs-ng | Motor JS para kuri-fetch |
| Lightpanda | Pioneiro em navegador headless nativo em Zig, padrões de compatibilidade com CDP |
| Zig 0.16.0 | Toda a stack |