NEXUS OFFICE
ERP Operacional da Connecta.ai — escritório virtual em pixel art com agentes de IA em tempo real
Visão Geral
O NEXUS OFFICE é uma plataforma operacional que combina um escritório virtual em pixel art com um ERP leve e agentes de IA autônomos. Ele mostra em tempo real o que cada agente (Claude Code, Codex, OpenCode, n8n) está fazendo, e permite operar as duas empresas da Connecta — Lava Já! e Ferreira Alumínio — via interface visual ou API.
🏢 Escritório Virtual
Pixel art com andares, salas e personagens. Agentes aparecem como personagens que se movem e reagem ao status (working / waiting / idle / offline).
🤖 ANIMUS Nucleo
Agente operacional em n8n com 8 tools HTTP que chama o Worker. Groq llama-3.3-70b, memória por sessão, confirmação antes de escrever.
📊 ERP Leve
Cloudflare D1 com tabelas erp_activities, erp_events, erp_tasks, erp_leads, erp_finance, erp_company_state. Sem ORM, SQL direto.
🔒 Zero-Trust
Cloudflare Access (JWT RS256) em todas as rotas. Ingress separado com HMAC-SHA256 para n8n. Nenhum secret no código ou no git.
Arquitetura
nexus-empresa.app-ai
| Camada | Tecnologia | Onde roda | Acesso externo |
|---|---|---|---|
| Cliente | TypeScript + Canvas (pixel art) | Browser | CF Pages / ASSETS |
| Servidor local | Node.js (sem dependências de runtime) | Máquina dev | Não — apenas LAN / CF Tunnel |
| Cloud Worker | Cloudflare Workers (Edge) | CF PoP global | HTTPS + CF Access |
| ERP DB | Cloudflare D1 (SQLite) | CF edge | Via Worker apenas |
| n8n | n8n self-hosted | Railway (primary-production-db42.up.railway.app) | Webhook público |
| LLM | Groq — llama-3.3-70b-versatile | Groq Cloud | Via n8n agent |
Cloudflare Worker
Arquivo principal: cloud/empresa/index.ts. Deploy via cf deploy (cf CLI, não Wrangler).
Variáveis de ambiente (Secrets)
| Nome | Descrição | Obrigatório |
|---|---|---|
ACCESS_TEAM | Domínio do CF Access (ex: sua-equipe.cloudflareaccess.com) | Sim |
ACCESS_AUD | Application Audience Tag do CF Access | Sim |
ERP_DB | Binding D1 — banco de dados ERP | Sim |
NEXUS_SECTOR_KEY | Chave HMAC do setor (ingress principal) | Sim |
N8N_BRIDGE_KEY | Chave HMAC do bridge n8n (rotação sem quebrar o setor) | Recomendado |
N8N_API_KEY | API key do n8n — lê execuções de agentes para o escritório | Para agents |
N8N_BASE_URL | URL base do n8n (default: Railway URL acima) | Não |
NEXUS_CONNECTA_URL | URL do servidor local para proxy de setor | Não |
VISITOR_DB | Binding D1 — banco de visitas | Para /visita |
VISITOR_INVITE_CODE | Código de convite do modo visita | Para /visita |
Fluxo de autenticação
Toda requisição (exceto /visita/* e /api/connecta/eventos) passa por accessOk():
GET /qualquer-rota
→ Worker verifica header cf-access-jwt-assertion
→ Busca chaves JWKS de https://{ACCESS_TEAM}/cdn-cgi/access/certs
→ Valida RS256, aud, iss, exp
→ 403 se falhar, segue se ok
n8n (Railway)
Instância self-hosted em https://primary-production-db42.up.railway.app. Credential principal: CONNECTA | ANIMUS API (httpTemplatedCustomAuth, id alCluSaU0zLe6UEM) — injeta o header de autenticação do CF Access em todas as chamadas ao Worker.
Credentials cadastradas
| ID | Nome | Tipo | Usado em |
|---|---|---|---|
alCluSaU0zLe6UEM | CONNECTA | ANIMUS API | httpTemplatedCustomAuth | Todos os tools HTTP do ANIMUS Nucleo |
RYItbqdE4ckIi1mn | CONNECTA | Groq | groqApi | LLM node do ANIMUS Nucleo |
Servidor Local (Node)
Roda localmente; não tem acesso externo por padrão. Alimenta o cliente via SSE (GET /api/stream) com o snapshot do escritório (agentes, salas, feed).
Os endpoints /api/connecta/* (dashboard, clientes, agendamentos, pagamentos, agentes, sentinela) existem no servidor local — não no Worker. Para que o n8n (Railway) acesse esses dados, o Worker precisa fazer proxy via NEXUS_CONNECTA_URL.
Cliente Browser
Pixel art puro em client/src/ — TypeScript compilado, sem frameworks. Usa <canvas> para o mapa e elementos HTML para os painéis. SSE para estado em tempo real.
- art/ — tilemap, sprites, rendering
- ui/ — painéis, modais, sidebars, command center
- world/ — layout do prédio, constantes, posicionamento
Worker API — Referência Completa
Base: https://nexus-empresa.app-ai.workers.dev. Todas as rotas requerem JWT do CF Access, exceto /api/connecta/eventos.
Escritório (Snapshot / SSE)
/api/stream — SSE em tempo real: eventos snapshot e feed/api/snapshot — snapshot único do escritório/api/access — capabilities do modo online (remote: true, local: false)ERP Endpoints
Persistência em Cloudflare D1 (ERP_DB). Dados reais das empresas.
Companies
/api/erp/companies — lista todas as empresas cadastradas no D1 (directory)Activities
/api/erp/activities — atividades recentes (últimas 100). Query param: ?companyId=/api/erp/activities — cria atividade. Body: { companyId, type, sector, description }State
/api/erp/state — estado da empresa. Query: ?companyId= (sem param = nexus-central agregado)/api/erp/state — salva/atualiza estado completo. Body: { companyId, companyName, groupCode, groupPosition, state }Schema D1 (ERP_DB)
CREATE TABLE erp_activities (
id TEXT PRIMARY KEY,
company_id TEXT,
type TEXT,
sector TEXT,
description TEXT,
animus_id TEXT,
animus_name TEXT,
room_id TEXT,
created_at INTEGER
);
CREATE TABLE erp_events (
id TEXT PRIMARY KEY,
company_id TEXT,
event_type TEXT,
source TEXT,
payload_json TEXT,
created_at INTEGER
);
CREATE TABLE erp_company_state (
company_id TEXT PRIMARY KEY,
company_name TEXT,
group_code TEXT,
group_position INTEGER,
state_json TEXT,
updated_at INTEGER
);
CREATE TABLE erp_company_directory (
company_id TEXT PRIMARY KEY,
company_name TEXT,
segment TEXT,
plan TEXT,
room_id TEXT,
room_name TEXT,
animus_id TEXT,
animus_name TEXT,
animus_role TEXT,
group_code TEXT,
group_position INTEGER,
state_summary_json TEXT,
updated_at INTEGER
);
CREATE TABLE erp_tasks (id TEXT PRIMARY KEY, title TEXT, priority TEXT, status TEXT, company_id TEXT, created_at INTEGER);
CREATE TABLE erp_leads (id TEXT PRIMARY KEY, name TEXT, contact TEXT, request TEXT, status TEXT, company_id TEXT, created_at INTEGER);
CREATE TABLE erp_finance (id TEXT PRIMARY KEY, kind TEXT, description TEXT, amount REAL, status TEXT, company_id TEXT, created_at INTEGER);
Connecta Endpoints
Setores operacionais das duas empresas. Os endpoints GET fazem proxy para o servidor local via NEXUS_CONNECTA_URL.
Informação
/api/connecta/health — health check de todos os serviços externos/api/connecta/empresas — lista empresas com status de serviços/api/connecta/setor/recepcao — fila de leads / recepção/api/connecta/setor/gestao — tarefas abertas e handoffs/api/connecta/setor/financeiro — receber / pagar / mêsOperações
/api/connecta/setor/financeiro — lança receita ou despesa. Body: { type, categoria, description, amount }/api/connecta/setor/gestao/missao — cria tarefa. Body: { title, priority }/api/connecta/setor/gestao/atendimento/:id/assumir — assume um lead/api/connecta/setor/:setor — atualiza status de item. Body: { id, status }Chat (ANIMUS Nucleo)
/api/connecta/gestor/chat — envia mensagem ao agente. Body: { message, sessionId? } → { response, sessionId }// Exemplo
const res = await fetch('https://nexus-empresa.app-ai.workers.dev/api/connecta/gestor/chat', {
method: 'POST',
headers: {
'content-type': 'application/json',
'cf-access-jwt-assertion': TOKEN,
},
body: JSON.stringify({ message: 'quais pagamentos estão pendentes?', sessionId: 'sess_123' }),
});
const { response, sessionId } = await res.json();
Ingress de Eventos (n8n → Worker)
Rota especial sem CF Access — autenticada por HMAC-SHA256:
/api/connecta/eventosAutenticação
Header x-connecta-ingress-key ou x-connecta-animus-key com o valor de NEXUS_SECTOR_KEY ou N8N_BRIDGE_KEY.
Body
{
"companyId": "nexus-central", // obrigatório
"description": "Lead chegou", // obrigatório
"type": "lead_chegada",
"sector": "Recepcao",
"source": "whatsapp",
"animusId": "animus:recepcao",
"animusName": "ANIMUS Recepção",
"roomId": "core:recepcao"
}
Resposta: 201 { ok: true, received: true, id: "ingress:uuid" }
O evento é gravado em erp_events e erp_activities simultaneamente.
ANIMUS Nucleo — Agente Operacional
Workflow n8n 00x78dEACc6Mz0JL (abrir no n8n).
Webhook: POST https://primary-production-db42.up.railway.app/webhook/nexus-nucleo-v1
Tools disponíveis
| Tool | Método | Endpoint | Descrição |
|---|---|---|---|
ler_dashboard | GET | /api/connecta/dashboard | KPIs gerais |
ler_sentinela | GET | /api/connecta/sentinela | Visão cross-business |
ler_financeiro | GET | /api/connecta/pagamentos | Pagamentos e status |
ler_clientes | GET | /api/connecta/clientes | Base de clientes |
ler_agendamentos | GET | /api/connecta/agendamentos | Agenda completa |
ler_agentes | GET | /api/connecta/agentes | Status dos IAs no NEXUS |
reagendar | POST | /api/connecta/op/reagendar | Reagenda (pede confirmação) |
confirmar_pagamento | POST | /api/connecta/op/confirmar-pagamento | Confirma pagamento (pede confirmação) |
LLM + Memória
Groq llama-3.3-70b-versatile, temperatura 0.3. Memória por sessão via sessionId (janela de 10 turnos).
Workflows n8n
| ID | Nome | Webhook | Função |
|---|---|---|---|
| 00x78dEACc6Mz0JL | NEXUS | ANIMUS Nucleo | /webhook/nexus-nucleo-v1 |
Agente principal com 8 tools + Groq + memória |
| kXVhlmrUK4lHA5rI | NEXUS | Recepção Lead Entrada | /webhook/nexus-recepcao-lead |
Normaliza lead e envia ao Worker via ingress |
019NWceQUalEONQ9 |
Agente principal (legado) | — | Monitorado pelo Worker para feed de atividades |
HVXCNWpiVKIfYgIZ |
WhatsApp Leads | — | Integração WhatsApp → chama nexus-recepcao-lead |
MCP Server — Integração com Qualquer IA
Para conectar Claude Code, OpenCode, Cursor ou qualquer IA ao NEXUS via MCP, crie um servidor MCP que expõe as tools do Worker. Exemplo mínimo:
Instalação rápida (Claude Code)
Adicione ao ~/.claude/settings.json:
{
"mcpServers": {
"nexus-office": {
"command": "node",
"args": ["C:/caminho/para/nexus-mcp/index.js"],
"env": {
"NEXUS_WORKER_URL": "https://nexus-empresa.app-ai.workers.dev",
"NEXUS_CF_ACCESS_TOKEN": "seu-service-token-aqui"
}
}
}
}
Implementação do servidor MCP (nexus-mcp/index.js)
// nexus-mcp/index.js — MCP Server para NEXUS OFFICE
// Requer: npm install @modelcontextprotocol/sdk
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const BASE = process.env.NEXUS_WORKER_URL;
const TOKEN = process.env.NEXUS_CF_ACCESS_TOKEN;
const nexus = async (path, opts = {}) => {
const res = await fetch(`${BASE}${path}`, {
...opts,
headers: {
'cf-access-jwt-assertion': TOKEN,
'content-type': 'application/json',
...(opts.headers ?? {}),
},
});
return res.json();
};
const server = new Server({ name: 'nexus-office', version: '1.0.0' }, {
capabilities: { tools: {} },
});
server.setRequestHandler('tools/list', async () => ({
tools: [
{ name: 'nexus_snapshot', description: 'Retorna snapshot atual do escritório NEXUS (agentes ativos, status).', inputSchema: { type: 'object', properties: {} } },
{ name: 'nexus_erp_state', description: 'Estado ERP da Connecta (tarefas, leads, financeiro).', inputSchema: { type: 'object', properties: { companyId: { type: 'string' } } } },
{ name: 'nexus_erp_companies', description: 'Lista empresas cadastradas no NEXUS.', inputSchema: { type: 'object', properties: {} } },
{ name: 'nexus_activities', description: 'Feed de atividades recentes. Param: companyId (opcional).', inputSchema: { type: 'object', properties: { companyId: { type: 'string' } } } },
{ name: 'nexus_health', description: 'Health check dos serviços externos da Connecta.', inputSchema: { type: 'object', properties: {} } },
{ name: 'nexus_chat', description: 'Envia mensagem ao ANIMUS Nucleo (agente IA com tools operacionais).', inputSchema: { type: 'object', required: ['message'], properties: { message: { type: 'string' }, sessionId: { type: 'string' } } } },
{ name: 'nexus_add_activity', description: 'Registra atividade no ERP.', inputSchema: { type: 'object', required: ['type','sector','description'], properties: { companyId: {type:'string'}, type:{type:'string'}, sector:{type:'string'}, description:{type:'string'} } } },
{ name: 'nexus_ingest_event', description: 'Injeta evento externo no ERP (requer N8N_BRIDGE_KEY).', inputSchema: { type: 'object', required: ['companyId','description','bridgeKey'], properties: { companyId:{type:'string'}, description:{type:'string'}, type:{type:'string'}, sector:{type:'string'}, animusName:{type:'string'}, bridgeKey:{type:'string'} } } },
],
}));
server.setRequestHandler('tools/call', async ({ params }) => {
const { name, arguments: args } = params;
let result;
if (name === 'nexus_snapshot') result = await nexus('/api/snapshot');
else if (name === 'nexus_erp_state') result = await nexus(`/api/erp/state${args.companyId ? `?companyId=${args.companyId}` : ''}`);
else if (name === 'nexus_erp_companies') result = await nexus('/api/erp/companies');
else if (name === 'nexus_activities') result = await nexus(`/api/erp/activities${args.companyId ? `?companyId=${args.companyId}` : ''}`);
else if (name === 'nexus_health') result = await nexus('/api/connecta/health');
else if (name === 'nexus_chat') result = await nexus('/api/connecta/gestor/chat', { method: 'POST', body: JSON.stringify(args) });
else if (name === 'nexus_add_activity') result = await nexus('/api/erp/activities', { method: 'POST', body: JSON.stringify(args) });
else if (name === 'nexus_ingest_event') {
const { bridgeKey, ...body } = args;
result = await fetch(`${BASE}/api/connecta/eventos`, {
method: 'POST',
headers: { 'content-type': 'application/json', 'x-connecta-animus-key': bridgeKey },
body: JSON.stringify(body),
}).then(r => r.json());
}
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
});
const transport = new StdioServerTransport();
await server.connect(transport);
Autenticação
| Mecanismo | Rota | Header | Como obter |
|---|---|---|---|
| Cloudflare Access JWT (RS256) | Todas exceto /visita/* e /api/connecta/eventos |
cf-access-jwt-assertion |
Service Token via Cloudflare Access → "Service Tokens" → criar token para app |
| HMAC-SHA256 (Bridge Key) | /api/connecta/eventos |
x-connecta-animus-key ou x-connecta-ingress-key |
Valor de N8N_BRIDGE_KEY ou NEXUS_SECTOR_KEY no Worker |
Criando um Service Token para o MCP
- Acesse one.dash.cloudflare.com
- Vá em Access → Service Auth → Service Tokens
- Clique Create Service Token
- Copie
CF-Access-Client-IdeCF-Access-Client-Secret - Adicione o token à sua Access Application em Access Policies
- O JWT gerado pelo CF Access ao usar o service token vai no header
cf-access-jwt-assertion
CF-Access-Client-Id + CF-Access-Client-Secret como headers). Ou use o JWT diretamente se já tiver um válido.
Secrets — Regras de Segurança
.env local.
| Secret | Onde armazenar | NUNCA |
|---|---|---|
NEXUS_SECTOR_KEY | CF Secrets + .env | Commits, chat, logs |
N8N_BRIDGE_KEY | CF Secrets + .env | Commits, chat, logs |
N8N_API_KEY | CF Secrets + .env | Commits, chat, logs |
ACCESS_AUD | CF Secrets | Commits |
| CF Service Token | Gerenciador de senhas | Código, repositório |
Deploy
Este projeto usa cf CLI (não Wrangler) conforme configurado no projeto.
Worker (Cloud)
# A partir de nexus-office-2026-11-01/nexus-office/cloud/empresa/
./node_modules/.bin/cf.ps1 deploy
Verificar versão deployada
./node_modules/.bin/cf.ps1 deployments list
Servidor local
# A partir de nexus-office/
npm run dev
Build do cliente
npm run build
Verificação i18n
npm run i18n:check