Documentation

Device install & signing

walnut device builds for a physical iPhone, signs with Automatic signing, installs via devicectl, and launches the app. Pair with walnut debug --attach over Wi‑Fi once the debug server is up.

Simulator stays on walnut simulator. This page is the on-device path.

One-minute path

# Sign in to the Apple ID you want in Xcode, then pick Team (saved to walnut.json)
walnut device login

# See paired phones / known teams
walnut device --list
walnut device --list-teams

# Build + sign + install + launch
walnut device
walnut device --device "f" --debug

# In-process hot reload on device (same as simulator --watch)
walnut device --watch --device f --host <phone-lan-ip>

Then on the Mac (or rely on --watch pushing reloads itself):

walnut debug --attach --host <phone-lan-ip>

--watch implies --hot + --debug: after cold install, edits recompile a device dylib, devicectl copy into Documents, and TCP reload. See hot-reload.md.

Login flow (another Apple ID / team)

Apple does not allow a CLI to collect Apple ID + password (2FA). The Walnut flow is:

  1. Xcode → Settings → Accounts — add / switch to the Apple ID that owns the profile you want
  2. walnut device login [dir] — pick Apple ID and Team separately (and optionally an App Store Connect API .p8 key)
  3. Values are written into walnut.json and reused by later walnut device runs

Xcode often caches teams under only one signed-in email even when Accounts lists two. walnut device --list-teams therefore shows:

  • Apple IDs from Xcode + the macOS Accounts database (so a second ID like [email protected] appears)
  • Teams by Team ID (signing identity); the “Xcode-linked email” column is a hint and can be wrong
walnut device login
# → pick Apple ID (or type the email)
# → pick Team ID
# → optional ASC API key for headless auth when the Xcode session is stale

# Install onto a specific phone (required when several are online)
walnut device --device f

login only saves signing identity — it does not pick a phone. --device belongs on the install command. Device pairing is independent of which Apple ID is signed into Xcode. Filter without re-running login:

walnut device --account [email protected]
walnut device --team X57Z768URP

App Store Connect API key (headless)

When Xcode says Unable to log in, or for walnut testflight, use a Team API key (not an Individual key — those fail export with ASC -20101 / a misleading password error):

walnut.json / envMeaning
authenticationKeyPath / WALNUT_ASC_KEY_PATHPath to AuthKey_….p8
authenticationKeyID / WALNUT_ASC_KEY_IDKey ID
authenticationKeyIssuerID / WALNUT_ASC_ISSUER_IDIssuer ID (UUID at top of the Keys page)

Create under App Store Connect → Users and Access → Integrations → App Store Connect API → Team Keys. walnut device login stores these; xcodebuild then gets -authenticationKeyPath / -ID / -IssuerID. See testflight.md.

Prerequisites

NeedNotes
Xcode + Command Line Toolsxcrun devicectl / xcodebuild
xcodegenbrew install xcodegen
Paired iPhoneFinder or Xcode; unlock; Developer Mode on
Apple ID / TeamPersonal or paid team; use login to pick

Team ID resolution

First match wins:

  1. --team YOURTEAMID
  2. Env WALNUT_DEVELOPMENT_TEAM or DEVELOPMENT_TEAM
  3. walnut.jsondevelopmentTeam (from login)
  4. --account filter over Xcode teams
  5. Interactive picker (TTY)
  6. First discovered team

Example walnut.json after login:

{
  "name": "Todo",
  "bundleId": "dev.walnut.todo",
  "sources": "src",
  "runtime": "$(brew --prefix)/share/walnut/runtime",
  "developmentTeam": "X57Z768URP",
  "developmentAccount": "[email protected]"
}

What the command does

  1. Typecheck src/
  2. Compile LLVM → Main.o with device triple arm64-apple-ios16.0 (not simulator)
  3. xcodegen + xcodebuild for generic/platform=iOS with CODE_SIGN_STYLE=Automatic and -allowProvisioningUpdates
  4. xcrun devicectl device install app --device …
  5. xcrun devicectl device process launch --terminate-existing …

--debug sets DEVICECTL_CHILD_WALNUT_DEBUG=1 so the in-app debug server listens (same as Simulator’s SIMCTL_CHILD_*).

Limits

  • No on-device hot reload yet — use Simulator --hot --watch for that loop
  • Personal teams re-sign often; first install may prompt Trust on the phone
  • Bundle IDs must be unique under your team if you collide with another app
  • watchOS / multi-destination builds are out of scope
  • CLI cannot replace Xcode’s Apple ID password UI — login only selects an already-signed-in account / team (or ASC API key)