β 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
M pyramid.htmlM server.js
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 & 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 & 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 β