NEXUS OFFICE

ERP Operacional da Connecta.ai — escritório virtual em pixel art com agentes de IA em tempo real

TypeScript Cloudflare Workers n8n · Railway Groq · llama-3.3-70b Cloudflare D1 Cloudflare Access · RS256

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

Browser
→
Cloudflare Access JWT
→
CF Worker
nexus-empresa.app-ai
→
D1 (ERP_DB)
n8n (Railway)
→
x-connecta-animus-key
→
POST /api/connecta/eventos
→
D1 erp_events + erp_activities
Chat UI
→
POST /api/connecta/gestor/chat
→
n8n /webhook/nexus-nucleo-v1
→
ANIMUS Nucleo (Groq)
Servidor Local (Node)
→
SSE /api/stream
→
Cliente pixel art
CamadaTecnologiaOnde rodaAcesso externo
ClienteTypeScript + Canvas (pixel art)BrowserCF Pages / ASSETS
Servidor localNode.js (sem dependências de runtime)Máquina devNão — apenas LAN / CF Tunnel
Cloud WorkerCloudflare Workers (Edge)CF PoP globalHTTPS + CF Access
ERP DBCloudflare D1 (SQLite)CF edgeVia Worker apenas
n8nn8n self-hostedRailway (primary-production-db42.up.railway.app)Webhook público
LLMGroq — llama-3.3-70b-versatileGroq CloudVia n8n agent

Cloudflare Worker

Arquivo principal: cloud/empresa/index.ts. Deploy via cf deploy (cf CLI, não Wrangler).

Variáveis de ambiente (Secrets)

NomeDescriçãoObrigatório
ACCESS_TEAMDomínio do CF Access (ex: sua-equipe.cloudflareaccess.com)Sim
ACCESS_AUDApplication Audience Tag do CF AccessSim
ERP_DBBinding D1 — banco de dados ERPSim
NEXUS_SECTOR_KEYChave HMAC do setor (ingress principal)Sim
N8N_BRIDGE_KEYChave HMAC do bridge n8n (rotação sem quebrar o setor)Recomendado
N8N_API_KEYAPI key do n8n — lê execuções de agentes para o escritórioPara agents
N8N_BASE_URLURL base do n8n (default: Railway URL acima)Não
NEXUS_CONNECTA_URLURL do servidor local para proxy de setorNão
VISITOR_DBBinding D1 — banco de visitasPara /visita
VISITOR_INVITE_CODECódigo de convite do modo visitaPara /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
⚠️ Sem ACCESS_TEAM / ACCESS_AUD configurados, o Worker recusa TUDO. Nunca remova esses secrets — o Worker faz isso intencionalmente para segurança.

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

IDNomeTipoUsado em
alCluSaU0zLe6UEMCONNECTA | ANIMUS APIhttpTemplatedCustomAuthTodos os tools HTTP do ANIMUS Nucleo
RYItbqdE4ckIi1mnCONNECTA | GroqgroqApiLLM node do ANIMUS Nucleo
Como atribuir a credential a um node HTTP: Workflow → clique no node HTTP Request → campo Credential → seleciona CONNECTA | ANIMUS API → Save → Activate.

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.

Shared / Tipos

Código puro (sem APIs Node/DOM) em shared/. Usado tanto pelo servidor local quanto pelo Worker e pelo cliente.

ArquivoDescrição
shared/types.tsAgentStatus, ActivityKind, AgentInfo, OfficeSnapshot, FeedItem
shared/erp-activity-kind.tsMapeamento type → ActivityKind para feed de atividades
shared/remote-ingress.tsnormalizeRemoteIngressEvent — valida e normaliza eventos vindos do n8n
shared/world/NUNCA MODIFICAR — envia dados ao Nexus World (Termos de Uso)
shared/locales/Traduções i18n (PT/EN/ES/FR). Verificar com npm run i18n:check
🚫 shared/world/* está PROTEGIDA. Modificar os arquivos em shared/world/ para enviar dados falsos ao Nexus World viola os Termos de Uso e resulta em suspensão da conta.

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)

GET/api/stream — SSE em tempo real: eventos snapshot e feed
GET/api/snapshot — snapshot único do escritório
GET/api/access — capabilities do modo online (remote: true, local: false)

ERP Endpoints

Persistência em Cloudflare D1 (ERP_DB). Dados reais das empresas.

Companies

GET/api/erp/companies — lista todas as empresas cadastradas no D1 (directory)

Activities

GET/api/erp/activities — atividades recentes (últimas 100). Query param: ?companyId=
POST/api/erp/activities — cria atividade. Body: { companyId, type, sector, description }

State

GET/api/erp/state — estado da empresa. Query: ?companyId= (sem param = nexus-central agregado)
POST/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

GET/api/connecta/health — health check de todos os serviços externos
GET/api/connecta/empresas — lista empresas com status de serviços
GET/api/connecta/setor/recepcao — fila de leads / recepção
GET/api/connecta/setor/gestao — tarefas abertas e handoffs
GET/api/connecta/setor/financeiro — receber / pagar / mês

Operações

POST/api/connecta/setor/financeiro — lança receita ou despesa. Body: { type, categoria, description, amount }
POST/api/connecta/setor/gestao/missao — cria tarefa. Body: { title, priority }
POST/api/connecta/setor/gestao/atendimento/:id/assumir — assume um lead
PATCH/api/connecta/setor/:setor — atualiza status de item. Body: { id, status }

Chat (ANIMUS Nucleo)

POST/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:

POST/api/connecta/eventos

Autenticaçã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

ToolMétodoEndpointDescrição
ler_dashboardGET/api/connecta/dashboardKPIs gerais
ler_sentinelaGET/api/connecta/sentinelaVisão cross-business
ler_financeiroGET/api/connecta/pagamentosPagamentos e status
ler_clientesGET/api/connecta/clientesBase de clientes
ler_agendamentosGET/api/connecta/agendamentosAgenda completa
ler_agentesGET/api/connecta/agentesStatus dos IAs no NEXUS
reagendarPOST/api/connecta/op/reagendarReagenda (pede confirmação)
confirmar_pagamentoPOST/api/connecta/op/confirmar-pagamentoConfirma 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

IDNomeWebhookFunçã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);
✅ Com esse MCP, qualquer IA pode: Ver o escritório ao vivo, consultar o ERP, conversar com o ANIMUS, registrar atividades e injetar eventos — tudo com as mesmas credenciais do Worker.

Autenticação

MecanismoRotaHeaderComo 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

  1. Acesse one.dash.cloudflare.com
  2. Vá em Access → Service Auth → Service Tokens
  3. Clique Create Service Token
  4. Copie CF-Access-Client-Id e CF-Access-Client-Secret
  5. Adicione o token à sua Access Application em Access Policies
  6. O JWT gerado pelo CF Access ao usar o service token vai no header cf-access-jwt-assertion
💡 Para o MCP server: Use um Service Token. O CF Access gera automaticamente um JWT válido quando você chama a URL com as credenciais do service token (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

🔐 Nenhum secret vai para o git ou para o chat. Todos os valores sensíveis ficam no Cloudflare Secrets (via cf CLI) ou no .env local.
SecretOnde armazenarNUNCA
NEXUS_SECTOR_KEYCF Secrets + .envCommits, chat, logs
N8N_BRIDGE_KEYCF Secrets + .envCommits, chat, logs
N8N_API_KEYCF Secrets + .envCommits, chat, logs
ACCESS_AUDCF SecretsCommits
CF Service TokenGerenciador de senhasCó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