feat: pulse-dev — dev environment full-stack com hot reload + agentes + taskboard + vault Obsidian
- Stack Swarm 'dev': redis + taskboard + frontend HMR (Vite) + backend HMR (tsx) + 5 agents - TaskBoard standalone React com Redis pub/sub e BLPOP queue - Backend API Express: /tasks /agents /health com hot reload - Agentes: 2 FE (frontend), 2 BE (backend), 1 DevOps (parallel workers) - Vault Obsidian: /root/Obsidian-Pulse/ com estrutura Inbox/Projetos/Docker/Dev/Codex/Logs/Memorias/Templates - Skill obsidian-vault-linker instalada e documentada - Caddy labels: board.octal.tec.br + api.octal.tec.br + frontend.octal.tec.br - Protocolo task queue Redis documentado em MEMORY.md
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "pulse-dev-backend",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tsx watch --dir ./src server.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^5.1.0",
|
||||
"ioredis": "^5.4.1",
|
||||
"cors": "^2.8.5",
|
||||
"helmet": "^8.0.0",
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.19.2",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/uuid": "^10.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import helmet from 'helmet'
|
||||
import Redis from 'ioredis'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
const app = express()
|
||||
app.use(cors())
|
||||
app.use(helmet())
|
||||
app.use(express.json())
|
||||
|
||||
// ─── Redis ──────────────────────────────────────────────────────────
|
||||
const redis = new Redis({ host: 'redis', port: 6379, lazyConnect: true })
|
||||
redis.on('error', (err) => console.error('[Redis] error:', err.message))
|
||||
redis.on('ready', () => console.log('[Redis] conectado'))
|
||||
|
||||
// ─── Task Queue ─────────────────────────────────────────────────────
|
||||
const TASK_QUEUE = 'dev-tasks'
|
||||
const LOG_CHANNEL = 'dev-logs'
|
||||
|
||||
function log(level, msg) {
|
||||
const line = JSON.stringify({ ts: new Date().toISOString(), level, msg })
|
||||
redis.publish(LOG_CHANNEL, line).catch(() => {})
|
||||
console.log(`[${level}] ${msg}`)
|
||||
}
|
||||
|
||||
// GET /tasks — lista todas
|
||||
// POST /tasks — cria tarefa
|
||||
// PUT /tasks/:id — atualiza status
|
||||
// GET /agents — lista agentes conectados
|
||||
// GET /logs/:limit — logs recentes
|
||||
|
||||
app.get('/tasks', async (_req, res) => {
|
||||
const keys = await redis.keys('task:*')
|
||||
const tasks = []
|
||||
for (const k of keys) {
|
||||
const t = await redis.get(k)
|
||||
if (t) tasks.push(JSON.parse(t))
|
||||
}
|
||||
tasks.sort((a, b) => b.updated_at?.localeCompare(a.updated_at) || 0)
|
||||
res.json(tasks)
|
||||
})
|
||||
|
||||
app.post('/tasks', async (req, res) => {
|
||||
const id = uuidv4()
|
||||
const task = {
|
||||
id, title: req.body.title, type: req.body.type || 'feature',
|
||||
priority: req.body.priority || 'medium', status: 'todo',
|
||||
domain: req.body.domain || 'fullstack', assignee: null,
|
||||
created_at: new Date().toISOString(), updated_at: new Date().toISOString(),
|
||||
}
|
||||
await redis.set(`task:${id}`, JSON.stringify(task))
|
||||
await redis.rpush('task-queue', id)
|
||||
log('TASK', `Nova tarefa: "${task.title}" [${task.type}]`)
|
||||
res.status(201).json(task)
|
||||
})
|
||||
|
||||
app.put('/tasks/:id', async (req, res) => {
|
||||
const key = `task:${req.params.id}`
|
||||
const raw = await redis.get(key)
|
||||
if (!raw) return res.status(404).json({ error: 'Não encontrada' })
|
||||
const task = JSON.parse(raw)
|
||||
Object.assign(task, req.body, { updated_at: new Date().toISOString() })
|
||||
await redis.set(key, JSON.stringify(task))
|
||||
log('INFO', `Tarefa atualizada: "${task.title}" status=${task.status}`)
|
||||
res.json(task)
|
||||
})
|
||||
|
||||
app.get('/agents', async (_req, res) => {
|
||||
const keys = await redis.keys('agent:*')
|
||||
const agents = []
|
||||
for (const k of keys) {
|
||||
const a = await redis.get(k)
|
||||
if (a) agents.push(JSON.parse(a))
|
||||
}
|
||||
res.json(agents)
|
||||
})
|
||||
|
||||
app.get('/health', (_req, res) => {
|
||||
res.json({ status: 'ok', ts: new Date().toISOString() })
|
||||
})
|
||||
|
||||
// Promover tarefa automaticamente ao criar/modificar
|
||||
app.use('*', (req, res) => { res.status(404).json({ error: 'Not found' }) })
|
||||
|
||||
const PORT = 3001
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
// Registrar agente backend
|
||||
;(async () => {
|
||||
try { await redis.connect() } catch (e) { console.error('Redis connect:', e.message) }
|
||||
await redis.set('agent:dev-backend', JSON.stringify({
|
||||
id: 'dev-backend', role: 'backend', status: 'online',
|
||||
started_at: new Date().toISOString(),
|
||||
}))
|
||||
log('AGENT', `Backend API online :${PORT}`)
|
||||
log('AGENT', `Redis conectado — queue: ${TASK_QUEUE}`)
|
||||
})()
|
||||
})
|
||||
Reference in New Issue
Block a user