Skip to main content
Glama
README.md
<div align="center">

# NOMOS Browser

**O navegador do seu agente, com você na sala.**

Infraestrutura de navegação governada para agentes de IA. O navegador vira um
recurso da plataforma, não um brinquedo acoplado a um modelo — e o dono vê,
autoriza e interrompe o que o agente faz.

`0.3.2` · MIT · Node ≥ 22.18 · Chromium via Playwright · sem passo de build

[![CI](https://github.com/Voltolini-SPACE/nomos-browser/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Voltolini-SPACE/nomos-browser/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/Voltolini-SPACE/nomos-browser?label=release)](https://github.com/Voltolini-SPACE/nomos-browser/releases/latest)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

</div>

<div align="center">

<img src="evidence/nomos-public-presence/01-console/console-og.png" width="820"
     alt="Live Agent Console do NOMOS Browser: a faixa de estado mostra agente gi, sessão #464B, status AGUARDANDO APROVAÇÃO, autonomia PERGUNTAR e ação atual browser.click nível A2. No centro, o pedido de aprovação com ação, onde, nível, consequência, motivo e parâmetros, e os botões NEGAR e APROVAR. Ao fundo, a página espelhada de um portal de pagamento.">

<sub>O console no instante em que o agente para e pergunta. Captura do produto
rodando, gerada por <code>scripts/capturar-console.mjs</code> — não é maquete.</sub>

</div>

---

## O problema

Dar a um agente o poder de usar um navegador é dar a ele o poder de comprar,
enviar, apagar e vazar em nome de alguém. Isso costuma ser resolvido de dois
jeitos ruins:

- **confiança cega** — o agente age e o dono descobre depois;
- **paralisia** — o agente pergunta tudo, e o dono clica "sim" no automático até
  a aprovação virar reflexo.

O NOMOS Browser separa **o que o dono já autorizou** do **que precisa de
consentimento agora**, e torna a diferença visível, auditável e reversível.

## Como fica

```
NOMOS · Claude · Gemini · Qwen · Ollama · agente próprio
                        │
      MCP  ·  REST v1  ·  WebSocket  ·  SDK  ·  CLI  ·  Live Agent Console
                        │
              NOMOS BROWSER RUNTIME
        política → autonomia → aprovação → auditoria
                        │
                 Playwright · CDP
                        │
                     Chromium
```

O estado da navegação pertence ao **Runtime**, não ao modelo. O agente
desconecta, morre ou é trocado por outro de outro fornecedor: a sessão continua
viva, com as mesmas abas, cookies e task.

---

## Recursos

| | |
|---|---|
| **Gi no side panel** | A partir da 0.4.0, o navegador abre com um painel lateral: converse com a Gi, veja o agente trabalhar (AGORA), aprove ao lado da página, alterne ASK/AUTO, pause, assuma o volante e audite — sem sair da janela |
| **Spotlight** | Antes de clicar ou digitar, o runtime destaca o alvo NA própria página com o selo "● NOMOS controlando" |
| **Live Agent Console** | Espelho da página, cursor do agente, faixa de estado, feed de atividade, centro de aprovação e histórico somente leitura |
| **Dois modos de autonomia** | `ASK` pergunta antes de cada ação que muda a página; `AUTO` executa sozinho o que você já autorizou |
| **`AUTO` não é bypass** | O modo automático nunca remove uma aprovação obrigatória. Isso é topologia do código, não promessa |
| **Aprovação com amarra** | Single-use, ligada à ação, à sessão e aos argumentos exatos. Aprovar "Cancelar" não autoriza "Confirmar compra" |
| **Segredo mascarado** | O texto a digitar aparece como `[oculto: 24 caractere(s), C…Z]`: o bastante para decidir, nunca o bastante para vazar |
| **Auditoria e replay** | Trilha encadeada por hash, selo ao encerrar a sessão, replay somente leitura em três camadas |
| **Controle humano** | Assumir o volante congela o agente; devolvê-lo obriga a reobservar antes de agir |
| **23 verbos, 16 ferramentas MCP** | Um contrato só, servido por MCP, REST, WebSocket, SDK e CLI |

## Demonstração

O Live Agent Console é servido pelo próprio runtime, na mesma origem, com CSP
`connect-src 'self'` — não há CORS permissivo a explorar.

```bash
node packages/api/src/daemon.ts
# abra a URL com o token que o daemon imprime
```

Roteiros reproduzíveis com resultado esperado estão em
[`docs/demos.md`](docs/demos.md).

---

## Instalação

### Como usuário (recomendado) — sem terminal de desenvolvedor

Baixe o release mais recente em
[Releases](https://github.com/Voltolini-SPACE/nomos-browser/releases) e:

```bash
tar -xzf nomos-browser-v*.tar.gz && cd nomos-browser-v*/
bash packaging/release/install.sh
```

O instalador verifica o Node (≥ 22.18 — único requisito), instala o app em
`~/.nomos-browser/app` com o Chromium do Playwright, **detecta o Ollama** e
grava a configuração, instala a CLI `nomos-browser` e um LaunchAgent que inicia
no login. Ao final o navegador abre sozinho, e abrir a Gi é **um clique**:

> **Clique no ícone NOMOS** (quebra-cabeça → **NOMOS**; fixe na barra se quiser).
> O painel abre ao lado da página **já conectado** — o campo de mensagem
> focado, é só conversar com a Gi. Sem colar token, sem terminal, sem formulário.

O painel conecta sozinho porque o runtime injeta um handshake de mesma origem
dentro da própria extensão que ele carregou (0600, não exposto à web). O caminho
manual (runtime remoto + token) continua existindo para uso avançado.

```bash
nomos-browser start|stop|restart|status|logs|uninstall
```

Sem Ollama, tudo funciona menos o planejamento de tasks — e a Gi diz isso na
tela. Guia completo: [`docs/USER_GUIDE.md`](docs/USER_GUIDE.md).

### Como desenvolvedor (a partir do repositório)

```bash
npm ci --include=dev
npx playwright install chromium
node packages/extension/launch.ts   # experiência embutida (Developer Mode)
```

`--include=dev` não é decoração: com `NODE_ENV=production` ou
`npm config omit=dev`, o `npm ci` pula as devDependencies e o typecheck falha
depois com *"This is not the tsc command you are looking for"*. Detalhes em
[`docs/INSTALLATION.md`](docs/INSTALLATION.md).

**Não há passo de build** para o runtime: o Node executa TypeScript nativamente.
A interface tem um passo próprio (`node packages/ui/build.ts`) porque lê os
tokens de marca do cofre a cada geração.

## Quick start

```bash
# 1. subir o runtime
node packages/api/src/daemon.ts &

# 2. estado
node packages/cli/src/main.ts health

# 3. abrir uma página (a sessão nasce com a política padrão)
node packages/cli/src/main.ts open https://example.com

# 4. ver o que ficou gravado
node packages/cli/src/main.ts sessions
node packages/cli/src/main.ts replay <SESSION_ID>
```

A CLI **nunca concede capability sensível**: sessão criada por ela nasce com
download, upload, send, purchase, payment e delete negados.

## Exemplos

```bash
# capturar a tela de uma sessão
node packages/cli/src/main.ts screenshot <SESSION_ID> --out /tmp/tela.png

# entregar um objetivo ao agente
node packages/cli/src/main.ts task --session <SESSION_ID> "encontre o preço do plano anual"

# acompanhar os eventos ao vivo
node packages/cli/src/main.ts events --session <SESSION_ID>

# verificar a integridade do replay gravado
node packages/cli/src/main.ts replay verify <SESSION_ID>
```

---

## ASK e AUTO

A hierarquia, e a ordem **é** a garantia:

```
POLÍTICA DO DONO → MODO DE AUTONOMIA → CAPABILITY DO NOMOS → GATES DE APROVAÇÃO → AÇÃO
```

### `ASK` — perguntar

Leituras passam direto. Toda ação que muda a página para e pergunta, com
consequência e recurso escritos em português, para que a decisão seja consciente
e não um clique reflexo.

Aprovar uma `browser.task` **não** é cheque em branco: cada passo que o plano
decidir executar reentra no portão.

### `AUTO` — agir sem perguntar

O agente executa sozinho tudo o que você já autorizou pela sua política. O que
**não** muda:

- ações com efeito financeiro, envio externo ou irreversibilidade alta continuam
  pedindo aprovação — `browser.upload` pergunta em `AUTO` porque *envia dado seu
  para fora, e isso não se retira*;
- rota sem perfil de risco declarado cai em "sempre aprovar" (fail-closed);
- se o estado de autonomia não puder ser comprovado (runtime caído, reconexão),
  a interface **nunca** mostra `AUTO`: cai para desconhecido e trata como `ASK`.

`AUTO != BYPASS` não é uma promessa de documentação. O portão de autonomia roda
**depois** de capability e de controle humano: quando ele executa, tudo que a
política nega já devolveu `403`. Não existe ramo no código que transforme um
`deny` em `allow`.

Mais em [`docs/ask-mode.md`](docs/ask-mode.md) e
[`docs/auto-mode.md`](docs/auto-mode.md).

## Segurança

- **Autenticação por token com escopos** (`OBSERVE`, `NAVIGATE`, `INPUT`,
  `DOWNLOAD`, `UPLOAD`, `SECRET`, `CONTROL`, `ADMIN`). Toda rota tem escopo
  **declarado**; nenhuma vive do default.
- **Quem age não autoriza.** O perfil de agente não alcança aprovar, delegar modo
  nem retomar. Parar, sim: `pause` e `emergency-stop` nunca podem ser mais
  difíceis do que agir.
- **Política fail-closed** por capability, com `A6_DESTRUCTIVE` negado.
- **Anti-SSRF**: navegar para host interno é ato explícito, nunca inferido.
- **Procedência anti-injeção** no caminho de execução: `observe` e `extract`
  devolvem `provenance`.
- **Lease de controle** obrigatório (`allow_unleased: false`).
- **Segredos** não aparecem na interface, na auditoria nem no replay.

O modelo de ameaça T1–T10, **com resíduos declarados**, está em
[`docs/SECURITY.md`](docs/SECURITY.md).

Nada aqui afirma "100% seguro". Nenhuma medida sustenta isso, e nenhuma jamais
sustentará.

## Auditoria e replay

Trilha de 19 campos por ação, encadeada por hash, com redação de segredo na
origem. Ao encerrar, a sessão é **selada**.

O replay é somente leitura em três camadas independentes: não existe verbo de
escrita na rota (405 + `Allow: GET`); ler o histórico não ressuscita a sessão; e
o modo é **declarado** pelo runtime, não deduzido pela tela.

Ele também é honesto sobre a própria leitura: relata linhas corrompidas e fontes
ausentes em vez de devolver uma linha do tempo mais curta que se apresenta como
completa. Sessão que nunca existiu é `404`, não um replay vazio de `200`.

Ver [`docs/audit-and-replay.md`](docs/audit-and-replay.md).

## MCP

16 ferramentas, sem acoplamento a modelo:

`browser_navigate` · `browser_observe` · `browser_find` · `browser_extract` ·
`browser_screenshot` · `browser_click` · `browser_type` · `browser_press` ·
`browser_scroll` · `browser_tabs` · `browser_tab_open` · `browser_tab_switch` ·
`browser_tab_close` · `browser_download` · `browser_upload` · `browser_task`

No ecossistema NOMOS, o browser é uma **capability governada** pela política do
dono, com catálogo assinado e confiança por impressão do manifesto normalizado.
Ver [`docs/mcp.md`](docs/mcp.md) e
[`docs/NOMOS-INTEGRATION.md`](docs/NOMOS-INTEGRATION.md).

## Configuração

51 chaves, consultáveis pelo próprio runtime:

```bash
curl -s localhost:7777/api/v1/config/schema   # a FORMA (pública)
curl -s localhost:7777/api/v1/config          # os VALORES efetivos (ADMIN)
```

A separação é deliberada: *"o que existe?"* pode ser respondida a qualquer
portador; *"o que está valendo aqui?"* não. Ver
[`docs/CONFIGURATION.md`](docs/CONFIGURATION.md).

## Troubleshooting

Sintoma → causa → verificação → correção em
[`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).

---

## Desenvolvimento

```bash
npx tsc --noEmit                    # tipos
bash scripts/run-suite.sh           # suíte inteira, um arquivo por vez
bash scripts/run-suite.sh --fast    # pula browser/bench
bash scripts/regressao-completa.sh  # 15 etapas, um veredito
bash scripts/limpar-orfaos.sh       # higiene por prova de posse
```

Use o executor, não `node --test tests/` direto. O runner do Node paraleliza por
CPU; sob pressão de memória o processo morre no meio e deixa saída truncada
**sem linha de sumário**, que parece sucesso. O `run-suite.sh` roda um arquivo
por vez, e um arquivo morto aparece como `MORTO`.

## Testes

Medido numa **sala limpa**, a partir de um clone anônimo do repositório público
por HTTPS, rodando `npm test` como qualquer estranho rodaria. Não a partir da
árvore de trabalho de quem escreveu o código:

```
suíte TypeScript     797 passes · 0 falhas · 38/38 arquivos
E2E do Live Agent    106 casos  · 9/9 baterias
sala limpa           clone anônimo → npm ci → typecheck → suíte, tudo verde
demos                24 passos OK em 6 demos, contra Chromium real
```

Reproduza com `scripts/clean-room-publico.sh`, que apaga o diretório antes de
clonar e não reaproveita nada da máquina.

O projeto se recusa a chamar de PASS o que não foi observado. Dois exemplos:

**O controle do navegador é real.** Um clique sintetizado por JavaScript chega à
página com `isTrusted=false`; um despachado por CDP chega com `true`. O spike
testa os dois — o segundo prova o controle, o primeiro prova que o teste não é
vácuo.

**O contador de aprovações não é cego.** Sob mutação do produto,
`UNEXPECTED_APPROVAL_PROMPTS` sai de `0` para `3` e para `5` conforme o defeito
injetado. Um contador que ficasse em zero sob mutação não estaria medindo nada.

## Limitações conhecidas

- **`p99` não é reportado** em nenhum caminho de latência: 30 amostras exigem 100
  para sustentar um p99. Nenhum máximo observado é chamado de p99.
- **Não há rota HTTP para emitir token com escopo** — existe na API interna.
- **O ramo `pr.page.isClosed()` é inalcançável** em operação normal; é defesa de
  corrida, não cobertura.
- **A faixa de estado da interface atualiza por polling de 700 ms.** Os eventos
  chegam em ~1 ms; a faixa, não.
- Validado em **macOS/Apple Silicon**. Outras plataformas não foram medidas.

A lista completa, com números, está em
[`docs/LIMITATIONS.md`](docs/LIMITATIONS.md), e o que é `PROVEN` contra o que é
`MEASURED` ou `NOT PROVEN` está em
[`PRODUCT_TRUTH_MATRIX.md`](PRODUCT_TRUTH_MATRIX.md).

## Roadmap

Em [`ROADMAP.md`](ROADMAP.md). O que está lá são dívidas legítimas, não
promessas de data.

---

## Documentação

**Comece por aqui**

| | |
|---|---|
| [quickstart.md](docs/quickstart.md) | Do zero ao primeiro browser task |
| [live-agent-console.md](docs/live-agent-console.md) | O console, seus estados e controles |
| [ask-mode.md](docs/ask-mode.md) · [auto-mode.md](docs/auto-mode.md) | Os dois modos, e o que não muda entre eles |
| [demos.md](docs/demos.md) | Roteiros reproduzíveis |

**Referência**

| | |
|---|---|
| [INSTALLATION.md](docs/INSTALLATION.md) · [CONFIGURATION.md](docs/CONFIGURATION.md) · [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Instalar, configurar, destravar |
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) · [API.md](docs/API.md) | Camadas e rotas |
| [browser-control.md](docs/browser-control.md) · [tasks.md](docs/tasks.md) · [TASK-ENGINE.md](docs/TASK-ENGINE.md) | Verbos, alvos e o motor de task |
| [security-overview.md](docs/security-overview.md) · [SECURITY.md](docs/SECURITY.md) | Segurança do produto e modelo de ameaça |
| [audit-and-replay.md](docs/audit-and-replay.md) · [AUDIT.md](docs/AUDIT.md) | Trilha, selo e replay |
| [mcp.md](docs/mcp.md) · [NOMOS-INTEGRATION.md](docs/NOMOS-INTEGRATION.md) · [GI-INTEGRATION.md](docs/GI-INTEGRATION.md) | Integrações |
| [RECOVERY.md](docs/RECOVERY.md) · [VISION-PROVIDER.md](docs/VISION-PROVIDER.md) | O que sobrevive ao quê; visão |
| [LIMITATIONS.md](docs/LIMITATIONS.md) · [EVIDENCIA.md](docs/EVIDENCIA.md) · [RASTREABILIDADE.md](docs/RASTREABILIDADE.md) | Limites, evidência, rastreabilidade |
| [RELEASE.md](docs/RELEASE.md) · [CHANGELOG.md](CHANGELOG.md) | Como se faz uma versão; o que mudou |

## Marca

A marca NOMOS está **congelada na v1.0** e o resolvedor oficial responde `rc=0`.
Os tokens são lidos do cofre a cada build e **não** são versionados neste
repositório — copiar token de marca para arquivo intermediário é proibido pelo
contrato de governança. Ver [`docs/BRAND.md`](docs/BRAND.md).

## Licença

**MIT.** Ver [`LICENSE`](LICENSE). Titular: **Voltolini-SPACE**.

A licença cobre o **código**. Ela não concede direito sobre as marcas "NOMOS" e
"NOMOS Browser" nem sobre os tokens de identidade visual, que são governados à
parte e não são versionados aqui. Detalhes em [`NOTICE.md`](NOTICE.md).

Até 2026-08-25 este repositório declarava "todos os direitos reservados" com um
titular placeholder. As duas coisas eram decisão do dono, e foram decididas.

<div align="center">

Parte do ecossistema **NOMOS** · [voltolini.space](https://voltolini.space)

</div>