Workflow for adding new CLI commands and subcommands to isimctl. Use when asked to create a new command, add a subcommand, implement a new CLI feature, or add a new user-facing operation. Covers Isimctl CLI layer (ArgumentParser), IsimctlUI command implementation, messaging, and registration checklist.
Step-by-step workflow for adding new CLI commands to isimctl, covering the Isimctl CLI layer (argument parsing) and IsimctlUI layer (command logic and UI).
isimctl delete, isimctl rename)copilot-instructions.md for overview)swift-argument-parser for CLI definitionsNoora package for terminal UI elementsCreate a command file in Sources/Isimctl/Commands/<CommandName>.swift:
AsyncParsableCommand with CommandConfigurationrun() methodRule: Isimctl should only parse arguments and delegate to IsimctlUI. No business logic or UI rendering here.
REQUIRED — Register the command in Sources/Isimctl/Isimctl.swift:
<CommandName>.self to CommandConfiguration.subcommands arrayRun swift run isimctl --help and confirm the new command appears in the list.
Create a feature directory and command implementation:
Directory structure: Sources/IsimctlUI/Commands/<Feature>/
Required files:
<Feature>Command.swift — Orchestration logic (main entry point from CLI)<Feature>Message.swift — Optional: command-specific user-facing messagesSubdirectory placement rule:
Sources/IsimctlUI/Shared/Sources/IsimctlUI/Commands/<Feature>/All command implementations use dual initializers:
public struct FeatureCommand: Sendable {
private let simctl: any Simctlable
private let featureMessage: any FeatureMessaging
// Public init - creates real dependencies
public init(noora: any Noorable) {
self.init(
simctl: Simctl(),
featureMessage: FeatureMessage(noora: noora)
)
}
// Internal init - for testing with mocks
init(
simctl: any Simctlable,
featureMessage: any FeatureMessaging,
) {
self.simctl = simctl
self.featureMessage = featureMessage
}
}
Create protocol-based message components for user-facing messages:
/// @mockable
protocol FeatureMessaging: Sendable {
func showSuccess(_ device: Device)
}
struct FeatureMessage: FeatureMessaging {
private let noora: any Noorable
init(noora: any Noorable) {
self.noora = noora
}
func showSuccess(_ device: Device) {
noora.success(...)
}
}
Messaging principles (isimctl uses a conversational tone):
"Which device would you like to boot?""Select a device", "Select a device to boot"noora.success() — Operation completed successfullynoora.info() — General information or alerts requiring user attention.alert() — Important messages with optional takeaways for guidanceAfter adding @mockable protocols:
make gen-mocks to generate mock fileswrite-unit-tests skill)| Issue | Solution |
|---|---|
Command not shown in --help | Ensure Step 2 is completed — register in Isimctl.swift subcommands array |
| Build error in IsimctlUI | Check that Package.swift includes required dependencies for the IsimctlUI target |
| Mock not generated | Run make gen-mocks after adding @mockable annotation to protocols |