web-access-claude-skill
Web Access Skill for Claude Code
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.
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:
| Capability | Detail |
|---|---|
| Auto tool selection | WebSearch / WebFetch / curl / Jina / CDP chosen per scenario |
| CDP Proxy | Connects directly to your running Chrome, inherits login state |
| Three click modes | /click (JS), /clickAt (real mouse events), /setFiles (upload) |
| Parallel dispatch | Multiple targets → sub-agents share one Proxy, tab-isolated |
| Site experience store | Per-domain URL patterns, quirks, traps — persisted across sessions |
| Media extraction | Pull image/video URLs from DOM, screenshot any video frame |
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:
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.
Prerequisites
Node.js 22+
node --version # must be >= 22
Enable Chrome Remote Debugging
- Open
chrome://inspect/#remote-debuggingin your Chrome - Check Allow remote debugging for this browser instance
- Restart Chrome if prompted
Verify Dependencies
bash ~/.claude/skills/web-access/scripts/check-deps.sh
Expected output:
✅ Node.js 22.x found
✅ Chrome DevTools reachable at localhost:9222
✅ curl available
CDP Proxy — Core Component
The proxy is a lightweight Node.js WebSocket bridge between Claude and your Chrome instance.
Start the Proxy
# Start in background (auto-exits after 20 min idle)
node ~/.claude/skills/web-access/scripts/cdp-proxy.mjs &
# Confirm it's running
curl -s http://localhost:3456/ping
# → {"status":"ok"}
Full HTTP API Reference
# ── Tab management ──────────────────────────────────────────
# Open new tab, returns tab ID
curl -s "http://localhost:3456/new?url=https://example.com"
# → {"targetId":"ABC123","url":"https://example.com"}
# Close a tab
curl -s "http://localhost:3456/close?target=ABC123"
# → {"closed":true}
# ── Page content ────────────────────────────────────────────
# Execute JavaScript, returns result
curl -s -X POST "http://localhost:3456/eval?target=ABC123" \
-d 'document.title'
# → {"result":"Example Domain"}
# Get full page HTML
curl -s -X POST "http://localhost:3456/eval?target=ABC123" \
-d 'document.documentElement.outerHTML'
# ── Interaction ─────────────────────────────────────────────
# JS click (fast, works for most buttons)
curl -s -X POST "http://localhost:3456/click?target=ABC123" \
-d 'button.submit'
# Real mouse click via CDP (use for upload triggers, canvas elements)
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"]}'
# ── Navigation ──────────────────────────────────────────────
# Scroll to bottom
curl -s "http://localhost:3456/scroll?target=ABC123&direction=bottom"
# Scroll to top
curl -s "http://localhost:3456/scroll?target=ABC123&direction=top"
# ── Visual ──────────────────────────────────────────────────
# Screenshot to file
curl -s "http://localhost:3456/screenshot?target=ABC123&file=/tmp/shot.png"
# Screenshot returned as base64
curl -s "http://localhost:3456/screenshot?target=ABC123"
# → {"base64":"iVBORw0KGgo..."}
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
Jina Usage (Token-Efficient Reads)
# Jina converts any URL to clean markdown — great for docs/articles
curl -s "https://r.jina.ai/https://docs.example.com/api"
Code Examples
Example 1: Open a Page and Extract Data
// 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}`);
Example 2: Login-Gated Page (Uses Existing Chrome Session)
// 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;
}
Example 3: File Upload Automation
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}`);
}
Example 4: Parallel Research with Sub-Agents
// 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);
Example 5: Video Frame Screenshot
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}`);
}
Common Patterns
Pattern: Check Proxy Before CDP Tasks
# Always verify proxy is up before a CDP workflow
curl -s http://localhost:3456/ping || \
node ~/.claude/skills/web-access/scripts/cdp-proxy.mjs &
Pattern: Use Jina for Documentation
# Cheaper and cleaner than WebFetch for text-heavy pages
curl -s "https://r.jina.ai/https://api.anthropic.com/docs"
Pattern: Prefer WebSearch for Discovery, CDP for Execution
1. WebSearch → find the right URLs
2. WebFetch → read static/public content
3. CDP → interact, authenticate, dynamic content
Pattern: Extract All Media URLs
// 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());
Troubleshooting
Proxy won't start
# Check if port 3456 is already in use
lsof -i :3456
# Kill existing proxy
kill $(lsof -ti :3456)
# Restart
node ~/.claude/skills/web-access/scripts/cdp-proxy.mjs &
Chrome not reachable
# Verify Chrome remote debugging is on
curl -s http://localhost:9222/json/version
# Should return Chrome version JSON
# If empty — go to chrome://inspect/#remote-debugging and enable it
/clickAt has no effect
- The element may need scrolling into view first:
curl -s -X POST "http://localhost:3456/eval?target=ID" \
-d 'document.querySelector(".btn").scrollIntoView()'
- Then retry
/clickAt
Page content is empty / JS not rendered
# Add a wait after /new before /eval
curl -s -X POST "http://localhost:3456/eval?target=ID" \
-d 'new Promise(r => setTimeout(r, 3000))'
# Then fetch content
File upload input not found
Some SPAs render <input type=file> only after the trigger click. Always:
/clickAtthe visible upload button first- Wait 500ms
- Then
/setFiles
Sub-agent tabs interfering
Each sub-agent should store its own targetId and never share it. The Proxy is stateless per-tab.
Proxy Auto-Shutdown
The proxy exits automatically after 20 minutes of no requests. For long-running tasks:
# Keep-alive ping in background
while true; do curl -s http://localhost:3456/ping > /dev/null; sleep 300; done &
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.
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
Capability Summary for Task Routing
| User says | Claude 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 |