web-access-claude-skill

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Web Access Skill for Claude Code

适用于Claude Code的Web访问Skill

Skill by ara.so — Daily 2026 Skills collection.
A skill that gives Claude Code complete internet access using three-layer channel dispatch (WebSearch / WebFetch / CDP), Chrome DevTools Protocol browser automation via a local proxy, parallel sub-agent task splitting, and cross-session site experience accumulation.
ara.so开发的Skill — 属于Daily 2026 Skills系列。
本Skill通过三层通道调度(WebSearch / WebFetch / CDP)、基于本地代理的Chrome DevTools Protocol浏览器自动化、并行子Agent任务拆分以及跨会话站点经验积累,为Claude Code提供完整的互联网访问能力。

What This Project Does

本项目功能

Claude Code ships with
WebSearch
and
WebFetch
but lacks:
  • Dispatch strategy — knowing when to use which tool
  • Browser automation — clicking, scrolling, file upload, dynamic pages
  • Accumulated site knowledge — domain-specific patterns reused across sessions
Web Access fills all three gaps with:
CapabilityDetail
Auto tool selectionWebSearch / WebFetch / curl / Jina / CDP chosen per scenario
CDP ProxyConnects directly to your running Chrome, inherits login state
Three click modes
/click
(JS),
/clickAt
(real mouse events),
/setFiles
(upload)
Parallel dispatchMultiple targets → sub-agents share one Proxy, tab-isolated
Site experience storePer-domain URL patterns, quirks, traps — persisted across sessions
Media extractionPull image/video URLs from DOM, screenshot any video frame
Claude Code原生支持
WebSearch
WebFetch
,但缺少以下能力:
  • 调度策略 — 不清楚何时使用哪种工具
  • 浏览器自动化 — 点击、滚动、文件上传、动态页面处理
  • 站点知识积累 — 跨会话复用特定域名的交互模式
Web访问Skill填补了这三项空白,具备以下能力:
能力详情
自动工具选择根据场景自动选择WebSearch / WebFetch / curl / Jina / CDP
CDP代理直接连接本地运行的Chrome,继承登录状态
三种点击模式
/click
(JS点击)、
/clickAt
(真实鼠标事件)、
/setFiles
(文件上传)
并行调度多目标任务 → 子Agent共享一个代理,标签页相互隔离
站点经验存储按域名保存URL模式、特殊交互逻辑、陷阱 — 跨会话持久化
媒体提取从DOM中提取图片/视频URL,截取任意视频帧截图

Installation

安装方法

Option A — Let Claude install it:
帮我安装这个 skill:https://github.com/eze-is/web-access
or in English:
Install this skill for me: https://github.com/eze-is/web-access
Option B — Manual:
bash
git clone https://github.com/eze-is/web-access ~/.claude/skills/web-access
The skill file is
~/.claude/skills/web-access/SKILL.md
. Claude Code loads all
SKILL.md
files under
~/.claude/skills/
automatically.
选项A — 让Claude自动安装:
帮我安装这个 skill:https://github.com/eze-is/web-access
英文指令:
Install this skill for me: https://github.com/eze-is/web-access
选项B — 手动安装:
bash
git clone https://github.com/eze-is/web-access ~/.claude/skills/web-access
Skill文件路径为
~/.claude/skills/web-access/SKILL.md
。Claude Code会自动加载
~/.claude/skills/
目录下所有
SKILL.md
文件。

Prerequisites

前置依赖

Node.js 22+

Node.js 22+

bash
node --version   # must be >= 22
bash
node --version   # 版本需 >= 22

Enable Chrome Remote Debugging

启用Chrome远程调试

  1. Open
    chrome://inspect/#remote-debugging
    in your Chrome
  2. Check Allow remote debugging for this browser instance
  3. Restart Chrome if prompted
  1. 在Chrome中打开
    chrome://inspect/#remote-debugging
  2. 勾选允许此浏览器实例进行远程调试
  3. 如提示则重启Chrome

Verify Dependencies

验证依赖

bash
bash ~/.claude/skills/web-access/scripts/check-deps.sh
Expected output:
✅ Node.js 22.x found
✅ Chrome DevTools reachable at localhost:9222
✅ curl available
bash
bash ~/.claude/skills/web-access/scripts/check-deps.sh
预期输出:
✅ 已找到Node.js 22.x
✅ Chrome DevTools可通过localhost:9222访问
✅ curl已安装

