Claude Code Plugin · Live-Validated

HeadSpin Control.

Drive real devices on a HeadSpin farm from inside Claude Code — authenticate, enumerate and lock devices, connect and control iOS / Android / Roku, run autonomous exploration, and file standardized bug reports. Every call hits the real HeadSpin API through REST, Appium, socket.io, and Janus surfaces. No mocks anywhere.

14
Skills
10
Commands
2
Agents
3
Hooks
20
MCP Tools
34 live
Devices Validated
01

Architecture

commands → skills → planes

Ctrl/Cmd + wheel to zoom · drag to pan · double-click to fit · ⛶ opens full size.

Loading...
Plugin components
Live-verified plane (account token)
Browser-credential-gated plane
02

Component Inventory

skills are model-invoked · commands are entry points

Auth & Discovery
  • headspin-login — browser sign-in, keychain token
  • headspin-list-devices — REST roster + socket.io devicelist
  • headspin-session-manager — lock / unlock / lease
  • headspin-connection-manager — WS lifecycle
Connect & Control
  • headspin-connect-ios · -android · -roku
  • headspin-control-ios · -roku
  • iOS = Appium xcuitest + :5002 touch paths
  • Android = socket.io input.* events
Capture & Reports (new in 1.1–1.2)
  • headspin-capture — lock → record → drive → stop
  • headspin-reports — issue card, status, time series
  • headspin-waterfall — HAR / MP4 / device-log download
Explore & Report
  • headspin-explore-bugs — autonomous drive
  • headspin-bug-report — standardized report
  • agent device-explorer
  • agent bug-reporter
Commands
  • /headspin:setup · :login
  • /headspin:devices · :connect
  • /headspin:control · :capture
  • /headspin:explore · :report
  • /headspin:sessions · :waterfall
MCP Server

Stdlib-only stdio JSON-RPC 2.0. Twenty REST tools — device roster, lock/shell, full capture lifecycle, Waterfall reports, time series, artifact downloads — every one a real HTTPS call, no stubbed responses. Errors surface the live HTTP status + body.

Hooks

Three guards in hooks/hooks.json: token-safety before device commands, session release on Stop / SessionEnd, and interaction logging.

03

Authentication Model

5 carriers · orgkey:token is fabricated

PlaneCredentialCarrierSourceStatus
REST api-dev.headspin.io/v0 API token Authorization: Bearer User Settings → API Tokens Live-verified
Appium W3C wd/hub session API token 32-hex token in URL path :70xx/v0/<token>/wd/hub same API token Live-verified
socket.io control Android / Cast / Fire TV Identity JWT claims name·email·plain_email ?access_token=<JWT> browser login re-minted on lock → <lockUUID>@lock.hspin.io Browser-gated
iOS control WS :5002 Identity JWT ?jwt=<JWT> browser login Browser-gated
Janus WebRTC H264 media 16-char session secret body token + watch pin per-session, UI-issued Browser-gated
orgkey:token header Asserted in the original spec Fabricated · 0 in 350 HAR + 141 docs
04

MCP Tools

20 / 20 exercised over real JSON-RPC stdio · latest full pass 2026-07-05

ToolREST callLive result
hs_login_detailsGET /v0/logindetails200 · org/env probe (unauthenticated)Pass
hs_list_devicesGET /v0/devices34 devices · 33 online · android/ios/roku/safari/tizentvPass
hs_idevice_infoGET /v0/idevice/{addr}/infolockdownd dump · iPhone12,1 · iOS 14.4.2Pass
hs_installer_listGET .../installer/list6 apps · io.headspin.webdriveragentPass
hs_lock_device / hs_unlock_devicePOST .../lock|unlockstatus:0 both directions on a real iPhone 11 — route proven livePass
hs_adb_lock / hs_adb_unlockPOST /v0/adb/{id}/lock|unlockstatus:0 · Galaxy S10 reserved & releasedPass
hs_adb_shellPOST /v0/adb/{id}/shellgetprop → SM-G973W · swipes, taps, am start all rc=0Pass
hs_start_capture / hs_stop_capturePOST|PATCH /v0/sessionssession e8024cb0 recorded 80 s · "Video uploaded" confirmedPass
hs_list_sessionsGET /v0/sessionssession listed first · state=ended · error_code=nullPass
hs_analysis_statusGET .../analysis/statusdone · "Analysis completed for the session."Pass
hs_session_issuesGET .../analysis/issuesWaterfall card: "Audio Too Quiet" −30.8 LUFSPass
hs_session_timestampsGET .../timestampscapture start/end/complete epochs · 79.9 s spanPass
hs_session_video_metadataGET .../video/metadata512×1184 h264 · 23.105 fps · 80,111 ms · audio 1chPass
hs_session_timeseries_info / _downloadGET .../timeseries/…16 series · screen_change CSV 67,767 bytes to diskPass
hs_session_downloadGET /v0/sessions/{sid}.{ext}6,899,682-byte MP4 to disk · HAR 404 surfaced verbatim (no network capture — correct)Pass
hs_session_tls_exceptionsGET .../tlsexceptions{} · no TLS-pinning capture gapsPass
05

