LumaBrowser - Now in Beta

LumaBrowser - the programmable browser for AI agents.

LumaBrowser is a programmable browser for AI agents, QA engineers, and backend developers. It ships with a REST API, a built-in MCP server, and CDP-based network interception, and every click or find can fall back to an LLM when selectors break. Install in one command; bring your own local, OpenAI, or Anthropic model.

lumabrowser - REST API
# Create a tab and navigate
curl -X POST http://localhost:3000/api/browser/tabs \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'

# Click with LLM fallback - never break on selector changes
curl -X POST http://localhost:3000/api/browser/tabs/0/click \
  -d '{"selector": "button.submit", "llmFallback": "Click submit"}'

# Get the page content
curl http://localhost:3000/api/browser/tabs/0/source?type=text
Windows macOS Linux
v1.0 - Beta
Install via npm
$ npx lumabrowser start
Requires Node 18+ · See all commands
Drives from Selenium, Puppeteer, and Playwright, same LLM selector fallback across all three. Compare →
Built for every developer workflow
AI Builders

How do AI agents drive a real browser?

LumaBrowser exposes a built-in Model Context Protocol (MCP) server, so Claude Desktop, OpenClaw, and any MCP-compatible agent can navigate pages, click elements, fill forms, and read the DOM natively. No glue code, no custom tool wrappers; the browser shows up as first-class tools to whatever LLM you point at it.

QA & Data Extraction

How do I stop CSS selectors from breaking when a site changes?

LumaBrowser's selector resolver tries stable attributes first, data-testid, aria-label, placeholder, visible text, with zero LLM cost, then escalates to a model grounded in a live DOM snapshot when nothing matches. Every resolved selector is validated before it fires, and the template cache warms itself on every run.

Backend & Integration

How do I intercept browser network traffic without a proxy?

LumaBrowser's Network Watcher uses the Chrome DevTools Protocol to capture HTTP traffic from inside the browser process, no root certificates, no MITM plumbing. Define URL patterns, pick methods, and the full request and response are forwarded to any backend webhook you configure.

See it in action

Three lines to navigate, click, and extract, against any website.

# Create a tab and navigate to a page
curl -X POST http://localhost:3000/api/browser/tabs \
  -H "Content-Type: application/json" \
  -d '{"url": "https://news.ycombinator.com"}'

# Click with LLM fallback - if the selector breaks, AI finds the right element
curl -X POST http://localhost:3000/api/browser/tabs/0/click \
  -H "Content-Type: application/json" \
  -d '{"selector": ".titleline > a", "llmFallback": "Click the first story link"}'

# Get the page content as clean text
curl http://localhost:3000/api/browser/tabs/0/source?type=text
import requests

# Create a tab and navigate
requests.post("http://localhost:3000/api/browser/tabs", json={
    "url": "https://news.ycombinator.com"
})

# Click with LLM fallback - resilient to DOM changes
requests.post("http://localhost:3000/api/browser/tabs/0/click", json={
    "selector": ".titleline > a",
    "llmFallback": "Click the first story link"
})

# Fill a form - each field has its own LLM fallback
requests.post("http://localhost:3000/api/browser/tabs/0/fill", json={
    "fields": [
        {"selector": "input[name=q]", "value": "LumaBrowser",
         "llmFallback": "The search input"}
    ]
})
// Create a tab and navigate
await fetch("http://localhost:3000/api/browser/tabs", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ url: "https://news.ycombinator.com" })
});

// Click with LLM fallback - AI resolves the selector if it breaks
await fetch("http://localhost:3000/api/browser/tabs/0/click", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    selector: "button.submit",
    llmFallback: "Click submit"
  })
});

// Wait for an element - LLM resolves if selector is stale
await fetch("http://localhost:3000/api/browser/tabs/0/wait", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    selector: ".success-msg",
    llmFallback: "The success confirmation message"
  })
});

What this actually looks like

Real request/response flows showing what LumaBrowser handles for you.

