Text-based DOM density maps for LLM browser automation — 79% fewer tokens than screenshots
Project description
dom-density-map
Text-based DOM density maps for LLM browser automation — 79% fewer tokens than screenshots.
Connects to any Chrome instance via CDP (Chrome DevTools Protocol), walks the visible DOM, and renders a character grid showing element density and type. Interactive elements are indexed with labels and pixel coordinates. Designed for LLM agents that need to understand page layout without burning tokens on base64 screenshots.
Quick Start
# Run without installing
uvx dom-density-map https://example.com
# Or install
pip install dom-density-map
dom-density-map https://example.com
Prerequisite: Chrome must be running with remote debugging enabled:
google-chrome --remote-debugging-port=9222
# or on macOS:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222
Usage
# Map the current page
dom-density-map
# Navigate to a URL and map it
dom-density-map https://example.com
# Sparse mode — RLE-compressed, ~420 tokens for a complex page
dom-density-map --sparse --cols 60
# Unicode block art mode
dom-density-map --blocks --cols 80
# Reverse lookup — full DOM stack at a pixel coordinate
dom-density-map --at 694,584
# Reverse lookup — by grid coordinate (prefix 'g')
dom-density-map --at g48,40 --cols 60
# Custom CDP port (default: 9222)
dom-density-map --port 9515 https://example.com
# Works with python -m too
python -m dom_density_map --sparse --cols 60
Output Modes
Default Mode
Full character grid with column rulers. Each cell represents a pixel region:
=== DOM Density Map ===
Page: Example Domain
Viewport: 1920x1080 Grid: 80x45 (24px/cell)
Elements: 42 visible, 5 interactive
Legend: (space)=empty .=1elem :=2-3 #=4-7 @=8+
B=button L=link F=input I=image/video T=text
0 1 2 3
0123456789012345678901234567890123456789
LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
::::::::::::::::::::::::::::::::::::::::
TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
--- Interactive (5) ---
L1: "More information..." at grid(40,22) px(980,540)
Sparse Mode (--sparse)
RLE-compressed with row deduplication — minimal tokens for LLM consumption:
=== DOM Density Map (sparse) ===
Page: Example Domain
Viewport: 1920x1080 Grid: 60x34 (32px/cell)
Elements: 42 visible, 5 interactive
Key: _=empty .=1 :=2-3 #=4-7 @=8+ B=btn L=link F=input I=img T=text
RLE: X5 = XXXXX
r0: L60
r1-r3: :60 (x3)
r4-r8: T60 (x5)
r9-r33: (empty)
--- Interactive (5) ---
L1: "More information..." (980,540)
Reverse Lookup (--at)
Full DOM stack at a specific point — tag, classes, attributes, state, and styles:
=== Elements at px(694,584) ===
Stack depth: 6
[0] <button> data-e2e="like-icon"
class: btn-like active
aria-pressed: true
cursor: pointer
bg: rgb(254, 44, 85)
rect: (680,570) 28x28
[1] <div>
class: action-bar
rect: (670,520) 48x200
...
Python API
import asyncio
from dom_density_map import CDP, get_ws_url, render_sparse_map
async def main():
ws_url = get_ws_url(port=9222)
cdp = CDP(ws_url)
await cdp.connect()
# Navigate
await cdp.navigate("https://example.com", wait=3)
# Run DOM walker
result = await cdp.execute_js("""
// ... DOM_WALKER_JS is available as dom_density_map.core.DOM_WALKER_JS
""")
data = result.get("result", {}).get("value")
# Render
output = render_sparse_map(data, max_cols=60)
print(output)
await cdp.close()
asyncio.run(main())
Or use the JS constants directly:
from dom_density_map.core import DOM_WALKER_JS, ELEMENTS_AT_JS
Token Comparison
| Method | Tokens | Info |
|---|---|---|
| Screenshot (base64 PNG) | ~2,300 | Visual only, no selectors |
dom-density-map (default) |
~1,200 | Layout + all interactive elements |
dom-density-map --sparse |
~420 | RLE-compressed, same info |
dom-density-map --at X,Y |
~550 | Full DOM stack at a point |
Element Types
| Char | Block | Meaning |
|---|---|---|
B |
\u25a3 |
Button (<button>, role="button", submit/reset inputs) |
F |
\u25a4 |
Form input (<input>, <textarea>, <select>, contenteditable) |
L |
\u25a8 |
Link (<a href="...">) |
I |
\u25a7 |
Image/media (<img>, <video>, <canvas>, <svg>) |
T |
\u25a5 |
Text block (element with >20 chars of direct text) |
. |
\u2591 |
1 element |
: |
\u2592 |
2-3 elements |
# |
\u2593 |
4-7 elements |
@ |
\u2588 |
8+ elements |
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dom_density_map-0.1.0.tar.gz.
File metadata
- Download URL: dom_density_map-0.1.0.tar.gz
- Upload date:
- Size: 16.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91ff476ca00e71e97f51977c333f9efc01a4423555987a13ed0fe3b138bce014
|
|
| MD5 |
e947f9ba5d4adcec39f3525bd8650b1d
|
|
| BLAKE2b-256 |
81c9447cb7c7a3e3cb823aeb26c1a12a5b561d18973e369aa0151bae10602660
|
File details
Details for the file dom_density_map-0.1.0-py3-none-any.whl.
File metadata
- Download URL: dom_density_map-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
981a3df84cfcbe8b3b80c8f6137a746b7d26fbd76f0c3c12838b0ce0fd5d67e6
|
|
| MD5 |
5f86660003ae67e7d3aef6cc0b76b7d6
|
|
| BLAKE2b-256 |
d0f4b1102edda604eb66e7c406a5e9b4b94bb4a44b44848beb4ef3fc82aab5f3
|