Device Time Series

hs_session_timeseries_info → up to 31 signals · _download → CSV

What the platform measured, second by second

Every capture session records a set of device time series — "what occurred on the device" as measurable signals over the session timeline. hs_session_timeseries_info discovers what was captured; hs_session_timeseries_download streams any one series to disk as CSV (it returns {saved_to, bytes}, never the raw rows). The set scales with what the capture recorded: a live video session returned 31 series; a network-only session returned 6.

Video & screen
  • video_quality_mos · screen_change
  • blockiness · blurriness · brightness
  • colorfulness · contrast · downsampling_index
  • page_content · screen_rotation
Network
  • download_rate · throughput · net_cpu
  • concurrency · connections
  • network_in_bytes (+ _rvi / _total / _packets)
  • network_out_bytes (+ _rvi / _total / _packets)
  • signal_wifi_rssi
Power & memory
  • battery_current
  • battery_energy_drain (+ _percent)
  • memory_used · memory_used_percent
Impact (composite)
  • impact · impact_kde
  • HeadSpin's blended performance score
Live download — real byte countsSeriesBytes
session e8024cb0 · Galaxy S10 · 80 s YouTube drivescreen_change67,767CSV to disk
session 968f82b2 · video-qualityvideo_quality_mos8,800CSV to disk
session 968f82b2 · memorymemory_used583CSV to disk
session 968f82b2 · network totalnetwork_in_bytes_total97CSV to disk
06

Validation Results

full passes 2026-07-02 / 07-03 / 07-05 · real system, no mocks

MCP surface · PASS

All 20 tools driven end-to-end against live api-dev. The 2026-07-05 pass ran a complete capture lifecycle on a real Galaxy S10 — lock → record → drive YouTube → stop → 6.9 MB MP4 + issue card + 16 time series pulled to disk — and proved the iOS REST lock/unlock route live on an iPhone 11.

Lock lifecycle · PASS

lock → reserved → unlock → free, confirmed on the roster. Found + fixed a defect: a bare lock_id is an ambient idle marker (13/33 devices carry one at rest); the real hold is owner_email / session_id.

WS planes · characterized

socket.io + Janus are network-reachable and TLS-valid; socket.io even completes the WS upgrade (HTTP 101). Both correctly reject the account JWT — they require the browser-login identity credential.

Structure & discovery · PASS

claude plugin validate passes; the plugin installs and enables clean (v1.2.0, user scope). All 14 skills, 10 commands, 2 agents, hooks, and the 20-tool MCP server auto-discover.

07

The One Thing To Understand

two credentials, not one

Two JWT classes — and the API token can only mint one

POST /v0/jwt/permissions mints a lease JWT (claims sub · aud · exp). The control/streaming planes validate a different identity JWT (claims name · email · plain_email), obtained during the /headspin:login browser sign-in and re-minted on lock with the lock UUID as the email.

If a control WebSocket ever answers "Failed to decode jwt access_token.", the wrong credential class was used. Fix: re-run /headspin:login so the browser-minted identity JWT is captured. This is a real, verified boundary — proven live, not a plugin bug.

08

Install & First Run

full guides in docs/

Add the marketplace & install

The plugin ships its own dev marketplace. Point Claude Code at the directory, install, and restart.

/plugin marketplace add /path/to/headspin-control
/plugin install headspin-control@headspin-dev

Log in

/headspin:login opens your environment's UI so you can sign in and copy your API token (and capture the browser identity JWT for the control planes). The token is stored in the OS keychain.

Verify it's live

List the roster — if you see your device count, the plugin is wired to your environment.

/headspin:devices   # → real roster; validation run returned 34 devices

Reserve · drive · release

/headspin:connect locks a device and opens its control session; /headspin:control drives it; the Stop / SessionEnd hooks auto-release your lock so you never strand a device.