English | 简体中文 | 日本語 | Tiếng Việt
Xcelerate is a high-performance, lightweight browser-automation engine with idiomatic bindings for Rust, .NET, Python, JavaScript (Node.js), Kotlin, Java, Swift, Ruby, Dart/Flutter, and Go. It speaks to three familiar API styles — Playwright, Puppeteer, and Selenium — so you keep the code you already write, on a faster engine.
All three styles are generated from declarative profiles and share one engine, so
you can switch styles (or languages) without rewriting a script. A native
xcelerate style exists too, but it is a later option: start with the style
you already know.
- Supported engines
- Bindings
- Features
- Installation
- Quick start
- Plugins
- API-style adapters
- Native bindings
- Command-line interface
- Scripting (XCL)
- MCP server
- Video recording
- Proxy
- Persistent profiles
- Accessibility snapshots
- Agent snapshots
- Workspace layout
- Development
- Security
- License
One core, two engines: Chromium over CDP, Firefox over WebDriver BiDi.
| Engine | Protocol | Backend |
|---|---|---|
| Chrome DevTools Protocol (CDP) | xcelerate::Browser |
|
| WebDriver BiDi | xcelerate::firefox |
// Chromium (CDP)
let browser = xcelerate::Browser::launch(Default::default()).await?;
// Firefox (WebDriver BiDi)
let browser = xcelerate::firefox::FirefoxBrowser::launch(Default::default()).await?;
let page = browser.clone().new_page("https://example.com".to_string()).await?;
println!("{}", page.title().await?);Engine support by surface. The CLI (
xcelerate run/session/ one-shot commands) and the XCL runner drive Chromium over CDP only; passing a Firefox-family browser (--browser firefox) fails fast with a clear message. Firefox is available through the Rust API (xcelerate::firefox::FirefoxBrowser) shown above.
| Language | Package | Registry |
|---|---|---|
| Rust | xcelerate |
crates.io |
| Python | xcelerate |
PyPI |
| JavaScript | xcelerate |
npm |
| .NET | Xcelerate |
NuGet |
| Kotlin | io.github.azzodude:xcelerate |
Maven Central |
| Java | io.github.azzodude:xcelerate-java |
Maven Central |
| Swift | Xcelerate |
built from source (readme) |
| Ruby | xcelerate |
built from source (readme) |
| Dart / Flutter | xcelerate |
pub.dev |
| Go | xcelerate |
built from source (readme) |
| PowerShell | Xcelerate |
PowerShell Gallery |
- Automated process management - discovers and launches Chrome or Edge, and manages the lifecycle of the browser process.
- Security-first plugins - a default-deny plugin system with an append-only audit log. No plugin is compiled into the core: external ones are sandboxed WebAssembly components, capability-gated, and loaded from a shared plugin home.
- Browser & app control as plugins -
plugins/browserandplugins/appare ready-made WebAssembly plugins that expose the browser and native-app surfaces as capability-gated ops, so the core stays small and both surfaces evolve independently. - Human-like input by default - clicks and typing drive the real mouse and
keyboard: the cursor travels to the element along a Bezier path and text is
typed at a human pace, so runs look less robotic. Pass
--linearfor a straight-line mouse and fast typing when you want a reproducible run. - Async-first - built on
tokioin Rust andasync/awaitin every binding. - Three familiar API styles first - write Playwright, Puppeteer, or Selenium
code unchanged over the same engine, each generated from a declarative profile.
A native
xcelerateAPI is available for advanced cases. - Multi-language bindings - one core, generated bindings for Rust, Python,
JavaScript (Node.js), .NET, Kotlin, Java, Swift, Ruby, Dart/Flutter, and Go via
uniffi, plus a PowerShell module over the .NET SDK. - Session video recording - capture the page to a video through the CDP
screencast; writes Motion-JPEG AVI with no external tools, or H.264/VP9
MP4/WebM when
ffmpegis onPATH. - Proxy pool - route the browser through one or more upstream HTTP proxies (with credentials) via a built-in local gateway.
- Persistent profiles - keep logins, cookies, and site storage between runs
with a durable
user-data-dir. - Accessibility snapshots - a semantic
role/nameview of the page for robust selectors and agent-driven automation. - Agent snapshots - an indexed, LLM-friendly rendering of the page in which
every interactive element can be acted on by its
[index]. - CLI and MCP server - the
xceleratecommand for one-shot actions, andxcelerate mcp(or thexcelerate-mcpbinary) to drive the browser from an MCP client. - XCL scripting - a line-oriented
.xclscripting language that both a human and an AI can read and write, with variables, bounded functions, control flow, HTTPrequest, pluginrun, andassert, backed by default-deny security.
[dependencies]
xcelerate = "1.0.14"
tokio = { version = "1", features = ["full"] }pip install xceleratenpm install xceleratedotnet add package XceleratePublished to Maven Central as io.github.azzodude:xcelerate (Kotlin) and
io.github.azzodude:xcelerate-java (Java):
// build.gradle.kts
dependencies {
implementation("io.github.azzodude:xcelerate:1.0.14") // Kotlin
implementation("io.github.azzodude:xcelerate-java:1.0.14") // Java
}The Java bindings require JDK 22+ and run with --enable-native-access=ALL-UNNAMED.
See bindings/kotlin/README.md and
bindings/java/README.md for usage.
Building them from source:
python scripts/install_toolchains.py # JDK 22+ via winget, Gradle, uniffi-bindgen-java
python scripts/generate_bindings/kotlin.py # Kotlin sources + Gradle build
python scripts/generate_bindings/java.py # Java sources + Gradle buildThese targets are built from source with the universal generator. Swift and Ruby
are built-in UniFFI targets; Dart needs uniffi-bindgen-dart; Go needs the
NordSecurity uniffi-bindgen-go generator and a Go toolchain.
python scripts/generate_bindings/swift.py # Swift sources + SwiftPM package
python scripts/generate_bindings/ruby.py # Ruby sources + gemspec
cargo install uniffi-bindgen-dart # once
python scripts/generate_bindings/dart.py # Dart sources + pubspec
go install github.com/NordSecurity/uniffi-bindgen-go/v2/uniffi-bindgen-go@latest
python scripts/generate_bindings/go.py # Go sources + go.modInstall-PSResource Xcelerate # PSResourceGet (PowerShell 7.4+)
# or, with PowerShellGet:
Install-Module XceleratePowerShell has no UniFFI generator, so the module wraps the .NET SDK. To build and stage the payload from source instead:
python scripts/generate_bindings/powershell.py
Import-Module ./bindings/powershell/Xcelerate.psd1See bindings/powershell/README.md for usage, or
run the end-to-end example at examples/powershell/quickstart.ps1.
See bindings/ for each package's README.
Pick the API style you already write. Every style runs on the same engine, so you can switch later without rewriting your scripts.
from xcelerate import use
pw = use("playwright")
browser = await pw.launch()
page = await browser.new_page()
await page.goto("https://example.com")
await page.click("#submit")
print(await page.inner_text("h1"))
await browser.close()from xcelerate import use
pp = use("puppeteer")
browser = await pp.launch()
page = await browser.new_page()
await page.goto("https://example.com")
await page.click("#submit")
await browser.close()from xcelerate import use
sel = use("selenium")
driver = await sel.launch()
await driver.get("https://example.com")
element = await driver.find_element("css selector", "#submit")
await element.click()
await driver.quit()Rust reaches the same three styles through
xcelerate::adapters::{playwright, puppeteer, selenium}. See
API-style adapters for the full surface of each.
Xcelerate also has its own API (Browser, Page, Element) - the engine the
three styles are built on. Reach for it only when a style does not cover what you
need; start with the style you already know.
use xcelerate::{Browser, BrowserConfig};
#[tokio::main]
async fn main() -> Result<(), xcelerate::XcelerateError> {
let browser = Browser::launch(BrowserConfig::default()).await?;
let page = browser.new_page("https://example.com".to_string()).await?;
println!("Title: {}", page.title().await?);
browser.close().await?;
Ok(())
}BrowserConfig defaults to headless and detached. Opt out of those defaults, and
load a plugin by path if you want one (there are none built in):
use xcelerate::BrowserConfig;
let config = BrowserConfig {
headless: false,
detached: false,
executable_path: None, // auto-discover Chrome/Edge/…
plugins: None, // no built-in plugins
..Default::default()
};Xcelerate ships a security-first plugin system and no built-in plugins. A
plugin is a named bundle of launch-time configuration, page hooks, and invokable
operations, and it does nothing unless you enable it (default-deny). Add one
as a trusted Rust library (install_plugins) or as a sandboxed WebAssembly
component loaded by path (load_plugin).
Load a sandboxed plugin from disk (a directory or a plugin.json), then invoke
its ops through the same fixed bridge in every language:
use xcelerate::{Browser, BrowserConfig};
let browser = Browser::launch(BrowserConfig::default()).await?;
browser.load_plugin("path/to/plugin".to_string())?; // …/plugin.json also works
let handle = browser.plugin("acme.hello".to_string())?;
handle.invoke("ping".into(), "{}".into()).await?;There is none. available_plugins() reports what is installed or loaded on this
browser, which is empty until you add something. Human-like mouse and keyboard
input is built into the core's input path; fingerprint work belongs in your own
plugin.
Each op runs under the invocation budget and is written to the audit log; a plugin only ever acts on pages it has been handed.
Browser and native-app control are plugins.
plugins/browser (browser) and
plugins/app (app) are ready-made WebAssembly
components that expose the whole surface as ops:
| Plugin | Exposes | Capability |
|---|---|---|
plugins/browser (browser) |
the browser surface - open, click, fill, text, snapshot, … |
browser |
plugins/app (app) |
native-window control - launch, window, tree, find, click, set_value, … |
app |
Both call a capability-gated host bridge (host.browser / host.app)
instead of speaking CDP, BiDi, or the OS directly: a plugin asks the host for a
semantic verb and the host performs it. That is how browser and app control stay
decoupled from the interpreter while the core stays small.
The CLI owns the build. xcelerate build --wasm-only writes wit/plugin.wit
(the canonical host ABI) and stages the .wasm beside plugin.json, so a plugin
never hand-copies or maintains the interface:
cd plugins/browser && xcelerate build --wasm-only # -> browser.wasmInstall once, use by name. Drop a built plugin in the user-global home
($XCELERATE_HOME/plugins, else ~/.xcl/plugins) or ./plugins, and
xcelerate run auto-loads the standard browser and app plugins. A script then
uses the plain verbs and never names the plugin:
# native app: the `app` plugin is auto-loaded; grant its capability and the window
XCELERATE_PLUGIN_ALLOW=app \
xcelerate run --native --allow-app "Calculator" app.xcl# app.xcl - the plugin is just the library behind these verbs
launch "calc" "Calculator" # start (or attach), and select it
window "Calculator" # ...or select an already-running window
find "Equals"
click "Equals"
The same work is reachable through the plugin's ops directly
(run browser open {"url":"…"}); the plain verbs are the preferred,
plugin-agnostic surface.
Every binding exposes the same tiny, fixed bridge, so a new plugin never requires new binding code:
| Method | Purpose |
|---|---|
available_plugins() |
Plugins installed or loaded on this browser |
plugin_names() |
Plugins enabled on this browser |
use_plugin(name) |
Enable a built-in plugin at runtime |
install_plugins([plugin]) |
Install trusted plugins compiled in as a Cargo library (Rust only) |
load_plugin(path) |
Load a sandboxed plugin (a directory or plugin.json) |
plugin(name) |
A handle to an enabled plugin |
plugin(name).ops() |
The operations the plugin exposes |
plugin(name).invoke(op, args_json) |
Invoke an operation with JSON args, returning JSON |
let enabled = browser.plugin_names(); // e.g. ["acme.hello"]
let handle = browser.plugin("acme.hello".into())?; // error if not enabled
let info = handle.invoke("info".into(), "{}".into()).await?;| Model | Where it runs | Privileges |
|---|---|---|
| Built-in | In-process, compiled in | Launch flags, binary patching, detached spawn, init scripts |
| Loaded from disk | WebAssembly, sandboxed, capability-gated | Default-deny subset, audited |
Capabilities are classified before they can ever be granted. LaunchControl,
BinaryPatch, and DetachedSpawn are built-in only; Evaluate,
CdpProxy, Browser, App, cookie access, init scripts, screenshots, and
network capture are dangerous and require explicit consent. A plugin loaded
from disk is a WebAssembly component that runs sandboxed with no ambient
authority: the host imports are the only way out, they are capability-gated,
and every call is audited - so a plugin cannot reach the filesystem, the
network, or the desktop on its own. Dangerous callbacks are denied by
default and must be opted into per host via XCELERATE_PLUGIN_ALLOW (per
plugin, or broadly), under per-invocation time and response-size budgets. See
docs/plugins/.
Every privileged action (launch configuration, page hooks, and every invoke) is
recorded in an append-only, hash-chained audit log. Secrets such as cookies and
credentials are never written to it.
assert!(browser.audit_verify()); // the hash chain is intact
println!("{}", browser.audit_log());Plugins loaded from disk remain the user's responsibility to trust: the engine's job is to make what they can do explicit, auditable, and impossible by default.
A plugin is an external WebAssembly component (or a trusted in-process crate).
Scaffold a starter, build the .wasm, then load it:
xcelerate plugin new acme.hello # scaffold from the template
cd hello && xcelerate build --wasm-only # writes wit/plugin.wit, builds the .wasmYou write plain Rust op handlers; xcelerate handles the WebAssembly plumbing and
writes wit/plugin.wit for you. Load the built component with
Browser::load_plugin(path) (a directory or a plugin.json), or drop it in
~/.xcl/plugins/ and load it by name. See the
plugin authoring guide, the
WASM reference, the JSON
schema, and the
examples.
The adapter layer exposes familiar Selenium, Playwright, and Puppeteer method names over the native engine, so existing scripts can be ported with minimal changes. Each adapter is generated from a declarative profile and is fully typed in Rust and Python.
Rust:
use xcelerate::adapters::playwright;
let browser = playwright::launch(None).await?;
let page = browser.new_page("https://example.com".to_string()).await?;
let paragraphs = page.query_selector_all("p".to_string()).await?;
println!("{} paragraph(s)", paragraphs.len());
browser.close().await?;Python:
from xcelerate import use
pw = use("playwright")
browser = await pw.launch()
page = await browser.new_page()
await page.goto("https://example.com")
print(await page.title())
await browser.close()The same engine is available through use("selenium") and use("puppeteer"). See
adapters/README.md for the profile format and how to extend
an adapter.
The generated bindings expose the same asynchronous API in each language.
Python:
import asyncio
from xcelerate import Browser, BrowserConfig
async def main():
browser = await Browser.launch(BrowserConfig())
page = await browser.new_page("https://example.com")
print(await page.title())
await browser.close()
asyncio.run(main())JavaScript (Node.js):
const { Browser } = require("xcelerate");
async function main() {
const browser = await Browser.launch();
const page = await browser.newPage("https://example.com");
console.log(await page.title());
await browser.close();
}
main();The xcelerate command performs one browser action per invocation. It is
deliberately small: it navigates, writes artifacts to disk, and manages the
install. Reading a live page — its title, text, HTML, media, or an indexed
snapshot — is the job of the interactive session, which keeps the browser open.
xcelerate open https://example.com # navigate; prints the title and URL
xcelerate screenshot https://example.com -o shot.png --full
xcelerate pdf https://example.com -o page.pdf
xcelerate save https://example.com/logo.png -o logo.png # download through the browser
xcelerate grab https://…/playlist.m3u8 -o movie.mp4 # HLS/DASH: assemble the segments
xcelerate capture https://www.youtube.com/watch?v=… -o movie.mp4 # MSE: capture what the page plays
xcelerate har https://example.com -o network.har # record network activity (HAR)
xcelerate record https://example.com -o video.mp4 # video of a run
xcelerate list # built-in devices + plugins
xcelerate plugins
xcelerate --device "iPhone 13" screenshot https://example.com -o phone.pngTo inspect a page, open a session and run the verb there:
xcelerate session
xcelerate> open example.com
xcelerate> title
xcelerate> text
xcelerate> snapshot # indexed, LLM-friendly
xcelerate> media # images/video/audio as JSON
xcelerate> eval 'document.title'Global flags apply to every command: --headless (the browser window is shown
by default), --detached, --executable-path <path>, --plugins <path,...>,
--device <name>, --linear (straight-line mouse and fast typing instead of the
default human-like input), --output-dir <dir> (the workspace root every file
verb is confined to), and --timeout <ms>. xcelerate --device <name> <command> renders as a built-in
mobile device, and xcelerate list prints every device and plugin.
Native application control is a plugin too: the auto-loaded app
backs the XCL launch/window/tree/find/click verbs. There is no
xcelerate app subcommand - drive a desktop app with xcelerate run --native
(see Plugins).
Install it with cargo install --path crates/xcelerate-cli (the installed
binary is xcelerate-cli; the release archives and winget ship it as
xcelerate), or winget install Chaosware.Xcelerate on Windows; from a
checkout, prefix any command with cargo run -p xcelerate-cli --.
XCL (.xcl) is a small, line-oriented scripting language that captures a
multi-step automation run in a file both a human and an AI agent can
read and write. One line = one action. It is deliberately not
Turing-complete: loops are bounded, functions are flat and do not recurse, and
there is no expression sublanguage — so a script is safe to emit, safe to read,
and cannot hang the runner.
New to XCL? Start with the tutorial.
xcelerate run login.xcl # run a script
xcelerate run login.xcl --param user=ada@example.com
xcelerate run login.xcl --allow-http # enable `request` (browserless HTTP)# register-then-login.xcl
param base "https://www.practicesoftwaretesting.com"
func fill_field(id, value)
fill $id $value
end
open $base
wait 2000
click "Register"
fill_field "#email" "ada@example.com"
fill_field "#password" "correct-horse-battery"
submit
wait-idle
assert url contains "/login"
request GET "{base}/api/health"
assert $STATUS == 200
done
| Keyword | Meaning |
|---|---|
# comment |
Full-line comment (blank lines ignored). |
let <name> <value> |
Define a variable; interpolate as $name. An = after the name is optional. |
set <name> <value> |
Reassign a variable. |
param <name> [default] |
Declare a runtime parameter (override with --param k=v). |
func <name>(a, b) … end |
A named, no-return callable (depth 1, no recursion). |
<name> <arg>… |
Invoke a function defined above (call <name> … also works). |
open / goto <url> |
Navigate. |
back reload title url text markdown |
Read the page. |
click <selector|text> mouse [click] <selector|text> tap fill <sel> <text> type press submit hover scroll |
Interact. |
media download <url> <path> upload <sel> <path> |
Discover media, save a URL/HLS/DASH stream, or send a file to a file input. |
cookie [get] [name] cookie set <name> <value> [domain] [path] cookie add <json> cookie delete <name> cookie clear |
Read, set (including HttpOnly session cookies), or clear cookies. |
capture <url> <path> [secs] |
Record an MSE page's own segments (YouTube/Facebook). |
shot [path] shot-full [path] |
Save a viewport or full-page screenshot. |
wait <ms|selector> wait-idle wait-stable |
Wait for a selector, or sleep a number. |
sleep await wait-ms / wait-sec / wait-min / wait-hr wait-random |
core std sleeps (ms, seconds, the named unit, or a random delay). |
eval <js> |
Run JavaScript (requires --allow-unsafe). |
request <METHOD> <url> [headers] [body] |
HTTP without a browser (requires --allow-http). |
import <id> [op]… run <plugin> <op> [json] plugins plugin-config <id> |
Plugins / workers; each imported op becomes a bare callable. |
repeat <n> … retry <n> … if-ok … if-fail … goto <label> label <name> |
Bounded control flow. |
assert <subject> <op> <value> |
Fail-fast check (url, title, status, contains, ==, …). |
print <arg>... |
A core std function: write the resolved arguments to stdout. |
done / quit |
End the run. |
Every path a script names — in download, capture, shot/shot-full, and
upload — is resolved against a workspace root and confined to it. The root
is the directory you run from, or the --output-dir <dir> you pass. Absolute
paths and .. escapes are refused (path escapes the workspace root), and
missing parent directories are created. A .xcl file is executable input, so it
can never read or write outside the workspace you chose.
xcelerate-mcp - also reachable as xcelerate mcp - is a
Model Context Protocol server over stdio, so
an MCP client can drive a real browser. It exposes 40 tools covering navigation,
titles, page content, screenshots, PDFs, clicking, typing, hovering, key presses,
querying, JavaScript evaluation, and plugin invocation.
Configure it with the environment: XCELERATE_CHROME (browser path),
XCELERATE_HEADLESS (1/true, default), XCELERATE_DETACHED (1/true), and
XCELERATE_PLUGINS (comma-separated external plugin paths to load).
A page can be recorded to a video file. Recording is driven by the CDP screencast, so the capture is change-driven: an animating page produces real motion, while a static page yields a short clip.
use xcelerate::{Browser, BrowserConfig};
let browser = Browser::launch(BrowserConfig::default()).await?;
let page = browser.new_page("https://example.com".to_string()).await?;
page.start_video("demo.mp4".to_string()).await?;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
let path = page.stop_video().await?; // the path actually writtenThe output extension selects the back end:
.mp4/.mov/.mkv/.webmare muxed withffmpeg(H.264 or VP9) at the frames' real timestamps whenffmpegis onPATH. If it is missing the frames fall back to a sibling.avi, and the returned path says which was used.- Any other extension (for example
.avi) always uses the built-in Motion-JPEG AVI writer, which needs no external tools.
start_video_with_options takes tuning knobs (quality, max_width,
max_height, fps, ffmpeg). This API is intentionally not exported through
UniFFI yet, so it is available in Rust, the CLI, and the MCP server without
changing the generated bindings' checksums.
From the CLI:
xcelerate record https://example.com -o demo.mp4 --duration 5From the MCP server: browser_start_recording {path} … browser_wait {milliseconds} … browser_stop_recording.
A configured proxy applies to every page in the browser. Chrome's
--proxy-server cannot carry credentials and cannot switch proxies, so
xcelerate runs a small local HTTP/CONNECT gateway on 127.0.0.1 and points
Chrome at it; the gateway forwards each connection to an upstream chosen from a
pool and injects Proxy-Authorization.
Chrome --(HTTP/CONNECT)--> xcelerate gateway (127.0.0.1) --> upstream pool --> internet
# environment (works from every language binding)
XCELERATE_PROXY=http://user:pass@proxy.example:8080 xcelerate open https://example.com
XCELERATE_PROXY_POOL=http://a:8080,http://b:8080 ./your-app # round-robin
# CLI flag (repeatable)
xcelerate --proxy http://user:pass@proxy.example:8080 --proxy http://backup:8080 \
open https://example.comxcelerate::configure_proxy(&["http://user:pass@proxy.example:8080".to_string()])?;
let browser = Browser::launch(BrowserConfig::default()).await?;- Upstreams are
http://[user:pass@]host:port; the pool is rotated round-robin. - HTTPS uses the proxy's
CONNECTtunnel; plain HTTP uses absolute-form requests - both flow through the upstream with credentials injected. - SOCKS upstreams are unnecessary: Chrome speaks SOCKS natively via
--proxy-server, so point Chrome at it directly. https://upstreams (TLS to the proxy) are not supported yet.
By default each browser gets a throwaway profile that is deleted on close. Point it at a directory to keep cookies, logins, and site storage between runs:
XCELERATE_USER_DATA_DIR=~/.xcelerate/profile xcelerate open https://example.com
xcelerate --user-data-dir ./profile open https://example.comxcelerate::configure_user_data_dir(Some("./profile".to_string()))?;The path is canonicalized (a relative --user-data-dir would otherwise resolve
against Chrome's own cwd), and Browser::close lets Chrome flush the profile to
disk before it exits.
page.accessibility_snapshot() returns a compact semantic view of the page -
[{ role, name, value? }] in document order - which is far more resilient than
CSS selectors for asserting or driving a page. It is exposed as the MCP tool
browser_accessibility (and through the xcelerate library).
page.agent_snapshot() renders the page as indented text where every
interactive element is tagged with a stable [index] and, when one can be
derived from its DOM attributes, a usable CSS selector (#id, or
[name="…"]/[aria-label="…"]/[placeholder="…"]):
[0]<link> "Home" '#nav-home'
[1]<textbox> "Email" = "a@b.com" '[name="email"]'
[2]<button> "Sign in" '#signin'
Selectors are shown as quoted string literals ('[name="email"]') so they can be
copied straight into a command (fill '[name="email"]' …, click '#signin'). An
element can be acted on either by index (click 1) or by that selector.
Framework-generated ids (React's _R_…, Radix/MUI :r0:, and other long random
tokens) change between renders, so they are skipped: such an element falls
back to its name / aria-label / placeholder, or shows no selector at all
when none of those is stable.
Images and video are rendered too (the AX tree alone hides them), with a compact descriptor, so an agent can tell a thumbnail from a hero image from a player, and can see a hover preview before touching it:
[3]<img> "Golden retriever" (image, 640x480, hero.jpg) '#hero'
<video> (video, 1280x720, clip.mp4) '#player'
[4]<a> "Trailer" (hover: video preview) '[aria-label="Trailer"]'
The descriptor is kind, WxH, file. A hidden <video> (the usual hover-preview
idiom) is shown as hidden, and the element that contains it - the thumbnail an
agent actually sees - is tagged hover: video preview. snapshot_json() carries
the same information as a "kind" field (image/video/audio).
The snapshot is built entirely in Rust from a single
Accessibility.getFullAXTree and DOMSnapshot.captureSnapshot call on the
persistent CDP session, so it is far cheaper and more predictable than
serializing the DOM in a scripting language. Pass an index to
page.click_index(n) to click that element without re-resolving a CSS selector
(page.snapshot_json() returns the same elements with roles, names, selector,
bounds, and backend node ids). It is exposed in the interactive session
(snapshot, then click <index> or click '<selector>'), and as the MCP tools
browser_snapshot and browser_click_index.
xcelerate/
crates/
xcelerate-core/ # WebSocket transport and typed CDP command layer
xcelerate-plugin/ # plugin trait, manifest, capabilities, audit, host interface
xcelerate/ # high-level facade: Browser, Page, Element, adapters
xcelerate-bindgen/ # uniffi bindgen helper binary
xcelerate-interpreter/ # the XCL language: lexer, parser, engine, security, executor
xcelerate-cli/ # CLI (binary `xcelerate`), incl. the XCL runner
xcelerate-mcp/ # `xcelerate-mcp` Model Context Protocol server
xcelerate-codegen/ # script + typed-binding code generation (11 languages)
adapters/ # adapter profiles, runtime, and generator inputs
bindings/ # generated Python/JS/C#/Kotlin/Java/Swift/Ruby/Dart/Go packages (+ PowerShell)
docs/plugins/ # plugin authoring guide, JSON schema, examples
docs/xcl.md # the XCL scripting language reference
plugins/ # browser + app plugins (browser and native-app APIs as .wasm)
scripts/ # code generation, harvesting, and release tooling
The plugin API - the Plugin trait, Manifest, capabilities, audit log, and the
PageHost interface - lives in crates/xcelerate-plugin/. No plugins are built
into the core: plugins are external — either .wasm components loaded sandboxed
via load_plugin, or in-process crates an embedder installs. The facade owns the
PluginManager and bridges Page to the plugin host interface, so plugins never
touch a raw page or the transport.
The XCL scripting language lives in crates/xcelerate-interpreter/ (lexer,
parser, engine, runtime, security, and the browser/plugin/HTTP executor). See
docs/xcl.md.
# Generate the API-style adapters (Python and Rust) from the profiles.
python scripts/generate_adapters.py
# Validate the profiles and print coverage.
python scripts/harvest_adapters.py --check
# Build, lint, and test.
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p xcelerate --lib # plugin + unit tests (no browser)
cargo test -p xcelerate --test adapters_e2e -- --nocapture
# JVM bindings (JDK 22+, Gradle, uniffi-bindgen-java).
python scripts/install_toolchains.pyThe end-to-end tests launch a real browser and require Chrome or Edge. Point them at a
different site or browser with XCELERATE_TEST_URL and XCELERATE_CHROME.
Found a vulnerability? Please follow SECURITY.md - do not open a public issue. Supported versions, reporting channels, and scope are listed there.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
{ "mcpServers": { "xcelerate": { "command": "xcelerate-mcp" } } }