# How VPhoneFirmwarePicker Pairs iPhone and CloudOS Builds in vphone-cli

> Learn how VPhoneFirmwarePicker pairs iPhone and cloudOS builds by validating URLs, prompting for missing components, and returning firmware download URLs.

- Repository: [Lakr/vphone-cli](https://github.com/Lakr233/vphone-cli)
- Tags: internals
- Published: 2026-09-13

---

**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`](https://github.com/Lakr233/vphone-cli/blob/main/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.

```swift
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`](https://github.com/Lakr233/vphone-cli/blob/main/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:

```swift
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:

```swift
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:

```swift
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`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneFirmwareSelection.swift):

```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`](https://github.com/Lakr233/vphone-cli/blob/main/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`](https://github.com/Lakr233/vphone-cli/blob/main/VPhoneFirmwareSelection.swift) and the [`fw_prepare.sh`](https://github.com/Lakr233/vphone-cli/blob/main/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`](https://github.com/Lakr233/vphone-cli/blob/main/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`](https://github.com/Lakr233/vphone-cli/blob/main/fw_prepare.sh) defaults or environment-provided URLs.