Swift-first parsing. Total command over your CLI.
One signature. Infinite tooling.
All the ergonomics, none of the forks.
Commander is Peekaboo's Swift-native command-line framework. It combines declarative property wrappers, a lightweight parser/router, and runtime helpers that integrate tightly with async/await + approachable concurrency.
Platform story: Commander targets the platforms we actively test: macOS, Linux, and Apple simulators (iOS/tvOS/watchOS/visionOS). Windows and Android builds are not supported or exercised in CI, so we no longer advertise them.
- Property-wrapper ergonomics –
@Option,@Argument,@Flag, and@OptionGroupmirror the Swift Argument Parser API but simply register metadata. You keep writing declarative commands while Commander handles parsing and validation centrally. - Command signatures everywhere –
CommandSignaturereflects every option/flag/argument so docs, help output, agent metadata, and tests all rely on the exact same definitions. - Source-of-truth metadata –
CommandDescriptionreplaces the old ArgumentParserCommandConfiguration, giving us Commander-native builders for abstracts, discussions, versions, and subcommand trees. - Standard runtime options – Every command gets
-v/--verbose,--json-output, and--log-level <trace|verbose|debug|info|warning|error|critical>automatically so you can control logging without touching each command file. - Program router –
Program.resolve(argv:)walks the descriptor tree (root command → subcommand → default subcommand) and produces aCommandInvocationwith parsed values and the fully-qualified path. - Binder APIs –
CommanderCLIBinder(living in PeekabooCLI) shows how to hydrate existing command structs by conforming them toCommanderBindableCommand. This keeps runtime logic untouched while swapping in Commander incrementally. - Approachable concurrency ready – the package enables
StrictConcurrency,ExistentialAny, andNonisolatedNonsendingByDefaultso anything that depends on Commander inherits Peekaboo's concurrency guarantees.
Add Commander as a local dependency (it currently lives in /Commander inside the Peekaboo repo):
// Package.swift
dependencies: [
.package(path: "../Commander"),
// ...
],
targets: [
.executableTarget(
name: "my-cli",
dependencies: [
.product(name: "Commander", package: "Commander")
]
)
]Then declare your command using the familiar property-wrapper style:
import Commander
@MainActor
struct ScreenshotCommand: ParsableCommand {
@Argument(help: "Output path") var path: String
@Option(help: "Target display index") var display: Int?
@Flag(help: "Emit JSON output") var json = false
static var commandDescription = CommandDescription(
commandName: "capture",
abstract: "Capture a screenshot"
)
mutating func run() async throws {
// perform work…
}
}Then run it like any SwiftPM executable:
$ swift run capture --display 1 --json /tmp/screen.pngCommander handles --help, flag parsing, and error messages based on the metadata in your struct.
If you need more control over how parsed values reach your command type, conform to CommanderBindableCommand and use the helper APIs (decodeOption, makeFocusOptions, etc.). PeekabooCLI's window/agent commands are good examples.
By default the runtime injects the standard logging flags mentioned above; you can flip verbosity with -v or set an explicit level via --log-level warning (overrides environment variables like PEEKABOO_LOG_LEVEL).
Every ParsableCommand publishes a CommandDescription. The helper MainActorCommandDescription.describe { ... } builder keeps metadata construction on the main actor while staying nonisolated at the call-site:
@MainActor
struct AgentCommand: ParsableCommand {
nonisolated(unsafe) static var commandDescription: CommandDescription {
MainActorCommandDescription.describe {
CommandDescription(
commandName: "agent",
abstract: "Run a Peekaboo automation agent",
subcommands: [Serve.self, List.self],
defaultSubcommand: Serve.self
)
}
}
}Commander caches these descriptions and feeds them to the router, peekaboo learn, and the documentation/export tooling, so the CLI, agents, and MCP metadata all stay in sync without an ArgumentParser compatibility shim.
Commander ships with a DocC catalog at Sources/Commander/Commander.docc. Generate the developer documentation (including the new articles) with:
swift package --disable-sandbox generate-documentation \
--package-path Commander \
--target Commander \
--output-path .build/Commander.doccarchive \
--transform-for-static-hosting \
--hosting-base-path commander-docsThe resulting archive can be previewed locally in Xcode or hosted via GitHub Pages and Peekaboo's internal doc ingesters.
Sources/Commander– Core types (property wrappers, tokenizer, parser, program descriptors, metadata helpers).Tests/CommanderTests– Unit tests for the parser/router, tokenizer edge cases, andCommandDescriptionmetadata. Run them withswift test --package-path Commander.
Commander mirrors the ergonomics of Swift Argument Parser while keeping the parsing logic centralized. Key building blocks:
| Wrapper | Description | Notable Parameters |
|---|---|---|
@Argument |
Positional values. Commander automatically enforces optionals/non-optionals. | help |
@Option |
Named options (supports short, long, and custom spellings). | name, names, parsing (singleValue, upToNextOption, remaining) |
@Flag |
Boolean switches. Commander automatically wires both short & long spellings. | name, names, help |
@OptionGroup |
Reusable sets of options/flags (e.g., focus/window option structs). | – |
Every command automatically gets the standard runtime flags --verbose / -v and --json-output, courtesy of CommandSignature.withStandardRuntimeFlags().
Need compatibility spellings (like --json-output alongside --json)? Use the alias helpers so Commander’s help output only shows the canonical names while the parser still accepts every variant:
let jsonFlag = FlagDefinition(
label: "jsonOutput",
names: [
.short("j"),
.long("json"),
.aliasLong("json-output"),
.aliasLong("jsonOutput")
],
help: "Emit machine-readable JSON output"
)aliasLong / aliasShort behave exactly like the regular cases during parsing, but Commander omits them from generated help text and metadata exports so your CLI docs stay concise.
OptionParsingStrategy mirrors the most common CLI behaviors:
singleValue: exactly one argument follows the option (default).upToNextOption: consume all values until the next option/flag (perfect for--include foo bar).remaining: consume the rest ofargv(after--).
For advanced scenarios, CommanderBindableValues gives you helpers (decodeOption, requireOption, makeWindowOptions, etc.) so existing command types can conform to CommanderBindableCommand and hydrate themselves from parsed values without rewriting runtime logic.
Commander is released under the MIT license. Refer to LICENSE for details.
