Install Anvil once, ship to every surface.
Native Swift + Kotlin drivers, a Hono+Bun API, a JSON-RPC 2.0 orchestrator, and language SDKs for every stack. No App Store listing — Anvil is a developer product, installed directly through the channels you already use.
Clients, SDKs, CLI
Start here. The CLI and the language SDKs wrap the exact same REST, GraphQL, and WebSocket APIs the dashboard uses — no second-class citizens.
CLI — `anvil` (npm)
The Node-based command-line control plane. Build → install → launch → tunnel → dispatch — all one command. Ships with a YAML spec runner for reproducible smoke scenarios.
npm i -g @anvil/clipnpm
pnpm add -g @anvil/cliyarn
yarn global add @anvil/clibun
bun add -g @anvil/clidirect download (pre-npm)
curl -L https://github.com/Koydo/koydo-anvil-releases/releases/latest/download/anvil-macos-arm64.tgz | tar xz -C /usr/local/binfirst run
anvil login --api-key ak_live_... && anvil devices list && anvil devices ready --device <udid>After install, `anvil run ./specs/matura-smoke.yaml` drops a PNG + accessibility tree + transcript into `./runs/<ts>/` — everything a tester needs to attach to a bug report.
Homebrew
Preferred on macOS — wraps the CLI plus the signed `anvil-mcp` daemon binary and registers its launchd agent.
brew tap koydo/anvil && brew install anvilPython SDK
`anvil` on PyPI — the same REST + WebSocket + MCP surfaces, Pythonic. Works with uv, poetry, pip.
pip install anvil-sdkuv
uv pip install anvil-sdkpoetry
poetry add anvil-sdkNode SDK
`@anvil/sdk` — typed wrapper around the Anvil REST, GraphQL, and WebSocket APIs. Zod types, first-class ESM.
npm i @anvil/sdkSwift SDK
Swift Package Manager — `AnvilClient` for embedding in macOS agents, iOS test harnesses, and custom orchestrators.
.package(url: "https://github.com/koydo/anvil-swift-sdk", from: "0.1.0")Native drivers
Every driver is native — Swift + XCUITest / AppKit / RealityKit for Apple, Kotlin + UIAutomator2 + Espresso for Android. No WebDriver tax, no AX bridge overhead. All binaries are signed, cosign-verified, and ship through GitHub Releases.
KID — iOS driver
Native Swift + XCUITest, NWListener server. 82 verbs. Signed `.ipa` from GitHub Releases — current build is development-signed for internal testing. Ad-Hoc + TestFlight channels land in v0.1.0-beta.
curl -L -o KID.ipa https://github.com/Koydo/koydo-anvil-releases/releases/download/v0.1.0-alpha/KID.ipaInstall on a registered device
xcrun devicectl device install app --device <UDID> KID.ipaVerify cosign signature
curl -LO https://github.com/Koydo/koydo-anvil-releases/releases/download/v0.1.0-alpha/cosign.pub && curl -LO https://github.com/Koydo/koydo-anvil-releases/releases/download/v0.1.0-alpha/KID.ipa.cosign.bundle && cosign verify-blob --key cosign.pub --bundle KID.ipa.cosign.bundle KID.ipaDownload dSYM (crash symbolication)
curl -LO https://github.com/Koydo/koydo-anvil-releases/releases/download/v0.1.0-alpha/KoydoIOSDriver.app.dSYM.zipNo App Store listing — Anvil is a developer tool, not a consumer app. Current profile covers internal dev devices only.
KMD — macOS driver
Native Swift + AppKit. 82 verbs including mouse, menu, and window control. Notarized `.app.zip`.
curl -L https://github.com/koydo/koydo-anvil-releases/releases/latest/download/KMD-macos.app.zip -o KMD.zipKVD — visionOS driver
Spatial input — 60 verbs including gaze-and-pinch, head pose, anchor snapshots. Built on RealityKit + ARKit.
# TestFlight invite — join the visionOS betaOpen ↗KWD — watchOS driver
Digital crown + complication access. Paired iPhone transport, 35 watch-specific verbs.
# Bundled with KID — see the pairing guide in the docsKAD — Android driver
Kotlin + Ktor + UIAutomator2 + Espresso. 80 verbs. Signed `.aab` for Play internal distribution, plus a plain `.apk` for direct sideload.
curl -L https://github.com/koydo/koydo-anvil-releases/releases/latest/download/KAD-android.apk -o KAD.apk && adb install KAD.apkSelf-host · Servers · MCP
Bring your own Postgres, run the full stack on your infra. All services ship as keyless-cosign-signed OCI images on GHCR. The MCP server drops into any MCP-aware AI client — Claude Code, Cursor, Windsurf.
Docker — `anvil/api` + services
Pull keyless-cosign-signed images from GHCR. Run the API, kfleet scheduler, ai-agent, and mcp-server self-hosted in one docker-compose.
docker pull ghcr.io/koydo/anvil/api:latestFull stack (compose)
curl -L https://anvil.koydo.app/compose/anvil-stack.yaml -o docker-compose.yml && docker compose up -dkfleet
docker pull ghcr.io/koydo/anvil/kfleet:latestai-agent
docker pull ghcr.io/koydo/anvil/ai-agent:latestmcp-server
docker pull ghcr.io/koydo/anvil/mcp-server:latestMCP server for Claude Code / Cursor / Windsurf
Drop `anvil-mcp` into your AI editor's MCP registry to drive test runs in plain English. Ships as part of the Homebrew + npm installs.
# ~/.config/claude-desktop/mcp.json
{
"anvil": {
"command": "anvil-mcp",
"args": ["--api-key", "${ANVIL_API_KEY}"]
}
}After install — authenticate
Anvil enforces per-device JWT handshake. Get an API key from the dashboard, pass it to the driver on first launch, and the driver exchanges it for a short-lived signed token on every verb dispatch.
# 1) Create an API key in the dashboard
open https://anvil.koydo.app/dashboard/api-keys
# 2) Register the device with its API key (handshake → JWT)
KID-cli auth handshake --api-key ak_live_...
# 3) Drive it
anvil run --device "Demo iPad" --spec smoke.yamlNever re-fix the same Apple rejection twice
Every past App Store rejection is a permanent Anvil spec under specs/rejections/. Run the full suite before any TestFlight upload — if a previously-fixed rejection has regressed, the suite catches it on the test device before Apple ever sees the build.
# Run every rejection spec against whichever device is tunneled
anvil run --suite rejections
# Narrow to one app by bundle id
anvil run --suite rejections --app com.yourco.yourapp
# Ensure a device is ready + tunneled, then run the suite
anvil run --suite rejections --device <IPAD-UDID>
# Report lands in runs/<ts>-rejections-suite/report.jsonSpecs are stored per-app and per-guideline. Non-automatable rejections (network-level allow-lists, privacy manifest lint, Kids-Category traffic audits) carry status: not_automatable and are counted as "skipped" with a pointer to the CI-side check.