Monitoramento de segurança via webcam no browser: seleção de dispositivo, preview A+V, gravação contínua ou por movimento, upload para servidor e acesso remoto (ngrok).
- Runtime — Bun
- Server — Hono
- Client — HTML, CSS, vanilla JS (ES modules, sem build)
- Tunnel — ngrok (HTTPS para acesso mobile)
- Seleção de câmera (
enumerateDevices+getUserMedia) - Preview ao vivo (vídeo mudo para evitar feedback)
- Gravação WebM (áudio + vídeo) via
MediaRecorder - Modo continuous — grava a sessão inteira enquanto monitoramento ativo
- Modo motion — grava só com movimento; segmentos enviados após cooldown
- Upload multipart para
recordings/ - Listagem, playback e download de gravações
- Streaming ao vivo — host publica JPEG via WebSocket; viewers recebem MJPEG
- Autenticação por senha (cookie assinado) — desligável com
PASSWORD=FALSE
- Bun instalado
- Webcam e microfone
- ngrok (opcional, para acesso fora de localhost —
getUserMediaexige HTTPS)
bun install
cp .env.example .env # editar variáveis
bun run devApp em http://localhost:3000.
| Variável | Obrigatória | Default | Descrição |
|---|---|---|---|
PASSWORD |
sim* | — | Senha de acesso; FALSE desliga auth |
SESSION_SECRET |
sim** | — | HMAC do cookie de sessão |
PORT |
não | 3000 |
Porta do servidor |
RECORDINGS_DIR |
não | ./recordings |
Pasta das gravações |
LOGIN_RATE_MAX |
não | 5 |
Tentativas de login por janela |
LOGIN_RATE_WINDOW_MS |
não | 900000 |
Janela de rate limit (ms) |
MOTION_STOP_DELAY_SEC |
não | 5 |
Segundos após último movimento (modo motion) |
* Exceto quando PASSWORD=FALSE
** Obrigatório quando auth está ativa
Nunca commitar .env nem credenciais ngrok.
Proteção por senha no .env com cookie de sessão assinado (HMAC). Ativa quando PASSWORD está definida e não é FALSE.
# auth ligada (padrão)
PASSWORD=changeme
SESSION_SECRET=random-long-string
# auth desligada — app aberto sem login
PASSWORD=FALSECom auth desligada: middleware não monta, SESSION_SECRET dispensável, rotas abertas.
Com auth ligada: servidor não sobe sem PASSWORD e SESSION_SECRET no .env.
- Cliente acessa
/sem sessão → redirect para/login.html POST /api/logincom{ password }— compara comPASSWORDdo env- Sucesso → cookie
devcam_session(HttpOnly, SameSite=Lax,Secureem HTTPS) - Rotas e assets protegidos liberados;
auth.jsredireciona em 401 POST /api/logoutlimpa o cookie
| Aberto (sem sessão) | Protegido |
|---|---|
/login.html, CSS/JS do login |
/, /watch.html, demais estáticos |
POST /api/login |
/api/* (exceto login e auth/status) |
GET /api/auth/status |
/recordings/* |
WS /api/stream/ws |
GET /api/auth/status retorna { authEnabled, authenticated } sem exigir sessão — usado pelo client para decidir redirect.
Tentativas com senha errada limitadas por IP (memória):
LOGIN_RATE_MAX— máx. tentativas (default5)LOGIN_RATE_WINDOW_MS— janela em ms (default900000= 15 min)- Resposta
429quando excedido
Spec: specs/server/auth.yaml
bun run dev # servidor com hot reload
bun test # testes
bun run tunnel # ngrok http 3000O DevCam roda no PC com a webcam. Para ver o monitoramento no celular ou em outra rede, o servidor local precisa ficar acessível via HTTPS — os browsers só liberam getUserMedia (câmera/mic) em contexto seguro (localhost ou HTTPS).
O ngrok cria um túnel HTTPS público até o localhost:3000, sem configurar roteador, certificado ou DNS.
| Papel | Onde abrir | O que faz |
|---|---|---|
| Host | http://localhost:3000 (máquina com webcam) |
Seleciona câmera, grava, publica stream ao vivo |
| Viewer | URL HTTPS do ngrok (celular, outro PC) | Vê MJPEG ao vivo, lista e baixa gravações — sem acesso à câmera local |
Detecção automática em role.js: localhost / 127.0.0.1 / 192.168.x.x = host; qualquer outro hostname (ex. *.ngrok-free.app) = viewer.
- Na máquina com webcam:
bun run dev - No mesmo host:
bun run tunnel(requer ngrok instalado e autenticado) - Abrir
http://localhost:3000— iniciar monitoramento (host) - No celular: abrir a URL HTTPS exibida pelo ngrok — modo viewer
- Plano free do ngrok: URL muda a cada sessão
- Com auth ativa: login na URL ngrok; cookie
Secureem HTTPS (localhost HTTP segue semSecure) - iOS:
<video playsinline>já configurado no host - Gravações continuam no servidor local (
recordings/), não no celular
src/ # Hono — rotas, handlers, middleware, lib
public/ # HTML, CSS, JS (servido em /)
recordings/ # gravações recebidas (gitignored)
scripts/ # utilitários (tunnel, cleanup)
specs/ # contratos YAML (fonte de verdade)
tests/ # testes Bun
| Método | Rota | Descrição |
|---|---|---|
| POST | /api/login |
Autenticação |
| POST | /api/logout |
Encerra sessão |
| GET | /api/auth/status |
Estado de auth |
| GET | /api/config |
Config client-side |
| POST | /api/recordings |
Upload WebM |
| GET | /api/recordings |
Lista gravações |
| GET | /recordings/:name |
Playback/download |
| GET | /api/health |
Liveness |
| WS | /api/stream/ws |
Host publica frames |
| GET | /api/stream |
MJPEG para viewers |
| GET | /api/stream/status |
Status do stream |
Este repo é intencionalmente pequeno e legível. Não é um showcase de framework — é um app que resolve um problema com o mínimo de moving parts.
- Simples — Bun + Hono no server; HTML/CSS/vanilla JS no client; sem bundler, sem React, sem ORM
- Limites claros — um arquivo, uma responsabilidade; funções puras; sem classes
- Escopo mínimo — mudança só no que o pedido pede; copiar o vizinho em vez de inventar camada
- Manutenção > esperteza — código que um dev experiente lê em cinco minutos
Antes de planejar ou codificar — humano ou agente — a primeira ação é ler o contrato, não inferir do código legado:
- Identificar escopo: project, server, frontend, recording, streaming ou tunnel
- Consultar o mapa de specs (abaixo) e
specs/conventions/ - Usar o contrato lido como base — spec-as-source:
specs/é a fonte de verdade; o spec-driven development (SDD) define o que implementar
Sem spec no escopo (infra nova): fase intent (conversa no chat) → draft na spec → código.
As specs deste repo nasceram em vibe-spec — o mesmo espírito do vibe coding, mas o artefato é YAML em specs/, não código solto. Conversa iterativa com IA vira contrato versionável; não é documentação escrita depois do fato sobre código legado.
Os nomes das propriedades nos YAML foram escolhidos pelo modelo usado no vibe-spec, não há schema imposto nem convenção externa; o que importa é o contrato semântico, não o vocabulário das chaves.
Contratos em specs/ são a fonte de verdade para infraestrutura. O código implementa a spec; a spec não documenta o código depois.
intent → spec → plan → code → review
| Etapa | O quê |
|---|---|
| intent | conversa com o chat via prompt — alinhar escopo e intenção antes de escrever YAML |
| spec | YAML por domínio (server, frontend, recording, streaming) |
| plan | planejamento multi-arquivo no editor — não versionado |
| code | implementação alinhada ao contrato |
| review | conferir spec ↔ código antes de fechar |
A etapa intent não vira arquivo no repo; o rastro opcional na spec é o campo intention (trecho do prompt que originou o contrato).
Regras:
- PR só de spec → não mexe em código
- PR de implementação → spec existe ou é atualizada antes do código no mesmo PR
- Bug: corrige código se a spec está certa; ou altera a spec primeiro se o contrato mudou
- Sem validador automático das specs — consistência por leitura humana e cross-refs
Não inferir comportamento de código legado quando há spec. Spec ausente em infra nova → draft na spec, depois código.
| Caminho | Conteúdo |
|---|---|
specs/project/project.spec.yaml |
projeto, features, env |
specs/server/architecture.yaml |
rotas, handlers, storage |
specs/server/auth.yaml |
autenticação |
specs/frontend/architecture.yaml |
UI, módulos client, fluxos |
specs/frontend/modules/*.module.yaml |
contrato por módulo JS |
specs/recording/*.recording.yaml |
modos continuous, motion, upload, playback |
specs/streaming/*.stream.yaml |
host WebSocket, viewer MJPEG |
specs/conventions/*.spec.md |
como escrever specs por domínio |
Specs de feature (*.recording.yaml, *.module.yaml) têm status: draft → review → stable. Campo opcional intention guarda o trecho do prompt da fase intent.
AGENTS.md — guia operacional para coding agents. Não repete produto/setup (README) nem contratos de comportamento (specs/).
Agentes de IA não têm memória persistente entre sessões. Cada conversa começa sem contexto do repo — só o que entra no prompt (arquivos abertos, regras do workspace, AGENTS.md). O arquivo é grande porque concentra o que um dev experiente já sabe depois de semanas no projeto, mas o agente precisa ler em segundos:
- Onde olhar — mapa de specs e mapa do repo (qual arquivo tocar por domínio)
- Em que ordem — pré-raciocínio: spec antes de código; intent → spec → code → review
- Como se comportar — escopo mínimo, copiar o vizinho, não commitar sem pedido,
bun testantes de encerrar - Como escrever — persona, estilo (sem
;, funções puras, sem OOP novo)
Detalhe aqui não é documentação retroativa do código — é guardrail operacional. O contrato técnico continua em specs/; o README continua produto e setup.
| Sem AGENTS.md detalhado | Com AGENTS.md detalhado |
|---|---|
| Infere comportamento do código legado | Lê spec como fonte de verdade |
| Refactor e abstração fora do escopo | Diff pequeno, só o pedido |
| Toca arquivos errados ou inventa camada | Mapa do repo aponta o vizinho certo |
| Spec e código divergem | Spec atualizada antes do código |
| Estilo inconsistente entre sessões | Mesma persona e convenções a cada run |
Consciência do agente = saber onde está no projeto (domínio, arquivos, fluxo SDD) antes de agir — não “inteligência” extra, mas contexto explícito que reduz alucinação de arquitetura e decisões fora de contrato. Quanto mais o AGENTS.md deixa o caminho óbvio, menos o agente improvisa e mais o output se parece com contribuição de alguém que já leu o repo.
Parte do código e das specs foi escrita com assistência do Cursor Composer 2.5 em fluxo vibe-spec: intent no chat → contrato em specs/ → diff pequeno → review spec ↔ implementação. O SDD existe para que vibe coding não vire pasta de código órfão.
Pode criticar à vontade. Antes de abrir issue sobre “falta X”: confira se specs/ já define o comportamento esperado. Se a spec estiver errada, o bug é de contrato; se o código divergir, o bug é de implementação.
Tradeoffs conscientes:
- Sem bundler — deploy = copiar
public/; DX moderna sacrificada por simplicidade - YAML sem schema CI — leve, versionável, legível; validação manual
- Auth por senha única no
.env— adequado a uso pessoal/local, não multi-tenant - Rate limit em memória — reinicia com o processo
Issues e PRs bem fundamentados são bem-vindos.
