cdpilot 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -1
- package/bin/cdpilot.js +153 -16
- package/package.json +7 -4
- package/src/cdpilot.py +1637 -161
package/README.md
CHANGED
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
> Zero-dependency browser automation from your terminal. One command, full control.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/cdpilot)
|
|
6
|
+
[](https://www.npmjs.com/package/cdpilot)
|
|
6
7
|
[](LICENSE)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[](https://modelcontextprotocol.io)
|
|
10
|
+
[](https://glama.ai/mcp/servers/mehmetnadir/cdpilot)
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
<div align="center">
|
|
13
|
+
<img src="cdpilot-demo.gif" alt="cdpilot demo" width="600" />
|
|
14
|
+
</div>
|
|
9
15
|
|
|
10
16
|
## Quick Start
|
|
11
17
|
|
|
@@ -26,6 +32,9 @@ AI agents and developers need browser control that **just works**:
|
|
|
26
32
|
- **40+ commands** — Navigate, click, type, screenshot, network, console, accessibility, and more
|
|
27
33
|
- **AI-agent friendly** — Designed for Claude, GPT, Gemini, and any LLM tool-use workflow
|
|
28
34
|
- **Isolated sessions** — Your personal browser stays untouched. cdpilot runs in its own profile
|
|
35
|
+
- **Visual feedback** — Green glow overlay, cursor visualization, click ripples, and keystroke display keep you informed during automation
|
|
36
|
+
- **Multi-project isolation** — Each project gets its own browser instance and port automatically, no conflicts
|
|
37
|
+
- **AI control warning** — Red toast notification appears when you hover during active automation
|
|
29
38
|
- **Privacy-first** — Everything runs locally. No data leaves your machine
|
|
30
39
|
|
|
31
40
|
### Why Brave?
|
|
@@ -271,6 +280,41 @@ Future paid offerings:
|
|
|
271
280
|
|
|
272
281
|
Found a vulnerability? Please email the maintainer directly instead of opening a public issue.
|
|
273
282
|
|
|
283
|
+
## Roadmap
|
|
284
|
+
|
|
285
|
+
The only browser MCP with built-in test assertions. Here's what we've shipped and what's next:
|
|
286
|
+
|
|
287
|
+
### Shipped
|
|
288
|
+
|
|
289
|
+
- [x] 60+ CLI commands (navigate, click, fill, screenshot, PDF, console, network...)
|
|
290
|
+
- [x] MCP server for AI agent integration (Claude Code, Cursor, etc.)
|
|
291
|
+
- [x] **10 built-in test assertions** — assert, assert-url, assert-title, assert-count, assert-value, assert-attr, assert-visible/hidden, wait-for, check (batch), screenshot-diff
|
|
292
|
+
- [x] **Accessibility tree snapshot** (`a11y-snapshot`) — structured data with @ref references, 500x fewer tokens than screenshots
|
|
293
|
+
- [x] **Token-efficient screenshots** — element-level crop (13x smaller), JPEG quality control, format selection
|
|
294
|
+
- [x] **Vision fallback** (`describe`) — a11y + screenshot + text in one call
|
|
295
|
+
- [x] **Annotated screenshots** — @N badge overlays on interactive elements
|
|
296
|
+
- [x] **Auto-wait** — MutationObserver-based, 5s automatic element waiting
|
|
297
|
+
- [x] **Batch commands** — pipe JSON arrays via stdin for multi-step automation
|
|
298
|
+
- [x] Visual feedback system (persistent green glow, cursor, ripples, keystroke display)
|
|
299
|
+
- [x] AI control warning toast (red warning when user interacts during automation)
|
|
300
|
+
- [x] Multi-project browser isolation (each project gets its own port + profile)
|
|
301
|
+
- [x] Pre-flight wizard (auto-installs dependencies on first run)
|
|
302
|
+
- [x] Persistent MCP glow (stays on during entire AI session, like Claude's orange glow)
|
|
303
|
+
- [x] DevExtension system (native JS injection without browser store)
|
|
304
|
+
|
|
305
|
+
### Coming Soon
|
|
306
|
+
|
|
307
|
+
- [ ] **iframe & Shadow DOM** support — interact with elements inside iframes and shadow roots
|
|
308
|
+
- [ ] **Session recording & replay** — record browser sessions and replay them deterministically
|
|
309
|
+
- [ ] **Stealth mode** *(Pro)* — human-like mouse/typing, anti-fingerprint, CAPTCHA solving
|
|
310
|
+
- [ ] **cdpilot Cloud** — hosted browser sessions API, REST + WebSocket MCP endpoint
|
|
311
|
+
- [ ] **Chrome Extension** — use cdpilot from any browser without CLI
|
|
312
|
+
- [ ] **Performance audit** — Core Web Vitals (LCP, CLS, INP) via CDP Performance domain
|
|
313
|
+
- [ ] **WCAG accessibility audit** — automated a11y compliance reporting
|
|
314
|
+
- [ ] **Claude Code Skill mode** — run as a `.claude/skills/` skill in addition to MCP
|
|
315
|
+
|
|
316
|
+
Have an idea? [Open an issue](https://github.com/mehmetnadir/cdpilot/issues) or submit a PR!
|
|
317
|
+
|
|
274
318
|
## Contributing
|
|
275
319
|
|
|
276
320
|
```bash
|
package/bin/cdpilot.js
CHANGED
|
@@ -85,14 +85,13 @@ function findPython() {
|
|
|
85
85
|
|
|
86
86
|
function runSetup() {
|
|
87
87
|
const browser = findBrowser();
|
|
88
|
-
const
|
|
89
|
-
|| path.join(os.homedir(), '.cdpilot', 'profile');
|
|
90
|
-
const port = process.env.CDP_PORT || '9222';
|
|
88
|
+
const config = resolveProjectConfig();
|
|
91
89
|
|
|
92
90
|
console.log('\n cdpilot setup\n');
|
|
93
91
|
console.log(` Browser: ${browser || '❌ Not found'}`);
|
|
94
|
-
console.log(` Profile: ${profileDir}`);
|
|
95
|
-
console.log(` CDP Port: ${port}`);
|
|
92
|
+
console.log(` Profile: ${config.profileDir}`);
|
|
93
|
+
console.log(` CDP Port: ${config.port === '0' ? 'auto' : config.port}`);
|
|
94
|
+
console.log(` Project: ${config.projectId || 'manual mode'}`);
|
|
96
95
|
console.log(` Python: ${findPython() || '❌ Not found'}`);
|
|
97
96
|
|
|
98
97
|
if (!browser) {
|
|
@@ -109,21 +108,97 @@ function runSetup() {
|
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
// Create profile directory
|
|
112
|
-
if (!fs.existsSync(profileDir)) {
|
|
113
|
-
fs.mkdirSync(profileDir, { recursive: true });
|
|
114
|
-
console.log(`\n ✓ Created profile: ${profileDir}`);
|
|
111
|
+
if (!fs.existsSync(config.profileDir)) {
|
|
112
|
+
fs.mkdirSync(config.profileDir, { recursive: true });
|
|
113
|
+
console.log(`\n ✓ Created profile: ${config.profileDir}`);
|
|
115
114
|
} else {
|
|
116
|
-
console.log(`\n ✓ Profile exists: ${profileDir}`);
|
|
115
|
+
console.log(`\n ✓ Profile exists: ${config.profileDir}`);
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
console.log(' ✓ Setup complete! Run: cdpilot launch\n');
|
|
120
119
|
}
|
|
121
120
|
|
|
121
|
+
// ── Pre-flight Check (runs on first launch) ──
|
|
122
|
+
|
|
123
|
+
function checkWebsockets(python) {
|
|
124
|
+
try {
|
|
125
|
+
execSync(`${python} -c "import websockets"`, { stdio: 'pipe' });
|
|
126
|
+
return true;
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function preflight() {
|
|
133
|
+
const markerFile = path.join(os.homedir(), '.cdpilot', '.preflight-done');
|
|
134
|
+
|
|
135
|
+
// Skip if already passed (not first run) and all deps present
|
|
136
|
+
const python = findPython();
|
|
137
|
+
const browser = findBrowser();
|
|
138
|
+
if (fs.existsSync(markerFile) && python && browser && checkWebsockets(python)) {
|
|
139
|
+
return; // All good, skip silently
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(`\n cdpilot v${VERSION} — Pre-flight Check`);
|
|
143
|
+
console.log(' ' + '─'.repeat(35) + '\n');
|
|
144
|
+
|
|
145
|
+
// 1. Python
|
|
146
|
+
if (python) {
|
|
147
|
+
const ver = execSync(`${python} --version 2>&1`, { stdio: 'pipe' }).toString().trim();
|
|
148
|
+
console.log(` ✓ ${ver}`);
|
|
149
|
+
} else {
|
|
150
|
+
console.log(' ✗ Python 3.8+ not found');
|
|
151
|
+
console.log(' → Install: https://www.python.org/downloads/\n');
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 2. websockets
|
|
156
|
+
if (checkWebsockets(python)) {
|
|
157
|
+
console.log(' ✓ websockets');
|
|
158
|
+
} else {
|
|
159
|
+
console.log(' ✗ websockets — installing...');
|
|
160
|
+
try {
|
|
161
|
+
execSync(`${python} -m pip install websockets --quiet --disable-pip-version-check`, { stdio: 'pipe' });
|
|
162
|
+
if (checkWebsockets(python)) {
|
|
163
|
+
console.log(' ✓ websockets (installed)');
|
|
164
|
+
} else {
|
|
165
|
+
console.log(' ✗ websockets install failed');
|
|
166
|
+
console.log(' → Run manually: pip install websockets\n');
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
console.log(' ✗ websockets auto-install failed');
|
|
171
|
+
console.log(' → Run manually: pip install websockets\n');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 3. Browser
|
|
177
|
+
if (browser) {
|
|
178
|
+
const name = path.basename(browser).replace(/\.exe$/i, '');
|
|
179
|
+
console.log(` ✓ ${name} (${browser})`);
|
|
180
|
+
} else {
|
|
181
|
+
console.log(' ✗ No compatible browser found');
|
|
182
|
+
console.log(' → Install Brave (recommended): https://brave.com/download/');
|
|
183
|
+
console.log(' → Or Chrome: https://www.google.com/chrome/\n');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Mark as done
|
|
188
|
+
const dir = path.dirname(markerFile);
|
|
189
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
190
|
+
fs.writeFileSync(markerFile, new Date().toISOString());
|
|
191
|
+
|
|
192
|
+
console.log('\n Ready!\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
122
195
|
// ── Status Command ──
|
|
123
196
|
|
|
124
197
|
function runStatus() {
|
|
125
|
-
const
|
|
126
|
-
|
|
198
|
+
const config = resolveProjectConfig();
|
|
199
|
+
const port = config.port === '0' ? '9222' : config.port;
|
|
200
|
+
const projLabel = config.projectId ? ` [${config.projectId}]` : '';
|
|
201
|
+
console.log(`\n cdpilot status (port ${port})${projLabel}\n`);
|
|
127
202
|
|
|
128
203
|
try {
|
|
129
204
|
const http = require('http');
|
|
@@ -162,6 +237,52 @@ function showVersion() {
|
|
|
162
237
|
console.log(`cdpilot v${VERSION}`);
|
|
163
238
|
}
|
|
164
239
|
|
|
240
|
+
// ── Project-Based Multi-Instance ──
|
|
241
|
+
|
|
242
|
+
function getProjectId() {
|
|
243
|
+
const cwd = process.cwd();
|
|
244
|
+
const dirName = path.basename(cwd).replace(/[^a-zA-Z0-9-]/g, '').slice(0, 20);
|
|
245
|
+
const crypto = require('crypto');
|
|
246
|
+
const hash = crypto.createHash('md5').update(cwd).digest('hex').slice(0, 6);
|
|
247
|
+
return dirName ? `${dirName}-${hash}` : hash;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function resolveProjectConfig() {
|
|
251
|
+
const envPort = process.env.CDP_PORT;
|
|
252
|
+
const envProfile = process.env.CDPILOT_PROFILE;
|
|
253
|
+
|
|
254
|
+
// Full manual override
|
|
255
|
+
if (envPort && envProfile) {
|
|
256
|
+
return { port: envPort, profileDir: envProfile, projectId: null };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const projectId = getProjectId();
|
|
260
|
+
const registryFile = path.join(os.homedir(), '.cdpilot', 'registry.json');
|
|
261
|
+
const defaultProfile = path.join(os.homedir(), '.cdpilot', 'projects', projectId, 'profile');
|
|
262
|
+
|
|
263
|
+
let registry = {};
|
|
264
|
+
try {
|
|
265
|
+
const data = JSON.parse(fs.readFileSync(registryFile, 'utf-8'));
|
|
266
|
+
registry = data.projects || {};
|
|
267
|
+
} catch {}
|
|
268
|
+
|
|
269
|
+
const info = registry[projectId];
|
|
270
|
+
if (info) {
|
|
271
|
+
return {
|
|
272
|
+
port: envPort || String(info.port || 9222),
|
|
273
|
+
profileDir: envProfile || info.profile_dir || defaultProfile,
|
|
274
|
+
projectId,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// New project: let Python allocate port (pass 0 for auto)
|
|
279
|
+
return {
|
|
280
|
+
port: envPort || '0',
|
|
281
|
+
profileDir: envProfile || defaultProfile,
|
|
282
|
+
projectId,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
165
286
|
// ── Help ──
|
|
166
287
|
|
|
167
288
|
function showHelp() {
|
|
@@ -203,6 +324,11 @@ function showHelp() {
|
|
|
203
324
|
new-tab [url] Open new tab
|
|
204
325
|
close-tab [id] Close tab
|
|
205
326
|
|
|
327
|
+
PROJECTS
|
|
328
|
+
projects List all project browser instances
|
|
329
|
+
project-stop <id> Stop a specific project's browser
|
|
330
|
+
stop-all Stop all browser instances
|
|
331
|
+
|
|
206
332
|
AI AGENT
|
|
207
333
|
mcp Start MCP server (stdin/stdout JSON-RPC)
|
|
208
334
|
|
|
@@ -234,6 +360,11 @@ if (cmd === 'status') {
|
|
|
234
360
|
runStatus();
|
|
235
361
|
// Don't exit immediately — let http callback complete
|
|
236
362
|
} else {
|
|
363
|
+
// Pre-flight check on first run or 'launch' command
|
|
364
|
+
if (cmd === 'launch') {
|
|
365
|
+
preflight();
|
|
366
|
+
}
|
|
367
|
+
|
|
237
368
|
// Delegate to Python
|
|
238
369
|
const python = findPython();
|
|
239
370
|
if (!python) {
|
|
@@ -242,16 +373,22 @@ if (cmd === 'status') {
|
|
|
242
373
|
}
|
|
243
374
|
|
|
244
375
|
const browser = findBrowser();
|
|
245
|
-
const
|
|
246
|
-
|| path.join(os.homedir(), '.cdpilot', 'profile');
|
|
247
|
-
const port = process.env.CDP_PORT || '9222';
|
|
376
|
+
const config = resolveProjectConfig();
|
|
248
377
|
|
|
249
378
|
const env = {
|
|
250
379
|
...process.env,
|
|
251
|
-
|
|
252
|
-
CDPILOT_PROFILE: profileDir,
|
|
380
|
+
CDPILOT_PROFILE: config.profileDir,
|
|
253
381
|
};
|
|
254
382
|
|
|
383
|
+
// Only pass CDP_PORT if explicitly set or resolved from registry (not 0)
|
|
384
|
+
if (config.port !== '0') {
|
|
385
|
+
env.CDP_PORT = config.port;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (config.projectId) {
|
|
389
|
+
env.CDPILOT_PROJECT_ID = config.projectId;
|
|
390
|
+
}
|
|
391
|
+
|
|
255
392
|
if (browser && !process.env.CHROME_BIN) {
|
|
256
393
|
env.CHROME_BIN = browser;
|
|
257
394
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdpilot",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Zero-dependency browser automation from your terminal. One command, full control.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cdpilot": "./bin/cdpilot.js",
|
|
@@ -11,9 +11,12 @@
|
|
|
11
11
|
"start": "node bin/cdpilot.js"
|
|
12
12
|
},
|
|
13
13
|
"keywords": [
|
|
14
|
-
"browser
|
|
15
|
-
"
|
|
16
|
-
"
|
|
14
|
+
"browser-automation", "cdp", "chrome-devtools-protocol", "cli",
|
|
15
|
+
"mcp", "ai-agent", "web-scraping", "zero-dependency",
|
|
16
|
+
"headless-browser", "puppeteer-alternative", "playwright-alternative",
|
|
17
|
+
"screenshot", "pdf", "automation", "devtools", "browser",
|
|
18
|
+
"model-context-protocol", "claude", "brave", "testing",
|
|
19
|
+
"web-testing", "accessibility", "a11y"
|
|
17
20
|
],
|
|
18
21
|
"author": "Mehmet Nadir",
|
|
19
22
|
"license": "MIT",
|