CDP Proxy — Core Component

CDP代理 — 核心组件

The proxy is a lightweight Node.js WebSocket bridge between Claude and your Chrome instance.
该代理是Claude与本地Chrome实例之间的轻量级Node.js WebSocket桥接工具。

Start the Proxy

启动代理

bash
undefined
bash
undefined

Start in background (auto-exits after 20 min idle)

后台启动(闲置20分钟后自动退出)

node ~/.claude/skills/web-access/scripts/cdp-proxy.mjs &
node ~/.claude/skills/web-access/scripts/cdp-proxy.mjs &

Confirm it's running

确认代理运行状态

→ {"status":"ok"}

→ {"status":"ok"}

undefined
undefined

Full HTTP API Reference

完整HTTP API参考

bash
undefined
bash
undefined

── Tab management ──────────────────────────────────────────

── 标签页管理 ──────────────────────────────────────────

Open new tab, returns tab ID

打开新标签页,返回标签页ID

→ {"targetId":"ABC123","url":"https://example.com"}

→ {"targetId":"ABC123","url":"https://example.com"}

Close a tab

关闭标签页

→ {"closed":true}

→ {"closed":true}

── Page content ────────────────────────────────────────────

── 页面内容 ────────────────────────────────────────────

Execute JavaScript, returns result

执行JavaScript,返回结果

curl -s -X POST "http://localhost:3456/eval?target=ABC123"
-d 'document.title'
curl -s -X POST "http://localhost:3456/eval?target=ABC123"
-d 'document.title'

→ {"result":"Example Domain"}

→ {"result":"Example Domain"}

Get full page HTML

获取完整页面HTML

curl -s -X POST "http://localhost:3456/eval?target=ABC123"
-d 'document.documentElement.outerHTML'
curl -s -X POST "http://localhost:3456/eval?target=ABC123"
-d 'document.documentElement.outerHTML'

── Interaction ─────────────────────────────────────────────

── 交互操作 ─────────────────────────────────────────────

JS click (fast, works for most buttons)

JS点击(速度快,适用于大多数按钮)

curl -s -X POST "http://localhost:3456/click?target=ABC123"
-d 'button.submit'
curl -s -X POST "http://localhost:3456/click?target=ABC123"
-d 'button.submit'

Real mouse click via CDP (use for upload triggers, canvas elements)

通过CDP实现真实鼠标点击(适用于上传触发按钮、画布元素)

curl -s -X POST "http://localhost:3456/clickAt?target=ABC123"
-d '.upload-btn'
curl -s -X POST "http://localhost:3456/clickAt?target=ABC123"
-d '.upload-btn'

File upload via input element

通过输入框实现文件上传

curl -s -X POST "http://localhost:3456/setFiles?target=ABC123"
-H "Content-Type: application/json"
-d '{"selector":"input[type=file]","files":["/tmp/photo.png"]}'
curl -s -X POST "http://localhost:3456/setFiles?target=ABC123"
-H "Content-Type: application/json"
-d '{"selector":"input[type=file]","files":["/tmp/photo.png"]}'

── Navigation ──────────────────────────────────────────────

── 导航操作 ──────────────────────────────────────────────

Scroll to bottom

滚动到底部

Scroll to top

滚动到顶部

── Visual ──────────────────────────────────────────────────

── 视觉操作 ──────────────────────────────────────────────────

Screenshot to file

截图保存到文件

Screenshot returned as base64

截图以base64格式返回

→ {"base64":"iVBORw0KGgo..."}

→ {"base64":"iVBORw0KGgo..."}

undefined
undefined

Three-Layer Channel Dispatch

三层通道调度

The skill teaches Claude to pick the right tool automatically:
Task type                  → Tool choice
─────────────────────────────────────────
General search query       → WebSearch
Static page / docs / API   → WebFetch or Jina
Login-gated / dynamic page → CDP Proxy
Heavy JS / SPA             → CDP Proxy
Video / canvas interaction → CDP Proxy (clickAt)
Bulk text extraction       → Jina (token-efficient)
Raw HTTP / custom headers  → curl
本Skill会引导Claude自动选择合适的工具:
任务类型                  → 工具选择
─────────────────────────────────────────
通用搜索查询       → WebSearch
静态页面/文档/API   → WebFetch或Jina
登录受限/动态页面 → CDP代理
重度JS/SPA页面             → CDP代理
视频/画布交互 → CDP代理(clickAt)
批量文本提取       → Jina(高效节省Token)
原始HTTP/自定义请求头  → curl

