← back to Agent Cabinet
Game Mode: /game route — 6 dept HQs + 134 cards w/ avatars, stats, synergies
eca07f17b1f5b7331798b3bdd00eef225afbb2ca · 2026-05-06 12:29:16 -0700 · Steve
Files touched
Diff
commit eca07f17b1f5b7331798b3bdd00eef225afbb2ca
Author: Steve <steve@designerwallcoverings.com>
Date: Wed May 6 12:29:16 2026 -0700
Game Mode: /game route — 6 dept HQs + 134 cards w/ avatars, stats, synergies
---
server.js | 342 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 342 insertions(+)
diff --git a/server.js b/server.js
index bcce6bd..b5def9c 100644
--- a/server.js
+++ b/server.js
@@ -135,6 +135,337 @@ ${mm}
</body></html>`;
}
+// ---------- /game route — gamified roster with avatars, stats, synergies ----------
+
+// Deterministic emoji avatar pool. Pick by name hash so each director has stable avatar.
+const ANIMAL_AVATARS = ['🦊','🦅','🐺','🐉','🦁','🐯','🐼','🦉','🦄','🦋','🐙','🐢','🦏','🦎','🐝','🦂','🦇','🐠','🦜','🐧','🦩','🦚','🦔','🐿️','🦦','🐍','🦖','🐊','🐎','🐕','🦌','🦃','🦘','🐫','🦬','🦭','🐬','🦣','🪿','🐈⬛'];
+const TOOL_AVATARS = ['⚙️','🔧','🛠️','🔨','⚒️','🪛','🪚','📏','📐','🧰','🧲','🔬','🔭','🧪','🧫','💎','🪞','🪪','🗝️','🪝','🧭','📡','💾','💿','📀','💽','🖥️','🖨️','⌨️','🖱️','🛡️','⚔️','🏹','🗡️','🪙','💰','💼','🎯','🎲','🎮'];
+
+function avatarFor(kind, name) {
+ // Deterministic FNV-1a hash
+ let h = 2166136261;
+ for (let i = 0; i < name.length; i++) { h ^= name.charCodeAt(i); h = (h * 16777619) >>> 0; }
+ const pool = kind === 'subagent' ? ANIMAL_AVATARS : TOOL_AVATARS;
+ return pool[h % pool.length];
+}
+
+// Stats derived from name length + kind for variety (nothing scientific — game flavor).
+function statsFor(kind, name) {
+ let h = 5381;
+ for (let i = 0; i < name.length; i++) h = ((h << 5) + h + name.charCodeAt(i)) >>> 0;
+ const r = (n, mod) => ((h >>> n) % mod) + 1;
+ return {
+ POWER: kind === 'subagent' ? 60 + r(0, 40) : 40 + r(2, 50), // subagents punch harder
+ SPEED: kind === 'skill' ? 60 + r(4, 40) : 40 + r(6, 50), // skills are faster
+ DEPTH: 30 + r(8, 70),
+ REACH: 20 + r(10, 80),
+ };
+}
+
+// Specialty: one-liner derived from name (heuristic mapping)
+const SPECIALTIES = {
+ // operations
+ 'secrets': 'API token routing master',
+ 'domain-setup': 'Bare domain → live HTTPS site',
+ 'cloudflare-manager': 'Edge-network conjurer',
+ 'open-firewalls': 'Port-opening locksmith',
+ 'process-hawk': 'Watches every pm2 like a hawk',
+ 'pm2-crash-watcher': 'Last-hour crash reporter',
+ 'deployment-engineer': 'CI/CD pipeline weaver',
+ 'devops-engineer': 'Cloud ops generalist',
+ 'domain-name-agent': 'WHOIS + DNS keeper',
+ 'onboard-domain-agent': 'New-domain summoner',
+ 'secrets-manager': 'Master vault keeper',
+ 'analytics-agent': 'GA4 fleet commander',
+ // dw-commerce
+ 'dw-site-build': 'Universal DW storefront blueprint',
+ 'dw-shopify-theme-optimizer': 'Liquid speed alchemist',
+ 'dw-marketing-copy': 'Conversion-tuned wordsmith',
+ 'dw-seo-optimizer': 'Meta + structured-data tuner',
+ 'dw-purchasing-vendor': 'Vendor email diplomat',
+ 'dw-sku-analyst': 'SKU dedup detective',
+ 'dw-legal-compliance': 'Settlement-clause guardian',
+ 'room-setting-generator': 'Photoreal room conjurer',
+ 'commercial-room-setting-generator': 'Hospitality-render specialist',
+ // research-content
+ 'exa-agent': 'Grounded web search oracle',
+ 'website-analysis': 'Synthesis-grade audit polymath',
+ 'four-horsemen': 'Figma + Paper + Canva + 21st orchestrator',
+ 'reviewed-demo-video': 'Pre+post 4-horsemen review video',
+ 'session-debrief': 'VC-pitch debrief MP4 maker',
+ 'clone-voice': 'Voice-clone enrollment master',
+ 'mockups': 'N hand-built homepage variants',
+ 'site-audit': 'Synthesis report + 90-day roadmap',
+ 'peer-survey': 'Side-by-side competitor screenshots',
+ 'competitors': 'Differentiation-vector hunter',
+ 'tools-pack': 'Browser-side widget pack builder',
+ 'extension': 'MV3 Chrome extension scaffolder',
+ 'info-hub': 'Authoritative-source curator',
+ 'debate-team-fast': '4-LLM consensus engine',
+ // engineering
+ 'code-reviewer': 'PR audit specialist',
+ 'security-auditor': 'OWASP & auth-flow guardian',
+ 'performance-engineer': 'Profiler & bottleneck hunter',
+ 'database-optimizer': 'N+1 slayer',
+ 'backend-architect': 'API & microservice designer',
+ 'frontend-developer': 'React UI craftsman',
+ 'ai-engineer': 'LLM + RAG integration',
+ // compliance
+ 'comms-compliance': 'CAN-SPAM / TCPA / CCPA gatekeeper',
+ 'best-practices-reviewer': 'MEMORY.md pre-flight checker',
+ 'best-practices': 'Standing-rules reviewer',
+};
+
+// Synergy graph: which directors pair naturally (for "Synergy" display).
+// Curated minimal map — keeps the game readable.
+const SYNERGIES = {
+ 'dw-sku-analyst': ['shopify-vendor-bulk-updater', 'fullproduct'],
+ 'shopify-vendor-bulk-updater': ['dw-sku-analyst', 'enrich-ai-tags'],
+ 'secrets': ['domain-setup', 'cloudflare-manager'],
+ 'domain-setup': ['cloudflare-manager', 'open-firewalls', 'secrets'],
+ 'reviewed-demo-video': ['app-demo-video', 'four-horsemen', 'clone-voice'],
+ 'session-debrief': ['app-demo-video', 'clone-voice', 'use-voice'],
+ 'comms-compliance': ['best-practices-reviewer', 'dw-legal-compliance'],
+ 'best-practices-reviewer': ['comms-compliance'],
+ 'lawyer-build-agent': ['comms-compliance', 'la-research-agent'],
+ 'doctor-agent': ['comms-compliance'],
+ 'la-research-agent': ['lawyer-build-agent', 'exa-agent'],
+ 'exa-agent': ['search-specialist', 'technical-researcher', 'la-research-agent'],
+ 'website-analysis': ['site-audit', 'peer-survey', 'competitors', 'mockups', 'tools-pack', 'info-hub'],
+ 'site-audit': ['website-analysis'],
+ 'peer-survey': ['website-analysis', 'competitors'],
+ 'mockups': ['four-horsemen', 'stampede', 'website-analysis'],
+ 'four-horsemen': ['mockups', 'stampede', 'reviewed-demo-video'],
+ 'stampede': ['four-horsemen', 'mockups'],
+ 'pm2-crash-watcher': ['process-hawk', 'devops-engineer'],
+ 'process-hawk': ['pm2-crash-watcher'],
+ 'analytics-agent': ['dw-seo-optimizer', 'website-analysis'],
+ 'code-reviewer': ['architect-review', 'security-auditor'],
+ 'security-auditor': ['code-reviewer', 'best-practices-reviewer'],
+ 'ai-engineer': ['prompt-engineer', 'mcp-expert', 'claude-api'],
+ 'prompt-engineer': ['ai-engineer', 'prompt-cache-optimization'],
+ 'debate-team-fast': ['claude-codex'],
+ 'enrich-ai-tags': ['shopify-vendor-bulk-updater'],
+};
+
+const VP_THEME = {
+ 'vp-operations': { color: '#1d4ed8', boss: '🛡️', bossName: 'Operator', tagline: 'KEEPS THE LIGHTS ON' },
+ 'vp-dw-commerce': { color: '#16a34a', boss: '💰', bossName: 'Quartermaster', tagline: 'CATALOG, REVENUE, RETAIL' },
+ 'vp-directories': { color: '#ea580c', boss: '📜', bossName: 'Cartographer', tagline: 'VERTICAL DIRECTORIES' },
+ 'vp-engineering': { color: '#7c3aed', boss: '⚒️', bossName: 'Architect', tagline: 'CODE QUALITY & SHIP' },
+ 'vp-research-content': { color: '#0891b2', boss: '🔭', bossName: 'Scout', tagline: 'KNOW + CREATE' },
+ 'vp-compliance-policy': { color: '#dc2626', boss: '⚖️', bossName: 'Sentinel', tagline: 'GUARDS THE GATES' },
+};
+
+function renderGame(cabinet) {
+ // Need full director list (including vendor scrapers).
+ // Re-extract from cabinet.yaml directly — handles the multi-line array case.
+ const text = fs.readFileSync(CABINET, 'utf8');
+ const allLines = text.split('\n');
+ const fullCabinet = [];
+ let cur = null;
+ for (let i = 0; i < allLines.length; i++) {
+ const line = allLines[i];
+ if (line.match(/^ - vp:\s*(.+)$/)) {
+ cur = { vp: line.split(':')[1].trim(), domain: '', directors: [] };
+ fullCabinet.push(cur);
+ } else if (cur) {
+ const dm = line.match(/^ domain:\s*(.+)$/);
+ if (dm) cur.domain = dm[1].trim();
+ let m;
+ if ((m = line.match(/^\s+- skill:\s*([\w./-]+)/))) cur.directors.push({ kind: 'skill', name: m[1] });
+ else if ((m = line.match(/^\s+- subagent:\s*([\w./-]+)/))) cur.directors.push({ kind: 'subagent', name: m[1] });
+ else if (line.includes('alphabetical_skill_set:')) {
+ let buf = line, j = i;
+ while (!buf.includes(']') && j < allLines.length - 1) { j++; buf += ' ' + allLines[j]; }
+ const inner = (buf.match(/\[([^\]]+)\]/) || ['',''])[1];
+ for (const raw of inner.split(',')) {
+ const nm = raw.replace(/["']/g, '').trim();
+ if (/^[a-zA-Z][\w-]+$/.test(nm)) cur.directors.push({ kind: 'skill', name: nm });
+ }
+ }
+ }
+ }
+ // Dedup
+ for (const v of fullCabinet) {
+ const seen = new Set();
+ v.directors = v.directors.filter(d => { const k = d.kind+':'+d.name; if (seen.has(k)) return false; seen.add(k); return true; });
+ }
+ const totalDirectors = fullCabinet.reduce((a, v) => a + v.directors.length, 0);
+
+ function card(d) {
+ const av = avatarFor(d.kind, d.name);
+ const s = statsFor(d.kind, d.name);
+ const spec = SPECIALTIES[d.name] || (d.kind === 'subagent' ? 'Domain specialist' : 'Tool · skill bundle');
+ const synergies = (SYNERGIES[d.name] || []).slice(0, 3);
+ const sigil = d.kind === 'subagent' ? '@' : '/';
+ return `
+ <div class="card ${d.kind}" data-name="${d.name}" data-kind="${d.kind}">
+ <div class="avatar">${av}</div>
+ <div class="kind-tag">${d.kind === 'subagent' ? 'SUBAGENT' : 'SKILL'}</div>
+ <div class="name">${sigil}${d.name}</div>
+ <div class="spec">${spec}</div>
+ <div class="stats">
+ <div><span>POW</span><b>${s.POWER}</b></div>
+ <div><span>SPD</span><b>${s.SPEED}</b></div>
+ <div><span>DEP</span><b>${s.DEPTH}</b></div>
+ <div><span>RCH</span><b>${s.REACH}</b></div>
+ </div>
+ ${synergies.length ? `<div class="synergy">↔ ${synergies.map(x => `<span class="syn-pill" data-target="${x}">${x}</span>`).join(' ')}</div>` : ''}
+ </div>`;
+ }
+
+ const departmentBlocks = fullCabinet.map(v => {
+ const t = VP_THEME[v.vp] || { color: '#666', boss: '👑', bossName: 'Boss', tagline: '' };
+ const cards = v.directors.map(card).join('');
+ return `
+ <section class="dept" style="--theme:${t.color}">
+ <header class="dept-head">
+ <div class="boss-avatar">${t.boss}</div>
+ <div>
+ <div class="boss-title">${t.bossName.toUpperCase()}</div>
+ <h2>${v.vp}</h2>
+ <div class="boss-tag">${t.tagline} · ${v.directors.length} on team</div>
+ </div>
+ </header>
+ <div class="grid">${cards}</div>
+ </section>`;
+ }).join('');
+
+ return `<!doctype html>
+<html><head>
+<meta charset="utf-8">
+<title>The Cabinet — Steve's Agent Roster (Game Mode)</title>
+<style>
+ :root { --ink:#000; --paper:#fafaf6; }
+ * { box-sizing: border-box; }
+ body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; margin:0; background: var(--paper); color: var(--ink); }
+ header.global {
+ padding: 36px 40px 24px; border-bottom: 4px solid #000; background:
+ linear-gradient(135deg, #fef3c7 0%, #fde68a 50%, #fcd34d 100%);
+ }
+ h1 { font-size: 38px; margin: 0 0 6px; letter-spacing: -.03em; font-weight: 900; font-family: 'Archivo Black', -apple-system, sans-serif; }
+ .stats-line { font-size: 13px; color: #444; margin-top: 8px; }
+ .stats-line b { color: #000; }
+ main { padding: 24px 32px 60px; max-width: 1840px; margin: 0 auto; }
+
+ .dept {
+ margin: 32px 0;
+ background: #fff;
+ border: 4px solid #000;
+ box-shadow: 8px 8px 0 #000;
+ }
+ .dept-head {
+ display: flex; align-items: center; gap: 22px;
+ padding: 22px 28px;
+ background: var(--theme);
+ color: #fff;
+ border-bottom: 4px solid #000;
+ }
+ .boss-avatar { font-size: 56px; line-height: 1; }
+ .dept-head h2 { margin: 2px 0; font-size: 26px; font-weight: 900; letter-spacing: -.02em; }
+ .boss-title { font-size: 11px; letter-spacing: .15em; font-weight: 800; opacity: .85; }
+ .boss-tag { font-size: 12px; opacity: .85; margin-top: 2px; letter-spacing: .04em; font-weight: 600; }
+ .grid {
+ display: grid; gap: 14px; padding: 22px;
+ grid-template-columns: repeat(auto-fill, minmax(218px, 1fr));
+ }
+ .card {
+ background: #fff;
+ border: 2px solid #000;
+ border-radius: 0;
+ padding: 14px;
+ position: relative;
+ transition: transform .15s ease, box-shadow .15s ease;
+ cursor: pointer;
+ }
+ .card:hover {
+ transform: translate(-3px, -3px);
+ box-shadow: 4px 4px 0 var(--theme);
+ z-index: 10;
+ }
+ .card.subagent { background: #ede9fe; }
+ .card.skill { background: #dcfce7; }
+ .card .avatar { font-size: 32px; line-height: 1; margin-bottom: 6px; }
+ .card .kind-tag {
+ position: absolute; top: 8px; right: 8px;
+ font-size: 9px; font-weight: 800; letter-spacing: .12em;
+ background: #000; color: #fff;
+ padding: 2px 6px;
+ }
+ .card .name { font-weight: 800; font-size: 13px; word-break: break-word; margin-bottom: 4px; }
+ .card .spec { font-size: 11px; color: #444; min-height: 30px; line-height: 1.35; margin-bottom: 8px; font-style: italic; }
+ .card .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; margin: 8px 0; }
+ .card .stats div { background: rgba(0,0,0,.05); padding: 4px 0; text-align: center; font-size: 9px; border: 1px solid rgba(0,0,0,.08); }
+ .card .stats span { display: block; font-weight: 700; opacity: .65; letter-spacing: .08em; font-size: 8px; }
+ .card .stats b { display: block; font-size: 13px; color: #000; }
+ .card .synergy {
+ margin-top: 6px; font-size: 10px; line-height: 1.5;
+ border-top: 1px dashed rgba(0,0,0,.2); padding-top: 6px;
+ }
+ .syn-pill {
+ background: #000; color: #fff; padding: 1px 6px;
+ margin-right: 2px; cursor: pointer;
+ font-family: ui-monospace, monospace;
+ }
+ .syn-pill:hover { background: var(--theme); }
+
+ /* Highlight when synergy clicked */
+ .card.glow {
+ box-shadow: 0 0 0 4px gold, 6px 6px 0 #000;
+ animation: pulse 1s ease infinite;
+ }
+ @keyframes pulse { 0%,100% { box-shadow: 0 0 0 4px gold, 6px 6px 0 #000; } 50% { box-shadow: 0 0 0 8px gold, 6px 6px 0 #000; } }
+
+ .legend {
+ display: inline-flex; gap: 10px; margin-top: 14px; flex-wrap: wrap;
+ }
+ .legend span { background: #000; color: #fff; padding: 4px 10px; font-size: 11px; font-weight: 700; }
+ footer { text-align: center; color: #666; padding: 30px; font-size: 11px; }
+</style>
+</head><body>
+<header class="global">
+ <h1>🏛 The Cabinet — Game Mode</h1>
+ <div class="stats-line"><b>Steve</b> (President) · <b>${fullCabinet.length}</b> Departments · <b>${totalDirectors}</b> directors on the roster</div>
+ <div class="legend">
+ <span style="background:#1d4ed8">🛡️ Operations</span>
+ <span style="background:#16a34a">💰 DW Commerce</span>
+ <span style="background:#ea580c">📜 Directories</span>
+ <span style="background:#7c3aed">⚒️ Engineering</span>
+ <span style="background:#0891b2">🔭 Research/Content</span>
+ <span style="background:#dc2626">⚖️ Compliance</span>
+ </div>
+</header>
+<main>${departmentBlocks}</main>
+<footer>
+ Click any synergy pill to highlight the partner agent. Cards auto-glow gold for 2 seconds when summoned.<br>
+ Edit roster: <code>~/Projects/agent-cabinet/cabinet.yaml</code> · Last rendered ${new Date().toLocaleString()}
+</footer>
+<script>
+ document.querySelectorAll('.syn-pill').forEach(p => {
+ p.addEventListener('click', e => {
+ e.stopPropagation();
+ const target = p.dataset.target;
+ const card = document.querySelector('.card[data-name="' + target + '"]');
+ if (!card) { alert('Partner ' + target + ' not on the roster — add via agent-architect.'); return; }
+ card.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ card.classList.add('glow');
+ setTimeout(() => card.classList.remove('glow'), 2200);
+ });
+ });
+ document.querySelectorAll('.card').forEach(c => {
+ c.addEventListener('click', () => {
+ const n = c.dataset.name, k = c.dataset.kind;
+ const trigger = k === 'subagent' ? '@' + n : '/' + n;
+ navigator.clipboard?.writeText(trigger);
+ c.classList.add('glow');
+ setTimeout(() => c.classList.remove('glow'), 1200);
+ });
+ });
+</script>
+</body></html>`;
+}
+
const server = http.createServer((req, res) => {
if (req.url === '/health') { res.writeHead(200); res.end('ok'); return; }
if (req.url === '/cabinet.yaml') {
@@ -142,6 +473,17 @@ const server = http.createServer((req, res) => {
res.end(fs.readFileSync(CABINET, 'utf8'));
return;
}
+ if (req.url === '/game' || req.url === '/game/') {
+ try {
+ const html = renderGame();
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
+ res.end(html);
+ } catch (e) {
+ res.writeHead(500);
+ res.end(`Game render error: ${e.message}\n${e.stack}`);
+ }
+ return;
+ }
try {
const cab = parseYaml(fs.readFileSync(CABINET, 'utf8'));
const html = render(cab);
← 157848f Viewer: switch from Mermaid mindmap to flowchart TD with cla
·
back to Agent Cabinet
·
tighten .gitignore: add missing standing-rule patterns (dist 00a0d8a →