ax-grep turns HTML or a live DOM into a compact semantic tree that is easier
for agents and automation code to inspect than raw markup.
It is built for browser extensions, injected scripts, WebView bridges, Workers, and agent pipelines that need page structure without Chrome DevTools Protocol or a platform accessibility API.
import { extract, formatSemanticTreeText } from "ax-grep";
const html = await fetch("https://example.com").then((response) => response.text());
const tree = extract(html);
console.log(formatSemanticTreeText(tree));document
main
heading 'Example Domain'
p 'This domain is for use in illustrative examples...'
[i] link 'More information...'
ax-grep is not a replacement for a real browser accessibility tree. It
approximates one from DOM, ARIA, computed style, labels, focusability, and
element state.
pnpm add ax-grepnpm install ax-grep| Situation | Use |
|---|---|
HTML string from fetch(), SSR, or a Worker |
extract(html) from ax-grep |
| Small Worker/static-only bundle | extract(html) from ax-grep/static |
| Code already running inside the page | extract() from ax-grep/browser |
| Puppeteer, Playwright, WebView, or another external page controller | createExtractorScript() from ax-grep |
Use the root entry when you have an HTML string. This path does not create a DOM with jsdom and does not launch a browser.
import { extract } from "ax-grep";
const response = await fetch("https://example.com");
const html = await response.text();
const tree = extract(html);The root extract(html) function is the same static extractor exposed at
ax-grep/static.
import { extract } from "ax-grep/static";
const tree = extract(html, {
includeAttributes: false,
});Static extraction can infer roles, names, labels, ARIA state, links, forms, headings, tables, and lists from SSR markup. It also applies conservative prompt-size controls by default:
- skips non-semantic payload tags such as
script,style, andtemplate - prunes hidden markup and collapsed controlled regions
- summarizes very large child lists and repeated template-like subtrees
- detects broad wiki-like and forum-like HTML shapes to tune summarization
Static extraction cannot see computed CSS, layout bounds, client-rendered DOM, shadow DOM, iframe contents, or post-load mutations.
Use createExtractorScript() when you control a page from the outside. This is
the right path for Puppeteer, Playwright, Android WebView, iOS WKWebView, or an
agent browser that can evaluate JavaScript.
import { createExtractorScript } from "ax-grep";
const tree = await page.evaluate(createExtractorScript());Playwright example:
import { chromium } from "playwright";
import { createExtractorScript, formatSemanticTreeText } from "ax-grep";
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
const tree = await page.evaluate(createExtractorScript({
includeBounds: false,
includeAttributes: false,
}));
console.log(formatSemanticTreeText(tree));
await browser.close();WebView-style injection works the same way:
import { createExtractorScript } from "ax-grep";
const script = createExtractorScript({
mode: "interactive",
format: "json",
});
// Android: webView.evaluateJavascript(script, callback)
// iOS: webView.evaluateJavaScript(script, completionHandler)Use ax-grep/browser when your code is already executing in the page, such as a
browser extension content script.
import { extract, formatSemanticTreeText } from "ax-grep/browser";
const tree = extract({
mode: "interactive",
includeBounds: false,
});
console.log(formatSemanticTreeText(tree));extract() returns a SemanticNode tree:
type SemanticNode = {
id: string;
tag: string;
role: string | null;
name: string;
interactive: boolean;
focusable: boolean;
selector?: string;
xpath?: string;
text?: string;
value?: string;
state?: Record<string, unknown>;
attributes?: Record<string, string>;
children: SemanticNode[];
};Use formatSemanticTreeText(tree) for a compact prompt-friendly text view, or
flattenSemanticTree(tree) and summarizeSemanticTree(tree) for analysis and
benchmarks.
import { extract } from "ax-grep/static";
import { formatSemanticTreeText } from "ax-grep";
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url).searchParams.get("url");
if (!url) return new Response("Missing url", { status: 400 });
const response = await fetch(url);
const html = await response.text();
const tree = extract(html);
return new Response(formatSemanticTreeText(tree), {
headers: { "content-type": "text/plain; charset=utf-8" },
});
},
};The common options are shared across browser and static extraction where they make sense.
const tree = extract(html, {
mode: "compact",
includeAttributes: false,
includeHidden: false,
includeSelectOptions: true,
maxTextLength: 240,
});Useful options:
| Option | Default | Notes |
|---|---|---|
mode |
"compact" |
Use "interactive" to keep mostly actionable nodes. |
includeAttributes |
true |
Turn off for smaller prompt payloads. |
includeHidden |
false |
Keep hidden/collapsed content only when needed. |
includeSelectOptions |
true |
Useful for agent planning, verbose for huge selects. |
includeTextNodes |
browser: true, static: false |
Static extraction relies more on semantic names by default. |
maxTextLength |
240 |
Clips long direct text/name fragments. |
excludeLikelyAds |
false |
Optional heuristic pruning for benchmark or prompt use. |
summarizeLargeSubtrees |
static: true |
Keeps SSR payloads bounded. |
summarizeLikelyLinkFarms |
static: true |
Helps forum/sidebar/navigation-heavy pages. |
import { observeSemanticTree } from "ax-grep/browser";
const observer = observeSemanticTree((change) => {
console.log(change.mutationCount, change.tree);
}, { debounceMs: 50 });
observer.disconnect();For injected-script use, createObserverScript() installs an observer on
window.__AX_LITE_OBSERVER__ and dispatches __AX_LITE_OBSERVER__:change
events.
- It does not call the browser accessibility tree API.
- It does not use DevTools Protocol or CDP.
- It does not run JavaScript for static HTML input.
- It does not bypass login, bot checks, or site challenges.
- It does not guarantee identical output to Playwright or
agent-browseraccessibility snapshots.
pnpm compare:sample
pnpm compare:static https://example.com https://news.ycombinator.com
pnpm compare:tokens https://example.com https://news.ycombinator.com
pnpm compare:static:korea-social
pnpm compare:tokens:korea-social
pnpm compare:static:china-japan
pnpm compare:tokens:china-japanThe comparison scripts compare ax-grep output with agent-browser snapshot
output and estimate token cost for compact agent prompts. See
docs/comparison-baseline.md for the current baseline run.
Current benchmark suites include:
- static HTML vs browser snapshots
- token-cost comparison for compact prompt text
- Korean forum/search/social targets
- Chinese and Japanese wiki/news/forum/search targets
- challenge and volatile-page diagnostics
ax-grep is early-stage software. The public API is intentionally small:
extract(html)fromax-greporax-grep/staticextract()fromax-grep/browsercreateExtractorScript()andcreateObserverScript()fromax-grep
The long-form aliases extractStaticSemanticTree() and extractSemanticTree()
are kept for compatibility.