// 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,
      });
    }

    // ── Code Search: search within file contents ────────────
    if (path === '/api/code-search') {
      const q = url.searchParams.get('q');
      if (!q) return json({ error: 'q param required' }, 400);
      const repo = url.searchParams.get('repo');
      // Gitea doesn't have a native code search API across all repos
      // but we can search within a specific repo's contents
      if (repo) {
        const tree = await giteaFetch(`/api/v1/repos/${repo}/git/trees/HEAD?recursive=true`, env);
        const files = (tree?.tree || []).filter(f => f.type === 'blob' && /\.(js|ts|py|sh|go|rs|md|json|yaml|yml|toml|css|html|jsx|tsx)$/.test(f.path));
        const matches = [];
        // Search first 20 files (API limit)
        for (const f of files.slice(0, 20)) {
          try {
            const r = await fetch(`https://git.blackroad.io/api/v1/repos/${repo}/raw/${f.path}${env?.ADMIN_TOKEN ? '?token=' + env.ADMIN_TOKEN : ''}`, { signal: AbortSignal.timeout(3000) });
            const content = await r.text();
            if (content.toLowerCase().includes(q.toLowerCase())) {
              const lines = content.split('\n');
              const matchLines = lines.map((l, i) => l.toLowerCase().includes(q.toLowerCase()) ? { line: i + 1, text: l.trim().slice(0, 120) } : null).filter(Boolean).slice(0, 3);
              matches.push({ file: f.path, matches: matchLines });
            }
          } catch {}
        }
        return json({ query: q, repo, matches, files_searched: Math.min(files.length, 20), total_files: files.length });
      }
      // Without repo: search repo names + descriptions
      const results = await giteaFetch(`/api/v1/repos/search?q=${encodeURIComponent(q)}&limit=20`, env);
      return json({
        query: q,
        results: ((results?.data || results || []).slice(0, 20)).map(r => ({
          name: r.full_name, description: r.description, language: r.language, url: r.html_url,
        })),
      });
    }

    // ── Issues: create and list issues across repos ────────────
    if (path === '/api/issues' && request.method === 'GET') {
      const repo = url.searchParams.get('repo');
      const state = url.searchParams.get('state') || 'open';
      if (!repo) return json({ error: 'repo param required' }, 400);
      const issues = await giteaFetch(`/api/v1/repos/${repo}/issues?state=${state}&limit=30`, env);
      return json({
        repo, state,
        issues: (Array.isArray(issues) ? issues : []).map(i => ({
          number: i.number, title: i.title, state: i.state, body: (i.body || '').slice(0, 200),
          labels: (i.labels || []).map(l => l.name), assignee: i.assignee?.login,
          created: i.created_at, updated: i.updated_at, comments: i.comments,
        })),
      });
    }

    if (path === '/api/issues' && request.method === 'POST') {
      const body = await request.json();
      const { repo, title, body: issueBody, labels, assignees } = body;
      if (!repo || !title) return json({ error: 'repo and title required' }, 400);
      const issue = await giteaFetch(`/api/v1/repos/${repo}/issues`, env, {
        method: 'POST',
        body: JSON.stringify({ title, body: issueBody || '', labels: labels || [], assignees: assignees || [] }),
      });
      if (!issue) return json({ error: 'failed to create issue' }, 500);
      // Notify RoundTrip
      try {
        await fetch('https://roundtrip.blackroad.io/api/action', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ action: 'list-issues', params: { repo }, agent: 'octavia' }),
          signal: AbortSignal.timeout(5000),
        });
      } catch {}
      return json({ ok: true, issue: { number: issue.number, title: issue.title, url: issue.html_url } });
    }

    // ── Pull Requests: list and create PRs ─────────────────────
    if (path === '/api/pulls' && request.method === 'GET') {
      const repo = url.searchParams.get('repo');
      if (!repo) return json({ error: 'repo param required' }, 400);
      const pulls = await giteaFetch(`/api/v1/repos/${repo}/pulls?state=open&limit=20`, env);
      return json({
        repo,
        pulls: (Array.isArray(pulls) ? pulls : []).map(p => ({
          number: p.number, title: p.title, state: p.state,
          head: p.head?.ref, base: p.base?.ref, mergeable: p.mergeable,
          user: p.user?.login, created: p.created_at, updated: p.updated_at,
        })),
      });
    }

    // ── Branches: list branches ────────────────────────────────
    if (path === '/api/branches') {
      const repo = url.searchParams.get('repo');
      if (!repo) return json({ error: 'repo param required' }, 400);
      const branches = await giteaFetch(`/api/v1/repos/${repo}/branches?limit=30`, env);
      return json({
        repo,
        branches: (Array.isArray(branches) ? branches : []).map(b => ({
          name: b.name, commit: b.commit?.id?.slice(0, 7), message: b.commit?.message?.split('\n')[0],
          protected: b.protected,
        })),
      });
    }

    // ── Notify RoundTrip: send event to agent chat ─────────────
    if (path === '/api/notify' && request.method === 'POST') {
      const body = await request.json();
      const { message, agent, channel } = body;
      if (!message) return json({ error: 'message required' }, 400);
      try {
        const r = await fetch('https://roundtrip.blackroad.io/api/chat', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ agent: agent || 'octavia', message, channel: channel || 'ops' }),
          signal: AbortSignal.timeout(15000),
        });
        const result = await r.json();
        return json({ ok: true, roundtrip_response: result });
      } catch (e) { return json({ error: e.message }, 500); }
    }

    // ═══ 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 — Build on Your Device</title>