POST/api/browser/tabs
{ "url": "https://acme.com/signup" }
200Tab created
{ "id": 0, "url": "https://acme.com/signup", "title": "Sign Up - Acme" }
POST/api/browser/tabs/0/fill
{
  "fields": [
    { "selector": "#full_name", "value": "Jane Doe",
      "llmFallback": "The full name input field" }
  ],
  "llmFallback": "Fill the signup form"
}
MISSSelector #full_name not found; site renamed it to data-field-xk92
llmFallback is set → triggering AI resolution...
TEMPLATETemplate Builder auto-generates page map
{
  "pageInfo": { "selectorStability": "unstable", "jsRendered": true },
  "elements": [
    { "Name": "Full Name Input", "CssSelector": "input[data-field-xk92]",
      "FallbackSelector": "form .field-group:first-child input",
      "Type": "Input" },
    { "Name": "Email Input", "CssSelector": "input[data-field-ml47]",
      "Type": "Input" },
    { "Name": "Submit Button", "CssSelector": "button.btn-register",
      "Type": "Button" }
  ]
}
LLMAI matches “The full name input field” → input[data-field-xk92]
Prompt: "Action: fill | Description: The full name input field
  Page template elements:
  - "Full Name Input" (Input): input[data-field-xk92]
  - "Email Input" (Input): input[data-field-ml47]
  ..."

→ input[data-field-xk92]
200Form filled; resolved selector returned
{
  "success": true,
  "message": "Form filled successfully (resolved by LLM fallback)",
  "data": {
    "filled": 1,
    "resolvedSelector": "input[data-field-xk92]"
  }
}
GET/api/browser/tabs/0/source
Compare: ?type=full  vs  ?type=clean  vs  ?type=text
?type=full - Raw HTML (3,847 tokens)
<!DOCTYPE html>
<html lang="en" data-theme="dark" data-build="a8f3c">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="...">
  <title>Dashboard - Acme Corp</title>
  <link rel="stylesheet" href="/assets/main.c7x92k.css">
  <link rel="stylesheet" href="/assets/vendor.d83hf.css">
  <script src="/assets/runtime.k29d.js" defer></script>
  <script src="/assets/app.m48fk.js" defer></script>
  <style>.css-1a2b3c{display:flex}
  .css-4d5e6f{margin:0 auto;max-width:1200px}
  .css-7g8h9i{font-family:Inter,sans-serif}
  /* ... 847 more lines of CSS ... */</style>
  <script>window.__NEXT_DATA__={props:{pageProps:
  {session:{user:{id:"usr_382",name:"Jane"},
  token:"eyJhbG..."}}},page:"/dashboard"}
  </script>
</head>
<body>
  <div id="__next">
    <div class="css-1a2b3c">
      <nav class="css-k3m8x2">
        <img src="data:image/svg+xml;base64,PHN2..."
             alt="" class="css-p9q2r1" />
        <a href="/" class="css-m4n5o6">Dashboard</a>
        <a href="/settings" class="css-m4n5o6">Settings</a>
      </nav>
      <main class="css-4d5e6f">
        <h1 class="css-7g8h9i">Welcome back, Jane</h1>
        <div class="css-a1b2c3">
          <div class="css-d4e5f6">Revenue</div>
          <span class="css-g7h8i9">$12,450</span>
        </div>
        <!-- ... 200+ more nodes ... -->
?type=clean - Cleaned HTML (680 tokens)
<nav>
  <a href="/">Dashboard</a>
  <a href="/settings">Settings</a>
</nav>
<main>
  <h1>Welcome back, Jane</h1>
  <div class="metric-card">
    <div>Revenue</div>
    <span>$12,450</span>
  </div>
  <div class="metric-card">
    <div>Active Users</div>
    <span>1,284</span>
  </div>
  <table class="data-table">
    <thead>
      <tr><th>Product</th><th>Sales</th><th>Revenue</th></tr>
    </thead>
    <tbody>
      <tr><td>Widget Pro</td><td>342</td><td>$8,550</td></tr>
      <tr><td>Gadget X</td><td>156</td><td>$3,900</td></tr>
    </tbody>
  </table>
  <a href="/reports">View full report &rarr;</a>
</main>
?type=text - Plain text (89 tokens)
Dashboard | Settings

Welcome back, Jane

Revenue: $12,450
Active Users: 1,284

Product    Sales    Revenue
Widget Pro   342    $8,550
Gadget X     156    $3,900

View full report →
POST/api/ai-chat/run - one prompt, entire workflow
{
  "prompt": "Log into acme.com/login with [email protected] / demo123,
             go to settings, change company name to 'Acme Industries'",
  "autoCloseTab": false,
  "includeScreenshot": true
}
AGENTAutonomous loop: 4 iterations, 7 tool calls
1. navigate → acme.com/login (template attached)
2. fill_form → email + password
3. click → Login button (url changed → /dashboard)
4. wait_for → .dashboard-content 
5. navigate → /settings (template generated)
6. fill_form → company name = "Acme Industries"
7. click → Save Changes 
200Structured result with summary, tab, and screenshot
{
  "success": true,
  "summary": "Logged in and updated company name to Acme Industries",
  "finalResponse": "Done! I logged into acme.com, navigated to...",
  "tabId": 0,
  "iterations": 4,
  "durationMs": 18420,
  "toolCalls": [
    { "tool": "navigate", "durationMs": 1200 },
    { "tool": "fill_form", "durationMs": 340 },
    { "tool": "click", "durationMs": 890 },
    { "tool": "wait_for", "durationMs": 1100 },
    { "tool": "navigate", "durationMs": 1450 },
    { "tool": "fill_form", "durationMs": 280 },
    { "tool": "click", "durationMs": 760 }
  ],
  "screenshot": "data:image/png;base64,iVBOR..."
}

