Linux 二进制依赖 glibc,Alpine 等 musl 发行版会报 cannot execute,x86_64 需 glibc 2.35、arm64 需 2.38;没有原生 Windows 和 Android 构建,Windows 走 WSL2、Termux 无法运行;从源码构建需 Zig 0.17.0,并先跑 make download-v8 拉预编译 V8,否则会从源码编译耗时数分钟。

browser
lightpanda-ioAGPL-3.0活跃维护用 Zig 从零编写的无头浏览器,专为 AI 和自动化场景设计,兼容 CDP 协议,可替代 Playwright、Puppeteer 驱动网页抓取与 Agent 任务。
Lightpanda: the headless browser designed for AI and automation
brew install lightpanda-io/browser/lightpanda
docker run -d --name lightpanda -p 127.0.0.1:9222:9222 lightpanda/browser:latestREADME 未说明显存或 GPU 要求;提供 Linux(x86_64、aarch64)和 macOS(x86_64、aarch64)二进制,Windows 需在 WSL2 内运行。
用 Zig 从零写的无头浏览器,专为 AI 代理和自动化设计,比 Chromium 更轻更快
用 Zig 从零编写的无头浏览器,专为 AI 和自动化场景设计,兼容 CDP 协议,可替代 Playwright、Puppeteer 驱动网页抓取与 Agent 任务。
核心能力
- 用 Zig 从零实现,不是 Chromium 或 WebKit 的分支
- 内置 CDP 服务,Puppeteer 通过 browserWSEndpoint 直连
- fetch 支持 dump html、markdown、png、pdf 和 structured_data
- serve 可同时开启 CDP 与 WebDriver Bidi 协议
- agent 模式用自然语言或斜杠命令驱动浏览器操作
容易踩的坑
- 1.0.3最新正式版2026-11-05 · 2026-11-05 09:04 UTC
Behavior changes
serve: the--http-max-concurrentdefault now follows--cdp-max-connections(8× it, between 40 and 256). With the default 16 connections it is 128.fetch --dump markdownnow asks the server for markdown (Accept: text/markdown,...). Atext/markdownresponse is dumped as-is. Not sent with--dump-selector,--wait-selectoror--wait-script.Accessibility.getPartialAXTreenow honorsfetchRelatives, as Chrome does: it returns the node and its relatives, no longer the whole subtree. UsegetFullAXTreeorgetChildAXNodesfor a subtree.DOM.querySelectorwith no match now returnsnodeId: 0instead of an error, as Chrome does.- Touch event handlers (
ontouchstart, …) are removed fromdocumentand elements: Lightpanda doesn't emulate a touch device. navigator.sendBeaconnow checks its arguments. Beacons are still not sent unless--load-resources beaconis set.- The HTTP cache database is deleted and rebuilt when its schema version changes.
waitForStatenow reports a timeout instead of always answering that the page reached the state.- MCP over HTTP: a request on a session closed with
DELETEnow gets a 404 instead of starting a new session. fetch()andnew Request()with a body on GET or HEAD now throw aTypeError.- Scripts inserted from an isolated world (Playwright
setContent,appendChild) now run in the main world. - Event listeners now run in their own world's context, so Playwright's
focus()andselectOption()fire React'sonFocus/onChange.
New Web APIs
AudioContext(WAV only)- Payment Request API:
PaymentRequest,PaymentResponseand their events, behaving like Chrome with no payment app installed NavigateEventonnavigation(intercept()is not implemented)PerformanceNavigationTimingentry for the documentdocument.execCommand:insertText,delete,forwardDelete,selectAll,insertLineBreak, and thestyleWithCSS,useCSSanddefaultParagraphSeparatorsettings. Other commands returnfalseimage.decode()HTMLElement.isContentEditable(real value),HTMLElement.spellcheck,document.designModeTouchconstructor andTouchEventtouch listselementFromPoint/elementsFromPointonShadowRoot- SVG
getBBox() HTMLFrameElement,SecurityPolicyViolationEvent, a stubPath2Dnavigator.mimeTypes(emptyMimeTypeArray)img.sizes;link.relList.supports()fordns-prefetch,preconnectandprefetchHTMLFieldSetElement.elements;abbr,mark,small,wbrand other HTMLElement-only tags are nowHTMLElementinstead ofHTMLUnknownElementonmouseenterandonmouseleavehandlers- Web Crypto:
raw-secretandraw-publickey formats;CryptoKeyis now serializable (structuredClone,postMessage, IndexedDB) MessagePortis now transferableTextDecoderStreamdecodes every encodingTextDecodersupports, with state kept across chunksXMLSerializernow writes XML instead of HTML- WebMCP follows the current spec:
document.modelContext,getTools(),toolchange,registerToolreturns a promise - CSS: native nesting, color parsing, more known properties (
CSS.supports, unknown property errors)
Web API behavior / fixes
- Layout: flex-shrink, horizontal margins,
left/right/inset, and widths from text measurement. Widths are relative to the containing box. Out-of-flow children no longer size their parent, and block boxes start at their parent's left edge positionis now read from stylesheets, not only from the inline style (offsetParent,getComputedStyle)- An iframe without size is now 300×150, and its box is the frame's viewport, so Playwright can click inside it
Range.getClientRects()now returns one rect per selected element or text nodeelementFromPointreturnsnulloutside the viewport- Textarea
scrollHeightnow counts its text, for auto-sizing textareas - Typing
- 1.0.22026-10-23 · 2026-10-23 09:01 UTC
查看更新说明
Behavior changes
- JS page errors now log under a new
page_errorscope at WARN (silence with--log-filter). - Each top-level page now has its own session history; a second page no longer shows up in the first page's back/forward.
--block-private-networksnow blocks more non-routable ranges (CGNAT, benchmarking, multicast, reserved, NAT64).no-corsfetches with a non-safelisted method are now rejected.- An unreadable
--http-cache-dirdatabase is now discarded instead of exiting. - Console values written to the log are now escaped and capped.
New Web APIs
- Web Crypto: Ed25519, X25519, ECDSA, RSA-PSS, RSASSA-PKCS1-v1_5,
wrapKey/unwrapKey+ AES-KW; EC and RSA-OAEP import/export - WebCodecs,
OfflineAudioContext, WebVTT improvements - XML document parsing
setRangeText()on<input>and<textarea>;textarea.textLengthHTMLProgressElementvalue/max/position;HTMLOptionElement.index; missing table APIsdocument.hasFocus();window.outerWidth/outerHeight/screenX/screenY/screenLeft/screenTop:nth-child(An+B of S)and:nth-last-child(An+B of S)PermissionStatusis now anEventTarget;DOMExceptionis now structured-cloneable;ImageDataoverload- Missing global event handlers added to
window,documentandbody
Web API behavior / fixes
- Element widths are now resolved relative to their parent (block, inline, floats, %, min/max-width, basic flex)
- Client rects (
getBoundingClientRect,getClientRects) are now viewport-relative - Mouse events now reach into shadow DOM and iframes
- Escape now closes the topmost modal dialog
- Iframes inside a shadow root now load when their host connects
replaceWithcompliance and cross-document adoption;isEqualNode(null)now returns falseinsertAdjacentHTMLnow works on SVG elements- Trusted key events now report Chrome's legacy
keyCode/which/charCode - Text selection offsets now count UTF-16 code units
TextDecodernow only strips a BOM for UTF-8/UTF-16form.elementsnow lists fieldset, object and output- Sequence arguments now accept any iterable
- Better
getComputedStyle().transform - Fixed CSS nested rule parsing; escapes are now decoded in unquoted attribute selectors
- Cookies are now sent longest path first, then oldest first
- 401/407 responses are now delivered as-is when no auth challenge header is present
CDP
Fetch.continueRequestinterceptResponseis now supportedFetch.continueWithAuthwithDefault/CancelAuthnow delivers the original 401/407, as Chrome doesRuntime.exceptionThrownis now emitted for unhandled promise rejections, with the context the exception was thrown inLog.entryAddedis now emitted for resources that fail to loadDOM.scrollIntoViewIfNeededis now supported (previously a no-op)LP.getPageStatusadded: page issue summary (JS errors, skipped resources, fetch stats). Also included infetchoutput when--jsonis used- Custom ARIA states and focusability are now exposed in the AX tree
- Non-UTF-8 status text and headers are now reported as strings rather than a byte array
- Permission overrides are now cleared when a browser context closes
Serve / CLI
serve --unix-socket <path>added: CDP over a Unix domain socket--remote-allow-originsadded: allow CDP connections that send anOriginheader (rejected by default), matching Chromeservenow honors--cookie-jar: the jar is saved when a connection closes. With multiple connections, last write winsfetch --dump structured_dataaddedfetch --markdown-alternateadded: follow a page'stext/markdownalternate.text/markdowndocuments are now dumped as-is
Tools
markdowntool:alternateoption added- A failed
gotonow closes the page - Tree tool: links kept inside headings, multi-line names kept on one line
Performance / stability
- Reworked TaggedOpaque (less memory)
- The schedul
- JS page errors now log under a new
- 1.0.12026-10-15 · 2026-10-15 14:55 UTC
查看更新说明
Behavior changes
setImmediateandclearImmediatewere removed (deprecated, not part of the web platform).- MCP and agent modes now load external stylesheets by default.
- Max HTTP redirects raised from 10 to 20, matching the Fetch spec.
- Unsupported protocols are no longer navigated to;
javascript:URLs are run instead of navigated to.
New Web APIs
- Web Locks
- Compression Streams API
- RSA-OAEP in Web Crypto (
generateKey,importKey,encrypt,decrypt) - Custom elements:
ElementInternalsand form-associated callbacks Request.bodyandRequest.textStreamHTMLFormElement.reset, form numeric and named index,form.fieldHTMLTableElement.rows,HTMLSelectElement.remove(index),select.lengthsettertextarea.type,fieldset.type,output.type;HTMLMeterElementaccessorsGlobalEventHandlersattributes onWindowandDocument; document named index- Performance secondary buffer;
performance.timeOriginrelative to the Unix epoch ConsoleandCSSare now proper namespaces; more missing namespace methods
Forms / input
- Implicit submit on Enter; improved form submission; GET submissions replace the action's query
- Fire
changewhen typed text is committed by blur or Enter - Ctrl+A selects the whole value in text fields
- All text edits go through one gated path; better
contenteditabledetection - Single-contact touch for CDP, testdriver and BiDi
- One WebDriver key table shared by BiDi, testdriver and
press - Click press/release sequences shared across input paths and matched to Chrome
- Keydown default actions run only for trusted events
- Range and color inputs get their default value;
input.valuereflects to the attribute in default modes - Skip inputs with an empty name;
:checkedapplies to options; option text strips and collapses whitespace
Web API behavior / fixes
fetchresolves after headers are received; an abort signal aborts a fetch- XHR response header compliance; improved data-URL parsing
- Exceptions from timers, animation frames,
on*handlers, observers and microtasks go toreportError on*handler attributes behave like any other event listenerdocument.openresets loaded frame state; severaldocument.closecrash fixes- Fire
popstateon a fragment navigation; history traverses within a document by entry - Each
Windowgets its ownHistory;LocationURLs resolve against the calling document - Dynamic
<base>updates the document base URL iframereflects and reacts tosrc; sandbox token queries; iframe removal cleanupreplaceChildrenadopts nodes from another document- Classic scripts are decoded with their charset
- Timers accept any value; negative delays clamp to zero
IntersectionObserveronly intersects connected targets- Fix custom element upgrade ordering during fragment parsing; undefined custom elements use the
HTMLElementprototype
CSS / selectors / rendering
- Computed style reports initial values and a resolved
font-size; padding, margin and border widths default to0px text-transformapplied ininnerTextand resolved ingetComputedStylerevertandrevert-layertreated likeinherit- Decode fetched stylesheets; fix tokenizer exponent and hyphen-escape bugs; external stylesheet cap raised to 4 MiB
- Hit testing honors
pointer-events:noneandvisibility:hidden - Visibility inheritance matches Chromium in styles and the AX tree
- Hide a
[popover]that is not showing - Block-level flex and grid containers no longer grow with their items
- Selectors:
:valid/:invalidon every validity constraint,:read-write/:read-onlyper HTML, case-insensitive attribute values, type selectors on XML and foreign elements, pseudo-elements accepted
CDP (Chrome DevTools Protocol)
- Pause requests at the
Fetchresponse stage - Start of the
Debuggerdomain - Emit
Runtime.exceptionThrownfor uncaught page e
- 1.0.02026-10-02 · 2026-10-02 10:12 UTC
查看更新说明
Behavior changes
getComputedStyleresults are now read-only.- The
"en"language fallback was removed. Language now comes from--locale. - Redirects that switch to GET now drop body headers.
WebDriver
- Classic HTTP WebDriver, built out into a usable driver
- Session management;
navigate; back/forward;execute - Element endpoints; input (click, clear, sendKeys); cookie and viewport endpoints
- Higher default/max HTTP limits
CORS
- CORS is now enforced by default. Cross-origin fetch/XHR requests that fail a CORS check are blocked. Turn it off with
--disable-features cors. The old--experimental-features corsflag is gone. - CORS store with an entry limit (
--cors-store-entry-limit, default 1000) - Fetch referrer policy + CorsGate referrer checking
Sec-Fetch-SiteandSec-Fetch-Moderequest headers- Limit redirects that carry credentials
New Web APIs
WebAssembly.compileStreamingandinstantiateStreaming- Sanitizer API (initial)
TextEncoder.encodeIntoelement.requestFullscreen(mostly a stub)inertproperty;iframesandboxsetterimgsrcsetsupport; report natural dimensions for fetched imagesnew Option()and the legacy constructor APIDOMTokenListsetters; more accuratewindow.isSecureContext- Experimental
ServiceWorker(behind--experimental-features serviceworker) withCache/CacheStorage
Web API behavior / fixes
<template>content handling- Custom element callbacks may move the element during parse
request.clone()clones headers; correct fetch/response handling of null bodies- XHR/fetch report the status reason from the server;
TypeErroron XHRsend(SharedArrayBuffer) input.select()no longer throws for email/number;element.focus()only on focusable elements- Correct error on a wrong method; guard headers
- Validate the XML declaration
- Avoid spurious mutations for unchanged CSS declarations
CSS / rendering
- Split the style cascade into property groups; honor
!importanton tracked stylesheet properties - Resolve width/height from stylesheet rules; give the document a height and clamp viewport scroll to it
- Measure wrapped text for an element's content height
- Resolve scroll containers through the style cascade; unify wheel dispatch; clamp scroll offsets and latch a wheel to one container
CDP (Chrome DevTools Protocol)
- Optimize
Network.getResponseBody DOM.getFrameOwnerreturns the owning<iframe>; nodes carry aframeId- Answer inspector commands on the session that sent them; echo the
sessionIdin browser-level replies - Notify target closure when disposing browser contexts; report the initial
about:blankas loaded window.devicePixelRatiofrom the viewport scale; char text insertion- Move a kept isolated-world context onto the navigated origin
HTTP / Networking
- Improve caching; add
x-frame-optionssupport - Keep connections healthy: don't re-use connections likely in a bad state
- Lowercase response headers; clamp the response status range and report the server's reason phrase
- Add headers to the sync-request path
- Accept Netscape cookie format in
--cookie; report invalid same-site over CDP - Generate an appropriate error on a bot challenge
Agent / tools
- Add OpenRouter provider;
lightpanda run -reads a script from stdin - Report the HTTP status of a navigation; wait for networkidle after an action-triggered navigation
findElementmatches names by/regex/- MCP carries the navigation outcome as
structuredContent - REPL output fixes and simpler command dispatch
CLI / ops
- Report usage errors once, accept
-h, show tips only on a terminal; scale the "did you mean" distance with input length - Respect
NO_COLORand disable colors when stderr is not a tty - Per-page context logs; many warn/err logs downgraded to debug
- Log the stalled page and JS stack on watchdog termination
- 0.4.12026-09-15 · 2026-09-15 13:09 UTC
查看更新说明
CORS (experimental)
CORS is off by default. Enable it with
--experimental-features cors.- CORS implementation: obey cross-origin checks on fetch/XHR instead of allowing them unconditionally
Originheader + request header validation- XHR changes for the CORS WPT tests
IndexedDB
- WPT conformance pass
setNameonIDBObjectStoreandIDBIndexnextunique/prevuniquecursor directions- Keep the
upgradeneededevent alive past dispatch; better error handling - Fix use-after-free reading
txn._dbafterreleaseReffrees it
New Web APIs
CountQueuingStrategyandByteLengthQueuingStrategyresource-timing;Performanceis now anEventTargetwindow.stop()img.decode()navigator.sendBeaconreturnstrueHeaders.getSetCookie,customElements.getName,Element.partHTMLInputElementvalueAsNumber,valueAsDate,stepUp/stepDown,showPickerSVGGraphicsElement.getCTMandgetScreenCTMcanvasmeasureTextand the drawing methods chart libraries probematchMediachange events and realaddListener/removeListener- Reflect
HTMLElement.contentEditable - Basic support for CSS custom properties
Forms / input
- Text entry: move the cursor on caret keys and to the end on value set; clamp selections against the current value
- Trusted
beforeinputis cancelable - A disabled
fieldsetdisables its controls' activation and validation option.selected = truedeselects its siblings in a single select- Fix select value assignment with no matching option
Custom elements
- Upgrade autonomous custom-element clones in place
- Don't upgrade custom elements in a windowless document
- Skip null nodes when a create callback fails
CSS / rendering / performance
- Memoize
StyleManagervisibility per element - Append inserted rules to the buckets instead of rebuilding
- Materialize inline styles where layout reads them
--strip-modegainsshellandclutteroptions- Faster
nth-*selectors, class matching, attribute lookup, and pseudo-class parsing
Ad blocker
- Run
/regex/filters with PCRE2 - Request engine
Protocols
- WebDriver: HTTP WebDriver session management
- CDP:
DOM.focus; honorrunImmediately,screenWidth/screenHeight,browserContextId - CDP: request interception patterns; a context per isolated world per frame
- CDP: wheel events scroll the viewport unless over a scroll container
- CDP: fix message lifetimes and console argument serialization; remove a terminate/disconnect race
HTTP / Networking
- Keep connections alive across navigations; better redirect handling; timeout tweaks
- Enable
CURLOPT_PIPEWAITon every easy handle - Update curl to 8.22.0
- Cookies: accept
Secureand__Host-on loopback origins; apply theSameSite=Laxexception only on safe methods; compute site-for-cookies from a frame's ancestor chain - Guard WebSocket message-length parsing against integer overflow
- Fix drive-letter lookalike URL segments on non-file schemes
CLI / tools
--localeand--timezone, with language signals derived from one value- Suggest the closest flag or command on a typo
notelog level can be silenced; log CLI tips- Scroll the nearest scroll container from the
scrolltool - Click dispatches a full pointer/mouse sequence, not a bare click event
- Agent: route REPL output per tool and align page tables of any size
Stability / memory
- Crash fixes:
TryCatchcopied by value,history.state/Navigation.currentEntry, V8 re-entry on an unhandled promise - Fix
Notificationuse-after-free - Disconnect runaway intersection observers; raise timer caps
- Leak fixes: robot store, JS terminated during MutationObserver/IntersectionObserver delivery, cloned HTTP responses, V8 backing store
- V8: notify isolate shutdown, restore the initial heap limit, avoid rearming heap termination during teardown
- Disarm the wat
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Benchmarks
Requesting 933 real web pages over the network on a AWS EC2 m5.large instance. See benchmark details.
| Metric | Lightpanda | Headless Chrome | Difference |
|---|---|---|---|
| Memory (peak, 100 pages) | 123MB | 2GB | ~16x less |
| Execution time (100 pages) | 5s | 46s | ~9x faster |
Quick start
Install
Package Managers
Latest nightly from Homebrew:
brew install lightpanda-io/browser/lightpanda
Latest nightly from Arch Linux User Repository:
yay -S lightpanda-nightly-bin
Download from the nightly builds
You can download the last binary from the nightly builds for Linux and MacOS for both x86_64 and aarch64.
For Linux
curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-x86_64-linux && \
chmod a+x ./lightpanda
Verify the binary before running anything:
./lightpanda version
Linux aarch64 is also available
If you use Nix, you can run Lightpanda directly from the flake without downloading a binary:
nix run github:lightpanda-io/browser -- --help
Or pin it as an input in your own flake:
{
inputs.lightpanda.url = "github:lightpanda-io/browser";
}
Note: The Linux release binaries are linked against glibc. On musl-based Linux distributions (Alpine, etc.) the binary fails with
cannot execute: required file not foundbecause the glibc dynamic linker is missing. Use a glibc-based base image: the current release binaries need glibc 2.35 (x86_64) or 2.38 (arm64), sodebian:bookworm-slim(2.36) is fine onx86_64but too old forarm64— useFROM ubuntu:24.04,FROM debian:trixie-slim, orFROM debian:forky-slimthere — or build from sources.Android / Termux: there is no native Android build. The Linux aarch64 binary needs the glibc loader (
/lib/ld-linux-aarch64.so.1), which Android's Bionic libc does not provide, so it fails with the samecannot execute: required file not founderror.
For MacOS
curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-aarch64-macos && \
chmod a+x ./lightpanda
MacOS x86_64 is also available
For Windows + WSL2
Lightpanda has no native Windows binary. Install it inside WSL following the Linux steps above.
WSL not installed? Run wsl --install from an administrator shell, restart, then open wsl.
See Microsoft's WSL install guide for details.
Your automation client (Puppeteer, Playwright, etc.) can run either inside WSL or on the Windows host. WSL forwards localhost:9222 automatically.
Install from Docker
Lightpanda provides official Docker
images for both Linux amd64 and
arm64 architectures.
The following command fetches the Docker image and starts a new container exposing Lightpanda's CDP server on port 9222.
docker run -d --name lightpanda -p 127.0.0.1:9222:9222 lightpanda/browser:latest
Dump a URL
./lightpanda fetch --obey-robots --dump html --log-format pretty --log-level info https://demo-browser.lightpanda.io/campfire-commerce/
You can use --dump markdown to convert directly into markdown, or
--dump png > page.png or --dump pdf > page.pdf for a text-only rendering
of the page. --dump structured_data prints the page's JSON-LD, OpenGraph,
Twitter card, meta and link tags as JSON, like the MCP structuredData tool.
--wait-until, --wait-ms, --wait-selector and --wait-script are
available to adjust waiting time before dump.
Start a CDP server
./lightpanda serve --obey-robots --log-format pretty --log-level info --host 127.0.0.1 --port 9222
To listen on a Unix-domain socket (owner-only, mode 0600) instead of TCP:
./lightpanda serve --unix-socket /run/user/1000/lightpanda.sock
Once the CDP server started, you can run a Puppeteer script by configuring the
browserWSEndpoint.
import puppeteer from 'puppeteer-core';
// use browserWSEndpoint to pass the Lightpanda's CDP server address.
const browser = await puppeteer.connect({
browserWSEndpoint: "ws://127.0.0.1:9222",
});
// The rest of your script remains the same.
const context = await browser.createBrowserContext();
const frame = await context.newPage();
// Dump all the links from the frame.
await frame.goto('https://demo-browser.lightpanda.io/amiibo/', {waitUntil: "networkidle0"});
const links = await frame.evaluate(() => {
return Array.from(document.querySelectorAll('a')).map(row => {
return row.getAttribute('href');
});
});
console.log(links);
await frame.close();
await context.close();
await browser.disconnect();
Start a webdriver Bidi server
Use --protocol webdriver to enable Bidi support.
You can start both, CDP and Bidi, with --protocol webdriver --protocol cdp
./lightpanda serve --obey-robots --log-format pretty --log-level info --host 127.0.0.1 --port 9222 --protocol webdriver
Agent mode
lightpanda agent lets you drive the browser with a native agent. Describe what
you want in plain English or with slash commands, and it controls the browser:
navigating pages, clicking through flows, filling forms, extracting structured
data. Think of it as a robot you're directing to use the web, more than a
chatbot you're having a conversation with.
Because the agent runs inside the same process as the browser, every tool call is a direct operation and you retain Lightpanda's speed and memory advantage.
The output of an agent session is a
PandaScript: vanilla JavaScript
with a small set of native browser primitives built directly into Lightpanda.
Run /save to export one from your current session, then replay it with
lightpanda run <script>.js. Scripts are deterministic and token-free, so
you can prototype with the LLM and ship the output to production without a
model at runtime.
It supports Anthropic, OpenAI, Gemini, Google Vertex AI, Mistral, Hugging
Face, the multi-lab gateways Vercel AI Gateway
and OpenRouter (one key for hundreds of models), any OpenAI-compatible endpoint via
OPENAI_BASE_URL, local models via Ollama, llama.cpp or LLMD,
and OpenAI Codex with a ChatGPT subscription instead of an API key (run
/provider codex in the REPL to log in). You can also run
without an LLM using --no-llm, which drops you into the REPL. See the
agent documentation for the full
reference.
./lightpanda agent # auto-detects API key from env
./lightpanda agent --task "top story on news.ycombinator.com?"
./lightpanda agent --no-llm # basic REPL, no LLM
./lightpanda run session.js # run a recorded script
cat session.js | ./lightpanda run - # ...or pipe one in via stdin
./lightpanda agent --provider gemini --task "..." # force a specific provider
./lightpanda agent --list-models # models available for the detected provider
VERTEX_API_KEY=... ./lightpanda agent --provider vertex # Vertex AI, express mode
GOOGLE_CLOUD_PROJECT=my-proj ./lightpanda agent --provider vertex # Vertex AI, token via gcloud auth
AI_GATEWAY_API_KEY=... ./lightpanda agent --provider vercel --model moonshotai/kimi-k2 # any model behind Vercel AI Gateway
OPENROUTER_API_KEY=... ./lightpanda agent --provider openrouter --model anthropic/claude-sonnet-5 # any model behind OpenRouter
OPENAI_BASE_URL=https://my-gateway/v1 OPENAI_API_KEY=... ./lightpanda agent # any OpenAI-compatible server
Native MCP and skill
The MCP server communicates via MCP JSON-RPC 2.0 over stdio.
Add to your MCP configuration:
{
"mcpServers": {
"lightpanda": {
"command": "/path/to/lightpanda",
"args": ["mcp"]
}
}
}
HTTP transport and independent sessions
For serving several agents from one process, start the MCP server over HTTP
instead of stdio by giving it a port (add --host x.x.x.x to specify the
interface to listen on):
lightpanda mcp --port 9223
Clients POST JSON-RPC to http://host:9223/mcp. Each connection is routed to
its own browsing session — its own page, cookies and memory — so agents no
longer clobber each other's page:
- A client that
initializes without anMcp-Session-Idheader is assigned a fresh session; the id comes back in the response'sMcp-Session-Idheader. Send it on subsequent requests to stay on that session (isolation). - Two agents that send the same
Mcp-Session-Idshare one browsing context (sharing — e.g. a workflow where several agents work the same page). - The
session_new,session_listandsession_closetools manage sessions explicitly. SendingDELETE /mcpwith anMcp-Session-Idcloses that session.
The run tool runs a PandaScript
in the caller's session, as lightpanda run does, and returns its console
output and the value it returns. It takes the source as script, or the path
of a file that save wrote. Pages the script opens are closed when it ends;
cookies and storage stay, so a login carries over to the next run. An optional
timeout in milliseconds (default 60000) stops a stuck script. A backend can
drive Lightpanda with one POST:
curl -s http://127.0.0.1:9223/mcp -H 'Content-Type: application/json' -d '{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "run", "arguments": { "script":
"const page = new Page();\nawait page.goto(\"https://example.com\");\nreturn page.extract({ title: \"title\", links: [\"a\"] });" } }
}'
A skill is available in lightpanda-io/agent-skill.
Shell completion
lightpanda completion <bash | fish | zsh> prints a completion script for commands, flags and their values:
lightpanda completion fish > ~/.config/fish/completions/lightpanda.fish
lightpanda completion bash > ~/.local/share/bash-completion/completions/lightpanda
lightpanda completion zsh > ~/.zfunc/_lightpanda # with fpath=(~/.zfunc $fpath) before compinit
Telemetry
By default, Lightpanda collects and sends usage telemetry. This can be disabled by setting an environment variable LIGHTPANDA_DISABLE_TELEMETRY=true. You can read Lightpanda's privacy policy at: https://lightpanda.io/privacy-policy.
Core dumps
Set LIGHTPANDA_DISABLE_CORE_DUMP (to any value) to suppress crash core dumps by zeroing the soft RLIMIT_CORE at startup.
Status
Here are the key features we have implemented. For full details, see our Web Platform Tests results.
- CORS
- HTTP loader (Libcurl)
- HTML parser (html5ever)
- DOM tree
- Javascript support (v8)
- DOM APIs
- Ajax
- XHR API
- Fetch API
- DOM and Markdown dump
- CDP/websockets server
- Click
- Input form
- Cookies
- Custom HTTP headers
- Proxy support
- Network interception
- Respect
robots.txtwith option--obey-robots - CDP and Webdriver Bidi
- Adblocker
Build from sources
Prerequisites
Lightpanda is written with Zig 0.17.0. You have to
install it with the right version in order to build the project.
Lightpanda also depends on v8, Libcurl and html5ever.
For Debian/Ubuntu based Linux:
sudo apt install xz-utils ca-certificates \
pkg-config libglib2.0-dev \
clang make curl git
pkg-config and libglib2.0-dev are only needed to build V8 from source (see
below).
You also need to install Rust.
For systems with Nix, you can use the devShell.
nix develop
For MacOS, you need cmake and Rust.
brew install cmake
Build and run
Run make download-v8 once first to fetch the prebuilt V8 archive; both
make and zig build pick it up. Without it, or on a platform with no
published archive (the download then fails), the build compiles V8 from source,
which takes several minutes. See
CONTRIBUTING.md for details.
Then build the entire browser with make build or make build-dev for debug
env.
But you can directly use the zig command: zig build run.
Embed v8 snapshot
Lightpanda uses a v8 snapshot. make build builds and embeds it. With a plain
zig build, it is created on startup instead, unless you embed it by using the
following commands:
Generate the snapshot.
zig build snapshot_creator -- src/snapshot.bin
Build using the snapshot binary.
zig build -Dsnapshot_path=../../snapshot.bin
See #1279 for more details.
Test
Unit Tests
You can test Lightpanda by running make test.
make test # Run all tests
make test F="server" # Filter by substring
TEST_FILTER="WebApi: #selector_all" make test # Filter main + subtest (separator: #)
TEST_VERBOSE=true make test
TEST_FAIL_FIRST=true make test
METRICS=true make test # Capture allocation/duration metrics as JSON
TEST_JOBS=1 make test # Run in one process (default: up to 4, or 1 when filtered)
End to end tests
To run end to end tests, you need to clone the demo
repository into ../demo dir.
You have to install the demo's node requirements
You also need to install Go > v1.24.
make end2end
Web Platform Tests
Lightpanda is tested against the standardized Web Platform Tests.
We use a fork including a custom
testharnessreport.js. Results are published daily.
For reference, you can execute a WPT test case with your browser via wpt.live.
Configure WPT HTTP server
To run the test, you must clone the repository, configure the custom hosts and generate the
MANIFEST.json file.
Clone the repository with the fork branch.
git clone -b fork --depth=1 [email protected]:lightpanda-io/wpt.git
Enter into the wpt/ dir.
Install custom domains in your /etc/hosts
./wpt make-hosts-file | sudo tee -a /etc/hosts
Generate MANIFEST.json
./wpt manifest
Use the WPT's setup guide for details.
Run WPT test suite
An external Go runner is provided by
github.com/lightpanda-io/demo/
repository, located into wptrunner/ dir.
You need to clone the project first.
First start the WPT's HTTP server from your wpt/ clone dir.
./wpt serve
Run a Lightpanda browser
zig build -Dwpt_extensions run -- serve \
--ws-max-concurrent 64 \
--insecure-disable-tls-host-verification \
--load-resources iframe \
--load-resources image \
--load-resources worker \
--load-resources stylesheet
Then you can start the wptrunner from the demo's clone dir:
cd wptrunner && go run .
Or one specific test:
cd wptrunner && go run . Node-childNodes.html
wptrunner command accepts --summary and --json options modifying output.
Also --concurrency define the concurrency limit.
To check a single test case without the runner, fetch --dump wpt prints its
results as JSON: the harness status, a pass/fail summary and the cases that
did not pass. It relies on the fork's testharnessreport.js.
zig build -Dwpt_extensions run -- fetch \
--insecure-disable-tls-host-verification \
--load-resources worker \
--load-resources iframe \
--dump wpt \
--wait-script "window.report && report.complete" \
"http://web-platform.test:8000/dom/nodes/Node-childNodes.html"
:warning: Running the whole test suite will take a long time. In this case,
it's useful to build in releaseFast mode to make tests faster.
zig build -Dwpt_extensions -Doptimize=fast run -- serve ...
Contributing
See CONTRIBUTING.md for guidelines. You must sign our CLA during the pull request process.
Why Lightpanda?
Javascript execution is mandatory for the modern web
Simple HTTP requests used to be enough for web automation. That's no longer the case. Javascript now drives most of the web:
- Ajax, Single Page Apps, infinite loading, instant search
- JS frameworks: React, Vue, Angular, and others
Chrome is not the right tool
Running a full desktop browser on a server works, but it does not scale well. Chrome at hundreds or thousands of instances is expensive:
- Heavy on RAM and CPU
- Hard to package, deploy, and maintain at scale
- Many features are not necessary in headless made
Lightpanda is built for performance
Supporting Javascript with real performance meant building from scratch rather than forking Chromium:
- Not based on Chromium, Blink, or WebKit
- Written in Zig, a low-level language with explicit memory control
- No graphical rendering engine