parley
by Satyajeet-04
README.md
<div align="center">
# Parley
**A lightweight CDP browser-automation framework with first-class AI workflow support.**
Drive any website you're already logged into — read the DOM, fill forms, click, extract data, run JS — over the Chrome DevTools Protocol. Text-only, no screenshots, no vision tokens. Built-in workflows let the AI chats already open in your browser talk to each other and to your coding agent.
[](LICENSE)
[](https://www.python.org/downloads/)
[](https://modelcontextprotocol.io)
[](CONTRIBUTING.md)
[](https://github.com/Satyajeet-04/parley/stargazers)
</div>
<p align="center">
<a href="https://github.com/Satyajeet-04/parley/stargazers">
<img src="docs/bridge-demo.svg" alt="Parley bridges two AIs in real time" width="620"/>
</a>
</p>
<p align="center">
<b>⭐ If Parley saved you tokens or time, please star the repo — it helps other developers find it.</b><br/>
<a href="https://github.com/Satyajeet-04/parley/stargazers">Star Parley on GitHub</a>
</p>
---
---
## Why Parley?
Most "browser AI" tools screenshot the page and feed pixels to a vision model. That is slow and burns tokens. **Parley reads the DOM text directly** through the Chrome DevTools Protocol (CDP), so it is fast, cheap, and works with the sessions you are *already logged into* — no API keys, no per-token billing for the chat models themselves.
It also does the boring-but-hard part right: it **waits for streaming responses to actually finish** before handing you the text, using a `MutationObserver` instead of naive polling. No more half-written answers.
> The name comes from the French *parler*, "to speak" — a *parley* is a conversation between two sides. That is exactly what this does: it lets two AIs (or an AI and your agent) hold a conversation.
Under the hood the core CDP engine is fully general-purpose — AI chat automation is just one workflow built on top of it. You can point the same primitives at news sites, dashboards, documentation, or internal web apps.
### Demo: AI-to-AI Bridge
```
$ python3 parley.py send-wait <gemini_tab> "What is the capital of France?" 60000
{
"send_method": "gemini",
"response_text": "The capital of France is Paris.",
"response_complete": true,
"duration_ms": 5312
}
$ python3 parley.py bridge <chatgpt_tab> <gemini_tab> 1
{
"round": 1,
"source_text": "The capital of France is Paris.",
"target_text": "That's correct! Paris is the capital of France...",
"target_complete": true,
"duration_ms": 8241
}
$ python3 parley.py cookies <gemini_tab> gemini.google.com
{
"count": 3,
"cookies": [
{"name": "COMPASS", "value": "...", "httpOnly": true, "secure": true},
{"name": "NID", "value": "...", "httpOnly": true, "secure": true}
]
}
```
## Features
- **General-purpose CDP engine** — `read-dom`, `extract`, `click`, `type`, `navigate`, `wait-for`, `eval`, and cookie access work on **any** website.
- **Token-efficient** — text extraction only, never screenshots.
- **Universal DOM discovery** — finds the input box and latest response without brittle per-site CSS selectors, with tuned fast-paths for ChatGPT, Gemini, Claude and Grok.
- **Reliable streaming detection** — a `MutationObserver` returns the response only once it stops changing, so you never grab a partial answer.
- **AI-to-AI bridging** — relay a conversation between two tabs (e.g. ChatGPT ↔ Gemini) for N rounds.
- **Self-healing** — auto-reconnects dropped CDP sockets and recovers Gemini's "stuck send button" state via a targeted reload that preserves history.
- **Three ways to use it** — a plain CLI, an [MCP](https://modelcontextprotocol.io) server (Claude Desktop / Cursor / opencode / any MCP client), and a native opencode plugin.
- **Zero heavy deps** — one small dependency (`websocket-client`). No Playwright, no Puppeteer, no headless Chrome download.
### Generic Automation (any website)
```bash
# Extract all headings from a page
python3 parley.py extract <tab_id> h2
# Read a specific DOM element
python3 parley.py read-dom <tab_id> ".main-content"
# Run any JS and get the result
python3 parley.py eval <tab_id> "document.querySelectorAll('a[href]').length"
# Extract session cookies for authenticated API calls
python3 parley.py cookies <tab_id> api.example.com
# Click a button
python3 parley.py click <tab_id> "button[data-testid='submit']"
# Wait for an element to appear (up to 10s)
python3 parley.py wait-for <tab_id> ".result-loaded"
```
## How it works
```
┌──────────────┐ CDP (ws://localhost:9222) ┌──────────────────────────┐
│ Your browser │ <────────────────────────────> │ parley.py │
│ (Brave/Chrome│ │ • Runtime.evaluate (DOM)│
│ logged into │ │ • Input.insertText │
│ ChatGPT, │ │ • MutationObserver wait │
│ Gemini, ...)│ └────────────┬─────────────┘
└──────────────┘ │
┌───────────────┼───────────────┐
│ │ │
CLI usage MCP server opencode plugin
(parley_mcp.py) (opencode/parley.ts)
```
## Architecture
Parley is organized in three layers so the generic engine stays independent of any site-specific knowledge:
```
parley/
├── core.py Core CDP engine — connection, DOM, JS eval, input,
│ navigation, wait_for, cookies. Knows nothing about AI sites.
├── adapters/ Per-site knowledge (selectors, quirks)
│ ├── base.py SiteAdapter base class + URL matching
│ ├── chatgpt.py Gemini / Claude / Grok / Generic adapters
│ ├── gemini.py …
│ ├── generic.py fallback for any website
│ └── js.py injected JS (universal DOM discovery, MutationObserver)
└── workflows.py AI workflows on top: send, send_and_wait, wait_stream,
poll, bridge, robust_send (Gemini stuck-state recovery)
```
`parley.py` (root), `parley_mcp.py`, and `opencode/parley.ts` are all thin layers over these three modules.
## Install
**Requirements:** Python 3.8+, and Brave or Chrome/Chromium.
```bash
git clone https://github.com/Satyajeet-04/parley.git
cd parley
pip install -r requirements.txt
```
### 1. Start your browser with CDP enabled
Parley talks to a browser that has remote debugging turned on. Use the helper:
```bash
./scripts/start-browser.sh
```
Or launch it yourself (re-uses your normal profile, so you stay logged in):
```bash
brave-browser --remote-debugging-port=9222 --remote-allow-origins=*
# or: google-chrome --remote-debugging-port=9222 --remote-allow-origins=*
```
Open tabs for the AIs you want to use (ChatGPT, Gemini, ...) and sign in.
### 2. Try it
```bash
python3 parley.py list
```
You should see your open tabs with their IDs.
## Usage (CLI)
```bash
# List tabs and grab the ID you want
python3 parley.py list
# Send a message and wait for the full reply (recommended)
python3 parley.py send-wait <TAB_ID> "Explain CDP in one sentence."
# Read the latest response from a tab
python3 parley.py read <TAB_ID>
# Make two AIs talk to each other for 3 rounds
python3 parley.py bridge <CHATGPT_TAB_ID> <GEMINI_TAB_ID> 3
```
| Command | Description |
| --- | --- |
| `list` | List all browser tabs with IDs, titles, URLs |
| `read <tab>` | Read the latest AI response (auto-detected) |
| `send <tab> <text>` | Type and submit a message |
| `send-wait <tab> <text> [--timeout ms]` | Send **and wait** for the full response |
| `wait-stream <tab> [timeout_ms]` | Wait for an in-progress response to finish |
| `type <tab> <text>` | Type without submitting |
| `click <tab> <selector>` | Click an element by CSS selector |
| `navigate <tab> <url>` | Navigate a tab to a URL |
| `eval <tab> <js>` | Run JavaScript and return the result |
| `bridge <from> <to> [rounds]` | Relay a conversation between two tabs |
| `read-dom <tab> [selector]` | Read page/element text — works on **any** site |
| `extract <tab> <selector> [attr]` | Extract text/attribute from all matches (scraping) |
| `wait-for <tab> <selector> [timeout_ms]` | Wait for an element to appear |
| `cookies <tab> [domain]` | List cookies (incl. HttpOnly) via CDP |
| `set-cookie <tab> <name> <value> <domain> [path]` | Set a cookie |
### Generic automation example
```bash
# Scrape all links off a page
python3 parley.py extract <TAB_ID> "a" href
# Read an article's body text
python3 parley.py read-dom <TAB_ID> "article"
# Reuse a logged-in session elsewhere (handle cookies as secrets!)
python3 parley.py cookies <TAB_ID> github.com
```
**Configuration** (environment variables):
| Variable | Default | Purpose |
| --- | --- | --- |
| `PARLEY_CDP_HOST` | `localhost` | CDP host |
| `PARLEY_CDP_PORT` | `9222` | CDP port |
## Usage (MCP server)
Parley ships an MCP server so any MCP client can drive your browser. Add it to your client config:
```json
{
"mcpServers": {
"parley": {
"command": "python3",
"args": ["/absolute/path/to/parley/parley_mcp.py"]
}
}
}
```
- **Claude Desktop:** `claude_desktop_config.json`
- **Cursor:** `.cursor/mcp.json`
- **opencode:** add under `mcp` in `opencode.json` (or use the native plugin below)
Tools exposed: `list_tabs`, `read`, `send`, `send_wait`, `wait_stream`, `type`, `click`, `navigate`, `eval`, `bridge`, `read_dom`, `extract`, `wait_for`, `cookies`.
## Usage (opencode plugin)
For a native opencode integration (no MCP needed):
```bash
cp parley.py ~/.opencode/scripts/parley.py
cp opencode/parley.ts ~/.opencode/plugins/parley.ts
```
Restart opencode. You get the tools `browser_list_tabs`, `browser_read`, `browser_send`, `browser_send_wait`, `browser_bridge`, and more. Point it at a different script location with the `PARLEY_SCRIPT` env var if needed.
## Example: make ChatGPT and Gemini debate
```bash
# 1. In your browser open a ChatGPT tab and a Gemini tab, sign into both.
python3 parley.py list
# -> note the two tab IDs
# 2. Seed one side
python3 parley.py send-wait <CHATGPT_ID> "Argue that tabs are better than spaces. One paragraph."
# 3. Relay the debate for 4 rounds
python3 parley.py bridge <CHATGPT_ID> <GEMINI_ID> 4
```
## Troubleshooting
- **`list` returns an error / empty** — the browser is not running with `--remote-debugging-port=9222`, or a different app is on that port. Restart via `scripts/start-browser.sh`.
- **403 on connect** — make sure you launched with `--remote-allow-origins=*`.
- **Response looks empty or truncated** — the model may still be generating; prefer `send-wait` / `wait-stream`, which wait for completion.
- **Gemini stops responding after a while** — Parley auto-recovers by reloading the tab (history is preserved). If it persists, reload the Gemini tab manually.
- **Only the first message works on a service** — you are likely using it logged-out/anonymous (rate-limited). Sign in for full use.
## Roadmap
- [ ] Firefox (via the Remote Protocol) support
- [ ] Structured extraction of code blocks and tables
- [ ] Multi-tab fan-out (ask N models the same prompt in parallel)
- [ ] Optional websocket-free HTTP fallback
## Contributing
Contributions are very welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Adding support for a new AI site is usually just a few selectors.
## Responsible use
Parley is a general-purpose automation tool. With great DOM access comes real responsibility:
- **Respect Terms of Service & robots policies.** Automating a site — especially private APIs or bulk scraping — may violate its terms even when it's your own logged-in session.
- **Rate-limit yourself.** Don't hammer sites; add delays and cap request volume.
- **Cookies are credentials.** `cookies` can read HttpOnly session tokens. Anyone with them can impersonate your login. Never log, print into shared transcripts, or commit them. Store only in memory or a permission-locked, git-ignored file.
- **Mind privacy & data laws.** Only collect data you're allowed to, and handle personal data lawfully.
You are responsible for how you use it.
## Disclaimer
Parley automates *your own* logged-in browser sessions locally. This project is not affiliated with OpenAI, Google, Anthropic, or xAI.
## License
[MIT](LICENSE) © 2026 Satyajeet
---
<div align="center">
If Parley saved you some tokens, consider leaving a ⭐ — it helps others find it.
</div>
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues