[object Object]

← back to Model Arena

arena: iTerm2 Build button on every model card + publish *.agentabrams landing on build

af34518a50f44c5f096dc9d7a36c809c7138c68d · 2026-07-23 10:51:40 -0700 · Steve

Files touched

Diff

commit af34518a50f44c5f096dc9d7a36c809c7138c68d
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 23 10:51:40 2026 -0700

    arena: iTerm2 Build button on every model card + publish *.agentabrams landing on build
---
 public/index.html |  6 ++++--
 server.js         | 36 +++++++++++++++++++++++++++++++++++-
 2 files changed, 39 insertions(+), 3 deletions(-)

diff --git a/public/index.html b/public/index.html
index 9cf6a4c..8212678 100644
--- a/public/index.html
+++ b/public/index.html
@@ -140,6 +140,7 @@ tr.clk{cursor:pointer}tr.clk:hover td{background:rgba(0,229,255,.06)}
 .seed.bye{opacity:.35;font-style:italic}
 .champ{border:1px solid var(--gold);background:rgba(255,199,46,.08);padding:14px 18px;text-align:center;box-shadow:0 0 22px rgba(255,199,46,.25)}
 .champ .k{font-size:26px}.champ b{color:var(--gold);font-size:16px}
+.pane .buildbtm{display:block;width:100%;margin-top:8px;padding:10px;font-weight:700;letter-spacing:1px;border-radius:4px;cursor:pointer}
 </style>
 </head>
 <body>
@@ -309,7 +310,7 @@ async function buildInIterm(id, el, model){
     const url = API+'/api/challenges/'+id+'/build'+(model?'/'+model:'');
     const r = await fetch(url,{method:'POST'});
     if(!r.ok){ alert('Build launch failed: '+(await r.text())); }
-    else if(el){ el.classList.add('launched'); setTimeout(()=>{el.style.opacity=1;el.classList.remove('launched');},2000); }
+    else { const j=await r.json().catch(()=>({})); if(el){ el.classList.add('launched'); setTimeout(()=>{el.style.opacity=1;el.classList.remove('launched');},2000);} if(j.landing){ window.open(j.landing,'_blank','noopener'); } }
   }catch(e){ alert('Build launch failed: '+e.message); }
   finally{ if(el) el.style.opacity=1; }
 }
