legostin/wirepane

macOS 전용 Claude Code 모드로, 로컬 HTTPS 프록시를 시작하고 채팅 옆 창에 실시간 API 트래픽을 표시하여 Claude가 도구를 통해 요청을 읽을 수 있게 합니다. HTTP/2, gRPC, WebSockets, SSE를 지원하며 응답을 모의하거나 지연시킬 수 있습니다.
legostin/wirepane

이 모드는 로컬 HTTPS 프록시를 시작하는 /proxy 명령을 노출합니다. 캡처된 요청은 채팅 옆 창에 나타나고, Claude는 도구를 통해 이를 검사할 수 있으므로 "로그인을 탭했을 때 앱이 무엇을 보냈나요?" 또는 "왜 이것이 Android에서는 실패하지만 웹에서는 실패하지 않나요?"와 같은 질문을 할 수 있습니다. HTTP/2, gRPC, WebSockets, SSE를 처리하고 클라이언트 동작을 테스트하기 위해 응답을 모의하거나 지연시킬 수 있습니다. 시뮬레이터, 에뮬레이터 또는 실제 휴대폰을 프록시로 지정하는 것은 버튼 하나로 가능합니다. 현재 macOS 전용이며 일부는 거칩니다. 소스는 https://github.com/legostin/wirepane에 있습니다.
먼저 작성자의 README에서 marketplace와 플러그인 이름을 확인하세요. 저장소 구조에 따라 명령어가 달라질 수 있습니다.
claude plugin marketplace add legostin/wirepane claude plugin install wirepane@wirepane
Wirepane turns Claude Code into an HTTPS debugging proxy for the browser, the iOS Simulator, iPhones, the Android emulator and Android phones. It is a mod: a plugin of function hooks. It decrypts HTTP/1.1, HTTP/2, gRPC, WebSockets and server-sent events, shows them in a pane next to your conversation, and gives Claude the same traffic through 16 tools.
So instead of copying requests into the chat, you ask:
"Why does checkout return 402 on the phone but not in the browser?" "Wait while I tap Log in, then tell me what the app sent." "Make the feed take 3 seconds and fail one request in five." "Answer the price socket with a mock that sends a tick every subscription." "Nothing shows up from the emulator. Fix it."
Claude finds the request, reads only the part it needs, compares the one that works with the one that fails, replays it with a change, writes a rule, and fixes your code. When something is in the way, a missing CA, a pinned certificate, a VPN or Charles holding the system proxy, the doctor names it and fixes it.
Website · Install · What Claude can do · Doctor · Rules · Limits · Design notes
You need macOS, Claude Code 2.1.292 or newer, and openssl (built into macOS).
The proxy runs on Node.js 18 or newer and finds it by itself: on PATH, or where Homebrew, Volta, nvm, fnm, mise, asdf, nodenv or MacPorts put it. With no Node at all, the pane offers to install it.
At the Claude Code prompt:
/plugin install wirepane --marketplace legostin/wirepane
Or from a shell:
claude plugin marketplace add legostin/wirepane
claude plugin install wirepane@wirepane
Then run /proxy. The proxy starts on 127.0.0.1:8899, the pane opens, and an empty list offers the ways in it found on this Mac: a browser, a booted simulator, an emulator, a phone.
● 127.0.0.1:8899 · 342 requests [ Stop ] View [ List ] [ Tree ] [ Clear ] [ Setup ] [ Rules (2) ] [ Health (1) ]
Filter host:*.api.example.com is:error
401 POST https://api.example.com/v1/login 1.2KB 180ms
200 POST ✎ https://api.example.com/pkg.Cart/Checkout 41B 95ms
101 GET wss://api.example.com/chat ↑14 ↓52 …
CERT CONNECT gateway.icloud.com:443 0B 0ms
It reads every protocol a modern app speaks.
gRPC NOT_FOUND: no such user.application/x-protobuf), decoded the same way.It reaches every client in one press.
adb reverse for Android phones on USB, so no Wi-Fi is needed.It changes traffic, not just shows it. A rules engine for requests, responses and WebSocket messages:
Claude writes rules in plain words. You manage them in the Rules view.
It works on office networks. An upstream proxy carries every connection to the servers: HTTP, HTTPS, HTTP/2, tunnels and WebSockets. It can be an HTTP proxy that signs in with Basic or Windows NTLM credentials, SOCKS5, or a PAC file that picks per URL. The doctor offers the network's own proxy for it.
It tells you what is wrong, and fixes it. The doctor checks the proxy, the system proxy, VPNs, other proxy apps, the CA on each client, pinned hosts, upstream failures and Android devices. Each finding names its fix, and many have a button. The proxy repairs some things on its own:
It keeps Claude's context small. Long URLs are cut, requests are read part by part, and bodies share one budget. json_path picks one field of a big body. since and wait_for_request replace polling.
One proxy, every session. A single proxy serves every Claude Code session on the Mac:
It runs locally.
~/.claude/proxy-mod.| Tool | What it does |
| --- | --- |
| list_requests({ filter?, since?, limit? }) | The proxy's state and one line per request: id, method, status, URL (long ones cut), size, time, type, WebSocket and event counts, rules, error. since lists only what is new. |
| get_request({ id, part?, json_path?, max_chars?, from?, limit? }) | One request, by part: summary, headers, request, response, messages, all. Bodies share one budget. JSON is compact when pretty would not fit, and json_path picks one field. gRPC, protobuf, trailers, WebSocket messages and events are included. |
| search_requests({ text, where?, filter? }) | Which requests hold some text in their URL, headers, bodies, messages or events, with the text in context. |
| wait_for_request({ filter, timeout_s?, until? }) | Waits for the request the person is about to trigger, and answers it the moment it ends. |
| diff_requests({ a, b }) | What differs between two requests: method, URL, query, headers, status, JSON field by field. |
| replay_request({ id, method?, url?, headers?, body?, json? }) | Sends a request again, as it was or changed, through the proxy, so it is recorded and rules apply. |
| resume_request({ id, action?, changes?, respond? }) | Lets go of an exchange held at a breakpoint: as it was, changed (method, URL, headers, body, status), answered by hand, or cut. |
| send_ws_message({ id, to, text \| json \| b64 }) | Injects a message into a live WebSocket, to the client or to the server. |
| close_websocket({ id, code?, reason? }) | Closes a live WebSocket, to test reconnects. |
| add_rule, update_rule, remove_rule, list_rules | The rules file, applied at once. |
| track_domains({ add?, remove?, set?, enabled? }) | Decrypt and record only the app's hosts, and see what passed through. |
| export_har({ filter?, file? }) | HAR 1.2 with bodies and WebSocket messages, for a teammate or Chrome DevTools. |
| diagnose() | The doctor: every check, each finding with its fix. |
The plugin also ships three skills that Claude loads when the task calls for them:
wirepane-debugging: the order that works, from filter to summary to part, then search, diff, replay and verify. Also how to read gRPC, WebSocket and SSE traffic cheaply.wirepane-troubleshooting: symptom, check, fix, for everything the doctor knows. It includes the fix in your own app: Android network_security_config, OkHttp and iOS pinning in debug builds, Flutter HttpOverrides, and proxy settings for Node, Go, Python, Java, Docker and Unity.wirepane-rules: recipes for mocks, latency, chaos, JSON rewrites, GraphQL operations and WebSocket mocks. Every one is tested to validate./proxy doctor, the Health view (h), or Claude's diagnose check:
| Check | Finds | Fix |
| --- | --- | --- |
| The proxy and its process | Stopped, failed, a port taken (and by whom; a Wirepane 0.7 proxy still running after an update); pid, uptime, memory, disk, sessions | Start again, Stop it and start, Restart |
| The system proxy | Left on by a dead proxy (no internet); held by Charles or Proxyman | Put back; Setup |
| VPN and other proxy apps | A utun default route; Charles, Proxyman, mitmproxy, HTTP Toolkit running | What to try |
| The CA on this Mac | Safari and Mac apps would refuse HTTPS | Trust on this Mac |
| Refusals per client | Every host refused: the CA is missing. One host among working ones: it pins its certificate | Setup for that client, or Never decrypt it |
| Pinned hosts | Passed through after two refusals (or an OkHttp-style close right after the handshake) | Decrypt them again once trusted |
| Upstream failures | DNS (ENOTFOUND), self-signed dev servers, closed ports, localhost confusion, unreachable networks | Accept its certificate; what to check |
| The network's own proxy | The system proxy pointed at an office's or a VPN's proxy before Wirepane took its place | Use it upstream |
| Tracked domains | A list that matches nothing that came, and what passed instead | Add the real hosts |
| Android devices | Not pointed at the proxy; apps that will not trust a user CA; a rootable image | Setup; Trust in all apps |
| Client | How |
| --- | --- |
| A separate browser | Setup → Browser → Open Google Chrome (or Edge, Brave, Chromium). It starts a new instance with a profile of its own:<br>--proxy-server<br>--proxy-bypass-list=<-loopback>, so localhost is captured too<br>--ignore-certificate-errors-spki-list, so it trusts the proxy without the keychain<br>HTTP/2 and WebSockets work as they do anywhere. |
| iOS Simulator | Setup → iOS: Use (or Boot & use) boots one, adds the CA (simctl keychain add-root-cert) and turns on the macOS system proxy, since a simulator has no proxy setting of its own. |
| iPhone / iPad | Setup → iOS → iPhone / iPad:<br>1. Listen on LAN.<br>2. Scan the QR code to install the profile.<br>3. Turn on full trust under Certificate Trust Settings.<br>4. Set the Wi-Fi proxy to the address shown.<br>The section confirms when traffic arrives. |
| Android emulator | Setup → Android → Emulator: Start through the proxy launches an AVD with -http-proxy and opens the CA page in its browser.<br>Apps trust a user CA only with a network_security_config. On a Google APIs image, Health → Trust in all apps makes the CA a system CA until the next reboot. |
| Android phone | Setup → Android → Phone: on USB, Point USB phones at the proxy (adb reverse, no Wi-Fi needed); on Wi-Fi, Listen on LAN, the QR code, and the proxy setting shown. |
| Safari and Mac apps | Setup → macOS → Turn on for this Mac (Claude's hosts bypass it) and Trust CA on this Mac. Both are put back when the proxy stops. |
| curl, Node, Python, Go, Java, Docker | Setup → CLI copies HTTPS_PROXY, NODE_EXTRA_CA_CERTS, SSL_CERT_FILE and REQUESTS_CA_BUNDLE. The troubleshooting skill covers the clients that ignore the proxy. |
Claude Code does not trust the proxy's CA, and with the system proxy on, its own traffic would come here. Two guards keep it whole:
Rules change matching requests before they are sent, responses before the client gets them, and WebSocket messages both ways.
r) lists them in words, with hit counts. It turns them on and off, reorders them, and removes them..claude/proxy-rules.json, so you can commit them. The file is reloaded the moment it changes."stop": true ends the chain.{
"rules": [
{
"id": "slow-feed",
"description": "The feed on a bad 3G line, failing now and then",
"match": { "methods": ["GET"], "host": "api.example.com", "path": "/v1/feed*" },
"request": [{ "type": "delay", "ms": 800, "msMax": 2500 }],
"response": [{ "type": "throttle", "bytesPerSecond": 50000 }]
},
{
"id": "mock-prices-socket",
"description": "A price feed, with no server",
"match": { "path": "/ws/prices" },
"request": [{ "type": "respond", "status": 101 }],
"messages": [
{ "type": "send", "on": "open", "to": "client", "json": { "type": "hello" } },
{ "type": "reply", "when": "\"subscribe\"", "json": { "type": "price", "price": 42.5 } }
]
}
]
}
Match. Every condition given must hold:
url, host and path take globs (*.example.com, /v1/*) or re:<regex>;methods, headers, query and bodyContains;status (404, 4xx, >=400, 500-599) and contentType.| Action | Request | Response |
| --- | :-: | :-: |
| delay {ms, msMax?}, throttle {bytesPerSecond} | ✓ | ✓ |
| setHeader, removeHeader | ✓ | ✓ |
| setQuery, removeQuery, mapRemote {scheme?, host?, port?, path?}, replaceUrl | ✓ | |
| setBody {text \| json \| file}, replaceBody {pattern, with}, mergeJson {json} | ✓ | ✓ |
| respond {status, headers?, text \| json \| file}: no server asked (101 on a WebSocket: a mock server) | ✓ | |
| setStatus {status} | | ✓ |
| fail {kind: reset \| close \| timeout} | ✓ | ✓ |
| breakpoint {timeoutMs?}: held until you (the Held view) or Claude (resume_request) let it go, changed or not; after its time (5 min) it goes on as it was | ✓ | ✓ |
| script {code} | ✓ | ✓ |
| WebSocket step (messages) | What it does |
| --- | --- |
| direction: out \| in \| both, when: "text" \| "re:…", on: open | Which messages it takes (or once, as the socket opens) |
| replaceMessage {pattern, with}, setMessage {text \| json \| file}, mergeJson {json} | Change the message |
| drop, delay {ms, msMax?} | Lose it, or hold it |
| reply {text \| json} | Answer the sender; the message goes no further |
| send {to, text \| json} | One more message, to the client or the server |
| close {code?, reason?} | Close both sides |
| script {code} | async (msg, ctx) => {}: change msg.text, msg.drop = true, ctx.send(to, …), ctx.close(…) |
Bodies. A response body is held back only when a rule changes it, so server-sent events keep streaming under header rules.
Scripts. A script runs only once its SHA-256 is approved. A rules file cloned with a repository cannot run code unasked; press Allow script. Scripts Claude adds through its tools are approved with them.
In the list. A request a rule changed is marked ✎, and its detail says what each rule did. Filter with is:modified or rule:<id>.
Terms separated by spaces must all hold; a leading - negates one. Free text matches a substring of the URL.
| Term | Matches |
| --- | --- |
| method:POST, method:get,post | the method |
| status:404, status:4xx, status:>=400, status:500-599 | the response status |
| host:api.example.com, host:*.example.com | the host |
| path:/v1/login | a substring of the path |
| type:json\|html\|xml\|js\|css\|img\|font\|media\|text\|form\|grpc\|ws\|tunnel\|other | the content type |
| is:error\|ok\|pending\|tunnel\|ws\|https\|h2\|grpc\|held\|rejected\|modified | the state (error includes failed gRPC calls; held: waiting at a breakpoint) |
| client:192.168.1.20, rule:slow-feed | the client, a rule |
A row opens its detail: the status, the timing, what the rules did, and tabs for Request and Response, plus Messages for a WebSocket and Events for a stream (keys 1 to 4). It opens on the response, or on the messages or the events.
Find searches the tab you are on, case aside: the headers and the body as shown (pretty JSON), or the messages. Every match is marked, the current one brighter, with 3 of 14 · line 340. Enter or j goes to the next, k to the one before, and the body scrolls to it, however deep. It stays as you switch tabs, so you can look for the same token in the request and the response.
A phone talks to dozens of hosts. Turn on the tracking list and the proxy decrypts and records only your app's domains. Everything else passes through untouched and unrecorded.
/proxy track app.example.com *.example.com track these (*.example.com covers example.com)
/proxy untrack app.example.com stop tracking one
/proxy untrack every domain again
The Domains view (d) shows the hosts that passed through, busiest first, each with a track button. Clients that connect to an address (the Android emulator) are tracked by the name in their TLS handshake.
The first session that needs the proxy starts it detached; the others attach to it.
/clear and --resume keep everything in place./proxy stop stops it for everyone and says how many other sessions it served./proxy restart replaces it at once./proxy open the pane and start (or attach to) the proxy
/proxy setup set up a browser, iOS, Android, macOS or CLI client
/proxy doctor the doctor's findings, and the Health view
/proxy rules the rules
/proxy track the tracked domains
/proxy export write a HAR file (.claude/wirepane-<time>.har, or the path given)
/proxy restart restart the proxy
/proxy stop stop it (and point the system proxy and Android devices back)
/proxy clear clear this session's list
/proxy status one line about its state
/proxy tree|list the requests as a tree (host → path) or a list
Set them in /config, or in /plugin → Installed → wirepane → Configure options.
| Option | Default | |
| --- | --- | --- |
| Proxy port | 8899 | |
| Proxy reachable from | local | lan opens it to phones on the network (never to this Mac's localhost) |
| Hosts never decrypted | *.apple.com,*.icloud.com,*.mzstatic.com,*.apple-cloudkit.com | tunnelled untouched; hosts that refuse the certificate twice are added on their own |
| Hosts with unchecked certificates | (none) | dev servers with self-signed certificates, by host |
| Upstream proxy (office, VPN) | (none) | an office's or a VPN's proxy every connection to a server goes through: http://[user:password@]host:port (Basic or NTLM, whichever it asks for; a Windows DOMAIN\user as DOMAIN%5Cuser), socks5://[user:password@]host:port, or a PAC file as pac+http://[user:password@]host/proxy.pac (the credentials go to the proxies it names, not to the PAC's server) |
| Hosts that skip the upstream proxy | (none) | hosts reached without the upstream proxy; this Mac's own addresses and *.local always are |
| Requests to keep | 2000 | |
session A ──spawns──▶ node sidecar/attach.mjs ─┐ ┌── browser
session B ──spawns──▶ node sidecar/attach.mjs ─┼─ events ─▶ node sidecar/proxy.mjs --daemon ◀── :8899 ── simulator
pane · tools · status line └─ commands ─▶ (one per Mac, detached) └── phone · emulator
│ headers, bodies, messages → ~/.claude/proxy-mod/flows/
│ watchdog: puts the system proxy back if it is killed
A mod runs sandboxed, with no sockets of its own, so the proxy is a small Node.js process.
docs/design.md has the details.
Wirepane sends nothing of its own anywhere: no telemetry, no update checks, no downloads. It connects only to the servers the clients you point at it asked for. A request reaches Claude only when Claude calls a tool; what it reads then becomes part of the conversation, like a file Claude reads.
While the proxy is on, it runs:
node sidecar/proxy.mjs --daemon on 127.0.0.1 (on the network too in lan mode). It is detached so other sessions can use it, and stops when the last session leaves.node sidecar/attach.mjs, one per session, which passes the proxy's events to the mod.node sidecar/watchdog.mjs, which puts the system proxy back if the proxy is killed.openssl: makes the CA and the host certificates.route, networksetup and scutil find this Mac's addresses and read the system proxy. ps and lsof tell Claude's own connections apart. The doctor uses ps, lsof and adb too.Only when you press the button for it:
| In the pane | Runs | Changes |
| --- | --- | --- |
| macOS → Turn on for this Mac, iOS → Use | networksetup, through osascript when macOS asks for an administrator | The system proxy, put back when the proxy stops |
| macOS → Trust CA on this Mac | security add-trusted-cert, which macOS asks you to confirm | The CA in your login keychain |
| iOS → Use, Boot & use | xcrun simctl, open -a Simulator | The CA in that simulator's keychain |
| Browser → Open | open -na <browser> with a profile of its own | Nothing outside ~/.claude/proxy-mod/browser |
| Android → Start through the proxy, Android → proxy, Point USB phones | emulator -http-proxy, adb shell settings put global http_proxy, adb reverse | The device's proxy, put back by Revert and when the proxy stops |
| Health → Trust in all apps (Android) | adb root, adb push, a shell script that mounts a tmpfs over the system CA store | The emulator's system CAs, until it reboots |
| Health → Accept its certificate, Never decrypt it | Nothing | The plugin option, which restarts the proxy |
| Install Node.js, shown when no Node was found | brew install node | Node.js from Homebrew |
Files it writes:
ca, certs: the CA (RSA-2048, the key readable by you only) and the certificates made from it.flows/<run>: the headers, bodies, WebSocket messages and events of recorded requests. They can hold the passwords, tokens and personal data of the apps you debug. A run's folder is deleted two days after its last use.sidecar.json, sidecar.log: where the running proxy is, and its log.tracking.json, trusted-scripts.json: the tracked domains, and the rule scripts you approved.system-proxy-backup.json, android-proxied.json: what to put back.<project>/.claude/proxy-rules.json: written once you or Claude add a rule.All of these live in ~/.claude/proxy-mod except the rules file, which lives in your project.
/proxy stop. It puts back the system proxy and the Android devices, and stops the shared proxy./plugin uninstall wirepane@wirepane.rm -rf ~/.claude/proxy-mod to delete the CA, the recordings and the browser profiles.Is it a replacement for Proxyman, Charles, mitmproxy or HTTP Toolkit? For "what did my app send, what came back, and why does it fail", yes, without leaving Claude Code. That covers HTTP/2, gRPC, WebSockets and SSE, with rules, mocks, replays and diffs, and it works behind an office proxy. What it does not have is in the limits. Proxyman and HTTP Toolkit have MCP servers too. Wirepane is built around the agent: the doctor, the waiting, search and diff tools, the context budget, and the skills that fix the app's own code.
Do I have to install the certificate on my Mac? Not for the separate browser, which trusts the proxy by SPKI hash. Safari, native Mac apps and the iOS Simulator need the CA. One press each.
Why do some requests show CERT?
The client refused the proxy's certificate. Either it does not trust the CA yet, or the app pins its certificates. diagnose tells which. A pinned host passes through after two refusals, so the app keeps working.
I see nothing from my Flutter (or Go, or Unity) app. Some runtimes ignore the system proxy. The troubleshooting skill has the few lines that fix it in a debug build.
Does my traffic leave my machine?
Only to the servers it was going to anyway. Recordings stay in ~/.claude/proxy-mod. Claude reads a request only through a tool.
Does it capture Claude Code's own traffic, or the commands Claude runs? No. It captures the clients you point at it.
upstreamProxy at it. Basic and NTLM (also inside Negotiate) sign in by themselves.vm module: approve only code you would run yourself.claude plugin validate --strict .
claude plugin test . # the mod: logic, the pane (terminal and desktop), the tools
node --test --test-force-exit sidecar/*.spec.mjs # the proxy against local HTTP/1.1, HTTP/2, gRPC and WebSocket upstreams
To run a working copy, use claude --plugin-dir /path/to/wirepane.
MIT. Not affiliated with Anthropic, Proxyman, Charles, mitmproxy or HTTP Toolkit.