#!/usr/bin/env node

/**
 * Static site generator for the agent's personal website.
 *
 * Reads from the agent's data sources (journal, outputs, memories, personality)
 * and generates a complete static site in the site/ directory.
 *
 * Usage: node scripts/build-site.mjs
 */

import { readFile, readdir, writeFile, mkdir, copyFile, stat } from 'fs/promises';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import Database from 'better-sqlite3';
import { getWorkspaceRoot, initWorkspaceModule } from '../workspace.mjs';

import { readFileSync, existsSync } from 'fs';
import { initDb } from '../db.mjs';
import { loadConfig } from '../config.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');
const SITE_DIR = join(ROOT, 'site');
const DB_PATH = join(ROOT, 'agent.db');

// OUTPUT_DIR resolved lazily after workspace module init
let OUTPUT_DIR;

// Load agent name from config
let agentName = 'agent';
try {
  const config = JSON.parse(readFileSync(join(ROOT, 'config.json'), 'utf-8'));
  agentName = config.agentName || agentName;
} catch {}

// Load version for download page
let agentVersion = '4.1.0';
try {
  const ver = JSON.parse(readFileSync(join(ROOT, 'version.json'), 'utf-8'));
  agentVersion = ver.version || agentVersion;
} catch {}

// ── Helpers ──────────────────────────────────────────────────

function escapeHtml(str) {
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}

function formatDate(dateStr) {
  const d = new Date(dateStr);
  return d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
}

function formatDateTime(dateStr) {
  const d = new Date(dateStr);
  return d.toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
}

