Skip to main content
Glama
README.md
<div align="center">

# ๐Ÿงญ browser-atlas

**A map of the web for browser agents: where things are, what you may touch, and when to stop.**

[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-3776ab?logo=python&logoColor=white)](pyproject.toml)
[![Dependencies: 0](https://img.shields.io/badge/dependencies-0-7ec699)](pyproject.toml)
[![Sites mapped: 79](https://img.shields.io/badge/sites_mapped-79-d97757)](atlas/sites.json)
[![Button maps: 104 screens](https://img.shields.io/badge/button_maps-104_screens-d97757)](atlas/sites.json)
[![MCP ready](https://img.shields.io/badge/MCP-ready-8b8680)](#-plug-it-into-claude-code)
[![License: MIT](https://img.shields.io/badge/license-MIT-lightgrey)](LICENSE)

[English](README.md) | [ํ•œ๊ตญ์–ด](README.ko.md)

<img src="docs/assets/hero.svg" alt="A Claude Code session using browser-atlas: it looks up KOSIS in the atlas, opens the search page, reads the controls by role and name, clicks a link, and stops before an order button." width="860">

</div>

---

Browser drivers teach an agent **how** to click. Nobody tells it **where** the search box is on a
government statistics site, **whether** that site's terms allow a robot at all, or **that** the
button it is about to press spends your money.

`browser-atlas` is that missing layer: a reviewed, shared map of real sites, a Chrome driver
that reads it, and safety rules written in code instead of in a prompt.

```text
  you โ”€โ”€โ–บ Claude Code โ”€โ”€โ–บ browser-atlas (MCP) โ”€โ”€โ–บ your own Chrome (CDP)
                               โ”‚
              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
              โ–ผ                โ–ผ                  โ–ผ
        atlas/sites.json   safety rules      experience memory
        where, and         stop before       what worked last
        whether at all     pay and submit    time, per site
```

## โœจ What you get

|   |   |
|---|---|
| ๐Ÿ—บ๏ธ **Site atlas** | 79 sites, each with an access level (`open`, `session`, `api`, `blocked`), entry URLs, the official API to prefer, and for blocked ones the reason, quoted from robots.txt or the terms and dated. |
| ๐Ÿ”˜ **Button maps** | 104 real screens with the 5 to 15 controls people actually use, stored as **role + accessible name** (`button "๊ฒ€์ƒ‰"`) instead of brittle CSS. Every one was seen on the live page; all but two pass `check` (a dialog that has no URL, and a signed-in form that hides its fields when the account is at its plan limit). |
| ๐Ÿ›‘ **Safety in code** | Pay, order, submit, send and delete controls stop with *ask the human*. Only a person at a terminal can allow one click (`browser-atlas allow "์ฃผ๋ฌธํ•˜๊ธฐ"`); no MCP tool can. Blocked hosts are refused, even after a redirect. |
| ๐Ÿง  **Experience memory** | Every tab is an episode, judged from signals (captcha means `blocked`, never reading means `fail`). Next time, `browser_open` hands back the routes that worked on that site, best first. |
| ๐Ÿ–ฑ๏ธ **Your own pointer** | Record your mouse for a few minutes and clicks follow your Fitts' law timing and your path shapes, landing on a point that really hits the target. Typing goes key by key, Hangul composition included. |
| ๐Ÿ”Ž **Research mode** | `browser_search` asks five engines at once and interleaves one result per domain, so no single ranking decides what gets read. Long pages start with an outline. |

## ๐Ÿ—บ๏ธ What is in the map

๐Ÿ”˜ marks a site with a button map. Everything else has entry URLs and an access verdict.

| Area | Open | API instead | Blocked, with the quote |
|---|---|---|---|
| ๐Ÿ“Š **Statistics and open data** | KOSIS ๐Ÿ”˜, data.go.kr ๐Ÿ”˜, Seoul Open Data ๐Ÿ”˜, MDIS ๐Ÿ”˜, Data.gov ๐Ÿ”˜, World Bank ๐Ÿ”˜ | Bank of Korea ECOS, V-World | |
| ๐Ÿ›๏ธ **Finance and regulators** | FSS ๐Ÿ”˜, FSC ๐Ÿ”˜, KDIC ๐Ÿ”˜, FINE ๐Ÿ”˜, DART, KRX press, Naver Pay Securities | | Toss Securities, TradingView, IMF Data, Apple IR, NVIDIA IR |
| โš–๏ธ **Law, patents, parliament** | National Law Information Center, USPTO ๐Ÿ”˜ | | KIPRIS, National Assembly bills |
| ๐Ÿฉบ **Health and safety** | KDCA ๐Ÿ”˜, HIRA hospital finder ๐Ÿ”˜, Food Safety Korea ๐Ÿ”˜, WHO ๐Ÿ”˜ | PubMed (E-utilities) | MFDS drug portal |
| ๐ŸŒฆ๏ธ **Weather and environment** | KMA weather ๐Ÿ”˜, KMA open data ๐Ÿ”˜, AirKorea ๐Ÿ”˜ | | |
| ๐ŸŽ“ **Science and learning** | arXiv ๐Ÿ”˜, solved.ac ๐Ÿ”˜, Hacker News ๐Ÿ”˜, DACON ๐Ÿ”˜ | | KCI, Academy Info, JEDEC |
| ๐Ÿ“š **Culture and libraries** | National Library of Korea ๐Ÿ”˜, National Museum of Korea ๐Ÿ”˜, Culture Portal ๐Ÿ”˜, Korea Heritage ๐Ÿ”˜, Namuwiki ๐Ÿ”˜, Watcha Pedia ๐Ÿ”˜, Kyobo Book ๐Ÿ”˜ | | |
| ๐Ÿ“ฐ **Company newsrooms** | Samsung Electronics ๐Ÿ”˜, SK hynix ๐Ÿ”˜, Hyundai Motor ๐Ÿ”˜ | | |
| ๐Ÿ—บ๏ธ **Maps, places, travel** | Kakao Map ๐Ÿ”˜, Naver Map ๐Ÿ”˜, KOBUS ๐Ÿ”˜, Starfield ๐Ÿ”˜, COEX events ๐Ÿ”˜, real estate prices ๐Ÿ”˜ | | SRT, MOLIT transaction prices |
| ๐Ÿ›’ **Shopping and local** | Daangn ๐Ÿ”˜, Joongna ๐Ÿ”˜, Ohouse store ๐Ÿ”˜ | | Baemin |
| ๐ŸŽฎ **Games, 3D and design** | Google Play ๐Ÿ”˜, Steam ๐Ÿ”˜, Steamworks docs ๐Ÿ”˜, Kenney ๐Ÿ”˜, Quaternius ๐Ÿ”˜, Poly Pizza ๐Ÿ”˜, Fab ๐Ÿ”˜, Blender mirror ๐Ÿ”˜, ArchDaily ๐Ÿ”˜, Unsplash ๐Ÿ”˜ | | |
| ๐Ÿ› ๏ธ **Developer consoles** (sign in once by hand) | GitHub repository pages ๐Ÿ”˜, Supabase dashboard ๐Ÿ”˜ | GitHub REST API, Supabase Management API | |
| ๐Ÿค– **AI apps and chat** | | | Claude.ai, Perplexity, Kling AI, KakaoTalk client, Blind |

Each entry is plain JSON ([schema](docs/SCHEMA.md)). Agents decide with the closed fields
(`role`, `name`, `act`, `access`); `where` and `does` are for people, and today they are mostly Korean.
A taste, straight from the file:

```jsonc
"arxiv": {
  "label": "arXiv", "access": "open", "hosts": ["arxiv.org"],
  "pages": {
    "abs": {
      "url": "https://arxiv.org/abs/{arXiv ID}",
      "ready": "!!document.querySelector('#abs')",
      "controls": [
        {"role": "link", "name": "View PDF", "where": "์˜ค๋ฅธ์ชฝ Access Paper ์ƒ์ž ๋งจ ์œ„",
         "act": "download", "does": "๋…ผ๋ฌธ PDF๋ฅผ ์—ฐ๋‹ค", "css": "a[href^='/pdf/']"},
        {"role": "button", "name": "Export BibTeX Citation", "where": "์˜ค๋ฅธ์ชฝ Export BibTeX ์ƒ์ž",
         "act": "expand", "does": "BibTeX ์ธ์šฉ๋ฌธ์„ ํ™”๋ฉด์— ํŽผ์นœ๋‹ค"},
        ...
```

## โšก Plug it into Claude Code

```bash
git clone https://github.com/Gachon-junghyun/browser-atlas
cd browser-atlas
pip install -e .          # standard library only, nothing else is pulled in

claude mcp add browser-atlas -- python -m browser_atlas mcp
```

Then just ask:

```text
> open KOSIS and find the latest unemployment table
> what's new on arXiv's cs.AI list today?
> look up a company's filings on DART, but don't submit anything
```

The agent starts its own Chrome with a separate profile on a debug port (`browser_launch`), so
your everyday browser is never touched. When a site needs a login, sign in by hand once in that window.

## ๐Ÿงฐ CLI

```bash
python -m browser_atlas launch --port 9222          # Chrome with its own profile
python -m browser_atlas guard https://example.com   # may an agent act here?
python -m browser_atlas controls --tab example.com  # role and name of what is on screen
python -m browser_atlas check kosis                 # are the mapped controls still there?

python -m browser_atlas atlas validate              # schema, safety and personal-trace checks, offline
python -m browser_atlas atlas merge new.json --dry-run

python -m browser_atlas mouse record --seconds 300  # teach it your mouse
python -m browser_atlas mouse fit
python -m browser_atlas mouse plot                  # grey: yours, blue: generated
```

## ๐Ÿ›ก๏ธ The rules it lives by

- **A captcha or a rate limit means stop for the day.** It does not solve captchas and never will.
- **Terms first.** A site whose robots.txt or terms forbid automation goes in as `blocked`, with the
  sentence quoted and dated, and the pointer will not act there at all.
- **The map can only tighten.** Whether a control is irreversible is decided on your machine from the
  live control. Shared map text can mark more things dangerous, never fewer.
  [Why shared text must not become instructions](docs/SCHEMA.md#trust-model-shared-text-must-not-become-instructions)
- **Page text is data.** Everything read from a page comes back labelled `untrusted_page_content`.
- **Your traces stay home.** Mouse recordings, credentials and private sites live in `~/.browser_atlas/`
  and never enter this repository. Credentials go from the OS store straight into the page; the model
  never sees them.

## ๐Ÿ“š Learn once, share on review

Every run leaves something behind, and the next run starts from it:

- **Routes** are recorded by the server itself; `browser_open` hands back the ones that worked on that site.
- **Lessons** are one-sentence pitfalls (`lesson_record`, or `browser-atlas lesson <site> "..."`). The server's
  instructions ask the agent to leave one whenever something tripped it up, and `browser_open` returns the
  page's `map_traps` before the next attempt.
- **Renamed or new buttons** found while working are applied to your own map at once.

All of it stays on your machine. When you say "share it", `share_upload` sends a sanitized copy to a
**review queue**, GitHub-style: anyone can submit without an account, nobody but maintainers can read the
queue, and only what a maintainer merges (`browser-atlas share review`, `share merge ID`) becomes public.
Everyone then pulls merged rows with `share sync`.

## ๐Ÿค Add a site

The map grows by pull request. One site is one JSON object.

1. Open the site with the MCP tools and collect controls with `browser_read(mode="controls")`.
   Write role and name exactly as Chrome exposes them, and mark result-dependent names `dynamic`.
2. Read robots.txt and the terms. If either forbids automation, the entry is `blocked` with the quote.
3. Run `python -m browser_atlas atlas validate new.json`, then `check <site>` until nothing is missing.
4. Run `python -m browser_atlas atlas merge new.json` and open a pull request.

Field notes from real sites (iframes, overlays, sites that rename a button between screens) are in
[docs/LESSONS.md](docs/LESSONS.md). Read them before your first map; they will save you an evening.

## ๐Ÿ”ฌ Under the hood

<details>
<summary><b>Experience memory: a loop that runs even when the agent forgets it</b></summary>

The MCP server records every browser call itself. One tab is one episode; when the tab is closed,
sits idle for 15 minutes, or the server exits, the episode is judged from signals and stored:
captcha or rate limit means `blocked`, never reading content means `fail`, ending on an error means
`partial`, otherwise `success`, with quality lowered by errors and wasted steps.

`browser_open(url, goal=...)` returns `past_routes` for that site and goal, ranked by similarity,
Bayesian route quality and recency, plus one rarely tried route to explore. Failed routes fade and
confirmed ones rise. `memory_rate` lets a human or a second source re-grade.

Button-map repairs are learned too: a mapped control that goes missing and then succeeds under
another name becomes a rename, and a working control that is not mapped becomes a new one. They go
into your personal layer (`~/.browser_atlas/sites.learned.json`) and apply from the next call.

</details>

<details>
<summary><b>How the mouse learner works</b></summary>

1. **Record**: poll the cursor and the left button (Windows, via `ctypes`). Keys are never recorded.
2. **Segment**: one movement runs from the last rest (120 ms without motion) to the moment the cursor
   reaches within 3 px of a click. Drags and wandering paths are dropped.
3. **Normalize**: rotate and scale each movement onto (0,0) to (1,0) and resample it on a normalized
   time axis, so the shape *and* the speed profile are kept.
4. **Fit**: least squares on `T = a + bยทlog2(1 + D/W)` gives your speed; the normalized paths become the library.
5. **Plan**: for a new start and end, blend two of your movements recorded at a similar distance and
   direction, stretch the blend onto the new segment, and give it a Fitts-predicted duration.

Your recordings are a behavioural fingerprint. They stay in `~/.browser_atlas/mouse/` and are never
part of this repository.

</details>

<details>
<summary><b>Python API</b></summary>

```python
from browser_atlas import CDP, Pointer

with CDP(9222, match="example.com") as c:
    p = Pointer(c)                     # your fitted style if present, else a minimum-jerk curve
    p.click_control("button", "Search")
    p.type_control("textbox", "Company", "Example", clear=True)
```

</details>

## ๐Ÿงช Tests

```bash
python -m unittest discover tests
```

## ๐Ÿ“„ License

MIT. The map describes public pages; each site's own terms still govern how you use it.

TDQS

B3.4/5.0

Scored across 29 tools

Disambiguation4/5

Tools are grouped by clear namespaces and each has a distinct job: navigation (open/goto/back), interaction (click/type/wait/dismiss), inspection (read/screenshot/tabs), memory (recall/record/rate/stats), atlas lookup (find/site/guard) and sharing (preview/upload/sync). The closest pairs โ€” browser_open vs browser_goto (new tab vs same episode), browser_close vs browser_close_mine, memory_record vs memory_rate, lesson_record vs map_learned โ€” are each differentiated in their descriptions, though memory/lesson/atlas tools still require careful reading to keep apart.

Naming Consistency4/5

The dominant pattern is namespace-prefixed snake_case (browser_*, memory_*, atlas_*, share_*) with verb-last or verb-first sub-names, which is predictable and readable. Only a few strays break the pattern: lesson_record and map_learned are noun_verb while their peers are verb_noun, and login_sites is noun_noun with no namespace, so the shape is mostly but not fully uniform.

Tool Count3/5

29 tools is on the heavy side for one server, and a few are mergeable: atlas_site is largely a detail view of atlas_find results, browser_close and browser_close_mine could be one tool with a scope argument, and memory_stats is a thin reporting wrapper. The breadth (browser control plus memory plus atlas plus sharing) does justify many tools, so it is heavy rather than bloated, but it sits just past the comfortable range.

Completeness4/5

The browser lifecycle is well covered end to end: launch, tabs, open/goto/back, read with outlines and offsets, click, type, wait, dismiss, batched run, screenshot, close with verdict, plus login and remembered credentials. Remaining gaps are minor but real โ€” no scroll or raw key-press tool, no file upload or download handling, and no explicit select/option control โ€” all of which an agent would have to improvise around.

Maintenance

ActivityMaintained
ResponsivenessNo issues