What the AI Chat agent actually does behind the scenes, with Template Builder enabled for faster targeting.

POST/api/ai-chat/run
{ "prompt": "Log into acme.com/login with [email protected] / demo123, go to settings, change company name to 'Acme Industries'." }
Iter
Tool
What happened
#1
navigate
Navigate tab 0 to https://acme.com/login
get_template
Template exists for /login → returns 5 elements incl. input#email, input#password, button.login-btn
#2
fill_form
Fill input#email = "[email protected]", input#password = "demo123", selectors from template, instant match
click
Click button.login-btn, "Log In" button from template
wait_for
Wait for .dashboard-content to appear → visible in 1.2s
#3
navigate
Navigate to /settings, template auto-generated (no cache), 8 elements extracted
generate_template
No template cached → LLM analyzes page (HTML + screenshot) → generates template with input#company-name, button.save-settings, etc.
#4
fill_form
Clear & fill input#company-name = "Acme Industries", from freshly generated template
click
Click button.save-settings, "Save Changes"
wait_for
Wait for .toast-success → "Settings saved successfully" appears in 0.8s
complete
Agent returns final response. 4 iterations, 10 tool calls, 0 failures. Template cache means the login page will be instant next time.
Built-in extensions included with every install

AI Chat Sidebar

A built-in LLM assistant that can see and control the browser. Ask it to navigate pages, fill forms, extract data, or run multi-step automations, conversationally. It has full access to every browser tool and streams responses in real time.

  • Agentic tool loop - up to 10 iterations per message
  • Persistent conversation history in SQLite
  • Configurable model: local, OpenAI, or Anthropic
  • Template Builder integration for smarter selectors
Go to Hacker News and tell me the top 3 stories
navigate get_source

Here are the top 3 stories on Hacker News right now:

  1. Show HN: LumaBrowser - programmable browser for AI agents
  2. Why CSS selectors break and how LLMs fix them
  3. Building an MCP server from scratch in Node.js

AI Chat Artifacts

AI Chat doesn't just reply with text; it builds artifacts that render live next to the conversation: code, web pages, SVG, Markdown, generated images, and fully interactive mini-apps.

  • Six types: code, HTML, SVG, Markdown, image, and live interactive modules
  • Live modules run inline with a reactive runtime and optional charts
  • Generated code is auto-linted before it's called done
  • Versioned automatically - iterate without losing earlier drafts
  • Open in a real tab, download, or view over your LAN
Explore AI Chat Artifacts →
Build me a tip calculator
create_live_artifact

Here's an interactive tip calculator; drag the sliders and it recalculates instantly:

Bill$84.00
Tip 20%$16.80
Total$100.80

AI Sub-Agents

Instead of one do-everything assistant, define focused sub-agents, each with its own system prompt, its own curated set of tools, and optionally its own model. The main chat can delegate to them, or you can call them over the REST API.

  • Per-agent system prompt, tool allowlist, and pinned model
  • Narrow tool grants reduce mistakes and token cost
  • Delegate from chat with list_agents / chat_with_agent
  • Drive externally over /api/ext/agent-manager (SSE or JSON)
Explore AI Sub-Agents →
Have the research agent summarize today's AI news
chat_with_agent

Delegating to Research Agent (web-search + read-only tools, pinned to a fast local model)…

It found 5 sources and returned a 3-bullet brief.

Local Image Generation

A bundled image server built on stable-diffusion.cpp generates and edits images entirely on your machine, no cloud, no per-image fee, nothing leaves your device.

  • Text-to-image plus editing: img2img, inpainting, reference images
  • Curated catalog: Z-Image Turbo, SDXL, FLUX.1-schnell, Qwen-Image-Edit
  • Generation and edit models stay resident at the same time
  • Ask in AI Chat - generate_image / edit_image return artifacts
  • Runs on NVIDIA (CUDA), cross-vendor GPUs (Vulkan), and Apple Silicon
Explore Local Image Generation →
Make a watercolor fox, then put a hat on it
generate_image edit_image

Generated locally on your GPU and refined with the edit model, returned as an image artifact you can open or download.

