Scope
Simlock needs tools it does not install: Xcode for iOS, and for Android a JDK and the SDK's cmdline-tools, emulator and platform-tools packages. When one is missing, the platform is unavailable and the operator gets at most Android SDK missing or incomplete; searched: <paths> in the daemon log. Nothing says which part is missing or how to get it.
After this task simlock doctor names each missing prerequisite, per platform, with the command or step that installs it. It checks the machine again on every run. Simlock still installs none of them.
Checked for iOS:
- Xcode is installed and selected.
- The Xcode license is accepted.
- Xcode's first-launch setup is done.
Checked for Android:
- An Android SDK exists at one of the searched paths. When none does, that is one finding, not one per tool.
cmdline-tools (sdkmanager and avdmanager).
emulator.
platform-tools.
- A JDK that the SDK tools can run on.
Technical spec
File references are to main at b5f2222. Find code by symbol name.
Modules touched
src/core/driver.ts: two new types, both opaque to the core.
MissingPrerequisite { prerequisite: string; message: string; remedy: string }. prerequisite is a short kebab-case id the driver module owns. message says what is missing. remedy is the command or step that fixes it.
PrerequisiteCheck { platform: Platform; check(): Promise<readonly MissingPrerequisite[]> }. Read-only: it never installs, downloads, or writes. Every process it starts is bounded. It rejects when it cannot tell.
src/drivers/ios/prerequisites.ts (new): iosPrerequisites({ processRunner }) returns a PrerequisiteCheck. Each command has a 15-second timeout.
xcodebuild -version. A non-zero exit whose stderr matches requires Xcode, or a missing executable: xcode, with the remedy "install Xcode, then sudo xcode-select -s /Applications/Xcode.app". Stop here; the next two need Xcode.
xcodebuild -license check. Non-zero exit: xcode-license, remedy sudo xcodebuild -license accept.
xcodebuild -checkFirstLaunchStatus. Non-zero exit: xcode-first-launch, remedy sudo xcodebuild -runFirstLaunch.
A timeout, or a failure of step 1 that is not "no Xcode", rejects.
src/drivers/android/prerequisites.ts (new): androidPrerequisites({ env, homeDirectory, filesystem, processRunner }) returns a PrerequisiteCheck. It uses the same root list and the same per-tool path functions as discoverSdk and sdkPathsAt; move those into a shared file in the driver module so one function decides where a tool is (architecture rule 10).
- No searched root exists as a directory: one finding
android-sdk, whose message lists the searched paths and whose remedy names Android Studio or the command-line tools download, and ANDROID_HOME. Stop here.
- Otherwise take the first root that exists, and report each of:
android-cmdline-tools when no complete tool bin is found. Remedy: the command-line tools download page at developer.android.com. sdkmanager cannot install itself.
android-emulator when <root>/emulator/emulator is missing. Remedy: sdkmanager --install emulator.
android-platform-tools when <root>/platform-tools/adb is missing. Remedy: sdkmanager --install platform-tools.
android-jdk when cmdline-tools are there and sdkmanager --version exits non-zero. Timeout 30 seconds. The message carries the first line of stderr. Remedy: install a JDK and set JAVA_HOME. A timeout rejects; it is not reported as missing.
src/core/doctor.ts:
DoctorFinding gains { kind: "prerequisite-missing"; platform; prerequisite; message; remedy }.
DoctorOptions gains prerequisiteChecks?: readonly PrerequisiteCheck[] and runningPlatforms?: () => readonly Platform[].
DoctorReconcileOptions gains prerequisites?: boolean, default false. Startup convergence leaves it off. doctor.run sets it.
- When on, run every check. Map each result to a finding. A check that rejects is logged and yields nothing, as
advisories() does.
- A platform whose check returns nothing, that is not in
runningPlatforms(), and that has no entry in driverRejections: one finding with prerequisite: "daemon-restart", saying the prerequisites are present but the platform was not started, and the remedy from DRIVER_RETRY_REMEDY.
- A finding for a platform that is not running gets that same restart sentence appended to its
remedy.
--fix never acts on the kind. It is left out of doctor.reconciled's driftFindings, like driver-advisory.
src/contract/schemas.ts: doctorFindingSchema gains the new member, field for field.
src/daemon/main.ts: build the checks in the composition root: iOS only when hostPlatform is darwin, Android always. Pass them, and the list of platforms that have a driver, to Doctor. With SIMLOCK_DRIVERS_MODULE set, use the module's optional prerequisiteChecks export, or none.
src/daemon/dispatcher.ts: the doctor.run handler passes prerequisites: true.
src/cli/index.ts: next to writeDriverAdvisoryWarnings, print one stderr line per finding: Missing [<platform>] <prerequisite>: <message> <remedy>. Stdout keeps the JSON report unchanged in shape. The exit code stays 0.
e2e/fake-driver/: the module exports prerequisiteChecks, read from the driver script, so a flow can script a missing prerequisite.
- Docs:
docs/CLI.md (a "Prerequisites" list per platform that says Simlock does not install them; the new finding kind under doctor), docs/CLIENT.md if it lists finding kinds, docs/internal/ARCHITECTURE.md (where the checks live and why they are not on the driver object).
Contract and event changes
doctor.run's output gains the finding kind prerequisite-missing. No protocol change: doctor.run is refused on a gateway and never relayed.
- No event change.
doctor.reconciled does not carry the new kind.
Rules in play
architecture.md rule 1 and 2 (the core carries prerequisite, message and remedy unread; every tool name and path stays in the driver module), rule 3 (a new driver gets checks by exporting one, with no core change), rule 9 (processes and files through ProcessRunner and Filesystem), rule 10 (one function locates an SDK tool, for discovery and for the check), rule 11 (every command bounded).
safety.md rule 4: a check never installs or downloads, and --fix never does either.
documentation.md rule 3: a remedy names a command or a web page, never a file in this repo.
testing.md rules 1 to 4.
Tests
- With every prerequisite present and both platforms running,
doctor.run reports no prerequisite-missing finding.
- The Android check reports
android-emulator with the sdkmanager --install emulator remedy when only the emulator binary is missing.
- The Android check reports
android-platform-tools when only adb is missing.
- The Android check reports
android-cmdline-tools when no tool bin is complete, and does not run sdkmanager.
- The Android check reports one
android-sdk finding that lists the searched paths when no root exists, and no per-tool finding.
- The Android check reports
android-jdk with the first stderr line when sdkmanager --version fails.
- The Android check rejects when
sdkmanager --version times out, and reports nothing as missing.
- The iOS check reports
xcode when xcodebuild says it requires Xcode, and runs neither later command.
- The iOS check reports
xcode-license when the license check exits non-zero.
- The iOS check reports
xcode-first-launch when the first-launch check exits non-zero.
- Neither check starts a process other than the commands named above.
- A check that rejects yields no finding and does not fail
doctor.run.
- On a host that is not macOS, no iOS check is built.
- A platform with nothing missing and no running driver gets one
daemon-restart finding.
- A platform with a driver rejection gets no
daemon-restart finding.
- A finding for a platform that is not running tells the operator to restart the daemon.
doctor.run with fix: true reports the same prerequisite findings and starts no installer.
- Startup convergence runs no prerequisite check.
doctor.reconciled does not carry a prerequisite-missing finding.
- The CLI prints one stderr line per prerequisite finding and exits 0.
Done when
- On a machine whose SDK has no
emulator package, simlock doctor says the Android emulator package is missing and prints the sdkmanager command that installs it.
- With no
cmdline-tools, it says so and points at Android's command-line tools download, since sdkmanager cannot install itself.
- With no Android SDK at any searched path, it reports one finding that lists the paths.
- With no Xcode selected, it says so and names
xcode-select.
- With every prerequisite present, it reports none of these findings.
- After a missing package is installed, the next
simlock doctor no longer reports it, without a daemon restart.
- The findings are in
simlock doctor --json.
doctor --fix never installs a prerequisite.
- A platform that cannot run on the host, such as iOS off macOS, reports no missing prerequisite.
docs/CLI.md lists the prerequisites per platform and says Simlock does not install them.
- e2e with the fake driver: a scripted missing prerequisite shows in
simlock doctor output and on stderr.
pnpm check is green.
Out of scope
- Installing any prerequisite.
- Choosing between several installed Xcode versions.
- Hardware acceleration checks on Linux hosts.
- Android SDK licenses. A download that meets an unaccepted license already fails with a message that names the config key and the command.
- Reporting prerequisites in
status or in a gateway's worker view.
Depends on
Approval
Written by an agent.
Scope
Simlock needs tools it does not install: Xcode for iOS, and for Android a JDK and the SDK's
cmdline-tools,emulatorandplatform-toolspackages. When one is missing, the platform is unavailable and the operator gets at mostAndroid SDK missing or incomplete; searched: <paths>in the daemon log. Nothing says which part is missing or how to get it.After this task
simlock doctornames each missing prerequisite, per platform, with the command or step that installs it. It checks the machine again on every run. Simlock still installs none of them.Checked for iOS:
Checked for Android:
cmdline-tools(sdkmanagerandavdmanager).emulator.platform-tools.Technical spec
File references are to
mainat b5f2222. Find code by symbol name.Modules touched
src/core/driver.ts: two new types, both opaque to the core.MissingPrerequisite { prerequisite: string; message: string; remedy: string }.prerequisiteis a short kebab-case id the driver module owns.messagesays what is missing.remedyis the command or step that fixes it.PrerequisiteCheck { platform: Platform; check(): Promise<readonly MissingPrerequisite[]> }. Read-only: it never installs, downloads, or writes. Every process it starts is bounded. It rejects when it cannot tell.src/drivers/ios/prerequisites.ts(new):iosPrerequisites({ processRunner })returns aPrerequisiteCheck. Each command has a 15-second timeout.xcodebuild -version. A non-zero exit whose stderr matchesrequires Xcode, or a missing executable:xcode, with the remedy "install Xcode, thensudo xcode-select -s /Applications/Xcode.app". Stop here; the next two need Xcode.xcodebuild -license check. Non-zero exit:xcode-license, remedysudo xcodebuild -license accept.xcodebuild -checkFirstLaunchStatus. Non-zero exit:xcode-first-launch, remedysudo xcodebuild -runFirstLaunch.A timeout, or a failure of step 1 that is not "no Xcode", rejects.
src/drivers/android/prerequisites.ts(new):androidPrerequisites({ env, homeDirectory, filesystem, processRunner })returns aPrerequisiteCheck. It uses the same root list and the same per-tool path functions asdiscoverSdkandsdkPathsAt; move those into a shared file in the driver module so one function decides where a tool is (architecture rule 10).android-sdk, whose message lists the searched paths and whose remedy names Android Studio or the command-line tools download, andANDROID_HOME. Stop here.android-cmdline-toolswhen no complete tool bin is found. Remedy: the command-line tools download page at developer.android.com.sdkmanagercannot install itself.android-emulatorwhen<root>/emulator/emulatoris missing. Remedy:sdkmanager --install emulator.android-platform-toolswhen<root>/platform-tools/adbis missing. Remedy:sdkmanager --install platform-tools.android-jdkwhencmdline-toolsare there andsdkmanager --versionexits non-zero. Timeout 30 seconds. The message carries the first line of stderr. Remedy: install a JDK and setJAVA_HOME. A timeout rejects; it is not reported as missing.src/core/doctor.ts:DoctorFindinggains{ kind: "prerequisite-missing"; platform; prerequisite; message; remedy }.DoctorOptionsgainsprerequisiteChecks?: readonly PrerequisiteCheck[]andrunningPlatforms?: () => readonly Platform[].DoctorReconcileOptionsgainsprerequisites?: boolean, defaultfalse. Startup convergence leaves it off.doctor.runsets it.advisories()does.runningPlatforms(), and that has no entry indriverRejections: one finding withprerequisite: "daemon-restart", saying the prerequisites are present but the platform was not started, and the remedy fromDRIVER_RETRY_REMEDY.remedy.--fixnever acts on the kind. It is left out ofdoctor.reconciled'sdriftFindings, likedriver-advisory.src/contract/schemas.ts:doctorFindingSchemagains the new member, field for field.src/daemon/main.ts: build the checks in the composition root: iOS only whenhostPlatformisdarwin, Android always. Pass them, and the list of platforms that have a driver, toDoctor. WithSIMLOCK_DRIVERS_MODULEset, use the module's optionalprerequisiteChecksexport, or none.src/daemon/dispatcher.ts: thedoctor.runhandler passesprerequisites: true.src/cli/index.ts: next towriteDriverAdvisoryWarnings, print one stderr line per finding:Missing [<platform>] <prerequisite>: <message> <remedy>. Stdout keeps the JSON report unchanged in shape. The exit code stays 0.e2e/fake-driver/: the module exportsprerequisiteChecks, read from the driver script, so a flow can script a missing prerequisite.docs/CLI.md(a "Prerequisites" list per platform that says Simlock does not install them; the new finding kind underdoctor),docs/CLIENT.mdif it lists finding kinds,docs/internal/ARCHITECTURE.md(where the checks live and why they are not on the driver object).Contract and event changes
doctor.run's output gains the finding kindprerequisite-missing. No protocol change:doctor.runis refused on a gateway and never relayed.doctor.reconcileddoes not carry the new kind.Rules in play
architecture.mdrule 1 and 2 (the core carriesprerequisite,messageandremedyunread; every tool name and path stays in the driver module), rule 3 (a new driver gets checks by exporting one, with no core change), rule 9 (processes and files throughProcessRunnerandFilesystem), rule 10 (one function locates an SDK tool, for discovery and for the check), rule 11 (every command bounded).safety.mdrule 4: a check never installs or downloads, and--fixnever does either.documentation.mdrule 3: a remedy names a command or a web page, never a file in this repo.testing.mdrules 1 to 4.Tests
doctor.runreports noprerequisite-missingfinding.android-emulatorwith thesdkmanager --install emulatorremedy when only the emulator binary is missing.android-platform-toolswhen onlyadbis missing.android-cmdline-toolswhen no tool bin is complete, and does not runsdkmanager.android-sdkfinding that lists the searched paths when no root exists, and no per-tool finding.android-jdkwith the first stderr line whensdkmanager --versionfails.sdkmanager --versiontimes out, and reports nothing as missing.xcodewhenxcodebuildsays it requires Xcode, and runs neither later command.xcode-licensewhen the license check exits non-zero.xcode-first-launchwhen the first-launch check exits non-zero.doctor.run.daemon-restartfinding.daemon-restartfinding.doctor.runwithfix: truereports the same prerequisite findings and starts no installer.doctor.reconcileddoes not carry aprerequisite-missingfinding.Done when
emulatorpackage,simlock doctorsays the Android emulator package is missing and prints thesdkmanagercommand that installs it.cmdline-tools, it says so and points at Android's command-line tools download, sincesdkmanagercannot install itself.xcode-select.simlock doctorno longer reports it, without a daemon restart.simlock doctor --json.doctor --fixnever installs a prerequisite.docs/CLI.mdlists the prerequisites per platform and says Simlock does not install them.simlock doctoroutput and on stderr.pnpm checkis green.Out of scope
statusor in a gateway's worker view.Depends on
Approval
Written by an agent.