← back to Model Arena
arena: Build-in-iTerm button per challenge (+ detail view) — writes the challenge brief to a temp file and opens a real interactive Claude session in a new iTerm2 window to build it; safe file-based prompt escaping
584400e60b842c3bf46699e17d8f5d4a60442d3d · 2026-07-23 06:45:08 -0700 · Steve
Files touched
A data/artifacts/be56e35a6067/claude-code.htmlA data/artifacts/be56e35a6067/claude-code.pngM data/challenges.jsonM public/index.htmlM server.js
Diff
commit 584400e60b842c3bf46699e17d8f5d4a60442d3d
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 23 06:45:08 2026 -0700
arena: Build-in-iTerm button per challenge (+ detail view) — writes the challenge brief to a temp file and opens a real interactive Claude session in a new iTerm2 window to build it; safe file-based prompt escaping
---
data/artifacts/be56e35a6067/claude-code.html | 138 +++++++++++++++++++++++++++
data/artifacts/be56e35a6067/claude-code.png | Bin 0 -> 197060 bytes
data/challenges.json | 31 ++++++
public/index.html | 21 +++-
server.js | 24 +++++
5 files changed, 212 insertions(+), 2 deletions(-)
diff --git a/data/artifacts/be56e35a6067/claude-code.html b/data/artifacts/be56e35a6067/claude-code.html
new file mode 100644
index 0000000..ce241a3
--- /dev/null
+++ b/data/artifacts/be56e35a6067/claude-code.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Daily Inspiration</title>
+<style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+
+ body {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
+ font-family: Georgia, 'Times New Roman', serif;
+ color: #eaeaea;
+ padding: 2rem;
+ }
+
+ .card {
+ max-width: 640px;
+ width: 100%;
+ text-align: center;
+ }
+
+ h1 {
+ font-size: 0.85rem;
+ letter-spacing: 0.35em;
+ text-transform: uppercase;
+ color: #8fa3c8;
+ font-family: 'Helvetica Neue', Arial, sans-serif;
+ font-weight: 400;
+ margin-bottom: 2.5rem;
+ }
+
+ #quote-box {
+ min-height: 10rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ opacity: 1;
+ transition: opacity 0.35s ease;
+ }
+
+ #quote-box.fading { opacity: 0; }
+
+ blockquote {
+ font-size: 1.7rem;
+ line-height: 1.5;
+ font-style: italic;
+ quotes: "\201C" "\201D";
+ }
+
+ blockquote::before { content: open-quote; color: #e0a458; }
+ blockquote::after { content: close-quote; color: #e0a458; }
+
+ cite {
+ display: block;
+ margin-top: 1.25rem;
+ font-style: normal;
+ font-size: 1rem;
+ letter-spacing: 0.08em;
+ color: #e0a458;
+ font-family: 'Helvetica Neue', Arial, sans-serif;
+ }
+
+ cite::before { content: "— "; }
+
+ .placeholder {
+ font-size: 1.25rem;
+ color: #7d8bb0;
+ font-style: italic;
+ }
+
+ button {
+ margin-top: 3rem;
+ padding: 0.9rem 2.4rem;
+ font-size: 1rem;
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ font-family: 'Helvetica Neue', Arial, sans-serif;
+ color: #1a1a2e;
+ background: #e0a458;
+ border: none;
+ border-radius: 999px;
+ cursor: pointer;
+ transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
+ box-shadow: 0 4px 18px rgba(224, 164, 88, 0.35);
+ }
+
+ button:hover {
+ background: #eab876;
+ transform: translateY(-2px);
+ box-shadow: 0 8px 24px rgba(224, 164, 88, 0.45);
+ }
+
+ button:active { transform: translateY(0); }
+</style>
+</head>
+<body>
+ <main class="card">
+ <h1>Daily Inspiration</h1>
+ <div id="quote-box">
+ <p class="placeholder">Press the button to receive a spark of inspiration.</p>
+ </div>
+ <button id="quote-btn" type="button">Inspire Me</button>
+ </main>
+
+<script>
+ var quotes = [
+ { text: "The only way to do great work is to love what you do.", author: "Steve Jobs" },
+ { text: "It always seems impossible until it is done.", author: "Nelson Mandela" },
+ { text: "In the middle of difficulty lies opportunity.", author: "Albert Einstein" },
+ { text: "What you get by achieving your goals is not as important as what you become by achieving your goals.", author: "Zig Ziglar" },
+ { text: "The best time to plant a tree was twenty years ago. The second best time is now.", author: "Chinese Proverb" }
+ ];
+
+ var box = document.getElementById('quote-box');
+ var btn = document.getElementById('quote-btn');
+ var lastIndex = -1;
+
+ btn.addEventListener('click', function () {
+ var i;
+ do {
+ i = Math.floor(Math.random() * quotes.length);
+ } while (i === lastIndex && quotes.length > 1);
+ lastIndex = i;
+
+ box.classList.add('fading');
+ setTimeout(function () {
+ box.innerHTML = '<blockquote>' + quotes[i].text + '</blockquote><cite>' + quotes[i].author + '</cite>';
+ box.classList.remove('fading');
+ }, 350);
+ });
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/be56e35a6067/claude-code.png b/data/artifacts/be56e35a6067/claude-code.png
new file mode 100644
index 0000000..e4068fb
Binary files /dev/null and b/data/artifacts/be56e35a6067/claude-code.png differ
diff --git a/data/challenges.json b/data/challenges.json
index 5bd0ee5..58463b2 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -1443,5 +1443,36 @@
"thumb": true
}
]
+ },
+ {
+ "id": "be56e35a6067",
+ "title": "Max Plan Claude Test",
+ "prompt": "Single HTML file: a button that, when clicked, shows a random inspirational quote from an array of 5. Center it, nice typography.",
+ "category": "Custom",
+ "created_at": "2026-07-23T13:41:16.707Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 21,
+ "cost": 0,
+ "started_at": "2026-07-23T13:41:16.708Z",
+ "finished_at": "2026-07-23T13:41:37.779Z",
+ "bytes": 3624,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The design is clean and the button effectively fulfills the challenge of showing a random quote.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
+ }
+ ],
+ "judging": false,
+ "aiPick": "claude-code",
+ "judged_at": "2026-07-23T13:41:50.912Z"
}
]
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 47559b6..30d7a17 100644
--- a/public/index.html
+++ b/public/index.html
@@ -47,6 +47,11 @@ input[type=range]{accent-color:var(--neon);width:130px}
.chip.queued{border-color:var(--dim)}
.chip.error{border-color:var(--err);color:var(--err)}
.chip.winner{border-color:var(--gold);color:var(--gold);font-weight:700}
+.mini.build{margin-top:10px;background:none;border:1px solid var(--neon2);color:var(--neon2);padding:5px 12px;font:inherit;font-size:11px;
+ letter-spacing:1px;text-transform:uppercase;cursor:pointer;border-radius:3px;width:100%}
+.mini.build:hover{background:rgba(255,46,166,.12);box-shadow:0 0 10px rgba(255,46,166,.25)}
+.mini.build:disabled{opacity:.6;cursor:default}
+#d-build{color:var(--neon2)}
@keyframes pulse{50%{opacity:.35}}
.empty{color:var(--dim);padding:40px;text-align:center;border:1px dashed var(--line)}
/* new challenge */
@@ -181,6 +186,7 @@ tr.clk{cursor:pointer}tr.clk:hover td{background:rgba(0,229,255,.06)}
<section id="detail" class="view">
<span class="back" id="back">← back to challenges</span>
<a class="back" id="export" style="margin-left:16px" download>⬇ export battle as standalone HTML</a>
+ <span class="back" id="d-build" style="margin-left:16px">⚒ Build in iTerm</span>
<label class="blindtog"><input type="checkbox" id="blind"> 🕶 Blind judging — hide model names until a winner is crowned (kills brand bias)</label>
<h2 id="d-title"></h2>
<div class="meta" id="d-meta"></div>
@@ -285,12 +291,22 @@ async function loadList(){
div.innerHTML = `<h3>${esc(c.title)}</h3>
<div class="when" title="${c.created_at}">🕓 ${fmtWhen(c.created_at)}</div>
${shotsHtml}
- <div class="chips">${chips}</div>`;
- div.onclick = ()=>openDetail(c.id);
+ <div class="chips">${chips}</div>
+ <button class="mini build" data-build="${c.id}" title="Open a real Claude session in iTerm2, pre-loaded with this challenge, to actually build it">⚒ Build in iTerm</button>`;
+ div.onclick = (e)=>{ if(e.target.dataset.build) return; openDetail(c.id); };
+ div.querySelector('[data-build]').onclick = (e)=>{ e.stopPropagation(); buildInIterm(c.id, e.target); };
el.appendChild(div);
}
}
function esc(s){ return String(s).replace(/[&<>"]/g, c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); }
+async function buildInIterm(id, btn){
+ const t = btn && btn.textContent; if(btn){ btn.textContent='⚒ launching…'; btn.disabled=true; }
+ try{
+ const r = await fetch(API+'/api/challenges/'+id+'/build',{method:'POST'});
+ if(!r.ok){ alert('Build launch failed: '+(await r.text())); }
+ else if(btn){ btn.textContent='⚒ launched ↗'; setTimeout(()=>{btn.textContent=t;btn.disabled=false;},2500); }
+ }catch(e){ alert('Build launch failed: '+e.message); if(btn){btn.textContent=t;btn.disabled=false;} }
+}
// ---- new battle ----
async function loadModels(){
@@ -356,6 +372,7 @@ async function renderDetail(id, statusOnly){
const remixNote = c.remixOf ? '🧬 remixed from ' + mLabel(c.remixSource) + "'s artifact\n" : '';
$('#d-meta').textContent = '🕓 ' + fmtWhen(c.created_at) + (c.category?' · '+c.category:'') + '\n' + remixNote + c.prompt;
$('#export').href = API+'/export/'+c.id;
+ $('#d-build').onclick = (e)=>buildInIterm(c.id, e.target);
// AI referee status bar
const anyArt = c.runs.some(r=>r.status==='done'&&r.thumb);
const ai = $('#d-ai');
diff --git a/server.js b/server.js
index fbb6793..3ee7baa 100644
--- a/server.js
+++ b/server.js
@@ -507,6 +507,30 @@ const server = http.createServer(async (req, res) => {
return send(res, 201, nc);
}
+ if ((m = p.match(/^\/api\/challenges\/([a-f0-9]+)\/build$/)) && req.method === 'POST') {
+ const c = challenges.find(x => x.id === m[1]);
+ if (!c) return send(res, 404, { error: 'not found' });
+ if (process.platform !== 'darwin') return send(res, 400, { error: 'iTerm launch is macOS-only' });
+ // write the brief to a temp file so any quotes/newlines in the prompt can't break escaping
+ const tmp = '/tmp/arena-build-' + c.id + '.md';
+ const brief = `Build this Model Arena challenge as a real, working project.\n\nTITLE: ${c.title}\nCATEGORY: ${c.category || 'Custom'}\n\nCHALLENGE:\n${c.prompt}\n\nDeliver a complete, working result (a single-file HTML unless it clearly needs more). When done, open it in a browser to verify it works.`;
+ try { fs.writeFileSync(tmp, brief); } catch (e) { return send(res, 500, { error: 'temp write failed' }); }
+ const safeTitle = c.title.replace(/[^\w \-]/g, '').slice(0, 40);
+ // iTerm runs: cd ~/Projects, print a banner, launch interactive claude seeded with the brief
+ const payload = `cd "${os.homedir()}/Projects" && clear && printf '\\n==== ARENA BUILD: ${safeTitle} ====\\n\\n' && claude "$(cat ${tmp})"`;
+ const osaEsc = s => s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
+ const script = `tell application "iTerm"
+ activate
+ set w to (create window with default profile)
+ tell current session of w
+ set name to "arena: ${osaEsc(safeTitle)}"
+ write text "${osaEsc(payload)}"
+ 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 });
+ }
+
if ((m = p.match(/^\/api\/challenges\/([a-f0-9]+)\/vote$/)) && req.method === 'POST') {
const c = challenges.find(x => x.id === m[1]);
if (!c) return send(res, 404, { error: 'not found' });
← e93e641 arena: Claude via Max plan (Claude Code CLI adapter, $0 subs
·
back to Model Arena
·
auto-save: 2026-07-23T06:49:07 (7 files) — data/challenges.j 2897af6 →