diff --git a/Sources/tart/Commands/Create.swift b/Sources/tart/Commands/Create.swift index 1abdd1d..495572f 100644 --- a/Sources/tart/Commands/Create.swift +++ b/Sources/tart/Commands/Create.swift @@ -10,7 +10,7 @@ struct Create: AsyncParsableCommand { @Argument(help: "VM name") var name: String - @Option(help: ArgumentHelp("create a macOS VM using path to the IPSW file or URL (or \"latest\", to fetch the latest supported IPSW automatically)", valueName: "path")) + @Option(help: ArgumentHelp("create a macOS VM using path to the IPSW file or URL (or \"latest\", to fetch the latest supported IPSW automatically)", valueName: "path"), completion: .file()) var fromIPSW: String? @Flag(help: "create a Linux VM") diff --git a/Sources/tart/Commands/Export.swift b/Sources/tart/Commands/Export.swift index 5e70ae8..7d0c3d2 100644 --- a/Sources/tart/Commands/Export.swift +++ b/Sources/tart/Commands/Export.swift @@ -7,7 +7,7 @@ struct Export: AsyncParsableCommand { @Argument(help: "Source VM name.", completion: .custom(completeMachines)) var name: String - @Argument(help: "Path to the destination file.") + @Argument(help: "Path to the destination file.", completion: .file()) var path: String? func run() async throws { diff --git a/Sources/tart/Commands/Import.swift b/Sources/tart/Commands/Import.swift index 46a5162..645b5bd 100644 --- a/Sources/tart/Commands/Import.swift +++ b/Sources/tart/Commands/Import.swift @@ -4,10 +4,10 @@ import Foundation struct Import: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Import VM from a compressed .tvm file") - @Argument(help: "Path to a file created with \"tart export\".") + @Argument(help: "Path to a file created with \"tart export\".", completion: .file()) var path: String - @Argument(help: "Destination VM name.") + @Argument(help: "Destination VM name.", completion: .custom(completeLocalMachines)) var name: String func validate() throws { diff --git a/Sources/tart/Commands/List.swift b/Sources/tart/Commands/List.swift index a15ecb2..c1a24b4 100644 --- a/Sources/tart/Commands/List.swift +++ b/Sources/tart/Commands/List.swift @@ -18,7 +18,7 @@ struct List: AsyncParsableCommand { @Option(help: ArgumentHelp("Only display VMs from the specified source (e.g. --source local, --source oci).")) var source: String? - @Option(help: "Output format: text or json") + @Option(help: "Output format: text or json", completion: .list(["text", "json"])) var format: Format = .text @Flag(name: [.short, .long], help: ArgumentHelp("Only display VM names.")) diff --git a/Sources/tart/Commands/Prune.swift b/Sources/tart/Commands/Prune.swift index 777676c..5e41e03 100644 --- a/Sources/tart/Commands/Prune.swift +++ b/Sources/tart/Commands/Prune.swift @@ -7,7 +7,7 @@ import SwiftDate struct Prune: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Prune OCI and IPSW caches or local VMs") - @Option(help: ArgumentHelp("Entries to remove: \"caches\" targets OCI and IPSW caches and \"vms\" targets local VMs.")) + @Option(help: ArgumentHelp("Entries to remove: \"caches\" targets OCI and IPSW caches and \"vms\" targets local VMs."), completion: .list(["caches", "vms"])) var entries: String = "caches" @Option(help: ArgumentHelp("Remove entries that were last accessed more than n days ago", diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index 56b264f..e21a7dd 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -81,7 +81,7 @@ struct Run: AsyncParsableCommand { @Option(help: ArgumentHelp( "Attach an externally created serial console", discussion: "Alternative to `--serial` flag for programmatic integrations." - )) + ), completion: .file()) var serialPath: String? @Flag(help: ArgumentHelp("Force open a UI window, even when VNC is enabled.", visibility: .private)) @@ -116,8 +116,12 @@ struct Run: AsyncParsableCommand { #endif var vncExperimental: Bool = false + // Note that the valueName here should really be "path[:options]" instead of just "path", + // see ArgumentParser issue[1] for more details. + // + // [1]: https://github.com/apple/swift-argument-parser/issues/761 @Option(help: ArgumentHelp(""" - Additional disk attachments with an optional read-only and synchronization options (e.g. --disk="disk.bin", --disk="ubuntu.iso:ro", --disk="/dev/disk0", --disk "ghcr.io/cirruslabs/xcode:16.0:ro" or --disk="nbd://localhost:10809/myDisk:sync=none") + Additional disk attachments with an optional read-only and synchronization options in the form of path[:options] (e.g. --disk="disk.bin", --disk="ubuntu.iso:ro", --disk="/dev/disk0", --disk "ghcr.io/cirruslabs/xcode:16.0:ro" or --disk="nbd://localhost:10809/myDisk:sync=none") """, discussion: """ The disk attachment can be a: @@ -139,7 +143,7 @@ struct Run: AsyncParsableCommand { To work around this pass TART_HOME explicitly: sudo TART_HOME="$HOME/.tart" tart run sequoia --disk=/dev/disk0 - """, valueName: "path[:options]")) + """, valueName: "path"), completion: .file()) var disk: [String] = [] #if arch(arm64) @@ -158,7 +162,11 @@ struct Run: AsyncParsableCommand { #endif var rosettaTag: String? - @Option(help: ArgumentHelp("Additional directory shares with an optional read-only and mount tag options (e.g. --dir=\"~/src/build\" or --dir=\"~/src/sources:ro\")", discussion: """ + // Note that the valueName here should really be "[name:]path[:options]" instead of just "path", + // see ArgumentParser issue[1] for more details. + // + // [1]: https://github.com/apple/swift-argument-parser/issues/761 + @Option(help: ArgumentHelp("Additional directory shares with an optional read-only and mount tag options in the form of [name:]path[:options] (e.g. --dir=\"~/src/build\" or --dir=\"~/src/sources:ro\")", discussion: """ Requires host to be macOS 13.0 (Ventura) or newer. macOS guests must be running macOS 13.0 (Ventura) or newer too. Options are comma-separated and are as follows: @@ -170,7 +178,7 @@ struct Run: AsyncParsableCommand { Mount tag can be overridden by appending tag property to the directory share (e.g. --dir=\"~/src/build:tag=build\" or --dir=\"~/src/build:ro,tag=build\"). Then it can be mounted via "mount_virtiofs build ~/build" inside guest macOS and "mount -t virtiofs build ~/build" inside guest Linux. In case of passing multiple directories per mount tag it is required to prefix them with names e.g. --dir=\"build:~/src/build\" --dir=\"sources:~/src/sources:ro\". These names will be used as directory names under the mounting point inside guests. For the example above it will be "/Volumes/My Shared Files/build" and "/Volumes/My Shared Files/sources" respectively. - """, valueName: "[name:]path[:options]")) + """, valueName: "path"), completion: .directory) var dir: [String] = [] @Flag(help: ArgumentHelp("Enable nested virtualization if possible"))