Jina Usage (Token-Efficient Reads)

Jina使用(Token高效读取)

bash
undefined
bash
undefined

Jina converts any URL to clean markdown — great for docs/articles

Jina可将任意URL转换为简洁的markdown格式 — 非常适合文档/文章

Code Examples

代码示例

Example 1: Open a Page and Extract Data

示例1:打开页面并提取数据

javascript
// Claude runs this flow via CDP Proxy

// 1. Open tab
const tabRes = await fetch('http://localhost:3456/new?url=https://news.ycombinator.com');
const { targetId } = await tabRes.json();

// 2. Wait for load, then extract top story titles
const evalRes = await fetch(`http://localhost:3456/eval?target=${targetId}`, {
  method: 'POST',
  body: `
    Array.from(document.querySelectorAll('.titleline > a'))
      .slice(0, 10)
      .map(a => ({ title: a.textContent, href: a.href }))
  `
});
const { result } = await evalRes.json();
console.log(JSON.parse(result));

// 3. Clean up
await fetch(`http://localhost:3456/close?target=${targetId}`);
javascript
// Claude通过CDP代理执行以下流程

// 1. 打开标签页
const tabRes = await fetch('http://localhost:3456/new?url=https://news.ycombinator.com');
const { targetId } = await tabRes.json();

// 2. 等待页面加载,然后提取顶部10条标题
const evalRes = await fetch(`http://localhost:3456/eval?target=${targetId}`, {
  method: 'POST',
  body: `
    Array.from(document.querySelectorAll('.titleline > a'))
      .slice(0, 10)
      .map(a => ({ title: a.textContent, href: a.href }))
  `
});
const { result } = await evalRes.json();
console.log(JSON.parse(result));

// 3. 清理资源
await fetch(`http://localhost:3456/close?target=${targetId}`);

Example 2: Login-Gated Page (Uses Existing Chrome Session)

示例2:登录受限页面(复用现有Chrome会话)

javascript
// Chrome already has the user logged in — CDP inherits cookies automatically

async function scrapeAuthenticatedPage(url) {
  // Open tab in the user's real Chrome — no login needed
  const { targetId } = await fetch(`http://localhost:3456/new?url=${url}`)
    .then(r => r.json());

  // Wait for dynamic content
  await fetch(`http://localhost:3456/eval?target=${targetId}`, {
    method: 'POST',
    body: `new Promise(r => setTimeout(r, 2000))`
  });

  // Extract content
  const { result } = await fetch(`http://localhost:3456/eval?target=${targetId}`, {
    method: 'POST',
    body: `document.querySelector('.main-content')?.innerText`
  }).then(r => r.json());

  await fetch(`http://localhost:3456/close?target=${targetId}`);
  return result;
}
javascript
// Chrome已保存用户登录状态 — CDP会自动继承Cookie

async function scrapeAuthenticatedPage(url) {
  // 在用户的Chrome中打开标签页 — 无需重新登录
  const { targetId } = await fetch(`http://localhost:3456/new?url=${url}`)
    .then(r => r.json());

  // 等待动态内容加载
  await fetch(`http://localhost:3456/eval?target=${targetId}`, {
    method: 'POST',
    body: `new Promise(r => setTimeout(r, 2000))`
  });

  // 提取内容
  const { result } = await fetch(`http://localhost:3456/eval?target=${targetId}`, {
    method: 'POST',
    body: `document.querySelector('.main-content')?.innerText`
  }).then(r => r.json());

  await fetch(`http://localhost:3456/close?target=${targetId}`);
  return result;
}

Example 3: File Upload Automation

示例3:文件上传自动化

