Install
openclaw skills install @space-cadet/kimi-webbridge-desktopKimi WebBridge lets AI control the user's real browser — navigate, click, type, read, screenshot, and interact with any website using the user's actual login sessions. Use this skill whenever the user wants to interact with websites, automate browser tasks, scrape web content, or perform any action requiring a real browser. Also use when the user mentions "browser", "webpage", "open URL", "screenshot", or asks to read/interact with any website. Use even for simple-sounding browser requests — the daemon handles all complexity.
openclaw skills install @space-cadet/kimi-webbridge-desktopControl the user's real browser (with their login sessions) via a local daemon at http://127.0.0.1:10086.
This skill assumes the user is running the Kimi Desktop App, which bundles the daemon and manages its lifecycle (starts when the app opens, stops when it closes). You should NOT use CLI commands to start/stop/restart the daemon — doing so would conflict with the app.
curl -s http://127.0.0.1:10086/status
Then act on the result:
running: true and extension_connected: true — healthy. Proceed with the tool calls below.running: false, extension_connected: false) — Read references/operations.md in this skill directory. It explains how to recover via the Desktop App.| Tool | Args | Returns | Note |
|---|---|---|---|
navigate | url, newTab(bool), group_title | {success, url, tabId} | Always use newTab:true on first call. group_title sets the tab group's visible label |
find_tab | url, active(bool) | {success, url, tabId} | Reuse an already-open tab — pass any URL or domain; active:true picks the tab the user is currently viewing, default picks the leftmost match |
snapshot | — | {url, title, tree} with @e refs | Accessibility tree (text) — use this to read page content and locate elements |
click | selector (@e ref or CSS) | {success, tag, text} | Synthetic el.click() — does NOT focus [contenteditable]; see Trusted input primitives |
fill | selector, value | {success, tag} | |
evaluate | code (supports async/await) | {type, value} | |
screenshot | format(png|jpeg), quality(0-100), optional selector (@e/CSS) | {format, dataLength, data} (base64) | Full page floods context — use helper script (saves to disk, returns path). With selector only that element is captured (small, OK to call API directly) |
network | cmd(start|stop|list|detail), filter, requestId | request/response data | |
upload | selector, files(string[]) | {success, fileCount} | |
mouse_click | selector (@e ref or CSS) | {success, x, y, tag, text} | CDP real mouse events. Use when click fails OR to focus [contenteditable] |
key_type | text | {success, length} | CDP Input.insertText at cursor of focused element. Required for [contenteditable] |
send_keys | keys (e.g. "Enter", "Escape") | {success, dispatched, os} | Real keyboard events. Use for Enter (submit form) or Escape (close modal). For typing text use key_type |
save_as_pdf | paper_format, landscape, scale, print_background, file_name | {path, sizeBytes, mimeType, pageTitle} | Render current page → PDF, saved under /tmp/kimi-webbridge-pdfs/ |
list_tabs | — | {success, tabs:[{tabId, url, title, active, groupTitle}]} | Inspect tabs in the current session |
close_tab | — | {success, closed: bool} | Close the current tab in the session |
close_session | — | {success, closed: int} | Close all tabs — closed is the count. Always call at task end |
Use find_tab when the user explicitly asks to operate on an already-open tab. It matches by domain, so any URL on the same site works. Without active:true, returns the leftmost matching tab; with active:true, returns the tab the user is currently viewing — pass it when the user says "用我打开的 X" / "在我当前的 X 页面上".
curl -s -X POST http://127.0.0.1:10086/command \
-d '{"action":"find_tab","args":{"url":"https://www.kimi.com","active":true},"session":"kimi"}'
If find_tab returns "no open tab found", the page is not open — fall back to navigate with newTab:true.
curl -s -X POST http://127.0.0.1:10086/command \
-H 'Content-Type: application/json' \
-d '{"action":"navigate","args":{"url":"https://example.com","newTab":true}}'
Each session maps to a separate browser tab group. Use different session names for different sites to keep operations isolated.
Add "session":"name" to the request body:
curl -s -X POST http://127.0.0.1:10086/command \
-d '{"action":"navigate","args":{"url":"https://example.com","newTab":true},"session":"my-task"}'
Always assign distinct session names when working with multiple sites in parallel.
Never call the screenshot API directly — it returns base64-encoded image data (hundreds of KB of text) that will flood the context window.
Use scripts/screenshot.sh instead. It decodes and saves the image to disk, returning only the file path:
# Default — saves to /tmp/kimi-webbridge-screenshots/{timestamp}.png
bash "$(dirname "$SKILL_PATH")/scripts/screenshot.sh"
# With a session
bash "$(dirname "$SKILL_PATH")/scripts/screenshot.sh" -s my-task
# Custom output path
bash "$(dirname "$SKILL_PATH")/scripts/screenshot.sh" -o /tmp/page.png
# JPEG format, quality 60
bash "$(dirname "$SKILL_PATH")/scripts/screenshot.sh" -f jpeg -q 60
After getting the file path, use the Read tool to view the image.
If $SKILL_PATH is unavailable, call the script by its absolute path.
snapshot returns interactive elements with @e refs based on semantic role/name. Use them directly with click/fill — they survive CSS class hash changes that break manually-written selectors.
Fall back to evaluate (JS) only when:
@e ref in the snapshothref)JSON.stringify(data) — never add null, 2 formatting. Indentation and newlines can inflate the response several times over, causing truncation during transmission.click and fill are DOM-level — they cover the vast majority of cases and should be your default. The CDP-level primitives (mouse_click, key_type) exist for specific failures, not as a "more powerful" alternative.
mouse_click| Default | Escalation | When to escalate |
|---|---|---|
click | mouse_click | (a) Element triggers a popup via mousedown/mouseup (search inputs, dropdown triggers). Verify by running el.click() in DevTools — if the popup doesn't open, escalate. (b) You need to focus a [contenteditable] — click fires a synthetic event but leaves document.activeElement on BODY, so a follow-up key_type writes to nothing. |
Don't blindly prefer CDP. mouse_click is more fragile than el.click() on many sites — escalate only with evidence.
| Target | Tool | Why |
|---|---|---|
<input> / <textarea> | fill | Sets value and dispatches input/change. |
[contenteditable] / rich-text editor | key_type (after focusing) | fill cannot — contentEditable has no value. key_type uses CDP Input.insertText, which fires beforeinput/input for editor frameworks (ProseMirror, Lexical, etc.). |
key_type writes at the cursor of the currently focused element — works on inputs too, but fill is simpler for those. To use key_type on [contenteditable], first put real focus on it with one of:
# Option A: real CDP mouse click — places caret as a side-effect of focus
{"action":"mouse_click","args":{"selector":"#editor"}}
# Option B: explicit JS focus + caret placement
{"action":"evaluate","args":{"code":"(()=>{const el=document.querySelector('#editor');el.focus();const r=document.createRange();r.selectNodeContents(el);r.collapse(false);const s=getSelection();s.removeAllRanges();s.addRange(r);})()"}}
The click tool alone is not enough for contenteditable — empirically verified, document.activeElement stays on BODY.
save_as_pdf renders the current page to PDF, writes it to /tmp/kimi-webbridge-pdfs/, and returns the file path (the daemon strips the base64 — agent never sees raw PDF bytes).
All args optional:
paper_format: letter (default) | a4 | legal | a3 | tabloidlandscape: false (default)scale: 1.0 (default), range [0.1, 2.0]print_background: true (default) — keep background colorsfile_name: caller-supplied name; if absent, derived from page titleDecoded PDF cap is 100 MB. Above that the daemon refuses; reduce scale or split the page.
Daemon (bundled by the Kimi Desktop App), extension, and this skill share a 1:1 version string. Read both via:
curl -s http://127.0.0.1:10086/status | jq '{version, extension_version}'
If a tool returns an error containing "Please update the Kimi WebBridge extension", the user's extension is older than this skill. Tell the user:
请更新 Kimi WebBridge 浏览器扩展后重试:https://kimi.com/features/webbridge
Don't retry the failed tool. Don't auto-switch skill versions based on extension_version — the pairing protocol isn't finalized.