[object Object]

← back to Agent Cabinet

Pyramid: πŸ’‘ Ideas modals β€” 5 upgrade ideas as modals (Task Router live, Consolidation Advisor live, Org Export live JSON/MD; Health Overlay + Cost Heatmap as proposals); /api/queue-idea posts to CNCP parking-lot

52c593f1de69c5c6699e89b0c2d575cb9a3baf89 Β· 2026-06-16 16:36:15 -0700 Β· SteveStudio2

Files touched

Diff

commit 52c593f1de69c5c6699e89b0c2d575cb9a3baf89
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue Jun 16 16:36:15 2026 -0700

    Pyramid: πŸ’‘ Ideas modals β€” 5 upgrade ideas as modals (Task Router live, Consolidation Advisor live, Org Export live JSON/MD; Health Overlay + Cost Heatmap as proposals); /api/queue-idea posts to CNCP parking-lot
---
 pyramid.html | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 server.js    |  12 +++++++
 2 files changed, 110 insertions(+), 2 deletions(-)

diff --git a/pyramid.html b/pyramid.html
index f898e7e..93bd843 100644
--- a/pyramid.html
+++ b/pyramid.html
@@ -132,6 +132,42 @@
   #toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%) translateY(20px);background:#0a0e16;border:1px solid #2b3650;color:#e7ecf3;
     padding:10px 16px;border-radius:9px;font-size:12.5px;opacity:0;transition:.2s;z-index:120;box-shadow:0 8px 30px rgba(0,0,0,.5);pointer-events:none}
   #toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
