← 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
M public/index.htmlM server.js
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"></></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,'&').replace(/</g,'<').replace(/>/g,'>');
+ const escAttr = x => esc(x).replace(/"/g,'"');
+ 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 →