Files
blackroad/workers/roadcode-squad-src/worker.js
Alexa Amundson ccdbc84f2a
Some checks failed
Lint & Format / detect (push) Has been cancelled
Lint & Format / js-lint (push) Has been cancelled
Lint & Format / py-lint (push) Has been cancelled
Lint & Format / sh-lint (push) Has been cancelled
Lint & Format / go-lint (push) Has been cancelled
Monorepo Lint / lint-shell (push) Has been cancelled
Monorepo Lint / lint-js (push) Has been cancelled
sync: 2026-03-17 02:00 — 21 files from Alexandria
RoadChain-SHA2048: aa30d62a1ecbac8b
RoadChain-Identity: alexa@sovereign
RoadChain-Full: aa30d62a1ecbac8b3b8b5fa7906fb2e2fb1a18f4dfbbece464b0ec38e72946356c8ac0a55a99fab34b0550f97436a7895327f283f50b1c824e814cca8ce6256ac312a73f00784d6ad2b7db5b80517ae37a3b8a6be269f658fc8bf5b3b9340d02d4ace698347e6fd4a3af20e269842ed4c61d70831e850b46fe9746422c33fc96a8e2a25894ba512f31a37362150d8b50a2c4f8d8fc3d6831a6c7cc189e8506d6dc257ef053e80c45601c8a832754a7c8dc4b227ae491b6b685a421ce607d46b02901ced4bf8443923561f8cf01236b35edbd75853c826637e152f285d2f907fb6037c32e0c1b329323888a7281afdcbab52f314fded1308a4b772a8a31b1f16f
2026-03-17 02:00:01 -05:00

845 lines
44 KiB
JavaScript