Code Mode

An AI build agent that turns a plain-English request into a real, installable LumaBrowser extension. It plans the files, writes them with live syntax checking, then installs and activates the extension, no restart.

  • Describe it from the Extensions panel's “Vibe” button or the Code chat mode
  • Each file is validated as it's written, with a live status panel
  • Extensions can add REST routes, MCP tools, chat modes, and UI panels
  • Installs live, then yours to keep, disable, or delete
Explore Code Mode →
Add a reading-time badge to article pages
write_extension_file install_extension

Wrote manifest.js and main.js (both validated ✓), installed and activated. Try it on any article.

Network Watcher

Monitor HTTP traffic at the browser level using Chrome DevTools Protocol. Define URL patterns, and LumaBrowser captures matching requests and forwards the full request/response to your webhook, no proxy, no root certs.

  • Wildcard URL pattern matching
  • Filter by HTTP method (GET, POST, etc.)
  • Capture headers and response bodies
  • Trigger statistics and per-watcher history
  • REST API and MCP tool support
Active Watchers
*api.stripe.com/v1/charges*
POST 142 triggers
→ https://my-backend.com/payments
*api.example.com/v2/orders*
* 38 triggers
→ https://hooks.slack.com/services/...
*internal-api.corp.net/*
GET 7 triggers
→ https://logger.internal/webhook

Template Builder

The first time you visit a page, the LLM analyzes the full DOM and generates a cached selector map. Every subsequent visit uses the cached template, slashing token usage by 95%+ and making automation near-instant.

  • LLM analyzes page once, caches the selector map
  • Primary + fallback selectors for resilience
  • Handles SPAs with randomized class names
  • Repeating item extraction (lists, tables)
  • Cached in SQLite - works offline after first run
  • Full lifecycle API: generate, validate, repair, check any selector against a live tab in milliseconds, regenerate only when it breaks
Token Usage Comparison
First visit (LLM analyzes DOM)
~12,400 tokens
Cached visits (template reuse)
~380 tokens
97% fewer tokens on repeat visits
Cached template output:
{
  "elements": [
    { "name": "Login Button",
      "primary": "button[data-testid='login']",
      "fallbacks": ["button.auth-cta", "#login-btn"] },
    { "name": "Email Field",
      "primary": "input[name='email']",
      "fallbacks": ["input[type='email']"] }
  ]
}

Timed Tasks

Schedule recurring LLM-driven automations. Each execution runs the same headless AgentRunner that powers AI Chat, with the full template workflow, structured JSON output, and webhook callbacks. Set it and forget it.

  • Structured JSON output - supply a schema like {"rate":""} and the agent fills it exactly
  • Template reuse - first run caches selectors, subsequent runs finish ~8× faster
  • Webhook callbacks (HTTP POST) with parsed JSON body, not escaped strings
  • Paginated run history with full per-iteration logs, tool calls, and results (Copy as Markdown)
  • Configurable intervals from minutes to days
Check Product Prices Every 1h Active
Scrape Job Listings Every 6h Active
Submit Daily Report Every 24h Paused

Page Change Detector

Watch any web page, or any specific element on it, for content changes. Pick what you care about with a click, and get a webhook the second it updates. Real browser, so JS-rendered pages and SPAs just work. Fully local. Full details →

  • Point-and-click element picker, watch a price tile, a stock badge, a status banner, not the whole noisy page
  • SHA-256 checksum diff with line-level +/− summary on every change
  • Silent background tab by default (no tab-bar clutter); no-refresh mode piggy-backs on a tab you already have open
  • Webhook + desktop notification on change, pipe into Slack, n8n, Zapier, your own service
  • Per-monitor schedule with optional ±jitter % so monitors don't fire in lockstep
  • Full REST API at /api/page-change-detector, CRUD, history, manual check, picker
Webhook on change
{
  "monitorName": "Acme Pricing Page",
  "url": "https://acme.com/pricing",
  "timestamp": "2026-05-03T17:22:08Z",
  "diffSummary": "+2 / -1\n+ $19.99 - In stock\n- $24.99 - Out of stock",
  "changeCount": 17
}

Native Ad & Tracker Blocker

A first-class blocker runs at the Electron session layer, independent of browser extensions. Because Electron doesn't implement chrome.webRequest, Chrome-based ad blockers like uBlock Origin cannot actually block anything inside LumaBrowser. This is the replacement; and it works on every tab, every session, including those spun up by automation.

  • Powered by EasyList + EasyPrivacy via @ghostery/adblocker-electron (MPL-2.0)
  • Applies network and cosmetic filters at the session layer - no per-page injection
  • Filter engine cached locally; instant startup after first launch
  • Toggleable from Settings → General → Application; survives restarts
  • Works on every tab partition including automated sessions and headless runs
Blocked in the last session
doubleclick.net/*
ADS 412 blocked
→ dropped at session layer
google-analytics.com/*
TRK 218 blocked
→ dropped at session layer
facebook.com/tr/*
TRK 87 blocked
→ dropped at session layer
.ad-banner, .cookie-modal
CSS cosmetic
→ hidden via preload injection

Chrome Extensions limited

Load unpacked Chrome extensions directly into every tab session. Good for content-script extensions like Dark Reader, Stylus, or password manager autofill; but read the limitations carefully, because Electron only implements a subset of the Chrome extension APIs.

  • Install unpacked extensions from Settings → Chrome Extensions
  • Per-extension enable / disable / remove
  • Auto-loads into every tab session (persist:main-* partitions)
  • Supports storage · tabs · runtime · i18n · webNavigation · management
  • Content scripts and cross-origin fetches work. Toolbar popups, context menus, and badge icons do not.

Note: webRequest and declarativeNetRequest are not implemented, so Chrome-based ad blockers will load but will not block anything. Use the built-in Native Ad Blocker instead.

Dark Reader content-script Works
Bitwarden (autofill) content-script Works
Stylus content-script Works
uBlock Origin webRequest Use Native
Toolbar popups / badges browserAction Not rendered

Notification Interceptor

Captures browser Notification API calls from any page and forwards them to your webhook. Monitor alerts, messages, or status changes from any web app, without polling.

  • Intercepts title, body, icon, badge, tag, and data
  • Forward to Slack, Discord, or any webhook URL
  • Works with any site that uses the Notification API
  • Test mode to verify webhook connectivity
Webhook Payload
{
  "source": "app.example.com",
  "title": "New message from Sarah",
  "body": "Hey, the deploy finished!",
  "timestamp": "2026-03-29T14:22:00Z",
  "url": "https://app.example.com/chat"
}

One-Click Local AI

Local models without the setup tax. LumaBrowser reads your hardware, recommends a curated GGUF model that actually fits, and resumably downloads it onto a bundled llama.cpp server, one click, no API key, nothing leaves your machine. Then it's a streaming chat with live artifacts.

  • Hardware-aware picker - model, quant & context sized to your VRAM
  • Curated Gemma 4 & Qwen 3.6 lineup (CPU floor up to 35B MoE)
  • Bundled runtimes auto-selected: CUDA 12, Vulkan, or CPU
  • Resumable multi-GB downloads - drops don't restart from zero
  • Streaming chat, live artifact panel, regenerate-with-variants

How one-click local AI works →

Recommended for you
ModelQwen 3.6 35B-A3B - Q4_K_M
Runtimellama.cpp · CUDA 12
Context12,288 tokens
API KeyNot required
Data SentNone
Extensible by design

Everything wires into the Core.

Every feature is a module on one small runtime of six shared services. Most extensions plug straight into the Core; a few build on each other; and yours drops in anywhere. Hover an extension to see which services it taps — or hover a service to see who uses it.

LumaBrowser CoreOne runtime — every extension plugs in
BrowserDatabaseLLMTemplatesLicensingActivity Log
Plug straight into the Core · 8
Network Watcher Notifications Page Monitor WebGPU LLM MCP Connector Sub-Agents Code Mode Activity Log
Build on each other
  • Template Builder hub · 4 build on it
    • AI Chat extends ↑
      • Timed Tasks extends ↑
    • Selenium ↑ optional
    • CDP Driver ↑ optional
    • Test Harness ↑ optional
+ Your Extensiondrops in anywhere — tap the Core, or any extension
uses service (required) uses service / extends (optional) activity logging plugs into Core your extension
Every extension can add
Custom REST Routes MCP Tools UI Panels & Sidebars Chat Modes IPC Hooks Per-Extension LLM Slots SQLite Persistence
Free to Use No API Keys Required
MCP Native Claude Desktop & OpenClaw Ready
REST API Language Agnostic
More by LumaByte
OpenGridJs

A lightweight JavaScript data grid with virtual scrolling, sortable columns, context menus, column reordering, and CSV export. Fast and dependency-free.

ResonantJs

A lightweight reactive UI library for building dynamic interfaces with minimal overhead. Simple API, no build step, no virtual DOM.

Textbookly

The textbook price comparison engine. Find the best deals on textbooks across multiple retailers in one search.

Free Developer APIs

Explore our collection of free, no-auth-required APIs for your projects.