@@ -469,7 +470,7 @@ async function renderDetail(id, statusOnly){
           ${run.status==='done'?`<button class="btn" data-src="${c.id}/${run.model}" title="View HTML source">&lt;/&gt;</button>`:''}
           ${(run.status==='error'||run.status==='done')?`<button class="btn pink" data-retry="${run.model}">↻</button>`:''}
         </span>
-      </div>${bodyHtml}${run.aiReason&&run.status==='done'?`<div class="aireason">🤖 ${esc(run.aiReason)}</div>`:''}`;
+      </div>${bodyHtml}${run.aiReason&&run.status==='done'?`<div class="aireason">🤖 ${esc(run.aiReason)}</div>`:''}${run.status==='done'?`<button class="btn pink buildbtm" data-buildbtm="${run.model}" title="Build ${mLabel(run.model)}'s artifact into a real project (iTerm2 Claude session) + publish a *.agentabrams landing page">⚒ Build in iTerm2 → landing</button>`:''}`;
     arena.appendChild(pane);
   });
   arena.querySelectorAll('[data-win]').forEach(b=>b.onclick=async e=>{
@@ -485,6 +486,7 @@ async function renderDetail(id, statusOnly){
     el.outerHTML = `<iframe sandbox="allow-scripts" src="${API}/artifact/${cid}/${mid}"></iframe>`;
   });
   arena.querySelectorAll('[data-build]').forEach(b=>b.onclick=e=>buildInIterm(c.id, e.target, e.target.dataset.build));
+  arena.querySelectorAll('[data-buildbtm]').forEach(b=>b.onclick=e=>buildInIterm(c.id, e.currentTarget, e.currentTarget.dataset.buildbtm));
   arena.querySelectorAll('[data-src]').forEach(b=>b.onclick=async e=>{
     const [cid,mid]=e.target.dataset.src.split('/');
     const body = e.target.closest('.pane').querySelector('iframe,.poster,.msg,.srcview');
diff --git a/server.js b/server.js
index 60b3f24..a814cbd 100644
--- a/server.js
+++ b/server.js
@@ -45,6 +45,25 @@ const COST_FILE = path.join(DATA, 'costlog.jsonl');
 const GLOBAL_LEDGER = path.join(os.homedir(), '.claude', 'cost-ledger.jsonl');
 
 fs.mkdirSync(ART, { recursive: true });
+const LANDINGS = path.join(DATA, 'landings');
+fs.mkdirSync(LANDINGS, { recursive: true });
+// wrap a model's artifact in a shareable *.agentabrams landing page
+function buildLanding(c, modelId, art){
+  const ml = modelId ? mLabelServer(modelId) : 'Best build';
+  const esc = x => String(x).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  const escAttr = x => esc(x).replace(/"/g,'&quot;');
+  const title = esc(c.title);
+  return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>${title} — built by ${esc(ml)} · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>${title}</h1><span class="by">built by <b>${esc(ml)}</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="${escAttr(art)}" title="${title}"></iframe></div>
+</body></html>`;
+}
 
 // ---------- model roster ----------
 // Local ollama models are free and enabled by default. Metered models require
@@ -688,7 +707,16 @@ const server = http.createServer(async (req, res) => {
   end tell
 end tell`;
     execFile('osascript', ['-e', script], { timeout: 15000 }, (err) => { if (err) console.error('[build] osascript:', err.message); });
-    return send(res, 200, { ok: true, launched: c.id, model: modelId });
+    let landing = null;
+    try {
+      const slug = (c.id + (modelId ? '-' + modelId : '')).toLowerCase();
+      let art = '';
+      if (modelId) { try { art = fs.readFileSync(path.join(ART, c.id, modelId + '.html'), 'utf8'); } catch {} }
+      if (!art) { const dn = c.runs.find(r => r.status === 'done'); if (dn) { try { art = fs.readFileSync(path.join(ART, c.id, dn.model + '.html'), 'utf8'); } catch {} } }
+      fs.writeFileSync(path.join(LANDINGS, slug + '.html'), buildLanding(c, modelId, art || '<!doctype html><title>build</title><body style=\"font:16px monospace;padding:40px;background:#0b0d10;color:#ccc\">Build launching — run the model first.</body>'));
+      landing = '/landing/' + slug;
+    } catch (e) { console.error('[build] landing:', e.message); }
+    return send(res, 200, { ok: true, launched: c.id, model: modelId, landing });
   }
 
   if ((m = p.match(/^\/api\/challenges\/([a-f0-9]+)\/vote$/)) && req.method === 'POST') {
@@ -786,6 +814,12 @@ iframe{width:100%;height:460px;border:0;background:#000}.c.win iframe{height:560
     return res.end(fs.readFileSync(fp));
   }
 
+  if ((m = p.match(/^\/landing\/([\w-]+)$/))) {
+    const fp = path.join(LANDINGS, m[1] + '.html');
+    if (!fs.existsSync(fp)) { res.writeHead(404); return res.end('no landing'); }
+    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
+    return res.end(fs.readFileSync(fp));
+  }
   if ((m = p.match(/^\/artifact\/([a-f0-9]+)\/([\w-]+)$/))) {
     const fp = path.join(ART, m[1], m[2] + '.html');
     if (!fs.existsSync(fp)) { res.writeHead(404); return res.end('no artifact'); }

← 5b59fd4 auto-save: 2026-07-23T10:50:25 (14 files) — data/challenges.  ·  back to Model Arena  ·  auto-save: 2026-07-23T11:20:37 (21 files) — data/challenges. 0524312 →