javascript
async function uploadFile(pageUrl, filePath) {
  const { targetId } = await fetch(`http://localhost:3456/new?url=${pageUrl}`)
    .then(r => r.json());

  // Wait for page
  await new Promise(r => setTimeout(r, 1500));

  // Click the upload trigger button (real mouse event — required for some SPAs)
  await fetch(`http://localhost:3456/clickAt?target=${targetId}`, {
    method: 'POST',
    body: '.upload-trigger-button'
  });

  await new Promise(r => setTimeout(r, 500));

  // Set file on the (possibly hidden) input
  await fetch(`http://localhost:3456/setFiles?target=${targetId}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      selector: 'input[type=file]',
      files: [filePath]
    })
  });

  // Submit
  await fetch(`http://localhost:3456/click?target=${targetId}`, {
    method: 'POST',
    body: 'button[type=submit]'
  });

  // Screenshot to verify
  await fetch(`http://localhost:3456/screenshot?target=${targetId}&file=/tmp/upload-result.png`);

  await fetch(`http://localhost:3456/close?target=${targetId}`);
}
javascript
async function uploadFile(pageUrl, filePath) {
  const { targetId } = await fetch(`http://localhost:3456/new?url=${pageUrl}`)
    .then(r => r.json());

  // 等待页面加载
  await new Promise(r => setTimeout(r, 1500));

  // 点击上传触发按钮(真实鼠标事件 — 部分SPA需要此方式)
  await fetch(`http://localhost:3456/clickAt?target=${targetId}`, {
    method: 'POST',
    body: '.upload-trigger-button'
  });

  await new Promise(r => setTimeout(r, 500));

  // 为(可能隐藏的)输入框设置文件
  await fetch(`http://localhost:3456/setFiles?target=${targetId}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      selector: 'input[type=file]',
      files: [filePath]
    })
  });

  // 提交
  await fetch(`http://localhost:3456/click?target=${targetId}`, {
    method: 'POST',
    body: 'button[type=submit]'
  });

  // 截图验证结果
  await fetch(`http://localhost:3456/screenshot?target=${targetId}&file=/tmp/upload-result.png`);

  await fetch(`http://localhost:3456/close?target=${targetId}`);
}

Example 4: Parallel Research with Sub-Agents

示例4:使用子Agent进行并行调研

javascript
// Instruct Claude to dispatch parallel sub-agents like this:

const targets = [
  'https://product-a.com',
  'https://product-b.com',
  'https://product-c.com',
  'https://product-d.com',
  'https://product-e.com'
];

// Each sub-agent opens its own tab (tab-isolated, same Proxy)
const results = await Promise.all(
  targets.map(async (url) => {
    const { targetId } = await fetch(`http://localhost:3456/new?url=${url}`)
      .then(r => r.json());

    await new Promise(r => setTimeout(r, 2000));

    const { result } = await fetch(`http://localhost:3456/eval?target=${targetId}`, {
      method: 'POST',
      body: `({
        title: document.title,
        description: document.querySelector('meta[name=description]')?.content,
        h1: document.querySelector('h1')?.textContent,
        pricing: document.querySelector('[class*="pric"]')?.innerText?.slice(0,200)
      })`
    }).then(r => r.json());

    await fetch(`http://localhost:3456/close?target=${targetId}`);
    return { url, data: JSON.parse(result) };
  })
);

console.table(results);
javascript
// 引导Claude按以下方式调度并行子Agent:

const targets = [
  'https://product-a.com',
  'https://product-b.com',
  'https://product-c.com',
  'https://product-d.com',
  'https://product-e.com'
];

// 每个子Agent打开独立标签页(标签页隔离,共享同一个代理)
const results = await Promise.all(
  targets.map(async (url) => {
    const { targetId } = await fetch(`http://localhost:3456/new?url=${url}`)
      .then(r => r.json());

    await new Promise(r => setTimeout(r, 2000));

    const { result } = await fetch(`http://localhost:3456/eval?target=${targetId}`, {
      method: 'POST',
      body: `({
        title: document.title,
        description: document.querySelector('meta[name=description]')?.content,
        h1: document.querySelector('h1')?.textContent,
        pricing: document.querySelector('[class*="pric"]')?.innerText?.slice(0,200)
      })`
    }).then(r => r.json());

    await fetch(`http://localhost:3456/close?target=${targetId}`);
    return { url, data: JSON.parse(result) };
  })
);

console.table(results);

Example 5: Video Frame Screenshot

示例5:视频帧截图