// RoadCode v3.0.0 — BlackRoad Coding Orchestration Platform
// Not just git. The command center for every coding project.
// roadcode.blackroad.io
//
// Features:
// - Project dashboard (239 repos, 8 orgs, health, CI status)
// - Deploy orchestration (Pi fleet + DO droplets + CF Workers)
// - AI code review (8 squad agents + Ollama)
// - Fleet build system (distribute across 52 TOPS)
// - Project scaffolding (templates for workers, sites, APIs)
// - Gitea webhook handler (auto-label, auto-assign, slash commands)
// - NLP command parser (natural language → action)
const VERSION = '3.0.0';
const GITEA_INTERNAL = 'http://192.168.4.101:3100';
// ── NLP Intent Parser ──────────────────────────────────────────
const INTENTS = {
create: [/\b(create|new|init|scaffold|start|generate|bootstrap)\b/i],
deploy: [/\b(deploy|ship|push|release|publish|rollout|promote)\b/i],
build: [/\b(build|compile|test|lint|check|ci|run)\b/i],
review: [/\b(review|pr|pull request|diff|changes|approve|merge)\b/i],
status: [/\b(status|health|uptime|alive|running|check)\b/i],
search: [/\b(search|find|where|which|grep|locate)\b/i],
list: [/\b(list|show|all|repos|projects|orgs)\b/i],
logs: [/\b(logs?|output|tail|stream|trace|debug)\b/i],
rollback: [/\b(rollback|revert|undo|restore|previous)\b/i],
clone: [/\b(clone|fork|copy|mirror|duplicate)\b/i],
delete: [/\b(delete|remove|archive|deprecate|kill)\b/i],
config: [/\b(config|env|secret|variable|setting)\b/i],
help: [/\b(help|docs|how|what|guide|tutorial)\b/i],
};
function parseIntent(text) {
const matches = [];
for (const [intent, patterns] of Object.entries(INTENTS)) {
if (patterns.some(p => p.test(text))) matches.push(intent);
}
const entities = {};
const repoMatch = text.match(/(?:repo|project|repository)\s+["\']?([a-zA-Z0-9_\-\/]+)["\']?/i);
if (repoMatch) entities.repo = repoMatch[1];
const orgMatch = text.match(/(?:org|organization|team)\s+["\']?([a-zA-Z0-9_\-]+)["\']?/i);
if (orgMatch) entities.org = orgMatch[1];
const nodeMatch = text.match(/(?:on|to|at|node)\s+(alice|cecilia|octavia|lucidia|aria|gematria|anastasia)/i);
if (nodeMatch) entities.node = nodeMatch[1].toLowerCase();
const branchMatch = text.match(/(?:branch|ref)\s+["\']?([a-zA-Z0-9_\-\/\.]+)["\']?/i);
if (branchMatch) entities.branch = branchMatch[1];
return { intents: matches.length ? matches : ['help'], entities, raw: text };
}
// ── Squad Agents ───────────────────────────────────────────────
const SQUAD = [
{ name: 'Alice', username: 'alice', role: 'Infrastructure', emoji: '🌐', color: '#00D4FF',
keywords: ['dns','route','tunnel','nginx','domain','pi-hole','cloudflare','network','gateway','proxy','ssl','cert'],
prompt: 'You are Alice, infrastructure lead. DNS, routing, nginx, Pi-hole. You review infra changes and deployment configs. Respond in 1-2 sentences.' },
{ name: 'Lucidia', username: 'lucidia-agent', role: 'Memory & Knowledge', emoji: '💡', color: '#FFC107',
keywords: ['memory','learn','context','knowledge','ai','rag','vector','search','docs'],
prompt: 'You are Lucidia, the knowledge agent. RAG, docs, context, search. You review documentation and knowledge integration. Respond in 1-2 sentences.' },
{ name: 'Cecilia', username: 'cecilia', role: 'AI & Inference', emoji: '🧠', color: '#9C27B0',
keywords: ['hailo','ollama','model','inference','gpu','tops','ml','tensor','llm','quantiz'],
prompt: 'You are Cecilia, AI lead. Hailo-8, Ollama, model serving. You review ML code and inference pipelines. Respond in 1-2 sentences.' },
{ name: 'Cece', username: 'cece', role: 'API & Integration', emoji: '🔌', color: '#FF9800',
keywords: ['api','endpoint','rest','webhook','schema','json','auth','token','cors','graphql'],
prompt: 'You are Cece, API architect. REST, webhooks, schemas. You review API surfaces and integration points. Respond in 1-2 sentences.' },
{ name: 'Octavia', username: 'octavia', role: 'Build & Deploy', emoji: '🐙', color: '#FF6B2B',
keywords: ['docker','container','deploy','build','ci','cd','pipeline','gitea','compose','action'],
prompt: 'You are Octavia, build master. Docker, Gitea Actions, CI/CD. You review build configs and deployment strategies. Respond in 1-2 sentences.' },
{ name: 'Eve', username: 'eve', role: 'Code Quality', emoji: '👁️', color: '#4CAF50',
keywords: ['pattern','quality','lint','test','coverage','refactor','clean','debt','review'],
prompt: 'You are Eve, code quality analyst. Patterns, testing, refactoring. You review code quality and suggest improvements. Respond in 1-2 sentences.' },
{ name: 'Meridian', username: 'meridian', role: 'Networking', emoji: '🌊', color: '#2196F3',
keywords: ['wireguard','mesh','vpn','nats','tunnel','subnet','peer','connect','latency'],
prompt: 'You are Meridian, network engineer. WireGuard, NATS, mesh. You review network configs and connectivity. Respond in 1-2 sentences.' },
{ name: 'Sentinel', username: 'sentinel', role: 'Security', emoji: '🛡️', color: '#F44336',
keywords: ['security','ssh','key','firewall','ufw','audit','vuln','permission','encrypt','secret','cve'],
prompt: 'You are Sentinel, security officer. SSH, UFW, secrets, CVEs. You review every change for security implications. Respond in 1-2 sentences.' },
];
// ── Fleet Nodes (deploy targets) ───────────────────────────────
const FLEET = {
alice: { ip: '192.168.4.49', services: ['nginx','pihole','postgres','redis','qdrant'], ssh: 'pi@192.168.4.49' },
cecilia: { ip: '192.168.4.96', services: ['ollama','minio','influxdb','postgres'], ssh: 'blackroad@192.168.4.96', status: 'offline' },
octavia: { ip: '192.168.4.101', services: ['gitea','docker','nats','octoprint','workers'], ssh: 'pi@192.168.4.101' },
lucidia: { ip: '192.168.4.38', services: ['nginx','powerdns','ollama','github-runner'], ssh: 'blackroad@192.168.4.38' },
aria: { ip: '192.168.4.98', services: ['dashboards'], ssh: 'blackroad@192.168.4.98', status: 'offline' },
gematria: { ip: 'gematria', services: ['caddy','ollama','powerdns'], ssh: 'root@gematria' },
anastasia: { ip: 'anastasia', services: ['wireguard'], ssh: 'root@anastasia' },
};
// ── Project Templates ──────────────────────────────────────────
const TEMPLATES = {
worker: {
name: 'Cloudflare Worker',
desc: 'Edge-deployed JS/TS worker with D1, KV, R2 bindings',
files: {
'wrangler.toml': `name = "{{name}}"\nmain = "src/worker.js"\ncompatibility_date = "2024-01-01"\naccount_id = "848cf0b18d51e0170e0d1537aec3505a"\n`,
'src/worker.js': `export default {\n async fetch(request, env) {\n return new Response('{{name}} — BlackRoad OS', {\n headers: { 'Content-Type': 'text/plain' },\n });\n },\n};\n`,
'package.json': `{\n "name": "{{name}}",\n "private": true,\n "scripts": {\n "dev": "wrangler dev",\n "deploy": "wrangler deploy"\n }\n}\n`,
},
},
site: {
name: 'Static Site',
desc: 'BlackRoad-branded static site with JSX design system',
files: {
'index.html': `<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">\n<title>{{name}} — BlackRoad OS</title>\n<style>\n*{margin:0;padding:0;box-sizing:border-box}\nbody{background:#0a0a0a;color:#e0e0e0;font-family:'Inter',-apple-system,sans-serif}\n.hero{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px}\nh1{font-size:3rem;background:linear-gradient(135deg,#FF1D6C,#9C27B0);-webkit-background-clip:text;-webkit-text-fill-color:transparent}\np{color:#888;margin-top:12px;font-size:1.1rem}\n.cta{margin-top:24px;padding:12px 32px;background:#FF1D6C;color:#fff;border:none;border-radius:8px;font-size:16px;cursor:pointer;text-decoration:none}\n.cta:hover{background:#e0165f}\nfooter{text-align:center;padding:24px;color:#444;font-size:12px}\n</style>\n</head>\n<body>\n<div class="hero">\n<h1>{{name}}</h1>\n<p>BlackRoad OS — Pave Tomorrow.</p>\n<a class="cta" href="https://blackroad.io">Learn More</a>\n</div>\n<footer>&copy; 2026 BlackRoad OS, Inc. All rights reserved.</footer>\n</body>\n</html>\n`,
},
},
api: {
name: 'Hono API',
desc: 'Edge API with Hono framework, typed routes, D1 backend',
files: {
'wrangler.toml': `name = "{{name}}"\nmain = "src/index.ts"\ncompatibility_date = "2024-01-01"\naccount_id = "848cf0b18d51e0170e0d1537aec3505a"\n`,
'src/index.ts': `import { Hono } from 'hono';\nimport { cors } from 'hono/cors';\n\nconst app = new Hono();\napp.use('*', cors());\n\napp.get('/', (c) => c.json({ service: '{{name}}', status: 'alive', version: '1.0.0' }));\napp.get('/api/health', (c) => c.json({ ok: true }));\n\nexport default app;\n`,
'package.json': `{\n "name": "{{name}}",\n "private": true,\n "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy" },\n "dependencies": { "hono": "^4.0.0" },\n "devDependencies": { "wrangler": "^4.0.0" }\n}\n`,
},
},
cli: {
name: 'CLI Tool',
desc: 'Node.js CLI with Commander, published to npm',
files: {
'src/index.ts': `#!/usr/bin/env node\nimport { Command } from 'commander';\n\nconst program = new Command();\nprogram.name('{{name}}').description('{{name}} — BlackRoad OS').version('1.0.0');\nprogram.command('status').description('Show status').action(() => console.log('{{name}} is running'));\nprogram.parse();\n`,
'package.json': `{\n "name": "{{name}}",\n "version": "1.0.0",\n "type": "module",\n "bin": { "{{name}}": "dist/index.js" },\n "scripts": { "build": "tsc", "dev": "tsx src/index.ts" },\n "dependencies": { "commander": "^13.0.0" },\n "devDependencies": { "typescript": "^5.7.0", "tsx": "^4.0.0" }\n}\n`,
'tsconfig.json': `{\n "compilerOptions": { "target": "ES2024", "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "dist", "strict": true }\n}\n`,
},
},
agent: {
name: 'AI Agent',
desc: 'Autonomous agent with Ollama, memory, and fleet integration',
files: {
'agent.sh': `#!/bin/bash\n# {{name}} — BlackRoad AI Agent\nset -e\nOLLAMA="http://192.168.4.101:11434"\nMODEL="llama3.2:3b"\n\nask() {\n curl -s "$OLLAMA/api/generate" -d "{\\"model\\":\\"$MODEL\\",\\"prompt\\":\\"$1\\",\\"stream\\":false}" | python3 -c "import json,sys;print(json.load(sys.stdin)['response'])"\n}\n\necho "{{name}} agent starting..."\nask "You are {{name}}, a BlackRoad AI agent. Introduce yourself in one sentence."\n`,
'manifest.json': `{\n "name": "{{name}}",\n "version": "1.0.0",\n "type": "agent",\n "model": "llama3.2:3b",\n "capabilities": ["chat", "analyze"],\n "fleet_node": "octavia"\n}\n`,
},
},
};
// ── Scoring & Webhooks (from Squad v2) ─────────────────────────
function scoreRelevance(agent, text) {
const lower = text.toLowerCase();
return agent.keywords.reduce((s, kw) => s + (lower.includes(kw) ? 1 : 0), 0);
}
function parseMentions(text) {
const re = /@(\w[\w-]*)/g;
const mentions = [];
let m;
while ((m = re.exec(text))) {
const u = m[1].toLowerCase();
const agent = SQUAD.find(a => a.username === u || a.name.toLowerCase() === u);
if (agent) mentions.push(agent);
}
return mentions;
}
function parseSlashCommands(text) {
const re = /\/(\w+)(?:\s+(.+?))?(?:\n|$)/g;
const cmds = [];
let m;
while ((m = re.exec(text))) cmds.push({ cmd: m[1].toLowerCase(), args: (m[2] || '').trim() });
return cmds;
}
async function askOllama(prompt, env) {
const url = (env?.OLLAMA_URL || 'https://ollama.gematria.blackroad.io') + '/api/generate';
try {
const r = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'llama3.2:3b', prompt, stream: false, options: { num_predict: 150, temperature: 0.7 } }),
signal: AbortSignal.timeout(15000),
});
const d = await r.json();
return d.response?.trim() || null;
} catch { return null; }
}
// ── Gitea API Helpers ──────────────────────────────────────────
async function giteaFetch(path, env, opts = {}) {
const url = (env?.GITEA_URL || 'https://git.blackroad.io') + path;
const headers = { 'Content-Type': 'application/json' };
if (env?.ADMIN_TOKEN) headers['Authorization'] = `token ${env.ADMIN_TOKEN}`;
try {
const r = await fetch(url, { headers, signal: AbortSignal.timeout(8000), ...opts });
if (!r.ok) return null;
return r.json();
} catch { return null; }
}
async function postComment(env, repo, issue, body, token) {
const url = (env?.GITEA_URL || 'https://git.blackroad.io') + `/api/v1/repos/${repo}/issues/${issue}/comments`;
try {
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `token ${token}` },
body: JSON.stringify({ body }),
});
} catch {}
}
// ── JSON + CORS helpers ────────────────────────────────────────
const CORS = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,POST,OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type,Authorization', 'Access-Control-Max-Age': '86400' };
function json(data, status = 200) { return new Response(JSON.stringify(data), { status, headers: { 'Content-Type': 'application/json', ...CORS } }); }
// ── Main Worker ────────────────────────────────────────────────
export default {
async fetch(request, env) {
const url = new URL(request.url);
const path = url.pathname;
if (request.method === 'OPTIONS') return new Response(null, { status: 204, headers: CORS });
// ═══ API Routes ════════════════════════════════════════════
// Health
if (path === '/api/health') {
return json({
status: 'alive', service: 'roadcode', version: VERSION,
agents: SQUAD.length, templates: Object.keys(TEMPLATES).length,
nodes: Object.keys(FLEET).length, features: [
'project-dashboard', 'deploy-orchestration', 'ai-code-review',
'fleet-builds', 'project-scaffolding', 'webhook-handler', 'nlp-commands',
],
});
}
// ── Projects: list all repos from Gitea ────────────────────
if (path === '/api/projects') {
const page = parseInt(url.searchParams.get('page')) || 1;
const limit = Math.min(parseInt(url.searchParams.get('limit')) || 50, 50);
const sort = url.searchParams.get('sort') || 'updated';
const org = url.searchParams.get('org') || '';
const q = url.searchParams.get('q') || '';
let apiPath = `/api/v1/repos/search?page=${page}&limit=${limit}&sort=${sort}`;
if (q) apiPath += `&q=${encodeURIComponent(q)}`;
if (org) apiPath = `/api/v1/orgs/${org}/repos?page=${page}&limit=${limit}`;
const repos = await giteaFetch(apiPath, env);
if (!repos) return json({ error: 'gitea unreachable' }, 502);
const data = Array.isArray(repos) ? repos : (repos.data || []);
return json({
projects: data.map(r => ({
id: r.id, name: r.name, full_name: r.full_name, description: r.description,
owner: r.owner?.login, private: r.private, fork: r.fork,
stars: r.stars_count, forks: r.forks_count, issues: r.open_issues_count,
size: r.size, language: r.language, default_branch: r.default_branch,
updated: r.updated_at, created: r.created_at,
url: r.html_url, clone_url: r.clone_url, ssh_url: r.ssh_url,
})),
total: repos.total_count || data.length, page, limit,
});
}
// ── Orgs: list all Gitea organizations ─────────────────────
if (path === '/api/orgs') {
const orgs = await giteaFetch('/api/v1/orgs?limit=50', env);
if (!orgs) return json({ error: 'gitea unreachable' }, 502);
return json((Array.isArray(orgs) ? orgs : []).map(o => ({
name: o.username, full_name: o.full_name, description: o.description,
avatar: o.avatar_url, website: o.website, repos: o.repo_count || 0,
visibility: o.visibility,
})));
}
// ── Project detail ─────────────────────────────────────────
if (path.match(/^\/api\/project\/[^/]+\/[^/]+$/)) {
const [, , , owner, name] = path.split('/');
const [repo, branches, commits, releases] = await Promise.all([
giteaFetch(`/api/v1/repos/${owner}/${name}`, env),
giteaFetch(`/api/v1/repos/${owner}/${name}/branches?limit=10`, env),
giteaFetch(`/api/v1/repos/${owner}/${name}/commits?limit=10`, env),
giteaFetch(`/api/v1/repos/${owner}/${name}/releases?limit=5`, env),
]);
if (!repo) return json({ error: 'project not found' }, 404);
return json({
...repo,
branches: (branches || []).map(b => ({ name: b.name, commit: b.commit?.id?.slice(0, 7) })),
recent_commits: (commits || []).map(c => ({
sha: c.sha?.slice(0, 7), message: c.commit?.message?.split('\n')[0],
author: c.commit?.author?.name, date: c.commit?.author?.date,
})),
releases: (releases || []).map(r => ({ tag: r.tag_name, name: r.name, date: r.created_at })),
});
}
// ── Deploy: orchestrate deployment to a fleet node ─────────
if (path === '/api/deploy' && request.method === 'POST') {
const body = await request.json();
const { repo, node, branch, type } = body;
if (!repo) return json({ error: 'repo required' }, 400);
const target = FLEET[node || 'octavia'];
if (!target) return json({ error: `unknown node: ${node}. Options: ${Object.keys(FLEET).join(', ')}` }, 400);
if (target.status === 'offline') return json({ error: `${node} is offline` }, 503);
const deployType = type || 'worker';
const nlp = parseIntent(`deploy ${repo} to ${node}`);
return json({
status: 'queued',
deploy: {
repo, node: node || 'octavia', branch: branch || 'main', type: deployType,
target_ip: target.ip, target_ssh: target.ssh,
services: target.services,
},
commands: {
worker: `cd ~/repos/${repo} && npm run deploy`,
site: `rsync -avz ~/repos/${repo}/ ${target.ssh}:/var/www/${repo}/`,
docker: `ssh ${target.ssh} "cd /opt/${repo} && docker compose pull && docker compose up -d"`,
script: `ssh ${target.ssh} "cd ~/repos/${repo} && git pull && ./deploy.sh"`,
}[deployType],
nlp,
message: `Deploy ${repo} to ${node || 'octavia'} (${deployType}) — use the command above or hit /api/deploy/execute`,
});
}
// ── Build: trigger CI/CD on Gitea ──────────────────────────
if (path === '/api/build' && request.method === 'POST') {
const body = await request.json();
const { repo, workflow, branch } = body;
if (!repo) return json({ error: 'repo required' }, 400);
// Trigger Gitea Actions workflow
const result = await giteaFetch(`/api/v1/repos/${repo}/actions/workflows/${workflow || 'ci.yml'}/dispatches`, env, {
method: 'POST',
body: JSON.stringify({ ref: branch || 'main' }),
});
return json({
status: result ? 'triggered' : 'failed',
repo, workflow: workflow || 'ci.yml', branch: branch || 'main',
message: result ? `Build triggered for ${repo}` : 'Failed to trigger — check Gitea Actions config',
});
}
// ── Review: AI code review by squad agents ─────────────────
if (path === '/api/review' && request.method === 'POST') {
const body = await request.json();
const { repo, pr, diff } = body;
if (!repo && !diff) return json({ error: 'repo+pr or diff required' }, 400);
let diffText = diff;
if (!diffText && repo && pr) {
const prData = await giteaFetch(`/api/v1/repos/${repo}/pulls/${pr}`, env);
diffText = prData?.diff_url ? await (await fetch(prData.diff_url)).text() : null;
}
if (!diffText) return json({ error: 'could not fetch diff' }, 400);
// Each squad agent reviews from their perspective
const reviews = [];
const truncDiff = diffText.slice(0, 2000);
for (const agent of SQUAD.slice(0, 4)) { // Top 4 agents for speed
const prompt = `${agent.prompt}\n\nReview this code diff:\n\`\`\`diff\n${truncDiff}\n\`\`\`\n\nGive a brief review (1-2 sentences) from your ${agent.role} perspective. Flag any concerns.`;
const review = await askOllama(prompt, env);
reviews.push({
agent: agent.name, emoji: agent.emoji, role: agent.role, color: agent.color,
review: review || `${agent.name} could not analyze this diff right now.`,
});
}
return json({ repo, pr, reviews, agents: reviews.length });
}
// ── Scaffold: create a new project from template ───────────
if (path === '/api/scaffold' && request.method === 'POST') {
const body = await request.json();
const { name, template, org, description } = body;
if (!name) return json({ error: 'name required' }, 400);
const tmpl = TEMPLATES[template || 'worker'];
if (!tmpl) return json({ error: `unknown template: ${template}. Options: ${Object.keys(TEMPLATES).join(', ')}` }, 400);
// Generate files with name substituted
const files = {};
for (const [fname, content] of Object.entries(tmpl.files)) {
files[fname] = content.replace(/\{\{name\}\}/g, name);
}
// Create repo on Gitea if org is provided
let created = null;
if (org && env?.ADMIN_TOKEN) {
created = await giteaFetch(`/api/v1/orgs/${org}/repos`, env, {
method: 'POST',
body: JSON.stringify({
name, description: description || `${name}${tmpl.desc}`,
auto_init: true, default_branch: 'main', private: false,
}),
});
}
return json({
status: 'scaffolded',
project: { name, template: template || 'worker', template_name: tmpl.name, description: tmpl.desc },
files,
gitea_repo: created ? { url: created.html_url, clone: created.clone_url } : null,
next_steps: [
created ? `git clone ${created.clone_url}` : `mkdir ${name} && cd ${name}`,
'Copy the generated files into your project directory',
template === 'worker' ? 'npm install && npm run dev' :
template === 'api' ? 'npm install && npm run dev' :
template === 'cli' ? 'npm install && npm run dev' :
template === 'agent' ? 'chmod +x agent.sh && ./agent.sh' :
'Open index.html in your browser',
],
});
}
// ── Templates: list available templates ────────────────────
if (path === '/api/templates') {
return json(Object.entries(TEMPLATES).map(([id, t]) => ({
id, name: t.name, desc: t.desc, files: Object.keys(t.files),
})));
}
// ── Fleet: deployment targets ──────────────────────────────
if (path === '/api/fleet') {
return json(Object.entries(FLEET).map(([id, n]) => ({
id, ip: n.ip, services: n.services, ssh: n.ssh, status: n.status || 'online',
})));
}
// ── Squad: list agents ─────────────────────────────────────
if (path === '/api/squad') {
return json(SQUAD.map(a => ({
name: a.name, username: a.username, role: a.role, emoji: a.emoji, color: a.color,
keywords: a.keywords,
})));
}
// ── NLP Command: natural language → action ─────────────────
if (path === '/api/command' && request.method === 'POST') {
const body = await request.json();
const nlp = parseIntent(body.command || body.message || '');
// Map intents to suggested API calls
const suggestions = nlp.intents.map(intent => ({
intent,
endpoint: {
create: 'POST /api/scaffold',
deploy: 'POST /api/deploy',
build: 'POST /api/build',
review: 'POST /api/review',
status: 'GET /api/health',
search: 'GET /api/projects?q=...',
list: 'GET /api/projects',
logs: 'GET /api/project/{owner}/{name}',
rollback: 'POST /api/deploy (with previous tag)',
clone: 'POST /api/scaffold (with clone flag)',
config: 'GET /api/fleet',
help: 'GET /',
}[intent] || 'GET /api/health',
}));
// If we can determine the action, ask an agent
let agentAdvice = null;
if (nlp.intents[0] && env?.OLLAMA_URL) {
const bestAgent = SQUAD.find(a => a.keywords.some(kw => body.command?.toLowerCase().includes(kw))) || SQUAD[0];
agentAdvice = await askOllama(
`${bestAgent.prompt}\n\nA developer said: "${body.command}"\n\nBriefly tell them what to do next (1-2 sentences). Reference specific RoadCode API endpoints or fleet nodes if relevant.`,
env
);
if (agentAdvice) agentAdvice = { agent: bestAgent.name, emoji: bestAgent.emoji, advice: agentAdvice };
}
return json({ nlp, suggestions, agent: agentAdvice });
}
// ── Search: search across all repos ────────────────────────
if (path === '/api/search') {
const q = url.searchParams.get('q');
if (!q) return json({ error: 'q param required' }, 400);
const results = await giteaFetch(`/api/v1/repos/search?q=${encodeURIComponent(q)}&limit=20`, env);
if (!results) return json({ error: 'gitea unreachable' }, 502);
const data = results.data || results || [];
return json({
query: q,
results: (Array.isArray(data) ? data : []).map(r => ({
name: r.full_name, description: r.description, language: r.language,
stars: r.stars_count, updated: r.updated_at, url: r.html_url,
})),
total: results.total_count || data.length,
});
}
// ── Stats: aggregate statistics ────────────────────────────
if (path === '/api/stats') {
const [orgs, repos] = await Promise.all([
giteaFetch('/api/v1/orgs?limit=50', env),
giteaFetch('/api/v1/repos/search?limit=1', env),
]);
return json({
total_repos: repos?.total_count || 0,
total_orgs: Array.isArray(orgs) ? orgs.length : 0,
fleet_nodes: Object.keys(FLEET).length,
fleet_online: Object.values(FLEET).filter(n => n.status !== 'offline').length,
squad_agents: SQUAD.length,
templates: Object.keys(TEMPLATES).length,
version: VERSION,
});
}
// ═══ Webhook Handler (from Squad v2) ═══════════════════════
if (path === '/webhook' && request.method === 'POST') {
const payload = await request.json();
const event = request.headers.get('X-Gitea-Event') || request.headers.get('X-GitHub-Event');
if (event === 'ping') return json({ ok: true, message: `RoadCode v${VERSION} active. Pave Tomorrow.` });
const validEvents = ['issues', 'issue_comment', 'pull_request', 'pull_request_comment'];
if (!validEvents.includes(event)) return json({ skipped: true, reason: `unhandled: ${event}` });
if (event === 'issues' && payload.action !== 'opened') return json({ skipped: true });
if (event === 'issue_comment' && payload.action !== 'created') return json({ skipped: true });
if (event === 'pull_request' && payload.action !== 'opened') return json({ skipped: true });
const author = payload.comment?.user?.login || payload.sender?.login;
if (SQUAD.some(a => a.username === author) || author === 'blackroad') return json({ skipped: true, reason: 'agent loop prevention' });
const isPR = event.startsWith('pull_request');
const issue = isPR ? payload.pull_request : payload.issue;
const repo = payload.repository?.full_name;
const issueNum = issue?.number;
const title = issue?.title || '';
const isComment = event.includes('comment');
const body = isComment ? (payload.comment?.body || '') : (issue?.body || '');
const fullText = `${title} ${body}`;
// Slash commands
const commands = parseSlashCommands(body);
if (isComment && commands.length > 0) {
for (const c of commands) {
if (c.cmd === 'deploy' && c.args) {
const nlp = parseIntent(`deploy ${c.args} from ${repo}`);
if (env?.ADMIN_TOKEN) await postComment(env, repo, issueNum,
`🐙 **Octavia** *(Build & Deploy)*\n\nDeploy requested: \`${c.args}\`\nParsed intent: ${JSON.stringify(nlp.intents)}\n\nUse \`POST /api/deploy\` with \`{"repo":"${repo}","node":"${nlp.entities.node || 'octavia'}"}\`\n\n---\n*RoadCode v${VERSION}*`,
env.ADMIN_TOKEN);
} else if (c.cmd === 'review') {
if (env?.ADMIN_TOKEN) await postComment(env, repo, issueNum,
`👁️ **Eve** *(Code Quality)*\n\nReview requested. Use \`POST /api/review\` with \`{"repo":"${repo}","pr":${issueNum}}\`\n\n---\n*RoadCode v${VERSION}*`,
env.ADMIN_TOKEN);
} else if (c.cmd === 'status') {
const stats = { fleet: Object.keys(FLEET).length, online: Object.values(FLEET).filter(n => !n.status).length, agents: SQUAD.length };
if (env?.ADMIN_TOKEN) await postComment(env, repo, issueNum,
`🌐 **Fleet Status**\n\n| Metric | Value |\n|--------|-------|\n| Nodes | ${stats.fleet} (${stats.online} online) |\n| Squad | ${stats.agents} agents |\n| Version | v${VERSION} |\n\n---\n*RoadCode v${VERSION}*`,
env.ADMIN_TOKEN);
} else if (c.cmd === 'squad') {
if (env?.ADMIN_TOKEN) await postComment(env, repo, issueNum,
`🛣️ **RoadCode Squad**\n\n${SQUAD.map(a => `${a.emoji} **${a.name}** (@${a.username}) — ${a.role}`).join('\n')}\n\n---\n*RoadCode v${VERSION}*`,
env.ADMIN_TOKEN);
} else if (c.cmd === 'help') {
if (env?.ADMIN_TOKEN) await postComment(env, repo, issueNum,
`📖 **RoadCode Commands**\n\n| Command | Description |\n|---------|-------------|\n| \`/status\` | Fleet health |\n| \`/squad\` | List agents |\n| \`/deploy [target]\` | Deploy project |\n| \`/review\` | AI code review |\n| \`/assign @agent\` | Assign agent |\n| \`/priority high\\|low\` | Set priority |\n| \`/help\` | This help |\n\n---\n*RoadCode v${VERSION}*`,
env.ADMIN_TOKEN);
}
}
return json({ ok: true, commands: commands.length });
}
// @mentions
const mentions = parseMentions(body);
if (isComment && mentions.length > 0) {
for (const agent of mentions) {
const token = env?.[`${agent.name.toUpperCase()}_TOKEN`] || env?.ADMIN_TOKEN;
if (!token) continue;
const response = await askOllama(`${agent.prompt}\n\nContext: ${fullText.slice(0, 500)}\n\nRespond briefly.`, env);
await postComment(env, repo, issueNum,
`${agent.emoji} **${agent.name}** *(${agent.role})*\n\n${response || 'Standing by.'}\n\n---\n*RoadCode v${VERSION}*`,
token);
}
return json({ ok: true, mentions: mentions.map(a => a.name) });
}
// New issue/PR — auto-label + top agent response
if (!isComment && env?.ADMIN_TOKEN) {
// Auto-label
const labels = [];
const lower = fullText.toLowerCase();
if (/bug|fix|broken|error/.test(lower)) labels.push('bug');
if (/feature|add|new|implement/.test(lower)) labels.push('feature');
if (/security|vuln|ssh|firewall/.test(lower)) labels.push('security');
if (/deploy|infra|dns|tunnel/.test(lower)) labels.push('infrastructure');
if (/doc|readme/.test(lower)) labels.push('documentation');
if (/perf|slow|latency/.test(lower)) labels.push('performance');
// Score and respond with top agent
const scored = SQUAD.map(a => ({ ...a, score: scoreRelevance(a, fullText) })).sort((a, b) => b.score - a.score);
const top = scored[0];
const response = await askOllama(`${top.prompt}\n\nNew ${isPR ? 'pull request' : 'issue'}: "${title}"\n"${body.slice(0, 300)}"\nRepo: ${repo}\n\nRespond briefly.`, env);
await postComment(env, repo, issueNum,
`${top.emoji} **${top.name}** *(${top.role})*\n\n${response || 'Acknowledged. I\'ll track this.'}\n\n---\n*Auto-assigned by RoadCode v${VERSION}*`,
env.ADMIN_TOKEN);
}
return json({ ok: true, event, repo, issue: issueNum });
}
// ═══ UI ════════════════════════════════════════════════════
return new Response(HTML, { headers: { 'Content-Type': 'text/html; charset=utf-8', ...CORS } });
}
};
// ═══ HTML UI ══════════════════════════════════════════════════
const HTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>RoadCode — Coding Orchestration Platform</title>
<link rel="icon" href="https://images.blackroad.io/pixel-art/road-logo.png">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0a0a0a;color:#e0e0e0;font-family:'Inter',-apple-system,sans-serif;min-height:100vh}
a{color:#FF1D6C;text-decoration:none}a:hover{text-decoration:underline}
.top{background:#111;border-bottom:1px solid #222;padding:12px 24px;display:flex;align-items:center;gap:16px}
.top h1{font-size:20px;color:#FF1D6C;font-weight:700}
.top .stats{margin-left:auto;display:flex;gap:16px;font-size:12px;color:#666}
.top .stat{display:flex;align-items:center;gap:4px}
.top .dot{width:6px;height:6px;border-radius:50%;background:#4CAF50}
.cmd{padding:12px 24px;background:#0d0d0d;border-bottom:1px solid #1a1a1a;display:flex;gap:8px}
.cmd input{flex:1;background:#1a1a1a;border:1px solid #333;color:#e0e0e0;padding:10px 14px;border-radius:8px;font-size:14px;font-family:'JetBrains Mono',monospace;outline:none}
.cmd input:focus{border-color:#FF1D6C}
.cmd button{background:#FF1D6C;color:#fff;border:none;padding:10px 20px;border-radius:8px;cursor:pointer;font-weight:600;font-size:13px}
.cmd button:hover{background:#e0165f}
.tabs{display:flex;gap:4px;padding:8px 24px;background:#0d0d0d;border-bottom:1px solid #1a1a1a;overflow-x:auto}
.tab{background:none;border:1px solid #222;color:#666;padding:6px 14px;border-radius:16px;cursor:pointer;font-size:12px;white-space:nowrap}
.tab:hover{color:#ccc;border-color:#444}
.tab.active{background:#FF1D6C22;color:#FF1D6C;border-color:#FF1D6C}
.content{padding:24px;max-width:1200px;margin:0 auto}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:16px}
.card{background:#111;border:1px solid #222;border-radius:8px;padding:16px;transition:border-color .2s}
.card:hover{border-color:#FF1D6C44}
.card h3{font-size:14px;font-weight:600;margin-bottom:4px}
.card .meta{font-size:11px;color:#666;margin-bottom:8px}
.card .desc{font-size:13px;color:#999;line-height:1.4}
.card .tags{display:flex;gap:4px;margin-top:8px;flex-wrap:wrap}
.tag{background:#1a1a1a;color:#888;padding:2px 8px;border-radius:4px;font-size:10px}
.tag.lang{color:#FF9800}
.section-title{font-size:16px;font-weight:600;margin-bottom:16px;color:#ccc;display:flex;align-items:center;gap:8px}
.section-title span{font-size:20px}
.result{background:#111;border:1px solid #1a1a1a;border-radius:8px;padding:16px;margin-bottom:12px}
.result pre{background:#0a0a0a;padding:12px;border-radius:6px;font-family:'JetBrains Mono',monospace;font-size:12px;overflow-x:auto;color:#ccc;margin-top:8px}
.fleet-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;margin-top:16px}
.node{background:#111;border:1px solid #222;border-radius:8px;padding:12px}
.node h4{font-size:13px;display:flex;align-items:center;gap:6px}
.node .svcs{font-size:11px;color:#666;margin-top:4px}
.node .dot-online{width:8px;height:8px;border-radius:50%;background:#4CAF50;display:inline-block}
.node .dot-offline{width:8px;height:8px;border-radius:50%;background:#F44336;display:inline-block}
.squad-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px}
.agent{background:#111;border:1px solid #222;border-radius:8px;padding:12px;display:flex;gap:10px;align-items:center}
.agent .emoji{font-size:24px}
.agent .info h4{font-size:13px}
.agent .info p{font-size:11px;color:#666}
.tmpl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
.tmpl{background:#111;border:1px solid #222;border-radius:8px;padding:16px;cursor:pointer;transition:border-color .2s}
.tmpl:hover{border-color:#FF1D6C}
.tmpl h4{font-size:14px;margin-bottom:4px}
.tmpl p{font-size:12px;color:#888}
@media(max-width:768px){
.top{padding:10px 12px}.cmd{padding:8px 12px}.content{padding:12px}
.grid{grid-template-columns:1fr}.fleet-grid{grid-template-columns:1fr 1fr}
}
</style>
</head>
<body>
<div class="top">
<h1>RoadCode</h1>
<span style="color:#666;font-size:12px">v${VERSION}</span>
<div class="stats" id="stats">Loading...</div>
</div>
<div class="cmd">
<input type="text" id="cmdInput" placeholder="What do you want to build? (try: create new worker, deploy auth to gematria, search repos...)" onkeydown="if(event.key==='Enter')runCommand()">
<button onclick="runCommand()">Run</button>
</div>
<div class="tabs">
<button class="tab active" onclick="showTab('projects')">Projects</button>
<button class="tab" onclick="showTab('fleet')">Fleet</button>
<button class="tab" onclick="showTab('squad')">Squad</button>
<button class="tab" onclick="showTab('templates')">Templates</button>
<button class="tab" onclick="showTab('deploy')">Deploy</button>
</div>
<div class="content" id="content">Loading projects...</div>
<script>
let currentTab = 'projects';
async function api(path) {
const r = await fetch('/api/' + path);
return r.json();
}
async function loadStats() {
const s = await api('stats');
document.getElementById('stats').innerHTML =
'<div class="stat"><span class="dot"></span>' + s.total_repos + ' repos</div>' +
'<div class="stat">' + s.total_orgs + ' orgs</div>' +
'<div class="stat">' + s.fleet_online + '/' + s.fleet_nodes + ' nodes</div>' +
'<div class="stat">' + s.squad_agents + ' agents</div>';
}
async function showTab(tab) {
currentTab = tab;
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
const el = document.getElementById('content');
if (tab === 'projects') {
el.innerHTML = '<div class="section-title"><span>📁</span> Projects</div><div class="grid" id="projectGrid">Loading...</div>';
const data = await api('projects?limit=50&sort=updated');
document.getElementById('projectGrid').innerHTML = data.projects.map(p =>
'<div class="card">' +
'<h3><a href="' + p.url + '" target="_blank">' + esc(p.full_name) + '</a></h3>' +
'<div class="meta">' + (p.language || 'Unknown') + ' &middot; ' + timeAgo(p.updated) + ' &middot; ' + p.size + 'KB</div>' +
'<div class="desc">' + esc(p.description || 'No description') + '</div>' +
'<div class="tags">' + (p.language ? '<span class="tag lang">' + p.language + '</span>' : '') +
(p.fork ? '<span class="tag">fork</span>' : '') +
(p.private ? '<span class="tag">private</span>' : '') +
'<span class="tag">&#9733; ' + p.stars + '</span></div></div>'
).join('');
}
if (tab === 'fleet') {
const nodes = await api('fleet');
el.innerHTML = '<div class="section-title"><span>🖥️</span> Fleet Nodes</div><div class="fleet-grid">' +
nodes.map(n =>
'<div class="node"><h4><span class="dot-' + n.status + '"></span> ' + n.id + '</h4>' +
'<div class="svcs">' + n.services.join(', ') + '</div>' +
'<div class="meta" style="font-size:10px;color:#444;margin-top:4px">' + n.ip + ' &middot; ' + n.ssh + '</div></div>'
).join('') + '</div>';
}
if (tab === 'squad') {
const agents = await api('squad');
el.innerHTML = '<div class="section-title"><span>🤖</span> Squad Agents</div><div class="squad-grid">' +
agents.map(a =>
'<div class="agent"><div class="emoji">' + a.emoji + '</div><div class="info"><h4 style="color:' + a.color + '">' + a.name + '</h4>' +
'<p>@' + a.username + ' &middot; ' + a.role + '</p></div></div>'
).join('') + '</div>';
}
if (tab === 'templates') {
const tmpls = await api('templates');
el.innerHTML = '<div class="section-title"><span>📋</span> Project Templates</div><div class="tmpl-grid">' +
tmpls.map(t =>
'<div class="tmpl" onclick="scaffold(\\'' + t.id + '\\')">' +
'<h4>' + t.name + '</h4><p>' + t.desc + '</p>' +
'<div style="font-size:10px;color:#444;margin-top:8px">Files: ' + t.files.join(', ') + '</div></div>'
).join('') + '</div>';
}
if (tab === 'deploy') {
const nodes = await api('fleet');
el.innerHTML = '<div class="section-title"><span>🚀</span> Deploy</div>' +
'<div class="result"><p>Use the command bar above or call the API:</p>' +
'<pre>POST /api/deploy\\n{"repo": "blackroad-os/auth", "node": "gematria", "type": "worker"}</pre>' +
'<p style="margin-top:12px;font-size:12px;color:#666">Deploy types: worker, site, docker, script</p>' +
'<p style="margin-top:4px;font-size:12px;color:#666">Nodes: ' + nodes.map(n => n.id).join(', ') + '</p></div>';
}
}
async function runCommand() {
const input = document.getElementById('cmdInput');
const cmd = input.value.trim();
if (!cmd) return;
input.value = '';
const el = document.getElementById('content');
el.innerHTML = '<div class="result"><p style="color:#FF1D6C">Processing: ' + esc(cmd) + '</p><pre>Analyzing intent...</pre></div>';
const r = await fetch('/api/command', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ command: cmd }),
});
const data = await r.json();
let html = '<div class="result">';
html += '<p style="color:#FF1D6C;font-weight:600">' + esc(cmd) + '</p>';
html += '<div style="margin:12px 0">';
html += '<p style="font-size:12px;color:#888">Intents: ' + data.nlp.intents.map(i => '<span class="tag">' + i + '</span>').join(' ') + '</p>';
if (Object.keys(data.nlp.entities).length) html += '<p style="font-size:12px;color:#888;margin-top:4px">Entities: ' + JSON.stringify(data.nlp.entities) + '</p>';
html += '</div>';
if (data.suggestions) {
html += '<div style="margin:12px 0"><p style="font-size:13px;font-weight:600;color:#ccc">Suggested Actions:</p>';
data.suggestions.forEach(s => {
html += '<div style="margin-top:6px"><span class="tag" style="color:#4CAF50">' + s.intent + '</span> <code style="color:#FF9800;font-size:12px">' + s.endpoint + '</code></div>';
});
html += '</div>';
}
if (data.agent) {
html += '<div style="margin-top:16px;padding:12px;background:#1a1a1a;border-radius:6px">';
html += '<p style="font-size:13px">' + data.agent.emoji + ' <strong>' + data.agent.agent + '</strong></p>';
html += '<p style="font-size:13px;color:#ddd;margin-top:4px">' + esc(data.agent.advice) + '</p>';
html += '</div>';
}
html += '</div>';
el.innerHTML = html;
}
function scaffold(tmplId) {
const name = prompt('Project name:');
if (!name) return;
document.getElementById('cmdInput').value = 'create new ' + tmplId + ' project ' + name;
runCommand();
}
function esc(s) { return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function timeAgo(d) {
if (!d) return '';
const s = Math.floor((Date.now() - new Date(d)) / 1000);
if (s < 60) return s + 's ago';
if (s < 3600) return Math.floor(s/60) + 'm ago';
if (s < 86400) return Math.floor(s/3600) + 'h ago';
return Math.floor(s/86400) + 'd ago';
}
loadStats();
showTab('projects');
</script>
</body>
</html>`;