toolkit/packages/skills/debug-bridge/SKILL.md
Browser automation and inspection for AI agents via WebSocket
npx skillsauth add stevengonsalvez/agents-in-a-box debug-bridgeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Control web apps via WebSocket. Click, type, screenshot, inspect, capture network traffic, monitor navigation.
The webapp MUST have the debug-bridge SDK installed and configured before agents can control it.
npm install debug-bridge-browser
Create src/debug-bridge.ts (or add to your app's entry point):
import { createDebugBridge } from 'debug-bridge-browser';
// ONLY runs in development - safe to leave in production builds
if (import.meta.env.DEV) {
const params = new URLSearchParams(window.location.search);
const session = params.get('session');
const port = params.get('port') || '4000';
if (session) {
const bridge = createDebugBridge({
url: `ws://localhost:${port}/debug?role=app&sessionId=${session}`,
sessionId: session,
appName: 'My App', // Shows in CLI when connected
appVersion: '1.0.0',
// Feature toggles (all true by default)
enableNetwork: true, // Capture fetch/XHR requests
enableNavigation: true, // Track route changes
enableConsole: true, // Capture console.log/error
enableErrors: true, // Capture unhandled errors
// Optional: filter which URLs to capture
networkUrlFilter: (url) => !url.includes('/analytics'),
maxNetworkBodySize: 10000, // Truncate large request/response bodies
});
bridge.connect();
// Optional: expose for manual debugging
(window as any).__debugBridge = bridge;
}
}
// main.tsx or App.tsx
import './debug-bridge'; // Add this import
?session=X&port=Y from URLrole=approle=agent┌─────────────┐ WebSocket ┌─────────────┐ WebSocket ┌─────────────┐
│ AI Agent │ ◄─────────────────► │ CLI Server │ ◄─────────────────►│ Webapp │
│ │ role=agent │ (port 4000) │ role=app │ (browser) │
└─────────────┘ └─────────────┘ └─────────────┘
Once the webapp has the SDK configured, agents can control it.
# 1. Start debug server (in tmux for persistence)
SESSION="debug-$(date +%s)"
PORT=$(shuf -i 4000-4999 -n 1)
tmux new-session -d -s "$SESSION"
tmux send-keys -t "$SESSION" "npx debug-bridge-cli connect --session $SESSION --port $PORT 2>&1 | tee debug-bridge-$PORT.log" C-m
# 2. Open webapp with debug params (webapp must have SDK installed!)
open "http://localhost:5173?session=$SESSION&port=$PORT"
# 3. Attach to tmux to use CLI
tmux attach -t "$SESSION"
| Command | Example | Description |
|---------|---------|-------------|
| ui | ui | List interactive elements |
| click <target> | click 3 or click "Sign In" | Click element |
| type <target> <text> | type 1 "hello" or type "email" "[email protected]" | Type into input |
| js <code> | js document.title | Run JavaScript, shows result |
| screenshot | screenshot | Save viewport as PNG |
| state | state | Get cookies, localStorage |
| go <url> | go /login | Navigate to URL |
| find <query> | find email | Search UI tree |
| eval <code> | eval localStorage.clear() | Alias for js |
| help | help | Show all available commands |
AI agents can't attach to terminals interactively. Use tmux send-keys to send commands and tmux capture-pane to read output:
# Send a command to the debug-bridge CLI
tmux send-keys -t "$SESSION" "screenshot" C-m
# Wait for result and read output
sleep 2
tmux capture-pane -t "$SESSION" -p | tail -20
# Run JavaScript in the browser
tmux send-keys -t "$SESSION" "js document.title" C-m
sleep 1
tmux capture-pane -t "$SESSION" -p | tail -5
# Trigger events (e.g., for testing visibility handlers)
tmux send-keys -t "$SESSION" 'eval window.dispatchEvent(new Event("focus"))' C-m
Key Pattern for AI Agents:
tmux send-keystmux capture-paneElements can be targeted by:
click 3 (element #3 from ui output)click "Submit" (matches button/link text)type "email" "[email protected]"click btn-656b07 (hash shown in ui output)// Connect as agent
const ws = new WebSocket(`ws://localhost:4000/debug?role=agent&sessionId=my-session`);
// Base message (required fields)
const msg = {
protocolVersion: 1,
sessionId: 'my-session',
timestamp: Date.now(),
requestId: crypto.randomUUID()
};
// Send commands
ws.send(JSON.stringify({ ...msg, type: 'request_ui_tree' }));
ws.send(JSON.stringify({ ...msg, type: 'click', target: { stableId: 'btn-abc' } }));
ws.send(JSON.stringify({ ...msg, type: 'type', target: { selector: '#email' }, text: '[email protected]' }));
ws.send(JSON.stringify({ ...msg, type: 'evaluate', code: 'document.title' }));
ws.send(JSON.stringify({ ...msg, type: 'request_screenshot' }));
ws.send(JSON.stringify({ ...msg, type: 'navigate', url: '/dashboard' }));
// UI Tree response
{ type: 'ui_tree', items: [{ stableId, role, text, label, visible, meta }] }
// Command success
{ type: 'command_result', success: true, result: any, duration: 5 }
// Screenshot
{ type: 'screenshot', data: 'base64...', width: 1920, height: 1080 }
// Error
{ type: 'command_result', success: false, error: { code: 'TARGET_NOT_FOUND', message: '...' } }
// Network request (auto-streamed)
{ type: 'network_request', requestId: 'net-1-1234', method: 'POST', url: '/api/users', initiator: 'fetch' }
// Network response (auto-streamed)
{ type: 'network_response', requestId: 'net-1-1234', status: 200, statusText: 'OK', duration: 45, ok: true, body: '{"id":1}' }
// Navigation event (auto-streamed)
{ type: 'navigation', url: '/dashboard', previousUrl: '/login', trigger: 'pushstate' }
// Console message (auto-streamed)
{ type: 'console', level: 'error', args: ['Failed to fetch user', '{"status":401}'] }
ui # Discover form elements
type "email" "[email protected]" # Fill email field
type "password" "secret123" # Fill password field
click "Sign In" # Click submit button
screenshot # Capture result for verification
go /register # Navigate to page
ui # List interactive elements
type 1 "John" # Fill first input by index
type 2 "[email protected]" # Fill second input
click "Submit" # Submit form
state # Check localStorage for saved data
ui # See current page state
js localStorage.getItem('token') # Check auth token
js window.__REDUX_STATE__ # Inspect app state
screenshot # Capture for analysis
# Network requests are auto-captured - watch the CLI output:
# 🌐 [POST] /api/login
# ✓ 200 OK (45ms)
# 🌐 [GET] /api/users/me
# ✗ 401 Unauthorized (12ms)
# Navigation is also tracked:
# 🔀 [pushstate] /dashboard
# 🔀 [popstate] /login
// Connect as agent and filter for network events
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'network_response' && !msg.ok) {
console.log(`API Error: ${msg.status} on request ${msg.requestId}`);
}
if (msg.type === 'navigation') {
console.log(`User navigated to: ${msg.url}`);
}
};
| Error | Cause | Fix |
|-------|-------|-----|
| TARGET_NOT_FOUND | Element not in DOM | Run ui to refresh, verify element exists |
| TARGET_NOT_VISIBLE | Element off-screen | scroll 0 500 first, then retry |
| EVAL_DISABLED | App disabled eval | Use DOM commands instead |
| SCREENSHOT_FAILED | Modern CSS (oklch) | Use js to inspect DOM directly |
| No connection | Webapp missing SDK | Verify SDK is installed and initialized |
| Missing network events | SDK config | Check enableNetwork: true in config |
| Missing navigation events | SDK config | Check enableNavigation: true in config |
| Telemetry | Auto-sent | Description |
|-----------|-----------|-------------|
| ui_tree | On connect + changes | Interactive elements list |
| dom_mutations | Continuous | DOM changes (batched) |
| console | Continuous | Console.log/warn/error |
| error | On error | Unhandled errors |
| network_request | On fetch/XHR | Outgoing API calls |
| network_response | On response | API responses with status/body |
| navigation | On route change | URL changes (push/pop/replace/hash) |
| state_update | On change | Custom app state (if configured) |
# Port already in use
lsof -ti:4000 | xargs kill -9
# Check if server is running
tmux attach -t debug-*
# View server logs
tail -f debug-bridge-*.log
# Webapp not connecting?
# 1. Check URL has ?session=X&port=Y
# 2. Check browser console for [DebugBridge] logs
# 3. Verify SDK is imported in dev mode
documentation
Report reflect drain spend over a time window — tokens split by cached (cache_read), uncached writes (cache_creation), and io (input+output), with a $ estimate, grouped by day / outcome / model / transcript. Reads the drainer's cost log and surfaces outlier runs and cache-reuse health (the 41.5M-token failure mode = low cache reuse + high cache writes). Use to answer "what is reflection costing me" for the last day / week.
development
Show fleet status — every claude session running on the host, merged across ainb + claude-peers broker + background jobs. Use when you need to enumerate sessions before composing an action, see which sessions have a peer registered (broker-routable) vs tmux-only, check the `summary` of each session, or pipe the list into jq for filtering. Default output: text table. Pass --format json for LLM consumption.
testing
Ordered multi-step prompts to fleet targets, ack-gated between steps via JSONL assistant-turn-end detection. Use for cycles like disconnect→reconnect→verify, or any flow where step N+1 requires step N to have completed first. The skill BLOCKS until each target's transcript shows the next assistant turn finishing OR per-step timeout fires (default 300s).
development
Center control panel — enumerate every claude session that is blocked waiting on something: a user answer (AskUserQuestion fired), an API error retry, an idle assistant turn-end with no follow-up, or an explicit WAITING: marker. Returns rich JSON with signal kind + context per session. Use this when you've stepped away from the fleet and want one place to see everything that wants your attention and answer it.