How VPhoneFirmwarePicker Pairs iPhone and CloudOS Builds in vphone-cli

VPhoneFirmwarePicker pairs iPhone and cloudOS builds by validating input URLs, interactively prompting for missing components using catalog data, and returning a VPhoneFirmwareSources struct containing download URLs for both images.

The VPhoneFirmwarePicker class in the Lakr233/vphone-cli repository serves as the central coordinator for firmware selection. It transforms partial or empty user input into concrete download URLs, ensuring compatibility between iPhone hardware and the cloudOS virtualization layer.

Overview of the Firmware Pairing Process

The Three-Stage Resolution Logic

The pairing mechanism operates through three distinct stages within the resolve method located in sources/VPhoneCore/VPhoneFirmwarePicker.swift (lines 38-65).

First, the method normalizes empty strings to nil and checks if both iphone and cloudos URLs are already provided. If both exist, or if the session is non-interactive (isInteractive: false), it immediately returns a VPhoneFirmwareSources struct with the supplied values.

Second, if neither URL is provided, the picker invokes pickPairing (lines 69-82) to display a full pairing menu. This menu is constructed from VPhoneFirmwareCatalog.pairings, a static array of VPhoneFirmwarePairing objects that map specific iPhone models to compatible cloudOS releases.

Third, if only one side is missing, the picker calls pickIPhone (lines 84-95) or pickCloudOS (lines 97-108) to prompt for the missing component individually. Both methods delegate to the choose helper for rendering numbered menus and validating user input against the catalog.

The resolve Method Implementation

The entry point for all pairing operations is the static resolve method. It accepts optional URL strings, an interactivity flag, and injected I/O closures for testability.

public static func resolve(
    iphone: String?, cloudos: String?,
    isInteractive: Bool,
    maxRetries: Int = 5,
    read: () -> String?,
    write: (String) -> Void
) throws -> VPhoneFirmwareSources {
    // Normalise empty strings to nil
    let iphone = (iphone?.isEmpty == true) ? nil : iphone
    let cloudos = (cloudos?.isEmpty == true) ? nil : cloudos

    // 1️⃣ If we already have both URLs or cannot prompt → return as‑is
    if (iphone != nil && cloudos != nil) || !isInteractive {
        return VPhoneFirmwareSources(iphoneSource: iphone,
                                     cloudosSource: cloudos)
    }

    // 2️⃣ Neither supplied → show a full pairing menu
    if iphone == nil, cloudos == nil {
        let p = try pickPairing(maxRetries: maxRetries,
                                read: read, write: write)
        return VPhoneFirmwareSources(iphoneSource: p.iosURL,
                                     cloudosSource: p.cloudosURL)
    }

    // 3️⃣ One side missing → prompt only for the missing half
    if iphone == nil {
        let p = try pickIPhone(maxRetries: maxRetries,
                               read: read, write: write)
        return VPhoneFirmwareSources(iphoneSource: p.iosURL,
                                     cloudosSource: cloudos)
    }

    // iPhone already known → ask for CloudOS
    let c = try pickCloudOS(maxRetries: maxRetries,
                            read: read, write: write)
    return VPhoneFirmwareSources(iphoneSource: iphone,
                                 cloudosSource: c.url)
}

The method throws VPhoneFirmwarePickerError.invalidSelection if the user exceeds the retry limit, or VPhoneFirmwarePickerError.aborted if they submit empty input.

Interactive Selection Mechanics

Full Pairing Menu (pickPairing)

When both firmware URLs are absent, pickPairing presents a consolidated menu drawn from VPhoneFirmwareCatalog.pairings. Each entry displays a friendly pairing, such as "iPhone13,4 (17.5)" matched with "cloudOS-17.5-release". Selecting an entry returns the complete VPhoneFirmwarePairing object containing both iosURL and cloudosURL strings.

Single Component Selection (pickIPhone and pickCloudOS)

If only the cloudOS URL is known, pickIPhone displays iPhone-only options from the catalog. Conversely, pickCloudOS prompts for the cloudOS image when the iPhone URL is already specified. These single-column menus filter the available data to show only the missing component's options.

The choose Helper Function

All menus delegate to choose, which prints a numbered list with right-aligned indices, reads from the injected read closure (typically readLine()), trims whitespace, and validates numeric input against available options. The maxRetries parameter controls how many attempts the user has before the function throws an error.