+
+  /* ideas modal */
+  .modal-overlay{position:fixed;inset:0;background:rgba(4,6,11,.72);backdrop-filter:blur(3px);z-index:200;display:none;align-items:center;justify-content:center;padding:24px}
+  .modal-overlay.open{display:flex}
+  .modal-box{background:#0d1119;border:1px solid #2b3650;border-radius:16px;width:min(720px,96vw);max-height:88vh;overflow:auto;box-shadow:0 24px 70px rgba(0,0,0,.6);position:relative}
+  .modal-x{position:absolute;top:13px;right:14px;background:transparent;border:1px solid var(--line);color:var(--dim);border-radius:7px;width:30px;height:30px;cursor:pointer;font-size:15px;z-index:2}
+  .modal-x:hover{color:#fff;border-color:#4b5b7a}
+  .m-pad{padding:22px 24px}
+  .m-h{font-size:19px;font-weight:700;margin:0 0 4px;display:flex;align-items:center;gap:10px}
+  .m-sub{color:var(--dim);font-size:13px;margin-bottom:16px;line-height:1.5}
+  .idea-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:6px}
+  .idea-card{background:#101725;border:1px solid #2b3650;border-radius:12px;padding:14px;cursor:pointer;transition:transform .12s,border-color .12s}
+  .idea-card:hover{transform:translateY(-3px);border-color:#6d34f4}
+  .idea-card .ic{font-size:26px}
+  .idea-card .t{font-weight:700;font-size:13.5px;margin:7px 0 4px}
+  .idea-card .p{color:var(--dim);font-size:11.5px;line-height:1.45}
+  .idea-card .tag{display:inline-block;margin-top:8px;font-size:9.5px;font-weight:800;letter-spacing:.08em;padding:2px 7px;border-radius:5px}
+  .tag.live{background:#0f2a1c;color:#86efac;border:1px solid #16a34a}
+  .tag.prop{background:#33280a;color:#fbbf24;border:1px solid #fbbf24}
+  .m-actions{display:flex;gap:9px;flex-wrap:wrap;margin-top:18px;border-top:1px solid var(--line);padding-top:16px}
+  .m-btn{padding:9px 15px;border-radius:9px;font-size:13px;font-weight:700;cursor:pointer;border:1px solid}
+  .m-btn.primary{background:#241a52;color:#c4b5fd;border-color:#6d34f4}
+  .m-btn.primary:hover{background:#2d2168}
+  .m-btn.ghost{background:#161d2c;color:#bcd0f0;border-color:#2b3650}
+  .m-btn.ghost:hover{background:#1b2435}
+  .m-btn.queue{background:#10231a;color:#6ee7b7;border-color:#16a34a}
+  .m-btn.queue:hover{background:#16321f}
+  .ri{display:flex;gap:8px;margin-bottom:12px}
+  .ri input{flex:1;background:#0a0e16;border:1px solid #2b3650;color:#e7ecf3;border-radius:8px;padding:9px 11px;font-size:13px}
+  .route-off{background:#101725;border:1px solid var(--rc,#2b3650);border-radius:10px;padding:11px;margin-bottom:9px}
+  .route-off .ro-h{font-weight:700;font-size:13px;color:var(--rc)}
+  .route-chip{display:inline-flex;align-items:center;gap:5px;background:#0c1019;border:1px solid var(--line);border-radius:7px;padding:3px 8px;margin:5px 5px 0 0;font-size:11px;cursor:pointer}
+  .route-chip:hover{border-color:#6d34f4;color:#fff}
+  .m-list{font-size:12px}
+  .m-list .row{display:flex;justify-content:space-between;gap:10px;padding:6px 2px;border-bottom:1px solid #141b28}
+  .m-quote{font-style:italic;color:#9fb0c9;border-left:3px solid #6d34f4;padding:6px 12px;margin:10px 0;font-size:12px;background:rgba(109,52,244,.07)}
 </style>
 </head>
 <body>
@@ -182,6 +218,7 @@
   <div id="dr-body"></div>
 </div>
 <div id="toast"></div>
+<div id="modal" class="modal-overlay"><div class="modal-box"><button class="modal-x" onclick="closeModal()">βœ•</button><div id="modal-content" class="m-pad"></div></div></div>
 
 <script>
 const OFF = [
@@ -474,7 +511,8 @@ function buildToolbar(){
     `<label><input type="checkbox" id="tg-hier" checked> Hierarchy</label>`+
     `<label><input type="checkbox" id="tg-shared" checked><span class="tog-line" style="border-top:2px dashed #cbd5e1"></span>Shared now (${SHARED.length})</label>`+
     `<label><input type="checkbox" id="tg-sug" checked><span class="tog-line" style="border-top:2px dotted #fbbf24"></span>Suggested moves (${SUGGEST.length})</label>`+
-    `<button class="tb-btn" onclick="showAnalysis()">β–€ List shares &amp; suggestions</button>`;
+    `<button class="tb-btn" onclick="openIdeasGallery()">πŸ’‘ Ideas</button>`+
+    `<button class="tb-btn" style="margin-left:0" onclick="showAnalysis()">β–€ List</button>`;
   ['tg-hier','tg-shared','tg-sug'].forEach(id=>document.getElementById(id).addEventListener('change',applyToggles));
 }
 function offName(id){ const o=OFF.find(x=>x.id===id); return o?o.nm.replace('VP ',''):id; }
@@ -525,11 +563,69 @@ async function decide(key,action){
   }catch(e){ toast('server unreachable β€” open via http://localhost:9766/pyramid'); }
 }
 
+// ───────────────────── IDEAS MODALS ─────────────────────
+function openModal(html){ document.getElementById('modal-content').innerHTML=html; document.getElementById('modal').classList.add('open'); }
+function closeModal(){ document.getElementById('modal').classList.remove('open'); }
+const IDEAS=[
+  {icon:'πŸ”Ž', title:'Task Router β€” β€œWho handles this?”', live:true, render:'router', pitch:'Type any task; route it to the right officer β†’ agent β†’ skill, with one-click Run.'},
+  {icon:'🧬', title:'Consolidation Advisor', live:true, render:'consol', pitch:'Surface skills shared across officers and recommend folds β€” per the cabinet’s own architecture rules.'},
+  {icon:'πŸ“€', title:'Org Snapshot Export', live:true, render:'export', pitch:'Download the whole cabinet as JSON + Markdown for docs, diffs and sharing.'},
+  {icon:'🩺', title:'Live Health Overlay', live:false, render:'health', pitch:'Tint each officer node by its domain’s current uptime / canary verdict β€” see what’s on fire at a glance.'},
+  {icon:'πŸ’Έ', title:'Cost / Usage Heatmap', live:false, render:'cost', pitch:'Size & tint nodes by invocation count or $ from the cost-ledger so the hot (and dead) agents pop.'},
+];
+function openIdeasGallery(){
+  const cards=IDEAS.map((id,i)=>`<div class="idea-card" onclick="openIdea(${i})"><div class="ic">${id.icon}</div><div class="t">${id.title}</div><div class="p">${id.pitch}</div><span class="tag ${id.live?'live':'prop'}">${id.live?'● LIVE':'β—† PROPOSAL'}</span></div>`).join('');
+  openModal(`<h2 class="m-h">πŸ’‘ Ideas for the Cabinet viewer</h2><div class="m-sub">Five upgrades. The LIVE ones work right now; PROPOSALS can be queued to your CNCP parking-lot.</div><div class="idea-grid">${cards}</div>`);
+}
+function ideaFooter(i){ return `<div class="m-actions"><button class="m-btn ghost" onclick="openIdeasGallery()">← Ideas</button><button class="m-btn queue" onclick="queueIdea(${i})">βž• Queue to CNCP</button><button class="m-btn ghost" style="margin-left:auto" onclick="closeModal()">Close</button></div>`; }
+function openIdea(i){
+  const id=IDEAS[i]; let body='';
+  if(id.render==='router') body=routerHTML();
+  else if(id.render==='consol') body=consolHTML();
+  else if(id.render==='export') body=exportHTML();
+  else if(id.render==='health') body=`<div class="m-sub">Proposal. The cabinet server would read each DW canary’s <code>data/latest.json</code> + the uptime probe and tint every officer node green / amber / red by its domain’s live verdict (Operations=fleet uptime, Commerce=scraper-canary, Compliance=leak-scan…). One glance tells you which department is on fire.</div><div class="m-quote">Maps the abstract org chart onto real-time fleet health.</div>`;
+  else if(id.render==='cost') body=`<div class="m-sub">Proposal. Pull <code>~/.claude/cost-ledger.jsonl</code> + invocation counts and scale each node’s size / heat by spend or usage β€” so expensive agents and never-run skills are obvious. Directly serves the architecture goal of retiring dead skills.</div><div class="m-quote">Turns the pyramid into a spend &amp; usage heatmap.</div>`;
+  openModal(`<h2 class="m-h">${id.icon} ${id.title}</h2><div class="m-sub">${id.pitch}</div>${body}${ideaFooter(i)}`);
+  if(id.render==='router'){ const inp=document.getElementById('route-q'); if(inp){ inp.focus(); inp.addEventListener('keydown',e=>{ if(e.key==='Enter') routeTask(); }); } }
+}
+function queueIdea(i){ const id=IDEAS[i];
+  fetch('/api/queue-idea?title='+encodeURIComponent('[Cabinet] '+id.title)+'&summary='+encodeURIComponent(id.pitch)).then(r=>r.json())
+    .then(r=>toast(r.ok?'βž• queued to CNCP parking-lot':'queue failed ('+(r.error||r.status)+')')).catch(()=>toast('CNCP unreachable (:3333)')); }
+function routerHTML(){ return `<div class="ri"><input id="route-q" placeholder="e.g. scrape a new vendor Β· fix a slow SQL query Β· post a reel Β· check DNS"><button class="m-btn primary" onclick="routeTask()">Route</button></div><div id="route-res" class="m-sub">Type a task and hit Route β€” I’ll point you to the officer + the exact skills/agents, each runnable.</div>`; }
+function routeTask(){
+  const qv=(document.getElementById('route-q').value||'').toLowerCase();
+  const toks=qv.split(/[^a-z0-9]+/).filter(t=>t.length>2);
+  const res=document.getElementById('route-res');
+  if(!toks.length){ res.innerHTML='<div class="m-sub">Enter a few words.</div>'; return; }
+  const scored=OFF.map(o=>{
+    const hay=(o.dm+' '+o.agents.join(' ')+' '+o.skills.join(' ')+' '+(o.scrapers||[]).join(' ')).toLowerCase();
+    let score=0; toks.forEach(t=>{ if(hay.includes(t)) score++; });
+    const nodes=[]; [...o.agents.map(a=>({k:'subagent',n:a})),...o.skills.map(s=>({k:'skill',n:s})),...(o.scrapers||[]).map(s=>({k:'skill',n:s}))].forEach(x=>{ if(toks.some(t=>x.n.toLowerCase().includes(t))) nodes.push(x); });
+    return {o,score:score+nodes.length,nodes:nodes.slice(0,8)};
+  }).filter(x=>x.score>0).sort((a,b)=>b.score-a.score).slice(0,3);
+  if(!scored.length){ res.innerHTML='<div class="m-sub">No strong match β€” try words like <b>shopify, dns, video, research, security, compliance, instagram</b>.</div>'; return; }
+  res.innerHTML=scored.map(x=>`<div class="route-off" style="--rc:${x.o.col}"><div class="ro-h">${x.o.nm}</div>${x.nodes.length?x.nodes.map(n=>`<span class="route-chip" onclick="runNode('${n.k}','${n.n}')">${n.k==='skill'?'/':'@'}${n.n} β–Ά</span>`).join(''):'<div style="font-size:11px;color:#8a94a6;margin-top:4px">domain match β€” open this officer for its toolset</div>'}</div>`).join('');
+}
+function consolHTML(){
+  const rows=SHARED.map(s=>`<div class="row"><span style="color:hsl(${hashHue(s.id)} 82% 70%)">${s.id}</span><span style="color:#8a94a6;font-size:11px">${s.officers.map(offName).join(' Β· ')} <b style="color:#e7ecf3">(${s.officers.length})</b></span></div>`).join('');
+  return `<div class="m-sub"><b>${SHARED.length}</b> skills/agents are wired to 2+ officers. Per the cabinet’s rules, repeated cross-use is the signal to fold a Director into a VP or share a single owner.</div><div class="m-quote">β€œWhen a Director is invoked twice for the same VP, consider folding the Director’s logic into the VP. Skills are leaves β€” fewer agents, broader triggers, cleaner routing.” β€” architecture_principles</div><div class="m-list">${rows}</div>`;
+}
+function buildExport(){
+  const obj={president:'Steve', officers:OFF.map(o=>({id:o.id,name:o.nm,domain:o.dm,agents:o.agents,skills:o.skills.concat(o.scrapers||[])})), shared:SHARED.map(s=>({id:s.id,officers:s.officers})), suggestions:SUGGEST};
+  let md='# The Cabinet\n\n';
+  OFF.forEach(o=>{ md+=`## ${o.nm}\n_${o.dm}_\n\n- **Agents:** ${o.agents.join(', ')}\n- **Skills:** ${o.skills.concat(o.scrapers||[]).join(', ')}\n\n`; });
+  md+='## Cross-officer shared\n'+SHARED.map(s=>`- \`${s.id}\` β€” ${s.officers.map(offName).join(', ')}`).join('\n')+'\n';
+  return {json:JSON.stringify(obj,null,2), md};
+}
+function exportHTML(){ return `<div class="m-sub">Download the live cabinet structure (7 officers, all agents/skills, the ${SHARED.length} cross-officer shares + ${SUGGEST.length} suggestions).</div><div class="m-actions" style="border:none;padding:0;margin-top:6px"><button class="m-btn primary" onclick="dl('json')">⬇ cabinet.json</button><button class="m-btn primary" onclick="dl('md')">⬇ cabinet.md</button></div>`; }
+function dl(kind){ const e=buildExport(); const blob=new Blob([kind==='json'?e.json:e.md],{type:'text/plain'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='cabinet.'+(kind==='json'?'json':'md'); a.click(); toast('⬇ cabinet.'+kind+' downloaded'); }
+
 legend(); build(); timeline(); buildToolbar();
 document.getElementById('pyramid').addEventListener('click', onNodeClick);
 document.getElementById('xlines').addEventListener('click', e=>{ const t=e.target.closest('.sug-hit'); if(t) openSuggestion(t.dataset.key); });
 document.getElementById('sug-handles').addEventListener('click', e=>{ const t=e.target.closest('.sug-h'); if(t) openSuggestion(t.dataset.key); });
-document.addEventListener('keydown', e=>{ if(e.key==='Escape') closePanel(); });
+document.getElementById('modal').addEventListener('click', e=>{ if(e.target.id==='modal') closeModal(); });
+document.addEventListener('keydown', e=>{ if(e.key==='Escape'){ closePanel(); closeModal(); } });
 function redraw(){ drawLines(); drawCross(); }
 loadSugState().then(()=>requestAnimationFrame(()=>requestAnimationFrame(redraw)));
 window.addEventListener('resize', ()=>{ requestAnimationFrame(redraw); });
diff --git a/server.js b/server.js
index 3a54b99..4b3bc28 100644
--- a/server.js
+++ b/server.js
@@ -619,6 +619,18 @@ const server = http.createServer((req, res) => {
     }
     return;
   }
+  if (u.pathname === '/api/queue-idea') {
+    const title = q.get('title') || 'Cabinet idea';
+    const summary = q.get('summary') || '';
+    const payload = JSON.stringify({ title, summary, note: summary, project: 'agent-cabinet', source: 'cabinet-pyramid' });
+    const cr = http.request({ host: '127.0.0.1', port: 3333, path: '/api/parking-lot', method: 'POST',
+      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }, timeout: 5000 },
+      (cres) => { let d = ''; cres.on('data', c => d += c); cres.on('end', () => J({ ok: cres.statusCode >= 200 && cres.statusCode < 300, status: cres.statusCode })); });
+    cr.on('error', (e) => J({ ok: false, error: String(e) }));
+    cr.on('timeout', () => { cr.destroy(); J({ ok: false, error: 'CNCP timeout' }); });
+    cr.write(payload); cr.end();
+    return;
+  }
 
   if (req.url === '/cabinet.yaml') {
     try {

← e46f975 Suggestion lines truly clickable: amber β—† handle at each lin  Β·  back to Agent Cabinet  Β·  Build out both proposals LIVE: 🩺 Health Overlay (reads 6 DW 9634ba2 β†’