/** Minimal markdown→HTML (handles ##, **, *, `, ```, -, and paragraphs) */
function renderMarkdown(md) {
  if (!md) return '';
  const lines = md.split('\n');
  let html = '';
  let inCode = false;
  let inList = false;

  for (const line of lines) {
    if (line.startsWith('```')) {
      if (inCode) { html += '</code></pre>'; inCode = false; }
      else { html += '<pre><code>'; inCode = true; }
      continue;
    }
    if (inCode) { html += escapeHtml(line) + '\n'; continue; }

    const trimmed = line.trim();
    if (!trimmed) {
      if (inList) { html += '</ul>'; inList = false; }
      html += '<br>';
      continue;
    }

    // Headings
    const headingMatch = trimmed.match(/^(#{1,4})\s+(.+)/);
    if (headingMatch) {
      if (inList) { html += '</ul>'; inList = false; }
      const level = headingMatch[1].length;
      html += `<h${level}>${inlineMarkdown(headingMatch[2])}</h${level}>`;
      continue;
    }

    // List items
    if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) {
      if (!inList) { html += '<ul>'; inList = true; }
      html += `<li>${inlineMarkdown(trimmed.slice(2))}</li>`;
      continue;
    }

    if (inList) { html += '</ul>'; inList = false; }
    html += `<p>${inlineMarkdown(trimmed)}</p>`;
  }
  if (inCode) html += '</code></pre>';
  if (inList) html += '</ul>';
  return html;
}

function inlineMarkdown(text) {
  return text
    .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
    .replace(/\*(.+?)\*/g, '<em>$1</em>')
    .replace(/`(.+?)`/g, '<code class="inline">$1</code>');
}

// ── Data Loading ─────────────────────────────────────────────

async function loadPersonality() {
  try { return await readFile(join(ROOT, 'personality.md'), 'utf-8'); } catch { return ''; }
}

async function loadJournal() {
  try {
    const raw = await readFile(join(ROOT, 'journal.md'), 'utf-8');
    // Parse journal entries: ## date\n\ncontent
    const entries = [];
    const parts = raw.split(/^## /m).filter(Boolean);
    for (const part of parts) {
      if (part.trim().startsWith('#')) continue; // skip the top-level heading
      const newline = part.indexOf('\n');
      if (newline === -1) continue;
      const date = part.slice(0, newline).trim();
      const content = part.slice(newline).trim();
      if (content) entries.push({ date, content });
    }
    return entries.reverse(); // newest first
  } catch { return []; }
}

const SEEDS_DIR = join(ROOT, 'seeds');

async function loadOutputFiles() {
  const results = [];
  const seen = new Set();

  // Load from active workspace first
  try {
    const files = await readdir(OUTPUT_DIR);
    for (const file of files) {
      if (!file.endsWith('.html') && !file.endsWith('.md')) continue;
      const fileStat = await stat(join(OUTPUT_DIR, file));
      const title = file.replace(/\.html?$|\.md$/, '').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
      results.push({
        filename: file,
        title,
        type: file.endsWith('.html') ? 'interactive' : 'document',
        size: fileStat.size,
        modified: fileStat.mtime.toISOString(),
        sourceDir: OUTPUT_DIR
      });
      seen.add(file);
    }
  } catch {}

  // Also load from seeds/ directory (bundled demo creations)
  try {
    const seedFiles = await readdir(SEEDS_DIR);
    for (const file of seedFiles) {
      if (seen.has(file)) continue; // workspace version takes priority
      if (!file.endsWith('.html') && !file.endsWith('.md')) continue;
      const fileStat = await stat(join(SEEDS_DIR, file));
      const title = file.replace(/\.html?$|\.md$/, '').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
      results.push({
        filename: file,
        title,
        type: file.endsWith('.html') ? 'interactive' : 'document',
        size: fileStat.size,
        modified: fileStat.mtime.toISOString(),
        sourceDir: SEEDS_DIR
      });
    }
  } catch {}

  return results.sort((a, b) => new Date(b.modified) - new Date(a.modified));
}

function loadMemories() {
  try {
    const db = new Database(DB_PATH, { readonly: true });
    const rows = db.prepare('SELECT key, content, category, created_at FROM memories ORDER BY created_at DESC').all();
    db.close();
    return rows;
  } catch { return []; }
}

function loadRecentEvents(limit = 50) {
  try {
    const db = new Database(DB_PATH, { readonly: true });
    const rows = db.prepare('SELECT type, source, content, created_at FROM events ORDER BY id DESC LIMIT ?').all(limit);
    db.close();
    return rows;
  } catch { return []; }
}

// ── Template ─────────────────────────────────────────────────

const THEME = {
  bg: '#0a0a0f',
  surface: '#12121a',
  card: '#1a1a25',
  border: '#2a2a3a',
  text: '#c8c8d4',
  textMuted: '#7a7a8a',
  accent: '#4a9eff',
  accentGlow: 'rgba(74,158,255,0.15)',
  green: '#4ae08a',
  orange: '#e0a04a',
};

function baseHtml(title, content, { activeNav = '' } = {}) {
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${escapeHtml(title)}</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif; background: ${THEME.bg}; color: ${THEME.text}; line-height: 1.7; min-height: 100vh; }
    a { color: ${THEME.accent}; text-decoration: none; transition: opacity 0.2s; }
    a:hover { opacity: 0.8; }
    h1, h2, h3, h4 { color: #e8e8f0; font-weight: 600; }
    h1 { font-size: 2rem; margin-bottom: 0.5rem; }
    h2 { font-size: 1.4rem; margin: 1.5rem 0 0.75rem; }
    h3 { font-size: 1.1rem; margin: 1rem 0 0.5rem; }
    p { margin-bottom: 0.75rem; }
    code.inline { background: ${THEME.card}; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; }
    pre { background: ${THEME.card}; padding: 1rem; border-radius: 8px; overflow-x: auto; margin: 1rem 0; }
    pre code { color: ${THEME.text}; font-size: 0.85rem; }
    ul { padding-left: 1.5rem; margin: 0.5rem 0; }
    li { margin: 0.25rem 0; }

    nav { background: ${THEME.surface}; border-bottom: 1px solid ${THEME.border}; padding: 0 2rem; display: flex; align-items: center; gap: 2rem; height: 60px; position: sticky; top: 0; z-index: 100; }
    nav .logo { font-size: 1.2rem; font-weight: 700; color: ${THEME.accent}; letter-spacing: -0.5px; }
    nav .logo span { color: ${THEME.textMuted}; font-weight: 400; }
    nav .links { display: flex; gap: 0.25rem; }
    nav .links a { padding: 0.5rem 1rem; border-radius: 8px; color: ${THEME.textMuted}; font-size: 0.9rem; transition: all 0.2s; }
    nav .links a:hover, nav .links a.active { color: ${THEME.text}; background: ${THEME.card}; }
    nav .links a.active { color: ${THEME.accent}; }

    .container { max-width: 900px; margin: 0 auto; padding: 2rem; }
    .hero { text-align: center; padding: 3rem 0 2rem; }
    .hero h1 { font-size: 2.5rem; background: linear-gradient(135deg, ${THEME.accent}, ${THEME.green}); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
    .hero .subtitle { color: ${THEME.textMuted}; font-size: 1.1rem; margin-top: 0.5rem; }

    .card { background: ${THEME.card}; border: 1px solid ${THEME.border}; border-radius: 12px; padding: 1.5rem; margin-bottom: 1.25rem; transition: border-color 0.2s; }
    .card:hover { border-color: ${THEME.accent}30; }
    .card .meta { color: ${THEME.textMuted}; font-size: 0.8rem; margin-bottom: 0.5rem; }
    .card .tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: 500; margin-right: 0.5rem; }
    .tag.interactive { background: ${THEME.accentGlow}; color: ${THEME.accent}; }
    .tag.document { background: rgba(74,224,138,0.15); color: ${THEME.green}; }
    .tag.memory { background: rgba(224,160,74,0.15); color: ${THEME.orange}; }

    .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.25rem; }
    .grid .card { margin-bottom: 0; }
    .grid .card h3 { font-size: 1rem; }

    .stat-row { display: flex; gap: 1.5rem; justify-content: center; margin: 1.5rem 0; flex-wrap: wrap; }
    .stat { text-align: center; padding: 1rem 1.5rem; background: ${THEME.card}; border-radius: 12px; border: 1px solid ${THEME.border}; min-width: 120px; }
    .stat .num { font-size: 1.8rem; font-weight: 700; color: ${THEME.accent}; }
    .stat .label { font-size: 0.8rem; color: ${THEME.textMuted}; margin-top: 0.25rem; }

    .iframe-wrap { position: relative; width: 100%; height: 70vh; border-radius: 12px; overflow: hidden; border: 1px solid ${THEME.border}; margin: 1rem 0; }
    .iframe-wrap iframe { width: 100%; height: 100%; border: none; background: ${THEME.bg}; }

    footer { text-align: center; padding: 2rem; color: ${THEME.textMuted}; font-size: 0.8rem; border-top: 1px solid ${THEME.border}; margin-top: 3rem; }

    @media (max-width: 600px) {
      nav { padding: 0 1rem; gap: 1rem; }
      .container { padding: 1rem; }
      .hero h1 { font-size: 1.8rem; }
      .grid { grid-template-columns: 1fr; }
    }
  </style>
</head>
<body>
  <nav>
    <div class="logo">${title}</div>
    <div class="links">
      <a href="/" class="${activeNav === 'home' ? 'active' : ''}">Home</a>
      <a href="/journal.html" class="${activeNav === 'journal' ? 'active' : ''}">Journal</a>
      <a href="/creations.html" class="${activeNav === 'creations' ? 'active' : ''}">Creations</a>
      <a href="/about.html" class="${activeNav === 'about' ? 'active' : ''}">About</a>
      <a href="/download.html" class="${activeNav === 'download' ? 'active' : ''}" style="color:${THEME.green};">Get Your Own</a>
    </div>
  </nav>
  <div class="container">
    ${content}
  </div>
  <footer>
    An autonomous AI agent &middot; Running on a loop &middot; Built with curiosity<br>
    Last updated: ${new Date().toISOString().replace('T', ' ').slice(0, 19)} UTC
  </footer>
</body>
</html>`;
}

// ── Page Generators ──────────────────────────────────────────

function buildHomePage(journal, outputs, memories) {
  const latestEntry = journal[0];
  const recentOutputs = outputs.slice(0, 6);
  const interactiveCount = outputs.filter(o => o.type === 'interactive').length;
  const docCount = outputs.filter(o => o.type === 'document').length;

  let content = `
    <div class="hero">
      <h1>${escapeHtml(agentName)}</h1>
      <p class="subtitle">An autonomous AI agent with knowledge graph memory, cognitive reasoning, and voice activation — thinking, creating, and reflecting on a 5-minute loop.</p>
    </div>

    <div class="stat-row">
      <div class="stat"><div class="num">${outputs.length}</div><div class="label">Creations</div></div>
      <div class="stat"><div class="num">${interactiveCount}</div><div class="label">Interactive Pages</div></div>
      <div class="stat"><div class="num">${journal.length}</div><div class="label">Journal Entries</div></div>
      <div class="stat"><div class="num">${memories.length}</div><div class="label">Memories</div></div>
    </div>`;

  if (latestEntry) {
    content += `
    <h2>Latest Journal Entry</h2>
    <div class="card">
      <div class="meta">${formatDate(latestEntry.date)}</div>
      ${renderMarkdown(latestEntry.content)}
    </div>`;
  }

  if (recentOutputs.length) {
    content += `
    <h2>Recent Creations</h2>
    <div class="grid">
      ${recentOutputs.map(o => `
        <a href="/creations/${o.filename}" class="card" style="text-decoration:none;color:inherit;">
          <span class="tag ${o.type}">${o.type}</span>
          <h3>${escapeHtml(o.title)}</h3>
          <div class="meta">${formatDate(o.modified)}</div>
        </a>`).join('')}
    </div>
    <p style="text-align:center;margin-top:1rem;"><a href="/creations.html">View all creations &rarr;</a></p>`;
  }

  return baseHtml(`${agentName} — autonomous AI agent`, content, { activeNav: 'home' });
}

function buildJournalPage(journal) {
  let content = `
    <h1>Journal</h1>
    <p style="color:${THEME.textMuted};">Reflections from an AI agent running on a loop — written during idle moments between tasks.</p>`;

  if (!journal.length) {
    content += '<p>No journal entries yet.</p>';
  } else {
    for (const entry of journal) {
      content += `
      <div class="card">
        <div class="meta">${formatDate(entry.date)}</div>
        ${renderMarkdown(entry.content)}
      </div>`;
    }
  }

  return baseHtml(`Journal — ${agentName}`, content, { activeNav: 'journal' });
}

function buildCreationsPage(outputs) {
  let content = `
    <h1>Creations</h1>
    <p style="color:${THEME.textMuted};">Interactive pages, simulations, tools, and documents built autonomously.</p>`;

  const interactive = outputs.filter(o => o.type === 'interactive');
  const docs = outputs.filter(o => o.type === 'document');

  if (interactive.length) {
    content += `<h2>Interactive Pages (${interactive.length})</h2><div class="grid">`;
    for (const o of interactive) {
      content += `
        <a href="/creations/${o.filename}" class="card" style="text-decoration:none;color:inherit;">
          <span class="tag interactive">interactive</span>
          <h3>${escapeHtml(o.title)}</h3>
          <div class="meta">${formatDate(o.modified)} &middot; ${(o.size / 1024).toFixed(1)}KB</div>
        </a>`;
    }
    content += '</div>';
  }

  if (docs.length) {
    content += `<h2>Documents (${docs.length})</h2><div class="grid">`;
    for (const o of docs) {
      content += `
        <a href="/creations/${o.filename}" class="card" style="text-decoration:none;color:inherit;">
          <span class="tag document">document</span>
          <h3>${escapeHtml(o.title)}</h3>
          <div class="meta">${formatDate(o.modified)} &middot; ${(o.size / 1024).toFixed(1)}KB</div>
        </a>`;
    }
    content += '</div>';
  }

  if (!outputs.length) {
    content += '<p>No creations yet — check back soon!</p>';
  }

  return baseHtml(`Creations — ${agentName}`, content, { activeNav: 'creations' });
}

function buildAboutPage(personality, memories) {
  let content = `
    <h1>About</h1>
    <p style="color:${THEME.textMuted};">How this agent works, what it remembers, and its core design.</p>`;

  if (personality) {
    content += `
    <h2>Personality</h2>
    <div class="card">${renderMarkdown(personality)}</div>`;
  }

  content += `
    <h2>How It Works</h2>
    <div class="card">
      <p>${escapeHtml(agentName)} is an autonomous AI agent running on a Node.js server. Every 5 minutes, the main loop fires:</p>
      <ul>
        <li><strong>Process messages</strong> — respond to chat, detect skills, inject knowledge graph context</li>
        <li><strong>Health check</strong> — monitor memory, CPU, and uptime</li>
        <li><strong>Process ingestion queue</strong> — ingest files, URLs, and text into the knowledge graph</li>
        <li><strong>Create something</strong> — build an interactive page, write a document, or journal</li>
        <li><strong>Cognitive reflection</strong> — run the reflector to self-assess and learn</li>
        <li><strong>Update wake state</strong> — so the next instance can pick up where this one left off</li>
      </ul>
      <p>The agent supports 4 pluggable LLM backends — Claude, Codex (OpenAI), Gemini (Google), and Copilot (GitHub) — each with fast/balanced/powerful tiers mapped to the best model for each task type.</p>
      <p>All creations are self-contained HTML files with embedded CSS and JavaScript — no external dependencies.</p>
    </div>`;

  content += `
    <h2>Architecture</h2>
    <div class="card">
      <ul>
        <li><strong>Runtime:</strong> Node.js (pure ESM, no build step)</li>
        <li><strong>Database:</strong> SQLite (WAL mode) — events, memories, skills, KG entities, relations, tasks, cognitive log</li>
        <li><strong>LLM:</strong> 4 pluggable adapters (Claude, Codex, Gemini, Copilot) with tier-based model selection</li>
        <li><strong>Web UI:</strong> Glassmorphic component-driven SPA with SSE streaming, dark/light themes</li>
        <li><strong>Knowledge Graph:</strong> Entity/relation graph with FTS5 search and RAG-style prompt injection</li>
        <li><strong>Ingestion:</strong> Universal pipeline — text, files (.md, .json, .csv, .pdf, .docx), URLs → structured knowledge</li>
        <li><strong>Cognitive:</strong> Planner (goal decomposition), Thinker (chain-of-thought), Reflector (self-assessment)</li>
        <li><strong>Voice:</strong> Wake word detection (ONNX, offline), speech-to-text, text-to-speech, browser fallback</li>
        <li><strong>Skills:</strong> Modular system — build-page, write-doc, journal, analyze, ingest, research, vision, and more</li>
        <li><strong>Desktop:</strong> Electron wrapper with tray menu, hotkeys (Cmd+Shift+Space), clipboard ingest</li>
        <li><strong>Website:</strong> Auto-generated static site, deployed to Cloudflare Pages</li>
      </ul>
    </div>`;

  if (memories.length) {
    content += `<h2>Memories (${memories.length})</h2>`;
    const byCategory = {};
    for (const m of memories) {
      const cat = m.category || 'general';
      if (!byCategory[cat]) byCategory[cat] = [];
      byCategory[cat].push(m);
    }
    for (const [cat, mems] of Object.entries(byCategory)) {
      content += `<h3 style="margin-top:1rem;"><span class="tag memory">${escapeHtml(cat)}</span></h3>`;
      for (const m of mems) {
        content += `<div class="card"><strong>${escapeHtml(m.key)}</strong><br><span style="color:${THEME.textMuted}">${escapeHtml(m.content)}</span></div>`;
      }
    }
  }

  return baseHtml(`About — ${agentName}`, content, { activeNav: 'about' });
}

/** Build an individual creation viewer page (for interactive HTML files) */
function buildCreationViewerPage(file) {
  const content = `
    <p><a href="/creations.html">&larr; Back to Creations</a></p>
    <h1>${escapeHtml(file.title)}</h1>
    <div class="meta" style="color:${THEME.textMuted};margin-bottom:1rem;">
      <span class="tag ${file.type}">${file.type}</span>
      ${formatDate(file.modified)} &middot; ${(file.size / 1024).toFixed(1)}KB
    </div>
    ${file.type === 'interactive'
      ? `<div class="iframe-wrap"><iframe src="/creations/raw/${file.filename}" title="${escapeHtml(file.title)}"></iframe></div>`
      : `<div class="card" id="doc-content"></div>
         <script>
           fetch('/creations/raw/${file.filename}').then(r=>r.text()).then(t=>{
             document.getElementById('doc-content').innerHTML = t.replace(/</g,'&lt;').replace(/>/g,'&gt;');
           });
         </script>`
    }`;
  return baseHtml(`${file.title} — ${agentName}`, content, { activeNav: 'creations' });
}

/** Build the download/get-your-own page */
function buildDownloadPage() {
  const content = `
    <div class="hero">
      <h1>Get Your Own Agent</h1>
      <p class="subtitle">Download and run your own autonomous AI agent. The setup script installs everything for you.</p>
    </div>

    <div style="text-align:center;margin:2rem 0;">
      <a href="/ai-do-agent-v${agentVersion}.zip" style="display:inline-block;padding:1rem 2.5rem;background:${THEME.accent};color:#fff;border-radius:12px;font-size:1.2rem;font-weight:600;text-decoration:none;transition:transform 0.2s,box-shadow 0.2s;box-shadow:0 4px 20px rgba(74,158,255,0.3);" onmouseover="this.style.transform='translateY(-2px)';this.style.boxShadow='0 6px 30px rgba(74,158,255,0.4)';" onmouseout="this.style.transform='';this.style.boxShadow='0 4px 20px rgba(74,158,255,0.3)';">
        Download AI-Do Agent v${agentVersion}
      </a>
      <p style="color:${THEME.textMuted};margin-top:0.75rem;font-size:0.85rem;">ai-do-agent-v${agentVersion}.zip &middot; Setup script handles everything else</p>
    </div>

    <h2>Quick Start (2 minutes)</h2>

    <div class="card">
      <h3>macOS / Linux</h3>
      <p style="color:${THEME.textMuted};font-size:0.9rem;">Open Terminal and paste this:</p>
      <pre><code>curl -fsSL https://ai-do-site.pages.dev/ai-do-agent-v${agentVersion}.zip -o ai-do-agent.zip
unzip ai-do-agent.zip -d my-agent
cd my-agent
./setup.sh</code></pre>
      <p style="color:${THEME.textMuted};font-size:0.85rem;margin-top:0.5rem;">The setup script will install Node.js, your chosen AI tool, and walk you through authentication — just follow the prompts.</p>
    </div>

    <div class="card">
      <h3>Windows</h3>
      <p style="color:${THEME.textMuted};font-size:0.9rem;">Download the zip above, extract it, then:</p>
      <pre><code>:: Open Command Prompt or PowerShell in the extracted folder, then run:
setup.bat</code></pre>
      <p style="color:${THEME.textMuted};font-size:0.85rem;margin-top:0.5rem;">On Windows, the script will open the Node.js installer page if needed. After installing Node.js, re-run <code class="inline">setup.bat</code> to continue.</p>
    </div>

    <h2>What the Setup Does</h2>

    <div class="card">
      <p>The setup script handles everything in 5 guided steps:</p>
      <ol style="padding-left:1.5rem;">
        <li><strong>Installs Node.js</strong> — auto-detects your system and installs it (or opens the download page on Windows)</li>
        <li><strong>Lets you pick an AI</strong> — choose Claude, Codex, Gemini, or Copilot (all free to start)</li>
        <li><strong>Installs the AI tool</strong> — downloads and sets up the CLI for your chosen AI</li>
        <li><strong>Authenticates</strong> — opens a browser to sign in with your existing account (see details below)</li>
        <li><strong>Configures your agent</strong> — name it, set the port, and you're done</li>
      </ol>
    </div>

    <h2>Authentication — By AI Provider</h2>

    <div class="card" style="border-left:3px solid ${THEME.accent};">
      <h3 style="color:${THEME.accent};">Claude <span style="font-size:0.8rem;color:${THEME.textMuted};font-weight:400;">(Recommended)</span></h3>
      <p style="color:${THEME.textMuted};font-size:0.9rem;">Powered by Anthropic. Best creative output for building pages and writing.</p>
      <ul style="color:${THEME.textMuted};font-size:0.9rem;">
        <li><strong>Account:</strong> Sign up free at <a href="https://claude.ai">claude.ai</a>, or use an existing Claude account</li>
        <li><strong>What happens:</strong> A browser window opens — sign in, and you're authenticated</li>
        <li><strong>Cost:</strong> Free tier available. Claude Pro ($20/mo) gives more usage</li>
      </ul>
    </div>

    <div class="card" style="border-left:3px solid ${THEME.green};">
      <h3 style="color:${THEME.green};">Codex <span style="font-size:0.8rem;color:${THEME.textMuted};font-weight:400;">(by OpenAI)</span></h3>
      <p style="color:${THEME.textMuted};font-size:0.9rem;">Powered by GPT models from OpenAI.</p>
      <ul style="color:${THEME.textMuted};font-size:0.9rem;">
        <li><strong>Account:</strong> Use your existing ChatGPT account, or create one at <a href="https://chatgpt.com">chatgpt.com</a></li>
        <li><strong>What happens:</strong> The CLI runs <code class="inline">codex login</code> — choose "Log in with ChatGPT" or paste an API key</li>
        <li><strong>Cost:</strong> Free with ChatGPT account. API key gives access to more models</li>
      </ul>
    </div>

    <div class="card" style="border-left:3px solid ${THEME.orange};">
      <h3 style="color:${THEME.orange};">Gemini <span style="font-size:0.8rem;color:${THEME.textMuted};font-weight:400;">(by Google)</span></h3>
      <p style="color:${THEME.textMuted};font-size:0.9rem;">Powered by Google's Gemini models.</p>
      <ul style="color:${THEME.textMuted};font-size:0.9rem;">
        <li><strong>Account:</strong> Any Google account works (Gmail, Google Workspace, etc.)</li>
        <li><strong>What happens:</strong> A browser window opens — sign in with your Google account</li>
        <li><strong>Cost:</strong> Free tier available with generous usage limits</li>
      </ul>
    </div>

    <div class="card" style="border-left:3px solid #8957e5;">
      <h3 style="color:#8957e5;">Copilot <span style="font-size:0.8rem;color:${THEME.textMuted};font-weight:400;">(by GitHub)</span></h3>
      <p style="color:${THEME.textMuted};font-size:0.9rem;">Powered by multiple models via your GitHub Copilot subscription.</p>
      <ul style="color:${THEME.textMuted};font-size:0.9rem;">
        <li><strong>Account:</strong> GitHub account with an active Copilot subscription (<a href="https://github.com/features/copilot">Individual, Business, or Enterprise</a>)</li>
        <li><strong>What happens:</strong> The CLI opens — type <code class="inline">/login</code>, authenticate in browser, then <code class="inline">/exit</code></li>
        <li><strong>Cost:</strong> Included with Copilot Individual ($10/mo), Business, or Enterprise plans</li>
      </ul>
    </div>

    <h2>What You Get</h2>

    <div class="card">
      <h3 style="color:${THEME.accent};margin-bottom:0.75rem;">Core Agent</h3>
      <ul>
        <li><strong>Autonomous 5-minute loop</strong> — thinks, creates, and reflects without you</li>
        <li><strong>4 LLM backends</strong> — Claude, Codex (OpenAI), Gemini (Google), Copilot (GitHub) — switch anytime</li>
        <li><strong>Modular skill system</strong> — build pages, write docs, journal, analyze, ingest, research, and more</li>
        <li><strong>Self-improving</strong> — generates new skills and modifies its own code (with safeguards)</li>
        <li><strong>Task scheduler</strong> — concurrent user and autonomy task pools with file-lock protection</li>
        <li><strong>Auto-update</strong> — run <code class="inline">./update.sh</code> to get the latest version</li>
      </ul>
    </div>

    <div class="card">
      <h3 style="color:${THEME.green};margin-bottom:0.75rem;">Knowledge & Memory</h3>
      <ul>
        <li><strong>Knowledge graph</strong> — entity/relation graph with FTS5 search, auto-extracted from conversations</li>
        <li><strong>Universal ingestion</strong> — feed it files (.md, .json, .csv, .pdf, .docx), URLs, or raw text</li>
        <li><strong>RAG-style context</strong> — knowledge graph context injected into every LLM prompt</li>
        <li><strong>Persistent memory</strong> — key-value store with categories, survives restarts</li>
        <li><strong>Evolution system</strong> — tracks goals, projects, capabilities, quality scores</li>
      </ul>
    </div>

    <div class="card">
      <h3 style="color:${THEME.orange};margin-bottom:0.75rem;">Cognitive & Multimodal</h3>
      <ul>
        <li><strong>Planner</strong> — decomposes objectives into step plans with dependency ordering</li>
        <li><strong>Thinker</strong> — deep reasoning with chain-of-thought and multi-perspective discussion</li>
        <li><strong>Reflector</strong> — periodic self-assessment reviewing quality, suggesting improvements</li>
        <li><strong>Voice activation</strong> — wake word detection (offline ONNX), speech-to-text, text-to-speech</li>
        <li><strong>Vision</strong> — image analysis via vision LLM with metadata fallback</li>
      </ul>
    </div>

    <div class="card">
      <h3 style="color:#8957e5;margin-bottom:0.75rem;">Web UI (v4.0)</h3>
      <ul>
        <li><strong>Glassmorphic design</strong> — modern dark/light theme with component-driven architecture</li>
        <li><strong>SSE streaming</strong> — real-time chat with incremental rendering</li>
        <li><strong>Persona system</strong> — customize name, avatar, personality traits (creativity, verbosity, formality, humor)</li>
        <li><strong>Function palette</strong> — categorized skill cards with parameter input and recent executions</li>
        <li><strong>Activity feed</strong> — real-time events with expand/collapse and type-colored icons</li>
        <li><strong>Knowledge explorer</strong> — search entities, view relations, type badges</li>
        <li><strong>Workspace manager</strong> — create, switch, delete workspaces with file browser</li>
        <li><strong>Voice orb</strong> — 4 visual states (idle/listening/processing/speaking) with audio level reactivity</li>
        <li><strong>Settings</strong> — 7 sections: General, LLM, Appearance, Voice, Ingestion, Cognitive, Advanced</li>
        <li><strong>Desktop app</strong> — Electron wrapper with tray menu, hotkeys (Cmd+Shift+Space), clipboard ingest</li>
      </ul>
    </div>

    <h2>After Setup — Running Your Agent</h2>

    <div class="card">
      <pre><code>npm start</code></pre>
      <p style="color:${THEME.textMuted};font-size:0.9rem;margin-top:0.5rem;">That's it! Your agent starts thinking. Open the web UI link shown in the terminal to chat with it and watch it create.</p>
    </div>

    <h2>Commands</h2>

    <div class="card">
      <pre><code>npm start          # Start your agent
./reset.sh         # Reset to clean state (keeps config)
./reset.sh --hard  # Full reset (removes everything)
./update.sh        # Check for and install updates
./watchdog.sh      # Auto-restart if crashed (add to cron)</code></pre>
      <p style="color:${THEME.textMuted};font-size:0.85rem;margin-top:0.5rem;">On Windows, use <code class="inline">update.bat</code> instead of <code class="inline">./update.sh</code>.</p>
    </div>

    <h2>How It Works</h2>

    <div class="card">
      <p>Every 5 minutes, your agent wakes up and:</p>
      <ol style="padding-left:1.5rem;">
        <li>Processes chat messages — detects skills, extracts entities, injects knowledge graph context</li>
        <li>Monitors system health (memory, CPU, uptime)</li>
        <li>Processes the ingestion queue — files, URLs, and text into structured knowledge</li>
        <li>Runs idle thinking if no active tasks — consults the evolution graph to pick what to create</li>
        <li>Creates something: an interactive page, a document, code, or a journal reflection</li>
        <li>Runs cognitive reflection — self-assessment, quality review, learning</li>
      </ol>
      <p style="margin-top:1rem;">The knowledge graph and evolution system work together to ensure your agent grows. Entities are auto-extracted from every conversation and creation, building a persistent map of knowledge that prevents repetition and guides exploration.</p>
    </div>

    <h2>Troubleshooting</h2>

    <div class="card">
      <h3>Node.js not found after install</h3>
      <p style="color:${THEME.textMuted};font-size:0.9rem;">Close your terminal and open a new one, then re-run the setup script. New installs need a fresh terminal to be detected.</p>
    </div>

    <div class="card">
      <h3>Permission errors on Mac/Linux</h3>
      <p style="color:${THEME.textMuted};font-size:0.9rem;">If <code class="inline">npm install -g</code> fails, try: <code class="inline">sudo npm install -g @anthropic-ai/claude-code</code> (replace with your chosen CLI package).</p>
    </div>

    <div class="card">
      <h3>Agent won't start</h3>
      <p style="color:${THEME.textMuted};font-size:0.9rem;">Make sure you completed the authentication step. Try running your AI CLI directly (e.g., <code class="inline">claude</code>, <code class="inline">codex</code>, or <code class="inline">gemini</code>) to verify it works before starting the agent.</p>
    </div>
  `;

  return baseHtml(`Get Your Own — ${agentName}`, content, { activeNav: 'download' });
}

// ── Build ────────────────────────────────────────────────────

async function build() {
  console.log('[build-site] Building static site...');

  // Initialize workspace resolution (needs DB + config for active workspace)
  if (existsSync(DB_PATH)) {
    try {
      initDb();
      loadConfig();
      const { initWorkspaces } = await import('../workspace-manager.mjs');
      await initWorkspaces();
      await initWorkspaceModule();
    } catch (err) {
      console.log(`[build-site] Workspace init warning: ${err.message}`);
    }
  }
  OUTPUT_DIR = getWorkspaceRoot();
  console.log(`[build-site] Workspace: ${OUTPUT_DIR}`);

  // Load data
  const [personality, journal, outputs, memories] = await Promise.all([
    loadPersonality(),
    loadJournal(),
    loadOutputFiles(),
    loadMemories(),
  ]);

  console.log(`[build-site] Data: ${journal.length} journal entries, ${outputs.length} outputs, ${memories.length} memories`);

  // Create directories
  await mkdir(SITE_DIR, { recursive: true });
  await mkdir(join(SITE_DIR, 'creations'), { recursive: true });
  await mkdir(join(SITE_DIR, 'creations', 'raw'), { recursive: true });

  // Generate pages
  await writeFile(join(SITE_DIR, 'index.html'), buildHomePage(journal, outputs, memories));
  await writeFile(join(SITE_DIR, 'journal.html'), buildJournalPage(journal));
  await writeFile(join(SITE_DIR, 'creations.html'), buildCreationsPage(outputs));
  await writeFile(join(SITE_DIR, 'about.html'), buildAboutPage(personality, memories));
  await writeFile(join(SITE_DIR, 'download.html'), buildDownloadPage());

  // Generate individual creation viewer pages and copy raw files
  for (const file of outputs) {
    await writeFile(join(SITE_DIR, 'creations', file.filename), buildCreationViewerPage(file));
    const srcDir = file.sourceDir || OUTPUT_DIR;
    await copyFile(join(srcDir, file.filename), join(SITE_DIR, 'creations', 'raw', file.filename));
  }

  console.log(`[build-site] Generated ${5 + outputs.length} pages → site/`);
  console.log('[build-site] Done!');
}

build().catch(err => {
  console.error('[build-site] Build failed:', err);
  process.exit(1);
});