javascript
async function screenshotVideoAt(pageUrl, timestampSeconds) {
  const { targetId } = await fetch(`http://localhost:3456/new?url=${pageUrl}`)
    .then(r => r.json());

  await new Promise(r => setTimeout(r, 3000));

  // Seek video to timestamp
  await fetch(`http://localhost:3456/eval?target=${targetId}`, {
    method: 'POST',
    body: `
      const v = document.querySelector('video');
      v.currentTime = ${timestampSeconds};
      v.pause();
    `
  });

  await new Promise(r => setTimeout(r, 500));

  // Capture the frame
  await fetch(`http://localhost:3456/screenshot?target=${targetId}&file=/tmp/frame-${timestampSeconds}s.png`);

  await fetch(`http://localhost:3456/close?target=${targetId}`);
}
javascript
async function screenshotVideoAt(pageUrl, timestampSeconds) {
  const { targetId } = await fetch(`http://localhost:3456/new?url=${pageUrl}`)
    .then(r => r.json());

  await new Promise(r => setTimeout(r, 3000));

  // 将视频定位到指定时间点
  await fetch(`http://localhost:3456/eval?target=${targetId}`, {
    method: 'POST',
    body: `
      const v = document.querySelector('video');
      v.currentTime = ${timestampSeconds};
      v.pause();
    `
  });

  await new Promise(r => setTimeout(r, 500));

  // 截取当前帧
  await fetch(`http://localhost:3456/screenshot?target=${targetId}&file=/tmp/frame-${timestampSeconds}s.png`);

  await fetch(`http://localhost:3456/close?target=${targetId}`);
}

Common Patterns

常用模式

Pattern: Check Proxy Before CDP Tasks

模式:执行CDP任务前检查代理状态

bash
undefined
bash
undefined

Always verify proxy is up before a CDP workflow

执行CDP工作流前务必验证代理是否正常运行

curl -s http://localhost:3456/ping ||
node ~/.claude/skills/web-access/scripts/cdp-proxy.mjs &
undefined
curl -s http://localhost:3456/ping ||
node ~/.claude/skills/web-access/scripts/cdp-proxy.mjs &
undefined

Pattern: Use Jina for Documentation

模式:使用Jina读取文档

bash
undefined
bash
undefined

Cheaper and cleaner than WebFetch for text-heavy pages

对于文本密集型页面,Jina比WebFetch更节省Token且格式更清晰

Pattern: Prefer WebSearch for Discovery, CDP for Execution

模式:WebSearch用于发现,CDP用于执行

1. WebSearch  → find the right URLs
2. WebFetch   → read static/public content
3. CDP        → interact, authenticate, dynamic content
1. WebSearch  → 查找目标URL
2. WebFetch   → 读取静态/公开内容
3. CDP        → 交互操作、登录验证、动态内容处理

Pattern: Extract All Media URLs

模式:提取所有媒体URL

javascript
// Get all images and videos on current page
const media = await fetch(`http://localhost:3456/eval?target=${targetId}`, {
  method: 'POST',
  body: `({
    images: Array.from(document.images).map(i => i.src),
    videos: Array.from(document.querySelectorAll('video source, video[src]'))
               .map(v => v.src || v.getAttribute('src'))
  })`
}).then(r => r.json());
javascript
undefined

Troubleshooting

获取当前页面所有图片和视频URL

Proxy won't start

bash
undefined
const media = await fetch(
http://localhost:3456/eval?target=${targetId}
, { method: 'POST', body:
({     images: Array.from(document.images).map(i => i.src),     videos: Array.from(document.querySelectorAll('video source, video[src]'))                .map(v => v.src || v.getAttribute('src'))   })
}).then(r => r.json());
undefined

Check if port 3456 is already in use

故障排除

代理无法启动

lsof -i :3456
bash
undefined

Kill existing proxy

检查端口3456是否被占用

kill $(lsof -ti :3456)
lsof -i :3456

Restart

终止现有代理进程

node ~/.claude/skills/web-access/scripts/cdp-proxy.mjs &
undefined
kill $(lsof -ti :3456)

Chrome not reachable

重启代理

bash
undefined
node ~/.claude/skills/web-access/scripts/cdp-proxy.mjs &
undefined

Verify Chrome remote debugging is on

无法连接到Chrome

Should return Chrome version JSON

验证Chrome远程调试是否启用

If empty — go to chrome://inspect/#remote-debugging and enable it

/clickAt
has no effect

应返回Chrome版本JSON信息

如果返回空 — 打开chrome://inspect/#remote-debugging并启用远程调试

  • The element may need scrolling into view first:
bash
curl -s -X POST "http://localhost:3456/eval?target=ID" \
  -d 'document.querySelector(".btn").scrollIntoView()'
  • Then retry
    /clickAt
undefined

Page content is empty / JS not rendered

/clickAt
无效果

bash
undefined
  • 元素可能需要先滚动到可视区域:
bash
curl -s -X POST "http://localhost:3456/eval?target=ID" \
  -d 'document.querySelector(".btn").scrollIntoView()'
  • 然后重试
    /clickAt

Add a wait after /new before /eval

页面内容为空/JS未渲染

curl -s -X POST "http://localhost:3456/eval?target=ID"
-d 'new Promise(r => setTimeout(r, 3000))'
bash
undefined

Then fetch content

在/new之后、/eval之前添加等待时间

undefined
curl -s -X POST "http://localhost:3456/eval?target=ID"
-d 'new Promise(r => setTimeout(r, 3000))'

File upload input not found

然后再获取内容

Some SPAs render
<input type=file>
only after the trigger click. Always:
  1. /clickAt
    the visible upload button first
  2. Wait 500ms
  3. Then
    /setFiles
undefined

Sub-agent tabs interfering

找不到文件上传输入框

Each sub-agent should store its own
targetId
and never share it. The Proxy is stateless per-tab.
部分SPA仅在点击触发按钮后才渲染
<input type=file>
,请遵循以下步骤:
  1. 先使用
    /clickAt
    点击可见的上传按钮
  2. 等待500ms
  3. 再使用
    /setFiles

Proxy Auto-Shutdown

子Agent标签页相互干扰

The proxy exits automatically after 20 minutes of no requests. For long-running tasks:
bash
undefined
每个子Agent应保存自己的
targetId
,且绝不共享。代理对每个标签页是无状态的。

Keep-alive ping in background

代理自动关闭

while true; do curl -s http://localhost:3456/ping > /dev/null; sleep 300; done &
undefined
代理在无请求闲置20分钟后会自动退出。对于长时间运行的任务:
bash
undefined

Site Experience Store

后台发送保活请求

The skill accumulates domain knowledge in a local JSON store. When Claude visits
twitter.com
, it reads any saved notes about:
  • Known working URL patterns
  • Login flow quirks
  • Selectors that are stable vs dynamic
  • Rate limiting behavior
This persists across Claude sessions. The store lives at:
~/.claude/skills/web-access/data/site-experience.json
You can inspect or edit it manually to add your own domain knowledge.
while true; do curl -s http://localhost:3456/ping > /dev/null; sleep 300; done &
undefined

Project Structure

站点经验存储

~/.claude/skills/web-access/
├── SKILL.md                    ← This skill file (loaded by Claude)
├── scripts/
│   ├── cdp-proxy.mjs          ← CDP Proxy server (Node.js 22+)
│   └── check-deps.sh          ← Dependency checker
└── data/
    └── site-experience.json   ← Accumulated domain knowledge
本Skill会在本地JSON文件中积累域名相关知识。当Claude访问
twitter.com
时,会读取已保存的以下信息:
  • 已知可用的URL模式
  • 登录流程的特殊处理
  • 稳定与动态选择器
  • 限流行为
这些信息会跨Claude会话持久化。存储文件路径:
~/.claude/skills/web-access/data/site-experience.json
你可以手动查看或编辑该文件,添加自定义的域名知识。

Capability Summary for Task Routing

项目结构

User saysClaude should use
"Search for X"WebSearch
"Read this URL"WebFetch or Jina
"Go to my dashboard on X"CDP (login state)
"Click the submit button on X"CDP
/click
or
/clickAt
"Upload this file to X"CDP
/setFiles
"Research these 5 products"Parallel sub-agents via CDP
"Extract images from X"CDP
/eval
+ DOM query
"Screenshot X at 1:23 in the video"CDP
/eval
seek +
/screenshot
~/.claude/skills/web-access/
├── SKILL.md                    ← 本Skill文件(由Claude加载)
├── scripts/
│   ├── cdp-proxy.mjs          ← CDP代理服务器(需Node.js 22+)
│   └── check-deps.sh          ← 依赖检查脚本
└── data/
    └── site-experience.json   ← 积累的域名知识

任务路由能力汇总

用户指令Claude应使用的工具
"搜索X"WebSearch
"读取这个URL"WebFetch或Jina
"打开我在X上的仪表盘"CDP(复用登录状态)
"点击X上的提交按钮"CDP
/click
/clickAt
"上传文件到X"CDP
/setFiles
"调研这5个产品"并行子Agent + CDP
"从X提取图片"CDP
/eval
+ DOM查询
"截取X视频1分23秒处的画面"CDP
/eval
定位 +
/screenshot