WebBrain is a free, open-source browser agent for Chrome and Firefox. It reads pages, extracts data, and automates multi-step tasks. Unlike most browser AI plugins, it can also run entirely on a local model.
It is built by Emre Sokullu and licensed under MIT. The full source lives on GitHub.
Run the agent against a local model, and no page data leaves your machine. Connect a cloud API when you want more capability.
What is WebBrain?
WebBrain lives in your browser’s side panel. In Chrome it uses Manifest V3 and the sidePanel API. In Firefox it uses Manifest V2 and sidebar_action. Each tab keeps its own conversation history.
The extension operates inside your existing authenticated session. It sees your logged-in accounts exactly as you do. It stores no data externally and adds no telemetry or accounts.
The plugin ships in English, Español, Français, Türkçe, and 中文. It auto-detects your browser language on first launch.
Ask Mode, Act Mode, and How Actions Actually Fire
WebBrain has two modes: Ask mode is read-only and cannot change the page. Act mode can click, type, scroll, navigate, and run workflows.
Ask mode reads pages through ordinary content scripts. Act mode is different. It drives the page through the Chrome DevTools Protocol via the chrome.debugger API. That produces trusted input events that modern sites actually honor. It also reaches cross-origin iframes and shadow DOM that content scripts cannot see.
That power is scoped deliberately. WebBrain attaches the debugger only when an action needs it, per tab. Chrome surfaces its standard ‘WebBrain started debugging this browser’ banner while attached. Firefox has no CDP equivalent, so its Act mode is meaningfully weaker.
Temperatures are fixed for predictability. Act mode uses temperature 0.15. Ask mode uses 0.3. Dedicated vision screenshot descriptions use 0.
The Security Model
Browser agents run on an adversarial surface. Web pages can hide prompt injections that hijack an agent’s behavior. WebBrain’s design addresses this directly.
The agent starts in read-only Ask mode. It asks before consequential actions. You can disable those prompts in the Permissions settings. They are on by default.
There is also a UI-first rule for mutations. For anything that creates, sends, submits, or buys, WebBrain uses the visible UI. It refuses to call REST or GraphQL endpoints directly for mutations. A per-conversation /allow-api override exists when the UI genuinely fails.
Reading is treated separately. Fetching a README or comparing prices uses background HTTP through the fetch_url and research_url tools. Reading changes nothing remotely, so the strict rules do not apply.
Use Cases, With Concrete Examples
Data extraction is the obvious one: Open a catalog and ask: ‘Extract all product names and prices from this page.’ The agent reads the structure and returns rows. It also works with PDFs.
Research summaries are another: Ask ‘Summarize this article,’ then follow up with a specific question. WebBrain detects paywalls honestly and does not try to bypass them. It also dismisses common cookie-consent banners before reading.
Form filling suits repetitive signups: An optional Profile auto-fill stores a short bio in local plaintext. That text is sent to your configured LLM to complete low-stakes forms. Keep important passwords out of it.
Automation spans multiple steps: Try ‘Navigate to github.com and find trending repositories.’ In Act mode, the agent chains navigation, reads, and clicks.
Keeping Token Costs Down
Cloud tokens add up on long sessions. WebBrain bounds the cost in three ways.
Screenshots are resized and iteratively JPEG-compressed before they leave your machine. That keeps image tokens small.
Conversation history and tool outputs are trimmed oldest-first as the context window fills.
You can also pair a cheap text model for planning with a separate vision model for screenshots.
How It Compares
WebBrain sits between browser AI plugins and full agent frameworks. Here is the plugin comparison, drawn from the project’s own documentation.
FeatureWebBrainClaude in ChromeOpen sourceMIT LicenseProprietaryPriceFree foreverRequires Claude Pro ($20/mo)Local LLM supportllama.cpp, OllamaNo — Claude onlyMulti-providerAll OpenAI-compatible endpointsClaude onlyChromeYes (MV3)YesFirefoxYes (MV2)NoSide panel UIYesYesAsk / Act modesYesSimilarFully offlineYes (with local LLM)No — cloud requiredSelf-hostableYesNo
Frameworks like OpenClaw or Browser-Use are a different category. Those are developer SDKs for headless pipelines. WebBrain is an end-user extension you drive from a chat panel. You can use both.
Running It: Providers and Setup
WebBrain supports local and cloud models through one interface. Local options include llama.cpp, Ollama, LM Studio, Jan, vLLM, and SGLang. Cloud options include OpenAI, Anthropic Claude, Gemini, Mistral, DeepSeek, and xAI Grok. It also supports Groq, MiniMax, Alibaba Cloud (Qwen), Nvidia NIM, and OpenRouter.
A built-in managed option, WebBrain Cloud, needs no local setup. It costs $5 per month per device profile under a fair-use policy. For local use, llama.cpp needs no API key.
Starting a local server takes one command:
Copy CodeCopiedUse a different Browser# llama.cpp — load at least a 16k-token context window
llama-server -m your-model.gguf -c 16384 –port 8080
# Ollama (OpenAI-compatible) — set the extension-origin env var
OLLAMA_ORIGINS=”*” ollama serve
# then set the base URL to http://localhost:11434/v1 in settings
Point WebBrain at the endpoint in settings. For a cross-machine vLLM server, enable CORS with –allowed-origins ‘[“*”]’.
The recommended model is Qwen 3.6 35B (Qwen3.6-35B-A3B). It beat Gemma 4 on the project’s screenshot benchmark. An RTX 5090 is ideal; an RTX 4090 works with INT4 AutoRound quantization.
Each provider is a class that extends BaseLLMProvider. It normalizes to one response shape:
Copy CodeCopiedUse a different Browser{ content: string, toolCalls: Array|null, usage: Object|null }
Key Takeaways
WebBrain is a free, MIT-licensed AI browser agent for Chrome and Firefox, built by Emre Sokullu.
It runs on local models (llama.cpp, Ollama; Qwen 3.6 35B recommended) or any cloud API — no page data leaves your machine when local.
Ask mode reads pages read-only; Act mode clicks and types via the Chrome DevTools Protocol for trusted input events.
Security-first by design: starts read-only, approves consequential actions, and uses the UI instead of direct API calls for mutations.
Free forever self-hosted, or $5/month per device profile for the managed WebBrain Cloud under fair use.
Interactive Explainer with Demo
Demo-1
</div>
<div>
<h2>WebBrain — Interactive Demo</h2>
<p class=”wb-sub”>Pick a task, choose Ask or Act, and watch the agent work.</p>
</div>
<span class=”wb-badge”>Simulated · no real LLM calls</span>
</div>
<div class=”wb-controls”>
<div class=”wb-seg” id=”wb-modeseg”>
<button data-mode=”ask” class=”on”>Ask mode</button>
<button data-mode=”act”>Act mode</button>
</div>
<div class=”wb-chips” id=”wb-chips”></div>
</div>
<div class=”wb-grid”>
<div class=”wb-pane”>
<div class=”wb-bar”>
<div class=”wb-dots”><span class=”wb-dot” style=”background:#f87171″></span>
<span class=”wb-dot” style=”background:#fbbf24″></span>
<span class=”wb-dot” style=”background:#34d399″></span></div>
<div class=”wb-url” id=”wb-url”></div>
</div>
<div class=”wb-page” id=”wb-page”></div>
</div>
<div class=”wb-pane”>
<div class=”wb-panehead”> WebBrain
<span class=”wb-mode-pill ask” id=”wb-modepill”>Ask</span>
</div>
<div class=”wb-panel”>
<div class=”wb-prompt”>
<input id=”wb-input” type=”text” autocomplete=”off”>
<button class=”wb-run” id=”wb-run”>Run</button>
</div>
<div class=”wb-log” id=”wb-log”>
<div class=”wb-hint”>Choose a task above, then press <b>Run</b>. Read-only Ask mode is the default.</div>
</div>
</div>
</div>
</div>
<div class=”wb-foot”>
<button class=”wb-reset” id=”wb-reset”>Reset demo</button>
<span>Actions here are illustrative and safe. The real extension asks before consequential actions.</span>
<span class=”brand”>Built by Marktechpost</span>
</div>
</div>
<script>
(function(){
var SCEN = {
products: {
label:”Extract products”, req:”ask”,
url:”https://example.com/products”,
prompt:”Extract all product names and prices from this page”,
page:'<b>Product Catalog</b>’+
‘<div class=”prod”><span>Widget Pro</span><b>$29.99</b></div>’+
‘<div class=”prod”><span>Super Gadget</span><b>$49.99</b></div>’+
‘<div class=”prod”><span>MegaTool X</span><b>$19.99</b></div>’+
‘<div class=”prod”><span>Nano Clip</span><b>$8.50</b></div>’+
‘<div class=”prod”><span>HyperDock</span><b>$74.00</b></div>’+
‘<div class=”prod”><span>PixelCase</span><b>$14.25</b></div>’+
‘<p class=”wb-hint” style=”margin-top:8px”>…and 18 more</p>’,
steps:[
{t:’Reading page’, c:’get_accessibility_tree’},
{t:’Extracting structured rows’, c:’extract_data’}
],
result:function(){
return ‘<h4>Found 24 products</h4><table>’+
‘<tr><td>Widget Pro</td><td>$29.99</td></tr>’+
‘<tr><td>Super Gadget</td><td>$49.99</td></tr>’+
‘<tr><td>MegaTool X</td><td>$19.99</td></tr>’+
‘<tr><td>Nano Clip</td><td>$8.50</td></tr>’+
‘<tr><td>HyperDock</td><td>$74.00</td></tr>’+
‘<tr><td>PixelCase</td><td>$14.25</td></tr>’+
‘</table><p class=”wb-note” style=”margin-top:7px”>…and 18 more. Export as CSV or JSON.</p>’;
}
},
summary: {
label:”Summarize article”, req:”ask”,
url:”https://magazine.dev/local-ai-2026″,
prompt:”Summarize this article”,
page:'<b>The Future of Local AI</b><p class=”wb-hint”>By Jane Smith · 8 min read</p>’+
‘<p>Local LLMs now match cloud quality on many tasks. Better quantization, faster consumer GPUs, ‘+
‘and a privacy-first shift are the drivers. The author expects most consumer AI to run on-device by 2026.</p>’,
steps:[
{t:’Reading page text’, c:’read_page’}
],
result:function(){
return ‘<h4>Summary</h4><ul style=”margin:0;padding-left:18px”>’+
‘<li>Local LLMs now rival cloud quality on many tasks.</li>’+
‘<li>Drivers: better quantization, faster GPUs, privacy demand.</li>’+
‘<li>Author predicts 70%+ of consumer AI runs on-device by 2026.</li>’+
‘</ul><p class=”wb-note” style=”margin-top:8px”>Follow-up: <a href=”#” id=”wb-follow”>’+
‘What does the author predict for 2026?</a></p>’;
},
follow:{
steps:[{t:’Recalling article context’, c:’scratchpad_write’}],
result:'<h4>Answer</h4><p>By 2026, the author expects 70%+ of consumer AI workloads to run on-device.</p>’
}
},
video: {
label:”Download video”, req:”act”,
url:”https://socialvid.app/@nomad/clip-8472″,
prompt:”Download this video”,
page:'<div class=”vid”>▶ Sunset timelapse over Bodrum<br><span style=”font-size:11px;opacity:.7″>’+
‘@nomad · 0:24</span></div><p class=”wb-hint” style=”margin-top:8px”>Video post</p>’,
steps:[
{t:’Listing interactive elements’, c:’get_interactive_elements’},
{t:’Capturing visible tab’, c:’screenshot’, token:true},
{t:’Locating video stream’, c:’download_resource_from_page’}
],
approve:”Download the MP4 to your machine?”,
result:function(){
return ‘<h4>Saved</h4><p>Downloads/nomad-bodrum.mp4</p>’+
‘<p class=”wb-note”>1080p · 24s · 4.2 MB</p>’;
}
},
signup: {
label:”Fill signup form”, req:”act”,
url:”https://acmestudio.io/signup”,
prompt:”Sign me up using my profile”,
page:'<b>Create your Acme Studio account</b>’+
‘<div class=”field”><label>Work email</label><div class=”box” id=”f-email”></div></div>’+
‘<div class=”field”><label>Company</label><div class=”box” id=”f-company”></div></div>’+
‘<div class=”field”><label>Password</label><div class=”box” id=”f-pass”></div></div>’+
‘<p class=”wb-hint”>Uses optional Profile auto-fill (stored locally, plaintext).</p>’,
steps:[
{t:’Reading form fields’, c:’get_accessibility_tree’},
{t:’Filling email’, c:’set_field’, fill:{id:’f-email’,v:’dev@example.com’}},
{t:’Filling company’, c:’set_field’, fill:{id:’f-company’,v:’Acme Labs’}},
{t:’Filling throwaway password’, c:’set_field’, fill:{id:’f-pass’,v:’••••••••’}},
{t:’Checking values before submit’, c:’verify_form’}
],
approve:”Submit the signup form and create the account?”,
result:function(){
return ‘<h4>Account created</h4><p>Confirmation screen shown by the site.</p>’+
‘<p class=”wb-note”>Profile text is sent to your LLM each turn. Keep important passwords out of it.</p>’;
}
}
};
var order = [‘products’,’summary’,’video’,’signup’];
var state = { mode:’ask’, scen:’products’, running:false, aborted:false };
var timers = [];
var $ = function(id){ return document.getElementById(id); };
function clearTimers(){ timers.forEach(clearTimeout); timers = []; }
function renderChips(){
var box = $(‘wb-chips’); box.innerHTML=”;
order.forEach(function(k){
var s = SCEN[k];
var b = document.createElement(‘button’);
b.className = ‘wb-chip’ + (k===state.scen?’ on’:”);
b.innerHTML = s.label + ‘ <span class=”req”>· ‘+(s.req===’act’?’Act’:’Ask’)+'</span>’;
b.onclick = function(){ selectScen(k); };
box.appendChild(b);
});
}
function renderMode(){
var seg = $(‘wb-modeseg’).querySelectorAll(‘button’);
seg.forEach(function(b){ b.classList.toggle(‘on’, b.dataset.mode===state.mode); });
var pill = $(‘wb-modepill’);
pill.textContent = state.mode===’act’?’Act’:’Ask’;
pill.className = ‘wb-mode-pill ‘+(state.mode===’act’?’act’:’ask’);
}
function selectScen(k){
if(state.running) return;
state.scen = k;
var s = SCEN[k];
$(‘wb-url’).textContent = s.url;
$(‘wb-page’).innerHTML = s.page;
$(‘wb-input’).value = s.prompt;
if(s.req===’act’ && state.mode!==’act’){ state.mode=’act’; }
renderChips(); renderMode(); resetLog();
}
function resetLog(){
clearTimers(); state.running=false; state.aborted=false;
$(‘wb-run’).style.display=”; $(‘wb-run’).disabled=false;
var stop=$(‘wb-stopbtn’); if(stop) stop.remove();
$(‘wb-log’).innerHTML = ‘<div class=”wb-hint”>Press <b>Run</b> to start. Ask mode is read-only; Act mode can act.</div>’;
var s=SCEN[state.scen];
if(s.req===’signup’||state.scen===’signup’){ /* fields already blank in page html */ }
}
function addStep(html){
var d=document.createElement(‘div’); d.className=’wb-step’; d.innerHTML=html;
$(‘wb-log’).appendChild(d); postHeight(); return d;
}
function showStop(){
var stop=document.createElement(‘button’);
stop.id=’wb-stopbtn’; stop.className=’wb-stop’; stop.textContent=’Stop’;
stop.onclick=function(){ state.aborted=true; clearTimers(); finishAbort(); };
$(‘wb-run’).style.display=’none’;
$(‘wb-run’).parentNode.appendChild(stop);
}
function hideStop(){ var s=$(‘wb-stopbtn’); if(s) s.remove(); $(‘wb-run’).style.display=”; $(‘wb-run’).disabled=false; }
function finishAbort(){
state.running=false; hideStop();
addStep(‘<span style=”color:#b91c1c;font-weight:700″>×</span> Stopped by user.’);
}
function run(){
if(state.running) return;
var s = SCEN[state.scen];
if(s.req===’act’ && state.mode!==’act’){
$(‘wb-log’).innerHTML=”;
addStep(‘<span style=”color:#4338ca;font-weight:700″>ⓘ</span> ‘+
‘Ask mode is read-only. Switch to <b>Act mode</b> to run this task.’);
return;
}
state.running=true; state.aborted=false;
$(‘wb-log’).innerHTML=”; $(‘wb-run’).disabled=true; showStop();
var delay=350;
s.steps.forEach(function(st){
timers.push(setTimeout(function(){
if(state.aborted) return;
var el=addStep(‘<span class=”spin”></span> ‘+st.t+’ · <code>’+st.c+'</code>’);
timers.push(setTimeout(function(){
if(state.aborted) return;
el.innerHTML='<span class=”tick”>✓</span> ‘+st.t+’ · <code>’+st.c+'</code>’;
if(st.token) addStep(‘<div class=”wb-token”>Token-conscious: screenshot 2000×1200 ‘+
‘resized & JPEG-compressed → ~380 image tokens (illustrative).</div>’);
if(st.fill){ var f=document.getElementById(st.fill.id); if(f){ f.innerHTML=st.fill.v; f.classList.add(‘fill’); } }
postHeight();
}, 420));
}, delay));
delay += 780;
});
timers.push(setTimeout(function(){
if(state.aborted) return;
if(s.approve){ askApproval(s); } else { finish(s); }
}, delay+150));
}
function askApproval(s){
var box=document.createElement(‘div’); box.className=’wb-approve’;
box.innerHTML='<p>⚠ Consequential action — ‘+s.approve+'</p><div class=”row”>’+
‘<button class=”wb-yes”>Approve</button><button class=”wb-no”>Cancel</button></div>’;
$(‘wb-log’).appendChild(box); postHeight();
box.querySelector(‘.wb-yes’).onclick=function(){ box.remove(); finish(s); };
box.querySelector(‘.wb-no’).onclick=function(){
box.remove(); state.running=false; hideStop();
addStep(‘<span style=”color:#b91c1c;font-weight:700″>×</span> Action cancelled. Nothing was changed.’);
};
}
function finish(s){
state.running=false; hideStop();
var r=document.createElement(‘div’); r.className=’wb-result’; r.innerHTML=s.result();
$(‘wb-log’).appendChild(r);
addStep(‘<span class=”tick”>✓</span> Task complete · <code>done</code>’);
postHeight();
if(s.follow){
var link=document.getElementById(‘wb-follow’);
if(link) link.onclick=function(e){ e.preventDefault(); runFollow(s); };
}
}
function runFollow(s){
if(state.running) return; state.running=true; $(‘wb-run’).disabled=true; showStop();
var d=400;
s.follow.steps.forEach(function(st){
timers.push(setTimeout(function(){
if(state.aborted) return;
var el=addStep(‘<span class=”spin”></span> ‘+st.t+’ · <code>’+st.c+'</code>’);
timers.push(setTimeout(function(){
if(state.aborted) return;
el.innerHTML='<span class=”tick”>✓</span> ‘+st.t+’ · <code>’+st.c+'</code>’;
},380));
},d)); d+=650;
});
timers.push(setTimeout(function(){
if(state.aborted) return;
state.running=false; hideStop();
var r=document.createElement(‘div’); r.className=’wb-result’; r.innerHTML=s.follow.result;
$(‘wb-log’).appendChild(r); postHeight();
}, d+150));
}
// mode buttons
$(‘wb-modeseg’).querySelectorAll(‘button’).forEach(function(b){
b.onclick=function(){ if(state.running) return; state.mode=b.dataset.mode; renderMode(); };
});
$(‘wb-run’).onclick=run;
$(‘wb-reset’).onclick=function(){ state.mode=’ask’; selectScen(‘products’); };
$(‘wb-input’).addEventListener(‘keydown’,function(e){ if(e.key===’Enter’) run(); });
// auto-resize: component’s own offsetHeight + 40 (never scrollHeight)
function postHeight(){
try{
var el=document.getElementById(‘wb-root’);
var h=(el?el.offsetHeight:document.body.offsetHeight)+40;
if(window.parent && window.parent!==window){ window.parent.postMessage({wbHeight:h},’*’); }
}catch(e){}
}
window.postHeight=postHeight;
window.addEventListener(‘load’,function(){ postHeight(); setTimeout(postHeight,300); });
if(window.ResizeObserver){
try{ new ResizeObserver(postHeight).observe(document.getElementById(‘wb-root’)); }catch(e){}
}
// init
renderChips(); renderMode(); selectScen(‘products’);
})();
</script>
</body>
</html>
“>
(function(){
window.addEventListener(“message”, function(e){
if(e && e.data && typeof e.data.wbHeight === “number”){
var f = document.getElementById(“webbrain-frame”);
if(f){ f.style.height = e.data.wbHeight + “px”; }
}
});
})();
Demo-2
</div>
<div>
<h2>WebBrain for Developers — Interactive Demo</h2>
<p class=”wb-sub”>Read code, extract API specs, research sources, and act — from the side panel.</p>
</div>
<span class=”wb-badge”>Simulated · no real LLM calls</span>
</div>
<div class=”wb-controls”>
<div class=”wb-seg” id=”wb-modeseg”>
<button data-mode=”ask” class=”on”>Ask mode</button>
<button data-mode=”act”>Act mode</button>
</div>
<div class=”wb-chips” id=”wb-chips”></div>
</div>
<div class=”wb-grid”>
<div class=”wb-pane”>
<div class=”wb-bar”>
<div class=”wb-dots”><span class=”wb-dot” style=”background:#f87171″></span>
<span class=”wb-dot” style=”background:#fbbf24″></span>
<span class=”wb-dot” style=”background:#34d399″></span></div>
<div class=”wb-url” id=”wb-url”></div>
</div>
<div class=”wb-page” id=”wb-page”></div>
</div>
<div class=”wb-pane”>
<div class=”wb-panehead”> WebBrain
<span class=”wb-mode-pill ask” id=”wb-modepill”>Ask</span>
</div>
<div class=”wb-panel”>
<div class=”wb-prompt”>
<input id=”wb-input” type=”text” autocomplete=”off”>
<button class=”wb-run” id=”wb-run”>Run</button>
</div>
<div class=”wb-log” id=”wb-log”>
<div class=”wb-hint”>Choose a task above, then press <b>Run</b>. Read-only Ask mode is the default.</div>
</div>
</div>
</div>
</div>
<div class=”wb-foot”>
<button class=”wb-reset” id=”wb-reset”>Reset demo</button>
<span>Actions here are illustrative and safe. The real extension asks before consequential actions.</span>
<span class=”brand”>Built by Marktechpost</span>
</div>
</div>
<script>
(function(){
var CH = ‘wb-dev-demo’;
var SCEN = {
explain: {
label:”Explain code”, req:”ask”,
url:”https://github.com/acme/router/blob/main/debounce.js”,
prompt:”Explain what this function does”,
page:'<div class=”repo”><span>📄</span><span class=”path”>debounce.js</span></div>’+
‘<pre>export function debounce(fn, wait) {n let t;n return (…args) => {n’+
‘ clearTimeout(t);n t = setTimeout(() => fn(…args), wait);n };n}</pre>’+
‘<p class=”wb-hint”>Source file view</p>’,
steps:[{t:’Reading file contents’, c:’read_page’}],
result:function(){
return ‘<h4>What it does</h4><ul>’+
‘<li>Returns a wrapped version of <code>fn</code> that delays calls.</li>’+
‘<li>Each new call clears the pending timer and restarts it.</li>’+
‘<li><code>fn</code> runs only after <code>wait</code> ms of no new calls.</li>’+
‘</ul><p class=”wb-note” style=”margin-top:8px”>Follow-up: ‘+
‘<a href=”#” id=”wb-follow”>How do I use it?</a></p>’;
},
follow:{
steps:[{t:’Preparing an example’, c:’scratchpad_write’}],
result:'<h4>Usage</h4><pre>const onSearch = debounce((q) => {n fetchResults(q);n}, 300);nninput.addEventListener(“input”, e => onSearch(e.target.value));</pre>’
}
},
api: {
label:”Extract API endpoints”, req:”ask”,
url:”https://docs.acme.dev/api/reference”,
prompt:”List every endpoint and its method from this reference”,
page:'<b>Acme API Reference</b><p class=”wb-hint”>v1</p>’+
‘<div class=”ep”><span>List users</span><b>GET /v1/users</b></div>’+
‘<div class=”ep”><span>Create user</span><b>POST /v1/users</b></div>’+
‘<div class=”ep”><span>Get user</span><b>GET /v1/users/{id}</b></div>’+
‘<div class=”ep”><span>Update user</span><b>PATCH /v1/users/{id}</b></div>’+
‘<div class=”ep”><span>Delete user</span><b>DELETE /v1/users/{id}</b></div>’+
‘<div class=”ep”><span>Health check</span><b>GET /v1/health</b></div>’,
steps:[
{t:’Reading page structure’, c:’get_accessibility_tree’},
{t:’Extracting endpoints’, c:’extract_data’}
],
result:function(){
return ‘<h4>6 endpoints found</h4><table>’+
‘<tr><td>GET</td><td>/v1/users</td></tr>’+
‘<tr><td>POST</td><td>/v1/users</td></tr>’+
‘<tr><td>GET</td><td>/v1/users/{id}</td></tr>’+
‘<tr><td>PATCH</td><td>/v1/users/{id}</td></tr>’+
‘<tr><td>DELETE</td><td>/v1/users/{id}</td></tr>’+
‘<tr><td>GET</td><td>/v1/health</td></tr>’+
‘</table><p class=”wb-note” style=”margin-top:7px”>Export as JSON or an OpenAPI stub.</p>’;
}
},
research: {
label:”Research a topic”, req:”ask”,
url:”https://blog.statelib.dev/state-in-2026″,
prompt:”Compare these two state libraries and recommend one”,
page:'<b>State management in 2026</b><p class=”wb-hint”>By A. Rivera · 6 min read</p>’+
‘<p>Two small libraries dominate new projects: StateLib and Acme Store. ‘+
‘Both are signal-based. They differ on bundle size, server rendering, and devtools.</p>’+
‘<p class=”wb-hint”>The agent also reads docs.acme-store.dev in the background.</p>’,
steps:[
{t:’Reading blog.statelib.dev’, c:’research_url’},
{t:’Reading docs.acme-store.dev’, c:’research_url’}
],
result:function(){
return ‘<h4>Summary</h4><ul>’+
‘<li>Both are signal-based and ship first-class TypeScript types.</li>’+
‘<li>StateLib has a smaller core; Acme Store has stronger SSR support.</li>’+
‘<li>For a server-rendered app, the sources lean Acme Store.</li>’+
‘</ul><p class=”wb-note” style=”margin-top:8px”>Illustrative synthesis from two sources. ‘+
‘<a href=”#” id=”wb-follow”>Which is smaller?</a></p>’;
},
follow:{
steps:[{t:’Checking published bundle sizes’, c:’fetch_url’}],
result:'<h4>Bundle size (illustrative)</h4><table>’+
‘<tr><td>StateLib</td><td>3.1 kB min+gzip</td></tr>’+
‘<tr><td>Acme Store</td><td>5.4 kB min+gzip</td></tr></table>’
}
},
star: {
label:”Star the repo”, req:”act”,
url:”https://github.com/acme/router”,
prompt:”Star this repository”,
page:'<div class=”repo”><span>📁</span><span class=”path”>acme/router</span></div>’+
‘<p style=”margin:6px 0 10px”>Tiny type-safe router for modern web apps.</p>’+
‘<span id=”gh-star” style=”display:inline-block;border:1px solid #d0d7de;border-radius:7px;’+
‘padding:6px 12px;font-weight:700;font-size:12.5px;background:#f6f8fa;color:#1f2430″>’+
‘☆ Star · 8.4k</span>’+
‘<p class=”wb-hint” style=”margin-top:10px”>Signed in as you on GitHub.</p>’,
steps:[
{t:’Finding the Star control’, c:’get_interactive_elements’},
{t:’Capturing visible tab’, c:’screenshot’, token:true}
],
approve:”Star this repository on your signed-in GitHub account?”,
finishStep:{t:’Clicking Star via the page UI’, c:’click_ax’},
onFinish:function(){
var b=document.getElementById(‘gh-star’);
if(b){ b.innerHTML=’★ Starred · 8.4k’;
b.style.background=’#f8faf4′; b.style.borderColor=’#cfe6a8′; b.style.color=’#4f8a00′; }
},
result:function(){
return ‘<h4>Done</h4><p>Repository starred through the visible UI — not a direct API call.</p>’+
‘<p class=”wb-note”>Ran in your authenticated session; no credentials were stored.</p>’;
}
}
};
var order = [‘explain’,’api’,’research’,’star’];
var state = { mode:’ask’, scen:’explain’, running:false, aborted:false };
var timers = [];
var $ = function(id){ return document.getElementById(id); };
function clearTimers(){ timers.forEach(clearTimeout); timers = []; }
function renderChips(){
var box=$(‘wb-chips’); box.innerHTML=”;
order.forEach(function(k){
var s=SCEN[k];
var b=document.createElement(‘button’);
b.className=’wb-chip’+(k===state.scen?’ on’:”);
b.innerHTML=s.label+’ <span class=”req”>· ‘+(s.req===’act’?’Act’:’Ask’)+'</span>’;
b.onclick=function(){ selectScen(k); };
box.appendChild(b);
});
}
function renderMode(){
$(‘wb-modeseg’).querySelectorAll(‘button’).forEach(function(b){ b.classList.toggle(‘on’, b.dataset.mode===state.mode); });
var pill=$(‘wb-modepill’); pill.textContent=state.mode===’act’?’Act’:’Ask’;
pill.className=’wb-mode-pill ‘+(state.mode===’act’?’act’:’ask’);
}
function selectScen(k){
if(state.running) return;
state.scen=k; var s=SCEN[k];
$(‘wb-url’).textContent=s.url; $(‘wb-page’).innerHTML=s.page; $(‘wb-input’).value=s.prompt;
if(s.req===’act’ && state.mode!==’act’){ state.mode=’act’; }
renderChips(); renderMode(); resetLog();
}
function resetLog(){
clearTimers(); state.running=false; state.aborted=false;
$(‘wb-run’).style.display=”; $(‘wb-run’).disabled=false;
var stop=$(‘wb-stopbtn’); if(stop) stop.remove();
$(‘wb-log’).innerHTML='<div class=”wb-hint”>Press <b>Run</b> to start. Ask mode is read-only; Act mode can act.</div>’;
}
function addStep(html){
var d=document.createElement(‘div’); d.className=’wb-step’; d.innerHTML=html;
$(‘wb-log’).appendChild(d); postHeight(); return d;
}
function showStop(){
var stop=document.createElement(‘button’);
stop.id=’wb-stopbtn’; stop.className=’wb-stop’; stop.textContent=’Stop’;
stop.onclick=function(){ state.aborted=true; clearTimers(); finishAbort(); };
$(‘wb-run’).style.display=’none’; $(‘wb-run’).parentNode.appendChild(stop);
}
function hideStop(){ var s=$(‘wb-stopbtn’); if(s) s.remove(); $(‘wb-run’).style.display=”; $(‘wb-run’).disabled=false; }
function finishAbort(){ state.running=false; hideStop();
addStep(‘<span style=”color:#b91c1c;font-weight:700″>×</span> Stopped by user.’); }
function run(){
if(state.running) return;
var s=SCEN[state.scen];
if(s.req===’act’ && state.mode!==’act’){
$(‘wb-log’).innerHTML=”;
addStep(‘<span style=”color:#4338ca;font-weight:700″>ⓘ</span> ‘+
‘Ask mode is read-only. Switch to <b>Act mode</b> to run this task.’);
return;
}
state.running=true; state.aborted=false;
$(‘wb-log’).innerHTML=”; $(‘wb-run’).disabled=true; showStop();
var delay=350;
s.steps.forEach(function(st){
timers.push(setTimeout(function(){
if(state.aborted) return;
var el=addStep(‘<span class=”spin”></span> ‘+st.t+’ · <code>’+st.c+'</code>’);
timers.push(setTimeout(function(){
if(state.aborted) return;
el.innerHTML='<span class=”tick”>✓</span> ‘+st.t+’ · <code>’+st.c+'</code>’;
if(st.token) addStep(‘<div class=”wb-token”>Token-conscious: screenshot 2000×1200 ‘+
‘resized & JPEG-compressed → ~380 image tokens (illustrative).</div>’);
postHeight();
},420));
},delay));
delay+=780;
});
timers.push(setTimeout(function(){
if(state.aborted) return;
if(s.approve){ askApproval(s); } else { finish(s); }
}, delay+150));
}
function askApproval(s){
var box=document.createElement(‘div’); box.className=’wb-approve’;
box.innerHTML='<p>⚠ Consequential action — ‘+s.approve+'</p><div class=”row”>’+
‘<button class=”wb-yes”>Approve</button><button class=”wb-no”>Cancel</button></div>’;
$(‘wb-log’).appendChild(box); postHeight();
box.querySelector(‘.wb-yes’).onclick=function(){ box.remove(); finish(s); };
box.querySelector(‘.wb-no’).onclick=function(){
box.remove(); state.running=false; hideStop();
addStep(‘<span style=”color:#b91c1c;font-weight:700″>×</span> Action cancelled. Nothing was changed.’);
};
}
function finish(s){
state.running=false; hideStop();
if(s.finishStep){ addStep(‘<span class=”tick”>✓</span> ‘+s.finishStep.t+’ · <code>’+s.finishStep.c+'</code>’); }
if(s.onFinish){ try{ s.onFinish(); }catch(e){} }
var r=document.createElement(‘div’); r.className=’wb-result’; r.innerHTML=s.result();
$(‘wb-log’).appendChild(r);
addStep(‘<span class=”tick”>✓</span> Task complete · <code>done</code>’);
postHeight();
if(s.follow){ var link=document.getElementById(‘wb-follow’); if(link) link.onclick=function(e){ e.preventDefault(); runFollow(s); }; }
}
function runFollow(s){
if(state.running) return; state.running=true; $(‘wb-run’).disabled=true; showStop();
var d=400;
s.follow.steps.forEach(function(st){
timers.push(setTimeout(function(){
if(state.aborted) return;
var el=addStep(‘<span class=”spin”></span> ‘+st.t+’ · <code>’+st.c+'</code>’);
timers.push(setTimeout(function(){
if(state.aborted) return;
el.innerHTML='<span class=”tick”>✓</span> ‘+st.t+’ · <code>’+st.c+'</code>’;
},380));
},d)); d+=650;
});
timers.push(setTimeout(function(){
if(state.aborted) return;
state.running=false; hideStop();
var r=document.createElement(‘div’); r.className=’wb-result’; r.innerHTML=s.follow.result;
$(‘wb-log’).appendChild(r); postHeight();
}, d+150));
}
$(‘wb-modeseg’).querySelectorAll(‘button’).forEach(function(b){
b.onclick=function(){ if(state.running) return; state.mode=b.dataset.mode; renderMode(); };
});
$(‘wb-run’).onclick=run;
$(‘wb-reset’).onclick=function(){ state.mode=’ask’; selectScen(‘explain’); };
$(‘wb-input’).addEventListener(‘keydown’,function(e){ if(e.key===’Enter’) run(); });
function postHeight(){
try{
var el=document.getElementById(‘wb-root’);
var h=(el?el.offsetHeight:document.body.offsetHeight)+40;
if(window.parent && window.parent!==window){ window.parent.postMessage({wbHeight:h, ch:CH},’*’); }
}catch(e){}
}
window.postHeight=postHeight;
window.addEventListener(‘load’,function(){ postHeight(); setTimeout(postHeight,300); });
if(window.ResizeObserver){ try{ new ResizeObserver(postHeight).observe(document.getElementById(‘wb-root’)); }catch(e){} }
renderChips(); renderMode(); selectScen(‘explain’);
})();
</script>
</body>
</html>
“>
(function(){
window.addEventListener(“message”, function(e){
if(e && e.data && e.data.ch === “wb-dev-demo” && typeof e.data.wbHeight === “number”){
var f = document.getElementById(“webbrain-frame-2”);
if(f){ f.style.height = e.data.wbHeight + “px”; }
}
});
})();
WebBrain is available on the Chrome Web Store, Firefox Add-ons, and GitHub. Product details at webbrain website.
The post Meet WebBrain: An Open-Source, Local-First AI Browser Agent That Reads Pages and Automates Tasks in Chrome and Firefox appeared first on MarkTechPost.

