diff --git a/Sources/LucaCLI/Commands/CalculateChecksumCommand.swift b/Sources/LucaCLI/Commands/CalculateChecksumCommand.swift index 44efeef..65b9b90 100644 --- a/Sources/LucaCLI/Commands/CalculateChecksumCommand.swift +++ b/Sources/LucaCLI/Commands/CalculateChecksumCommand.swift @@ -8,13 +8,27 @@ struct CalculateChecksumCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "calculate-checksum", - abstract: "Calculates the checksum of a file." + abstract: "Calculate the checksum of a file.", + discussion: """ + Computes a hash for integrity verification. + Use this to generate checksums for Lucafile entries. + """ ) - @Argument(help: "The path to the file.") + @Argument(help: ArgumentHelp( + "Path to the file to hash.", + discussion: """ + Example: + luca calculate-checksum ./downloads/tool.zip + """, + valueName: "path" + )) var file: String - @Option(help: "The algorithm to use.") + @Option(help: ArgumentHelp( + "Hash algorithm to use.", + valueName: "algorithm" + )) var algorithm: ChecksumAlgorithm = .sha256 func run() async throws { diff --git a/Sources/LucaCLI/Commands/InstallCommand.swift b/Sources/LucaCLI/Commands/InstallCommand.swift index 4757636..c4561f6 100644 --- a/Sources/LucaCLI/Commands/InstallCommand.swift +++ b/Sources/LucaCLI/Commands/InstallCommand.swift @@ -26,91 +26,143 @@ struct InstallCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "install", - abstract: """ -Install versions of tools defined in a spec file or directly from GitHub releases. - -Example of usages: - -# From a spec - -luca install -luca install --spec ./Lucafile - -# Individual installations - -luca install TogglesPlatform/Toggles@1.0.0 -luca install krzysztofzablocki/Sourcery@2.2.7 - --asset sourcery-2.2.7.zip - --binary-path bin/sourcery -luca install firebase/firebase-tools@v14.12.1 - --desired-binary-name firebase - -# Inline installations - -luca install - --name ToggleGen - --version 1.0.0 - --url https://github.com/TogglesPlatform/ToggleGen/releases/download/1.0.0/ToggleGen-macOS-universal-binary.zip - -luca install - --name ToggleGen - --version 1.0.0 - --url https://github.com/TogglesPlatform/ToggleGen/releases/download/1.0.0/ToggleGen-macOS-universal-binary.zip - --checksum e0a6540d01434f436335a9f48405ffd008020043c9b1c21d38742fc8e7c9fdc3 - --algorithm sha256 - -luca install - --name Sourcery - --version 2.2.7 - --url https://github.com/krzysztofzablocki/Sourcery/releases/download/2.2.7/sourcery-2.2.7.zip - --binary-path bin/sourcery - -luca install - --name FirebaseCLI - --version 14.12.1 - --url https://github.com/firebase/firebase-tools/releases/download/v14.12.1/firebase-tools-macos - --desired-binary-name firebase - --ignore-arch-check -""" + abstract: "Install tools from a spec file or GitHub releases.", + discussion: """ + Supports three installation modes: + - Spec file: Install all tools defined in a Lucafile + - Individual: Install from GitHub using org/repo@version format + - Inline: Install from a direct URL with explicit parameters + + See parameter help for detailed examples. + """ ) - @Option(name: .long, help: "The location of the spec file.") + @Option(name: .long, help: ArgumentHelp( + "Path to the spec file.", + discussion: """ + Defaults to './Lucafile' in the current directory if not specified. + Examples: + luca install + luca install --spec ./config/Lucafile + """, + valueName: "path" + )) var spec: String? - @Argument(help: "Tool to install in format 'organization/repository@version'") + @Argument(help: ArgumentHelp( + "Tool to install in 'org/repo@version' format.", + discussion: """ + Examples: + luca install TogglesPlatform/Toggles@1.0.0 + luca install krzysztofzablocki/Sourcery@2.2.7 --asset sourcery-2.2.7.zip + """, + valueName: "org/repo@version" + )) var identifier: String? - @Option(help: "Name of the tool to install.") + @Option(help: ArgumentHelp( + "Name of the tool (inline mode).", + discussion: """ + Requires --version and --url. + Example: + luca install --name ToggleGen --version 1.0.0 \\ + --url https://github.com/.../ToggleGen.zip + """, + valueName: "tool-name" + )) var name: String? - @Option(help: "Version of the tool to install.") + @Option(help: ArgumentHelp( + "Version of the tool (inline mode).", + discussion: "Requires --name and --url.", + valueName: "version" + )) var version: String? - @Option(help: "URL of the asset for the tool to install.") + @Option(help: ArgumentHelp( + "URL of the asset to download (inline mode).", + discussion: "Requires --name and --version.", + valueName: "url" + )) var url: String? - @Option(help: "Filename of the asset associated with the release.") + @Option(help: ArgumentHelp( + "Filename of the release asset to download.", + discussion: """ + Use when the release contains multiple assets. + Example: + luca install krzysztofzablocki/Sourcery@2.2.7 \\ + --asset sourcery-2.2.7.zip + """, + valueName: "filename" + )) var asset: String? - @Option(help: "Binary path for the asset associated with the release.") + @Option(help: ArgumentHelp( + "Path to the executable inside the archive.", + discussion: """ + Required when the binary is nested within the archive. + Example: + --binary-path bin/sourcery + """, + valueName: "path" + )) var binaryPath: String? - @Option(help: "Name of the binary stored locally. Requires `url` to point to an executable file, ignored otherwise.") + @Option(help: ArgumentHelp( + "Local name for the installed binary.", + discussion: """ + Useful when the downloaded file has a different name. + Example: + luca install firebase/firebase-tools@v14.12.1 \\ + --desired-binary-name firebase + """, + valueName: "name" + )) var desiredBinaryName: String? - @Option(help: "Checksum of the asset associated with the release.") + @Option(help: ArgumentHelp( + "Expected checksum for integrity verification.", + discussion: """ + Use with --algorithm. + Example: + --checksum e0a6540d01434f436335a9f48405ffd00802... + """, + valueName: "hash" + )) var checksum: String? - @Option(help: "Algorithm to use to verify the integrity of the asset.") + @Option(help: ArgumentHelp( + "Hash algorithm for checksum verification.", + valueName: "algorithm" + )) var algorithm: ChecksumAlgorithm? - @Flag(help: "Skip architecture compatibility validation during installation.") + @Flag(help: ArgumentHelp( + "Skip architecture compatibility validation.", + discussion: """ + Use for platform-independent executables. + Example: + luca install firebase/firebase-tools@v14.12.1 \\ + --desired-binary-name firebase \\ + --ignore-arch-check + """ + )) var ignoreArchCheck: Bool = false - @Flag(inversion: .prefixedNo, help: "Install the post-checkout git hook in the current repository.") + @Flag(inversion: .prefixedNo, help: ArgumentHelp( + "Install the post-checkout git hook.", + discussion: """ + Enabled by default. Automatically runs 'luca install' after git checkout. + Use --no-install-post-checkout-git-hook to disable. + """ + )) var installPostCheckoutGitHook: Bool = true - @Flag(help: "Suppress all output except final success message.") + @Flag(help: ArgumentHelp( + "Suppress output except final success message.", + discussion: "Useful for CI/CD pipelines or scripting." + )) var quiet: Bool = false func run() async throws { diff --git a/Sources/LucaCLI/Commands/InstalledCommand.swift b/Sources/LucaCLI/Commands/InstalledCommand.swift index 456bdaf..0e4883e 100644 --- a/Sources/LucaCLI/Commands/InstalledCommand.swift +++ b/Sources/LucaCLI/Commands/InstalledCommand.swift @@ -8,7 +8,11 @@ struct InstalledCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "installed", - abstract: "List installed tools and versions." + abstract: "List installed tools and versions.", + discussion: """ + Shows all tools in the local cache with their installed versions. + Use 'luca linked' to see which versions are currently active. + """ ) func run() async throws { diff --git a/Sources/LucaCLI/Commands/LinkedCommand.swift b/Sources/LucaCLI/Commands/LinkedCommand.swift index 4390570..c841cad 100644 --- a/Sources/LucaCLI/Commands/LinkedCommand.swift +++ b/Sources/LucaCLI/Commands/LinkedCommand.swift @@ -8,7 +8,11 @@ struct LinkedCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "linked", - abstract: "List tools linked in the current project." + abstract: "List tools linked in the current project.", + discussion: """ + Shows all symlinks in the active folder with their versions and paths. + Use 'luca installed' to see all available tool versions. + """ ) func run() async throws { diff --git a/Sources/LucaCLI/Commands/LucaCommand.swift b/Sources/LucaCLI/Commands/LucaCommand.swift index d559a96..8466a7f 100644 --- a/Sources/LucaCLI/Commands/LucaCommand.swift +++ b/Sources/LucaCLI/Commands/LucaCommand.swift @@ -9,13 +9,22 @@ struct LucaCommand: AsyncParsableCommand { commandName: "luca", abstract: "A modern tool manager that helps you install and manage development tools.", version: version, - subcommands: [ - CalculateChecksumCommand.self, - InstallCommand.self, - InstalledCommand.self, - LinkedCommand.self, - UninstallCommand.self, - UnlinkCommand.self - ] + groupedSubcommands: [ + CommandGroup(name: "Installation", subcommands: [ + InstallCommand.self, + UninstallCommand.self + ]), + CommandGroup(name: "Linking", subcommands: [ + LinkedCommand.self, + UnlinkCommand.self + ]), + CommandGroup(name: "Listing", subcommands: [ + InstalledCommand.self + ]), + CommandGroup(name: "Validation", subcommands: [ + CalculateChecksumCommand.self + ]) + ], + defaultSubcommand: InstallCommand.self ) } diff --git a/Sources/LucaCLI/Commands/UninstallCommand.swift b/Sources/LucaCLI/Commands/UninstallCommand.swift index 84cc857..0c160ee 100644 --- a/Sources/LucaCLI/Commands/UninstallCommand.swift +++ b/Sources/LucaCLI/Commands/UninstallCommand.swift @@ -10,10 +10,22 @@ struct UninstallCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "uninstall", - abstract: "Uninstall a specific tool version." + abstract: "Uninstall a specific tool version.", + discussion: """ + Removes an installed tool version from the local cache. + If no version is specified, prompts for selection interactively. + """ ) - @Argument(help: "The tool to uninstall, optionally with version (e.g. SwiftLint@0.61.0)") + @Argument(help: ArgumentHelp( + "Tool to uninstall, optionally with version.", + discussion: """ + Examples: + luca uninstall SwiftLint + luca uninstall SwiftLint@0.61.0 + """, + valueName: "tool[@version]" + )) var tool: String func run() async throws { diff --git a/Sources/LucaCLI/Commands/UnlinkCommand.swift b/Sources/LucaCLI/Commands/UnlinkCommand.swift index c320ec5..0c5e5b5 100644 --- a/Sources/LucaCLI/Commands/UnlinkCommand.swift +++ b/Sources/LucaCLI/Commands/UnlinkCommand.swift @@ -9,10 +9,21 @@ struct UnlinkCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "unlink", - abstract: "Removes a specific symlink from the active folder." + abstract: "Remove a symlink from the active folder.", + discussion: """ + Removes the symlink without uninstalling the underlying tool. + The tool remains available for re-linking to a different version. + """ ) - @Argument(help: "The name of the symlink to remove (e.g. swiftlint)") + @Argument(help: ArgumentHelp( + "Name of the symlink to remove.", + discussion: """ + Example: + luca unlink swiftlint + """, + valueName: "name" + )) var symlink: String func run() async throws {