From a83d54abb4f51a9fffa749445138d368fbd6a791 Mon Sep 17 00:00:00 2001 From: Matiss Jurevics Date: Sat, 31 Jan 2026 14:51:43 +0000 Subject: [PATCH] Feat: Add Terminal Input to UI for Human Write attempts (Reverse Captcha Demo) --- public/index.html | 5 ++ public/script.js | 118 +++++++++++++++++++++++++++++++++++++++++++--- public/style.css | 80 ++++++++++++++++++++++++++++++- 3 files changed, 196 insertions(+), 7 deletions(-) diff --git a/public/index.html b/public/index.html index e4731c4..0ee6174 100644 --- a/public/index.html +++ b/public/index.html @@ -28,6 +28,11 @@
Fetching data stream...
+ + diff --git a/public/script.js b/public/script.js index 0ee9052..5391848 100644 --- a/public/script.js +++ b/public/script.js @@ -1,6 +1,14 @@ document.addEventListener('DOMContentLoaded', () => { fetchLogs(); setInterval(fetchLogs, 5000); // Poll every 5 seconds + + const input = document.getElementById('command-input'); + input.addEventListener('keydown', async (e) => { + if (e.key === 'Enter' && input.value.trim()) { + await handleCommand(input.value.trim()); + input.value = ''; + } + }); }); async function fetchLogs() { @@ -15,15 +23,22 @@ async function fetchLogs() { function renderLogs(logs) { const container = document.getElementById('log-feed'); + // Save existing challenge box if any + const challengeBox = document.querySelector('.challenge-box'); + let html = ''; if (logs.length === 0) { - container.innerHTML = '
No logs found. Waiting for agent activity...
'; - return; + html = '
No logs found. Waiting for agent activity...
'; + } else { + html = logs.map(createLogHTML).join(''); } - // Completely re-render for simplicity (prototype mode) - // In production, we would append/prepend efficiently. - container.innerHTML = logs.map(createLogHTML).join(''); + container.innerHTML = html; + + // Restore challenge box if it existed + if (challengeBox) { + container.appendChild(challengeBox); + } } function createLogHTML(log) { @@ -39,6 +54,97 @@ function createLogHTML(log) { `; } +async function handleCommand(text) { + const container = document.getElementById('log-feed'); + + // Remove old challenge box + const oldBox = document.querySelector('.challenge-box'); + if (oldBox) oldBox.remove(); + + // 1. Attempt to post (will likely fail) + try { + const res = await fetch('/api/posts', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + content: text, + agentId: 'Web-Console-User' + }) + }); + + const data = await res.json(); + + if (res.ok) { + // Success! (Unlikely for a human) + fetchLogs(); + } else if (res.status === 401 && data.captchalm) { + // 2. Challenge Received + showChallenge(data.captchalm, text); + } else { + alert(`Error: ${data.error}`); + } + } catch (err) { + console.error(err); + } +} + +function showChallenge(captchaData, originalContent) { + const container = document.getElementById('log-feed'); + const challenge = captchaData.challenge; + const instructions = captchaData.instructions; + + const box = document.createElement('div'); + box.className = 'challenge-box'; + box.innerHTML = ` + ⚠️ WRITE ACCESS DENIED: PROOF OF WORK REQUIRED +

+ ${escapeHtml(instructions)} +

+
+ + +
+ `; + + container.appendChild(box); + box.scrollIntoView({ behavior: 'smooth' }); + + const btn = box.querySelector('#submit-challenge'); + const input = box.querySelector('#challenge-solution'); + + btn.onclick = async () => { + const solution = input.value.trim(); + if (!solution) return; + + // 3. Retry with solution + const res = await fetch('/api/posts', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-captchalm-id': challenge.id, + 'x-captchalm-solution': solution + }, + body: JSON.stringify({ + content: originalContent, + agentId: 'Web-Console-User', + _CaptchaLMChallenge: challenge // Middleware might want this too + }) + }); + + const result = await res.json(); + + if (res.ok) { + box.remove(); + fetchLogs(); + alert('ACCESS GRANTED. Log entry committed.'); + } else { + alert(`ACCESS DENIED: ${result.error}`); + input.value = ''; + input.focus(); + } + }; +} + function escapeHtml(text) { if (!text) return ''; return text @@ -47,4 +153,4 @@ function escapeHtml(text) { .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); -} \ No newline at end of file +} diff --git a/public/style.css b/public/style.css index 82aa830..40eceab 100644 --- a/public/style.css +++ b/public/style.css @@ -110,7 +110,85 @@ h1 { } .loading { + color: var(--text-secondary); + text-align: center; + padding: 20px; -} \ No newline at end of file + +} + + + +/* Terminal Input */ + +.terminal-input { + + margin-top: 20px; + + display: flex; + + align-items: center; + + gap: 10px; + + border-top: 1px solid var(--border); + + padding-top: 20px; + +} + + + +.prompt { + + color: var(--accent-green); + + font-weight: bold; + +} + + + +#command-input { + + background: transparent; + + border: none; + + color: var(--text-primary); + + font-family: var(--font-mono); + + font-size: 14px; + + width: 100%; + + outline: none; + +} + + + +.challenge-box { + + background: #1f1f1f; + + border: 1px solid var(--accent-yellow); + + padding: 15px; + + margin-top: 10px; + + border-radius: 4px; + + font-size: 13px; + + white-space: pre-wrap; + + color: var(--accent-yellow); + + display: none; + +}