Data Source: VPhoneFirmwareCatalog

The pairing data originates in sources/VPhoneCore/VPhoneFirmwareCatalog.swift. This file defines two primary collections used by the picker:

  • VPhoneFirmwareCatalog.pairings: An array of VPhoneFirmwarePairing structs containing iosName, cloudosName, iosURL, and cloudosURL properties.
  • VPhoneFirmwareCatalog.cloudOSOptions: An array of standalone cloudOS options used by pickCloudOS.

Each VPhoneFirmwarePairing statically maps specific iPhone hardware identifiers to compatible cloudOS release images, ensuring version alignment between the two firmware components.

Usage Examples

Non-Interactive Resolution

For automated scripts where you want the picker to accept provided values or return nils without prompting:

let sources = try VPhoneFirmwarePicker.resolve(
    iphone: nil,
    cloudos: nil,
    isInteractive: false,
    read: { nil },
    write: { _ in }
)
// sources.iphoneSource and sources.cloudosSource may be nil;
// fw_prepare.sh will use its internal defaults.

Interactive Full Pairing

To present the complete pairing menu when starting fresh:

let sources = try VPhoneFirmwarePicker.resolve(
    iphone: nil,
    cloudos: nil,
    isInteractive: true,
    maxRetries: 5,
    read: { readLine() },
    write: { print($0) }
)
// sources now contains selected URLs for both firmware images

Supplying One Known URL

When you know the cloudOS URL but need to select a compatible iPhone build:

let sources = try VPhoneFirmwarePicker.resolve(
    iphone: nil,
    cloudos: "https://example.com/cloudos-17.5.img",
    isInteractive: true,
    read: { readLine() },
    write: { print($0) }
)
// Interactive menu shows only iPhone options; cloudOS URL is preserved

Consuming Results in Build Scripts

The returned structure feeds directly into the firmware preparation pipeline executed by VPhoneFirmwareSelection.swift:

let fwSources = try VPhoneFirmwarePicker.resolve(...)
let args = [
    "--iphone", fwSources.iphoneSource ?? "",
    "--cloudos", fwSources.cloudosSource ?? ""
]
Process.run("/usr/local/bin/fw_prepare.sh", args)

Summary

  • VPhoneFirmwarePicker in Lakr233/vphone-cli handles pairing logic through the static resolve method in sources/VPhoneCore/VPhoneFirmwarePicker.swift.
  • The process validates existing URLs, presents full pairing menus when both are missing, or prompts for single components when one is provided.
  • Data originates from VPhoneFirmwareCatalog.pairings, which statically defines compatible iPhone and cloudOS combinations with their download URLs.
  • Interactive menus use the choose helper with configurable retry logic and injected I/O for testability.
  • The method returns a VPhoneFirmwareSources struct consumable by downstream components like VPhoneFirmwareSelection.swift and the fw_prepare.sh script.

Frequently Asked Questions

What happens if I provide both URLs to VPhoneFirmwarePicker?

If you provide non-nil values for both the iphone and cloudos parameters, the resolve method returns immediately with a VPhoneFirmwareSources struct containing your supplied URLs. This bypasses all interactive menus regardless of the isInteractive flag, allowing scripted workflows to override catalog selections.

Where does the pairing data come from?

The picker reads from VPhoneFirmwareCatalog.pairings, a static array defined in sources/VPhoneCore/VPhoneFirmwareCatalog.swift. Each VPhoneFirmwarePairing object contains human-readable names (iosName, cloudosName) and direct download URLs (iosURL, cloudosURL) for specific iPhone hardware and cloudOS version combinations.

How does the picker handle invalid user input?

The choose helper validates that input is a valid index within the menu bounds. If the user enters an invalid number or non-numeric text, the picker decrements the retry counter. After exceeding maxRetries (default 5), it throws VPhoneFirmwarePickerError.invalidSelection. An empty line immediately throws VPhoneFirmwarePickerError.aborted.

Can I use VPhoneFirmwarePicker in non-interactive mode?

Yes. Set isInteractive: false when calling resolve. In this mode, the method returns whatever URLs you provide (including nil) without displaying menus or reading from stdin. This is essential for CI/CD pipelines and automated scripts that rely on fw_prepare.sh defaults or environment-provided URLs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →