Skip to content
ChaoswareHQPublic

About

Topics

Resources

Security policy

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

English | 简体中文 | 日本語 | Tiếng Việt

Xcelerate

CI CodeQL Semgrep OpenSSF Scorecard

Crates.io Crates.io downloads PyPI npm pub.dev NuGet Maven Central PowerShell Gallery

docs.rs Rust License: MIT OR Apache-2.0

Chromium Firefox

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.

Contents

Supported engines

One core, two engines: Chromium over CDP, Firefox over WebDriver BiDi.

Engine Protocol Backend
Chromium Chromium, Chrome, Edge Chrome DevTools Protocol (CDP) xcelerate::Browser
Firefox Firefox 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.

Bindings

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

Features

  • 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/browser and plugins/app are 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 --linear for a straight-line mouse and fast typing when you want a reproducible run.
  • Async-first - built on tokio in Rust and async/await in 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 xcelerate API 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 ffmpeg is on PATH.
  • 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/name view 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 xcelerate command for one-shot actions, and xcelerate mcp (or the xcelerate-mcp binary) to drive the browser from an MCP client.
  • XCL scripting - a line-oriented .xcl scripting language that both a human and an AI can read and write, with variables, bounded functions, control flow, HTTP request, plugin run, and assert, backed by default-deny security.

Installation

Rust

[dependencies]
xcelerate = "1.0.14"
tokio = { version = "1", features = ["full"] }

Python

pip install xcelerate

JavaScript (Node.js)

npm install xcelerate

.NET / C#

dotnet add package Xcelerate

Kotlin / Java

Published 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 build

Swift / Ruby / Dart / Go

These 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.mod

PowerShell

Install-PSResource Xcelerate      # PSResourceGet (PowerShell 7.4+)
# or, with PowerShellGet:
Install-Module Xcelerate

PowerShell 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.psd1

See 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.

Quick start

Pick the API style you already write. Every style runs on the same engine, so you can switch later without rewriting your scripts.

Playwright style

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()

Puppeteer style

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()

Selenium style

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.

Native API (advanced)

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()
};

Plugins

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).

Loading plugins

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?;

Built-in catalog

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 app plugins (the shared plugin home)

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.wasm

Install 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.

Inspecting and invoking plugins

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?;

Where plugins run and what they may do

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/.

Audit log

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.

Creating a plugin

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 .wasm

You 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.

API-style adapters

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.

Native bindings

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();

Command-line interface

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.png

To 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 --.

Scripting (XCL)

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)

Example

# 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

Commands

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.

Files and paths

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.

MCP server

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.

{ "mcpServers": { "xcelerate": { "command": "xcelerate-mcp" } } }

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).

Video recording

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 written

The output extension selects the back end:

  • .mp4 / .mov / .mkv / .webm are muxed with ffmpeg (H.264 or VP9) at the frames' real timestamps when ffmpeg is on PATH. 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 5

From the MCP server: browser_start_recording {path} … browser_wait {milliseconds} … browser_stop_recording.

Proxy

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.com
xcelerate::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 CONNECT tunnel; 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.

Persistent profiles

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.com
xcelerate::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.

Accessibility snapshots

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).

Agent snapshots

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.

Workspace layout

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.

Development

# 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.py

The 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.

Security

Found a vulnerability? Please follow SECURITY.md - do not open a public issue. Supported versions, reporting channels, and scope are listed there.

License

Licensed under either of

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.

About

Topics

Resources

Security policy

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages