A Claude Code plugin for driving real devices · HAR-verified · live-validated
// what it is
/v0wd/hub// footprint
// section
commands → skills → planes
// the five planes
flowchart TD CMD["/headspin commands"] --> SK["14 Skills"] SK --> MCP["MCP server
20 REST tools"] SK --> CM["connection-manager"] SK --> SM["session-manager
lock / unlock"] MCP -->|"Bearer token"| REST[("REST /v0")] SM -->|"Bearer token"| REST SK -->|"token in path"| APP["Appium :70xx"] CM -->|"?access_token= JWT"| SIO["socket.io :23100+"] CM -->|"body token + pin"| JAN["Janus :150xx H264"] classDef live fill:#0d2818,stroke:#50fa7b,color:#c8f5d6; classDef gate fill:#2a2410,stroke:#f1c453,color:#f5edd0; classDef core fill:#12161e,stroke:#62d0ff,color:#c8d6e5; class REST,APP live; class SIO,JAN gate; class CMD,SK,MCP,CM,SM core;
:setup :login:devices :connect:control :capture:explore :report:sessions :waterfall// components
// section
5 real carriers · 1 fabricated
// credential carriers
| Plane | Credential | Carrier | Status |
|---|---|---|---|
| REST api-dev /v0 | API token | Authorization: Bearer | Live |
| Appium wd/hub | API token | 32-hex token in URL path | Live |
| socket.io Android / Cast / Fire TV | Identity JWT | ?access_token= | Gated |
| iOS control WS :5002 | Identity JWT | ?jwt= | Gated |
| Janus H264 media | Session secret | body token + watch pin | Gated |
orgkey:token | — | — | Fabricated |
The spec asserted orgkey:token. The capture proved
zero such headers across
350 HAR entries + 141 docs.
— HAR forensics · auth correction
// section
real system · no mocks · latest full pass 2026-07-05
// mcp surface · 1 of 2 · live 2026-07-05
| Tool | Live result Galaxy S10 · Toronto · session e8024cb0 | |
|---|---|---|
hs_login_details | 200 · org/env probe | Pass |
hs_list_devices | 34 devices · 33 online · 5 platforms | Pass |
hs_adb_lock | status:0 · R38N70234FA reserved | Pass |
hs_adb_shell | getprop → SM-G973W · swipes/taps rc=0 | Pass |
hs_start_capture | session active · video on | Pass |
hs_stop_capture | "Video uploaded to …e8024cb0….mp4" | Pass |
hs_session_download | 6,899,682-byte MP4 · 512×1184 h264 | Pass |
hs_adb_unlock | status:0 · device released | Pass |
// mcp surface · 2 of 2 · live 2026-07-05
| Tool | Live result same session + real iPhone 11 | |
|---|---|---|
hs_analysis_status | done · report pipeline finished | Pass |
hs_session_issues | Waterfall card: "Audio Too Quiet" −30.8 LUFS | Pass |
hs_session_timestamps | start/end/complete epochs · 79.9 s span | Pass |
hs_session_video_metadata | 23.105 fps · 80,111 ms · audio 1ch | Pass |
hs_session_timeseries_info + download | 16 series · screen_change → 67,767-byte CSV | Pass |
hs_session_tls_exceptions · hs_list_sessions | {} clean · session listed, ended, no error | Pass |
hs_idevice_info · hs_installer_list | iPhone12,1 · iOS 14.4.2 · 6 apps | Pass |
hs_lock_device · hs_unlock_device | status:0 both ways — iOS REST lock proven live | Pass |
// worked example · exactly as run 2026-07-05
hs_adb_lock {"device_id": "R38N70234FA", "timeout": 30}
→ {"status": 0, "message": "R38N70234FA@dev-ca-tor-0-proxy-2-lin… locked."}
hs_start_capture {"device_address": "R38N70234FA@dev-ca-tor-0-proxy-2-lin.headspin.io"}
→ {"session_id": "e8024cb0-788e-11f1-8491-da3445a29211", "state": "active"}
hs_adb_shell {"device_id": "R38N70234FA", "command": "am start -a android.intent.action.VIEW -d https://www.youtube.com"}
hs_adb_shell {"device_id": "R38N70234FA", "command": "input swipe 500 1500 500 500 300"}
hs_stop_capture {"session_id": "e8024cb0-…"}
→ {"msg": "Video uploaded to https://api-dev.headspin.io:443/v0/sessions/e8024cb0-….mp4"}
hs_adb_unlock {"device_id": "R38N70234FA"} # always release
Result: an 80-second, 6.9 MB h264 recording of the drive — pulled to disk with hs_session_download.
// worked example · report retrieval
hs_analysis_status {"session_id": "e8024cb0-…"}
→ {"status": "done", "message": "Analysis completed for the session."}
hs_session_issues {"session_id": "e8024cb0-…"}
→ {"Audio Too Quiet": {"Integrated Loudness (LUFS)": ["-30.8"]}}
hs_session_timeseries_info {"session_id": "e8024cb0-…"}
→ 16 series: impact, network_in/out, download_rate, blurriness, screen_change, …
hs_session_timeseries_download {"session_id": "e8024cb0-…", "key": "screen_change"}
→ {"saved_to": "…/screen_change.csv", "bytes": 67767}
hs_session_download {"session_id": "e8024cb0-…", "ext": "mp4"}
→ {"saved_to": "…/session-e8024cb0.mp4", "bytes": 6899682, "content_type": "video/mp4"}
Every line above is a captured live response — the issue card even flagged the muted YouTube tab.
lock_id read as "held".13 / 33 devices
…sit at rest with a non-null lock_id and no owner. Keying reservation off lock_id falsely marks every idle device busy.
owner_email / session_id.Proven by a decisive adopt → release cycle: account /v0/devices/lock set the owner; /v0/devices/unlock cleared it — lock_id never moved.
Skill + MCP docstring corrected. Device left free.
// control planes, exercised live
HTTP 101, then app-layer "Failed to decode jwt access_token."create → 403 wrong/missing secret// the one thing to understand
POST /v0/jwt/permissions mints a lease JWT (sub · aud · exp). The control planes validate an identity JWT (name · email · plain_email) — a browser-login artifact, re-minted on lock with the lock UUID as the email.
See "Failed to decode jwt access_token."? Wrong credential class. Fix: re-run /headspin:login to capture the browser identity JWT. A real, verified boundary.
// section
install → login → drive
// first run
// shipped · public · MIT
▸ github.com/krzemienski/headspin-control