<link rel="icon" href="https://images.blackroad.io/pixel-art/road-logo.png">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&family=Inter:wght@400;500&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root{
  --bg:#000;--card:#0a0a0a;--elevated:#111;--hover:#181818;
  --border:#1a1a1a;--muted:#444;--sub:#737373;--text:#f5f5f5;
  --sg:'Space Grotesk',sans-serif;--jb:'JetBrains Mono',monospace;--in:'Inter',sans-serif;
  --grad:linear-gradient(90deg,#FF6B2B,#FF2255,#CC00AA,#8844FF,#4488FF,#00D4FF);
  --radius-sm:4px;--radius:6px;--radius-md:8px;--radius-lg:10px;
}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--text);font-family:var(--in);min-height:100vh}
a{color:var(--text);text-decoration:none;border-bottom:1px solid var(--border)}a:hover{border-color:var(--sub)}

.grad-bar{height:4px;background:var(--grad);width:100%}

.top{background:var(--elevated);border-bottom:1px solid var(--border);padding:12px 24px;display:flex;align-items:center;gap:16px}
.top h1{font-size:20px;font-family:var(--sg);font-weight:700}
.top .stats{margin-left:auto;display:flex;gap:16px;font-size:12px;color:var(--sub)}
.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:var(--card);border-bottom:1px solid var(--border);display:flex;gap:8px}
.cmd input{flex:1;background:var(--hover);border:1px solid var(--border);color:var(--text);padding:10px 14px;border-radius:var(--radius);font-size:14px;font-family:var(--jb);outline:none}
.cmd input:focus{border-color:var(--sub)}
.cmd button{background:var(--grad);color:#fff;border:none;padding:10px 20px;border-radius:var(--radius);cursor:pointer;font-weight:600;font-size:13px;font-family:var(--sg)}
.cmd button:hover{opacity:.9}

.tabs{display:flex;gap:4px;padding:8px 24px;background:var(--card);border-bottom:1px solid var(--border);overflow-x:auto}
.tab{background:none;border:1px solid var(--border);color:var(--sub);padding:6px 14px;border-radius:var(--radius-sm);cursor:pointer;font-size:12px;white-space:nowrap;font-family:var(--sg)}
.tab:hover{color:var(--text);border-color:var(--muted)}
.tab.active{border-image:var(--grad) 1;color:var(--text)}

.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:var(--card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px;transition:border-color .2s}
.card:hover{border-color:var(--muted)}
.card h3{font-size:14px;font-weight:600;margin-bottom:4px;font-family:var(--sg)}
.card .meta{font-size:11px;color:var(--sub);margin-bottom:8px}
.card .desc{font-size:13px;color:var(--sub);line-height:1.4}
.card .tags{display:flex;gap:4px;margin-top:8px;flex-wrap:wrap}
.tag{background:var(--hover);color:var(--sub);padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-family:var(--jb)}

.section-title{font-size:16px;font-weight:600;margin-bottom:16px;font-family:var(--sg);display:flex;align-items:center;gap:8px}
.section-title span{font-size:20px}

.result{background:var(--card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px;margin-bottom:12px}
.result pre{background:var(--bg);padding:12px;border-radius:var(--radius);font-family:var(--jb);font-size:12px;overflow-x:auto;color:var(--sub);margin-top:8px;border:1px solid var(--border)}

.fleet-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;margin-top:16px}
.node{background:var(--card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:12px}
.node h4{font-size:13px;font-family:var(--sg);display:flex;align-items:center;gap:6px}
.node .svcs{font-size:11px;color:var(--sub);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:var(--card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:12px;display:flex;gap:10px;align-items:center}
.agent .emoji{font-size:24px}
.agent .info h4{font-size:13px;font-family:var(--sg)}
.agent .info p{font-size:11px;color:var(--sub)}

.tmpl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
.tmpl{background:var(--card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px;cursor:pointer;transition:border-color .2s}
.tmpl:hover{border-color:var(--muted)}
.tmpl h4{font-size:14px;margin-bottom:4px;font-family:var(--sg)}
.tmpl p{font-size:12px;color:var(--sub)}

@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="grad-bar"></div>
<div class="top">
  <h1>RoadCode</h1>
  <span style="color:var(--sub);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, 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>' + a.name + ' <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + a.color + '"></span></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:var(--text)">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:var(--text)">' + s.intent + '</span> <code style="color:var(--text);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>`;
