From 4060502e49b1a03135fea4da71dd61b7f253029e Mon Sep 17 00:00:00 2001 From: aresbit Date: Fri, 8 May 2026 00:36:27 +0800 Subject: [PATCH 1/2] feat: add CDP_PORT fallback for Termux and manual browser launch Support CDP_PORT env var as fallback when DevToolsActivePort file is unavailable (e.g., Termux Chromium launched with --remote-debugging-port). Auto-discovers the WebSocket URL via HTTP /json/version endpoint. Also adds TERMUX.md with full setup guide and known pitfalls for running headless Chromium on Android/Termux. --- README.md | 4 + TERMUX.md | 130 ++++++++++++++++++++++++++++++ skills/chrome-cdp/scripts/cdp.mjs | 42 +++++++--- 3 files changed, 167 insertions(+), 9 deletions(-) create mode 100644 TERMUX.md diff --git a/README.md b/README.md index 877d4d4..38bc00f 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ Navigate to `chrome://inspect/#remote-debugging` and toggle the switch. That's i The CLI auto-detects Chrome, Chromium, Brave, Edge, and Vivaldi on macOS, Linux, and Windows. If your browser stores `DevToolsActivePort` in a non-standard location, set the `CDP_PORT_FILE` environment variable to the full path. +### Termux (Android) setup + +See [TERMUX.md](TERMUX.md) for running chrome-cdp with headless Chromium on Termux. Key differences: set `CDP_PORT` instead of relying on `DevToolsActivePort` auto-detection, and use `chromium-browser --headless --no-sandbox`. Playwright does not work on Android. + ## Usage ```bash diff --git a/TERMUX.md b/TERMUX.md new file mode 100644 index 0000000..58a1a54 --- /dev/null +++ b/TERMUX.md @@ -0,0 +1,130 @@ +# chrome-cdp on Termux (Android) + +This guide covers running Chromium + chrome-cdp on Termux (Android aarch64/Linux). + +## Prerequisites + +- Termux from F-Droid (not Google Play) +- Node.js 22+ (install via `pkg install nodejs`) +- 512MB+ free RAM for Chromium headless + +## Install Chromium + +```bash +pkg install x11-repo # Chromium is in the x11 repo +pkg install chromium # installs chromium-browser binary +``` + +**Current version**: Chromium 146 (as of 2026-05) + +## Known Pitfalls + +### 1. Chromium requires `x11-repo` + +Chromium is **not** in the default Termux repository. You must add `x11-repo` first: + +```bash +pkg install x11-repo +``` + +### 2. Playwright does NOT work on Android + +```bash +npm install playwright-core +npx playwright-core install chromium # Fails: "Unsupported platform: android" +``` + +Playwright's dependency graph (e.g., `@playwright/browser-chromium`) ships platform-specific binaries and refuses to install on Android. **Do not use Playwright** — use headless Chromium directly. + +### 3. No `DevToolsActivePort` file + +Termux's Chromium does **not** create the `DevToolsActivePort` file that chrome-cdp normally auto-discovers. You must use the `CDP_PORT` environment variable instead. + +### 4. `--headless` has no display output + +Headless Chromium on Termux works correctly — it renders HTML, evaluates JS, takes screenshots, etc. You just won't see a window. + +## Quick Start + +### 1. Launch Chromium with remote debugging + +```bash +chromium-browser \ + --headless \ + --no-sandbox \ + --disable-gpu \ + --disable-dev-shm-usage \ + --remote-debugging-port=9222 & +``` + +Key flags: +- `--headless` — no visible window (required on Termux, no display server) +- `--no-sandbox` — required in Termux (no user namespace support) +- `--disable-gpu` — no GPU available on most Android devices +- `--disable-dev-shm-usage` — avoid `/dev/shm` issues in containerized environments + +### 2. Set the CDP port + +```bash +export CDP_PORT=9222 +``` + +This tells chrome-cdp to discover the WebSocket URL via `http://127.0.0.1:9222/json/version` instead of scanning for `DevToolsActivePort`. + +### 3. Verify connection + +```bash +node scripts/cdp.mjs list +``` + +You should see a list of open pages (may be empty on first launch — Chromium opens a blank page). + +### 4. Navigate to a page and inspect + +```bash +node scripts/cdp.mjs list # copy a target prefix +node scripts/cdp.mjs snap # accessibility snapshot +node scripts/cdp.mjs eval # evaluate JS +``` + +## Automation Script + +Save this as `start-cdp.sh`: + +```bash +#!/data/data/com.termux/files/usr/bin/bash +# Start headless Chromium with CDP on Termux +CHROMIUM=chromium-browser +PORT=${1:-9222} + +pkill -f "$CHROMIUM.*remote-debugging" 2>/dev/null +sleep 1 + +$CHROMIUM \ + --headless \ + --no-sandbox \ + --disable-gpu \ + --disable-dev-shm-usage \ + --remote-debugging-port=$PORT & + +echo "Waiting for CDP on port $PORT..." +for i in $(seq 1 20); do + if curl -s http://127.0.0.1:$PORT/json/version >/dev/null 2>&1; then + echo "CDP ready: http://127.0.0.1:$PORT" + echo "Run: CDP_PORT=$PORT node scripts/cdp.mjs list" + exit 0 + fi + sleep 1 +done + +echo "Timed out waiting for CDP" +exit 1 +``` + +## Environment Variables for Termux + +| Variable | Purpose | Example | +|----------|---------|---------| +| `CDP_PORT` | Port Chromium is listening on (required on Termux) | `export CDP_PORT=9222` | +| `CDP_HOST` | Host address (default: 127.0.0.1) | `export CDP_HOST=127.0.0.1` | +| `CDP_PORT_FILE` | Direct path to DevToolsActivePort (not used on Termux) | — | diff --git a/skills/chrome-cdp/scripts/cdp.mjs b/skills/chrome-cdp/scripts/cdp.mjs index 408deaa..5ac9b1b 100755 --- a/skills/chrome-cdp/scripts/cdp.mjs +++ b/skills/chrome-cdp/scripts/cdp.mjs @@ -35,7 +35,7 @@ function sockPath(targetId) { : resolve(RUNTIME_DIR, `cdp-${targetId}.sock`); } -function getWsUrl() { +async function getWsUrl() { const home = homedir(); // macOS: ~/Library/Application Support//DevToolsActivePort const macBrowsers = [ @@ -80,11 +80,35 @@ function getWsUrl() { }) : []), ].filter(Boolean); const portFile = candidates.find(p => existsSync(p)); - if (!portFile) throw new Error('No DevToolsActivePort found. Enable remote debugging at chrome://inspect/#remote-debugging'); - const lines = readFileSync(portFile, 'utf8').trim().split('\n'); - if (lines.length < 2 || !lines[0] || !lines[1]) throw new Error(`Invalid DevToolsActivePort file: ${portFile}`); - const host = process.env.CDP_HOST || '127.0.0.1'; - return `ws://${host}:${lines[0]}${lines[1]}`; + if (portFile) { + const lines = readFileSync(portFile, 'utf8').trim().split('\n'); + if (lines.length >= 2 && lines[0] && lines[1]) { + const host = process.env.CDP_HOST || '127.0.0.1'; + return `ws://${host}:${lines[0]}${lines[1]}`; + } + } + + // Fallback: CDP_PORT env var (for Termux / manual browser launch) + // When browser is started with --remote-debugging-port=N, there's no + // DevToolsActivePort file — discover the WebSocket URL via HTTP. + const cdpPort = process.env.CDP_PORT; + if (cdpPort) { + const host = process.env.CDP_HOST || '127.0.0.1'; + try { + const resp = await fetch(`http://${host}:${cdpPort}/json/version`); + const data = await resp.json(); + if (data.webSocketDebuggerUrl) return data.webSocketDebuggerUrl; + } catch (e) { + throw new Error( + `CDP_PORT=${cdpPort} but cannot connect to ${host}:${cdpPort}. ` + + `Is Chrome running with --remote-debugging-port=${cdpPort}?\n` + + ` e.g. chromium-browser --headless --no-sandbox --disable-gpu --remote-debugging-port=${cdpPort}\n` + + ` (${e.message})` + ); + } + } + + throw new Error('No DevToolsActivePort found. Enable remote debugging at chrome://inspect/#remote-debugging'); } const sleep = (ms) => new Promise(r => setTimeout(r, ms)); @@ -488,7 +512,7 @@ async function runDaemon(targetId) { const cdp = new CDP(); try { - await cdp.connect(getWsUrl()); + await cdp.connect(await getWsUrl()); } catch (e) { process.stderr.write(`Daemon: cannot connect to Chrome: ${e.message}\n`); process.exit(1); @@ -791,7 +815,7 @@ async function main() { if (cmd === 'list' || cmd === 'ls') { const cdp = new CDP(); - await cdp.connect(getWsUrl()); + await cdp.connect(await getWsUrl()); const pages = await getPages(cdp); cdp.close(); writeFileSync(PAGES_CACHE, JSON.stringify(pages), { mode: 0o600 }); @@ -804,7 +828,7 @@ async function main() { if (cmd === 'open') { const url = args[0] || 'about:blank'; const cdp = new CDP(); - await cdp.connect(getWsUrl()); + await cdp.connect(await getWsUrl()); const { targetId } = await cdp.send('Target.createTarget', { url }); // Refresh cache; new tab may not appear in getTargets immediately, so add it manually const pages = await getPages(cdp); From b402a20ad1f52a30ed29cc72ba08026d88327e09 Mon Sep 17 00:00:00 2001 From: aresbit Date: Fri, 8 May 2026 00:50:46 +0800 Subject: [PATCH 2/2] docs: add viral article about CDP_PORT Termux adaptation story --- chrome-cdp-termux-article.md | 167 +++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 chrome-cdp-termux-article.md diff --git a/chrome-cdp-termux-article.md b/chrome-cdp-termux-article.md new file mode 100644 index 0000000..c8345a3 --- /dev/null +++ b/chrome-cdp-termux-article.md @@ -0,0 +1,167 @@ +# 在手机上跑Chrome远程调试?我把不可能变成了可能 + +> 当AI编码助手遇上Android Termux,一个困扰我两周的问题终于有了答案 + +## 起因:一个"不可能"的需求 + +想象一下这个场景:你躺在沙发上,手里只有一台手机,却想继续调试白天没写完的代码。你的AI编码助手需要访问浏览器页面,但所有浏览器自动化工具都说"Android?不支持。" + +这就是我两周前的困境。 + +我用的AI编码工具依赖一个叫 `chrome-cdp` 的精巧工具——它通过Chrome DevTools Protocol直接连接到浏览器,不需要Puppeteer,不需要重装浏览器,甚至不需要重新登录。但它有一个前提:Chrome必须开着远程调试端口。 + +而问题在于——我在Termux(Android上的终端模拟器)上。没有桌面环境,没有图形界面,连Chrome都得从x11仓库单独安装。 + +## 踩坑日记:每一步都是血泪 + +### 坑1:找不到Chromium + +``` +$ pkg install chromium +E: Unable to locate package chromium +``` + +一上来就吃闭门羹。搜索半天才发现,Chromium藏在 `x11-repo` 里——一个你根本不会想到要安装的仓库。 + +```bash +pkg install x11-repo # 先装仓库 +pkg install chromium # 才能装浏览器 +``` + +### 坑2:Playwright?不存在的 + +既然有了Chrome,那用Playwright来自动化吧?于是我天真地运行: + +```bash +npm install playwright-core +npx playwright-core install chromium +``` + +结果: + +``` +Unsupported platform: android +``` + +Playwright直接拒了。它的底层依赖有平台特定二进制文件,Android不在支持列表里。这条路走不通。 + +### 坑3:DevToolsActivePort 在哪? + +`chrome-cdp` 的工作原理是扫描操作系统各处的 `DevToolsActivePort` 文件来发现Chrome的调试端口。但在Termux上,Chromium根本不生成这个文件。 + +``` +No DevToolsActivePort found. +Enable remote debugging at chrome://inspect/#remote-debugging +``` + +在手机上访问 `chrome://inspect`?这可是headless模式,根本没有界面。 + +## 灵光一现:换个思路 + +既然找不到端口文件,那我直接告诉它端口号不就行了? + +Chrome headless模式下启动时,我可以指定 `--remote-debugging-port=9222`,然后通过 `http://127.0.0.1:9222/json/version` 获取WebSocket URL。 + +但原版 `cdp.mjs` 的 `getWsUrl()` 函数是同步的,只支持读取文件,不支持HTTP发现。源码大概长这样: + +```javascript +function getWsUrl() { + // 扫描 DevToolsActivePort 文件... + const portFile = candidates.find(p => existsSync(p)); + if (!portFile) throw new Error('Not found'); + // 从文件读取端口和路径... + return `ws://${host}:${lines[0]}${lines[1]}`; +} +``` + +我需要做三件事: +1. 把 `getWsUrl()` 改成异步函数 +2. 添加 `CDP_PORT` 环境变量支持 +3. 通过HTTP API自动发现WebSocket URL + +## 解决方案:一行环境变量搞定 + +改动其实不大,但效果显著。新增的fallback逻辑: + +```javascript +const cdpPort = process.env.CDP_PORT; +if (cdpPort) { + const resp = await fetch(`http://${host}:${cdpPort}/json/version`); + const data = await resp.json(); + if (data.webSocketDebuggerUrl) return data.webSocketDebuggerUrl; +} +``` + +现在在Termux上只需要: + +```bash +export CDP_PORT=9222 +chromium-browser --headless --no-sandbox --disable-gpu --remote-debugging-port=9222 & +cdp list +``` + +## 最终效果:手机上的Chrome调试 + +改动完成后,我在手机上做了个测试——访问GitHub issues页面: + +```bash +$ cdp list +9CC23064 Issues · pasky/chrome-cdp-skill · GitHub + +$ cdp snap 9CC23064 +[RootWebArea] Issues · pasky/chrome-cdp-skill · GitHub + [link] Skip to content + [heading] Navigation Menu + ... + [list] Search results + [listitem] Thanks for chrome-cdp-skill #38 + [listitem] mouse drag over canvas #24 + [listitem] Unix socket IPC fails with EPERM... #19 +``` + +成功了。AI编码助手现在可以直接"看"到浏览器页面,理解页面结构,甚至交互操作——全部在手机上完成。 + +## 技术总结:给同样在Termux上挣扎的你 + +``` +# 安装 +pkg install x11-repo chromium nodejs + +# 启动Chromium(headless) +chromium-browser \ + --headless \ + --no-sandbox \ + --disable-gpu \ + --disable-dev-shm-usage \ + --remote-debugging-port=9222 & + +# 设置环境变量 +export CDP_PORT=9222 + +# 开始使用 +cdp list +cdp snap +cdp eval "document.title" +``` + +### 完整踩坑清单 + +| 坑 | 解决方案 | +|----|---------| +| Chromium不在默认仓库 | `pkg install x11-repo` 先装x11仓库 | +| Playwright不支持Android | 放弃Playwright,用headless Chromium直连 | +| 没有DevToolsActivePort | 设置 `CDP_PORT=9222` 环境变量 | +| `--no-sandbox` 必须 | Termux不支持用户命名空间 | +| `--disable-gpu` 必须 | Android通常没有可用GPU | + +## 开源的意义 + +这个改动我已经提交到了 `github.com/aresbit/chrome-cdp-skill`。原项目 `chrome-cdp` 本身就是一个精巧的作品——不用Puppeteer,直接通过WebSocket连接Chrome,每个标签页有独立的持久化守护进程,解决了同类工具的所有痛点。 + +而我做的只是在它优雅的设计基础上,加了一小块拼图:让 `CDP_PORT` 环境变量成为fallback发现机制。这样无论是Termux用户,还是手动启动Chrome的开发者,都能轻松使用。 + +这次经历让我更加确信:好的开源项目就像乐高,每个人都可以贡献一块属于自己的积木。 + +--- + +*如果你也在Termux上折腾过什么有意思的配置,欢迎评论区分享你的踩坑故事 👇*