From 50518c6b9d9064776cec2ba0221a4b6a29b123b5 Mon Sep 17 00:00:00 2001 From: maniramezan Date: Thu, 21 May 2026 21:37:59 -0400 Subject: [PATCH] Add Gh command family --- .claude/skills/swiftyshell.md | 116 ++++- .github/workflows/build.yml | 3 +- .github/workflows/reusable-ci.yml | 2 +- AGENTS.md | 8 +- Package.swift | 3 +- README.md | 1 + Sources/SwiftyShell/Gh/Gh.swift | 481 ++++++++++++++++++ .../Articles/SelectingCommandFamilies.md | 4 +- Sources/SwiftyShell/SwiftyShell.docc/Gh.md | 111 ++++ .../SwiftyShell.docc/SwiftyShell.md | 7 +- Tests/SwiftyShellTests/Gh/GhTests.swift | 187 +++++++ 11 files changed, 909 insertions(+), 14 deletions(-) create mode 100644 Sources/SwiftyShell/Gh/Gh.swift create mode 100644 Sources/SwiftyShell/SwiftyShell.docc/Gh.md create mode 100644 Tests/SwiftyShellTests/Gh/GhTests.swift diff --git a/.claude/skills/swiftyshell.md b/.claude/skills/swiftyshell.md index 08b54c5..397b34d 100644 --- a/.claude/skills/swiftyshell.md +++ b/.claude/skills/swiftyshell.md @@ -29,14 +29,16 @@ Before writing any code, follow this decision tree: → Use `Brew` 7. Is this a Swift toolchain or SwiftPM operation (`swift build`, `swift test`, `swift run`, `swift package`, ...)? → Use `Swift` -8. Does the operation need typed output, structured results, or conditional follow-up? +8. Is this a GitHub CLI operation (`gh pr`, `gh repo`, `gh workflow`, `gh api`, `gh copilot`, `gh skill`, ...)? + → Use `Gh` +9. Does the operation need typed output, structured results, or conditional follow-up? → Use the appropriate typed client -9. Are two or more commands chained by pipe? +10. Are two or more commands chained by pipe? → Use `.pipe(to:)` to build a `Pipeline` -10. Does the command write output to a file? +11. Does the command write output to a file? → Use `.stdout(.file(path:append:))` on the command -11. Is this any other command? - → Use `Command` +12. Is this any other command? + → Use `Command` ### API Reference @@ -749,6 +751,106 @@ public struct Swift: RunnableCommandFamily { } ``` +#### GitHub CLI + +```swift +public enum GhSubcommand: String, Sendable, Equatable, Hashable { + case agentTask + case agent + case agents + case agentTasks + case alias + case api + case attestation + case auth + case browse + case cache + case completion + case config + case copilot + case extensionCommand + case gist + case gpgKey + case issue + case label + case licenses + case org + case pr + case project + case release + case repo + case ruleset + case run + case search + case secret + case skill + case sshKey + case status + case variable + case workflow +} + +public struct Gh: RunnableCommandFamily { + public init(context: ShellContext = .init()) + public func version() -> Self + public func subcommand(_ value: GhSubcommand) -> Self + public func subcommand(_ value: String) -> Self + public func subcommand(_ value: GhSubcommand, _ nested: String) -> Self + public func subcommand(_ value: String, _ nested: String) -> Self + public func agentTask(_ nested: String? = nil) -> Self + public func agent(_ nested: String? = nil) -> Self + public func agents(_ nested: String? = nil) -> Self + public func agentTasks(_ nested: String? = nil) -> Self + public func alias(_ nested: String? = nil) -> Self + public func api(_ endpoint: String? = nil) -> Self + public func attestation(_ nested: String? = nil) -> Self + public func auth(_ nested: String? = nil) -> Self + public func browse(_ path: String? = nil) -> Self + public func cache(_ nested: String? = nil) -> Self + public func completion(_ shell: String? = nil) -> Self + public func config(_ nested: String? = nil) -> Self + public func copilot() -> Self + public func extensionCommand(_ nested: String? = nil) -> Self + public func gist(_ nested: String? = nil) -> Self + public func gpgKey(_ nested: String? = nil) -> Self + public func issue(_ nested: String? = nil) -> Self + public func label(_ nested: String? = nil) -> Self + public func licenses() -> Self + public func org(_ nested: String? = nil) -> Self + public func pr(_ nested: String? = nil) -> Self + public func project(_ nested: String? = nil) -> Self + public func release(_ nested: String? = nil) -> Self + public func repoCommand(_ nested: String? = nil) -> Self + public func ruleset(_ nested: String? = nil) -> Self + public func runCommand(_ nested: String? = nil) -> Self + public func search(_ nested: String? = nil) -> Self + public func secret(_ nested: String? = nil) -> Self + public func skill(_ nested: String? = nil) -> Self + public func sshKey(_ nested: String? = nil) -> Self + public func status() -> Self + public func variable(_ nested: String? = nil) -> Self + public func workflow(_ nested: String? = nil) -> Self + public func repo(_ ownerAndName: String) -> Self + public func hostname(_ value: String) -> Self + public func json(_ fields: [String]) -> Self + public func json(_ fields: String...) -> Self + public func jq(_ expression: String) -> Self + public func template(_ value: String) -> Self + public func limit(_ count: Int) -> Self + public func web(_ enabled: Bool = true) -> Self + public func confirm(_ enabled: Bool = true) -> Self + public func silent(_ enabled: Bool = true) -> Self + public func option(_ name: String) -> Self + public func option(_ name: String, _ value: String) -> Self + public func argument(_ value: String) -> Self + public func arguments(_ values: [String]) -> Self + public func positionalArgument(_ value: String) -> Self + public func positionalArguments(_ values: [String]) -> Self + public func command() -> Command + public func run() async throws -> ShellOutput +} +``` + #### Archives (Tar / Zip / Unzip) ```swift @@ -1418,7 +1520,7 @@ SwiftyShell uses [SwiftPM Package Traits](https://github.com/swiftlang/swift-evo Declared in `Package.swift`: -- **Per-family** — `Git`, `Brew`, `Grep`, `Swift`, `Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Jq`, `Rsync`, `Tar`, `Zip`, `Unzip`. One trait per family directory; for `Common/`, one trait per file. +- **Per-family** — `Git`, `Brew`, `Grep`, `Fzf`, `Rg`, `Swift`, `Gh`, `Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Jq`, `Rsync`, `Tar`, `Zip`, `Unzip`. One trait per family directory; for `Common/`, one trait per file. - **Umbrellas** — `CommonUtilities` (every `Common/*` family), `All` (every command family). Consumers select families with `traits:` on `.package(...)`: @@ -1431,7 +1533,7 @@ Consumers select families with `traits:` on `.package(...)`: The contract is enforced by `Scripts/validate-traits.swift` and CI: -1. Every `.swift` file under a gated source directory (`Git/`, `Brew/`, `Grep/`, `Swift/`, and each file in `Common/`) is wrapped top-to-bottom in `#if ... #endif`. +1. Every `.swift` file under a gated source directory (`Git/`, `Brew/`, `Grep/`, `Fzf/`, `Rg/`, `Swift/`, `Gh/`, and each file in `Common/`) is wrapped top-to-bottom in `#if ... #endif`. 2. Every test file targeting a gated family is wrapped the same way. Cross-family tests use combined guards (`#if Git && Grep`). 3. Every family directory (or `Common/*.swift` file) has a matching `.trait(name:)` entry in `Package.swift`. 4. The `All` umbrella's `enabledTraits` transitively enables every per-family trait. The `CommonUtilities` umbrella enables every `Common/*` trait. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b07f281..9329172 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: - name: Compute affected traits id: compute run: | - FULL='["", "Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "Rsync", "Tar", "Zip", "Unzip", "CommonUtilities", "All"]' + FULL='["", "Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "Gh", "Rsync", "Tar", "Zip", "Unzip", "CommonUtilities", "All"]' CHANGED=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD") echo "Changed files:" @@ -54,6 +54,7 @@ jobs: echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Fzf/|Tests/SwiftyShellTests/Fzf/)' && family_traits+=("Fzf") echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Rg/|Tests/SwiftyShellTests/Rg/)' && family_traits+=("Rg") echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Swift/|Tests/SwiftyShellTests/Swift/)' && family_traits+=("Swift") + echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Gh/|Tests/SwiftyShellTests/Gh/)' && family_traits+=("Gh") echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Common/|Tests/SwiftyShellTests/Common/)' && family_traits+=("CommonUtilities") traits+=("${family_traits[@]}") diff --git a/.github/workflows/reusable-ci.yml b/.github/workflows/reusable-ci.yml index d635a1a..183b21b 100644 --- a/.github/workflows/reusable-ci.yml +++ b/.github/workflows/reusable-ci.yml @@ -25,7 +25,7 @@ on: Defaults to the full matrix when omitted. required: false type: string - default: '["", "Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "Rsync", "Tar", "Zip", "Unzip", "CommonUtilities", "All"]' + default: '["", "Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "Gh", "Rsync", "Tar", "Zip", "Unzip", "CommonUtilities", "All"]' jobs: validate-traits: diff --git a/AGENTS.md b/AGENTS.md index fc1a1de..2587abf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,10 @@ Typed wrapper for the Homebrew package manager: `Brew` and `BrewSubcommand`. Typed wrapper for the Swift toolchain: `Swift`, `SwiftSubcommand`, and `SwiftBuildConfiguration`. +### `Sources/SwiftyShell/Gh/` + +Typed wrapper for the GitHub CLI: `Gh` and `GhSubcommand`. + ### `Sources/SwiftyShell/Common/` Typed wrappers for frequently used shell utilities: `Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Jq`, `JqArgument`, `Rsync`, `Tar`, `TarOperation`, `TarCompression`, `Zip`, `ZipCompressionLevel`, `Unzip`, and `UnzipEntry`. Each follows the same fluent builder conventions as all other command families. @@ -44,7 +48,7 @@ DocC documentation catalog. `SwiftyShell.md` is the top-level landing page. Arti ### `Tests/SwiftyShellTests/` -Test suite. Sub-folders mirror the source layout: `Brew/`, `Common/`, `Core/`, `Git/`, `Grep/`, `Pipelines/`. Test files for gated families are wrapped in `#if ` so the test target compiles under any trait selection. `Common/` has one test file per family (`LsTests.swift`, `CpTests.swift`, …) plus `CommonTestSupport.swift` (shared helpers, ungated). +Test suite. Sub-folders mirror the source layout: `Brew/`, `Common/`, `Core/`, `Fzf/`, `Gh/`, `Git/`, `Grep/`, `Pipelines/`, `Rg/`, and `Swift/`. Test files for gated families are wrapped in `#if ` so the test target compiles under any trait selection. `Common/` has one test file per family (`LsTests.swift`, `CpTests.swift`, …) plus `CommonTestSupport.swift` (shared helpers, ungated). ### `Scripts/` @@ -165,7 +169,7 @@ SwiftyShell uses [SwiftPM Package Traits](https://github.com/swiftlang/swift-evo **Trait inventory (declared in `Package.swift`):** -- Per-family: `Git`, `Brew`, `Grep`, `Swift`, `Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Jq`, `Rsync`, `Tar`, `Zip`, `Unzip` (one trait per family directory; for `Common/`, one trait per file). +- Per-family: `Git`, `Brew`, `Grep`, `Fzf`, `Rg`, `Swift`, `Gh`, `Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Jq`, `Rsync`, `Tar`, `Zip`, `Unzip` (one trait per family directory; for `Common/`, one trait per file). - Umbrellas: `CommonUtilities` (all `Common/*`), `All` (every family). **The wiring contract** — enforced by `Scripts/validate-traits.swift` and CI: diff --git a/Package.swift b/Package.swift index feea0be..1c57250 100644 --- a/Package.swift +++ b/Package.swift @@ -22,6 +22,7 @@ let package = Package( .trait(name: "Fzf", description: "Typed wrapper for the fzf fuzzy finder."), .trait(name: "Rg", description: "Typed wrapper for ripgrep (rg)."), .trait(name: "Swift", description: "Typed wrapper for the Swift toolchain CLI."), + .trait(name: "Gh", description: "Typed wrapper for the GitHub CLI."), .trait(name: "Ls", description: "Typed wrapper for ls."), .trait(name: "Cp", description: "Typed wrapper for cp."), .trait(name: "Mkdir", description: "Typed wrapper for mkdir."), @@ -44,7 +45,7 @@ let package = Package( .trait( name: "All", description: "Enables every command family shipped by SwiftyShell.", - enabledTraits: ["Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "CommonUtilities"] + enabledTraits: ["Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "Gh", "CommonUtilities"] ), // Default is intentionally empty: consumers opt in to the families they want. .default(enabledTraits: []), diff --git a/README.md b/README.md index b66db23..8dc3b25 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ SwiftyShell ships typed wrappers for common tools. Each family is gated behind a | `Brew` | `brew` | `Brew` | Full top-level subcommand coverage, plus `--cask` and `--greedy` | | `Fzf` | `fzf` | `Fzf` | Fuzzy finder options for interactive and filter-mode pipelines | | `Swift` | `swift` | `Swift` | SwiftPM build, test, run, package commands, traits, compiler flags | +| `Gh` | `gh` | `Gh` | GitHub CLI automation for PRs, repos, workflows, Copilot, skills, API calls | | `Ls` | `ls` | `Ls` | All flags, recursive, human-readable sizes | | `Cp` | `cp` | `Cp` | Recursive, force | | `Mkdir` | `mkdir` | `Mkdir` | Parent directories, permissions | diff --git a/Sources/SwiftyShell/Gh/Gh.swift b/Sources/SwiftyShell/Gh/Gh.swift new file mode 100644 index 0000000..754b219 --- /dev/null +++ b/Sources/SwiftyShell/Gh/Gh.swift @@ -0,0 +1,481 @@ +#if Gh +import Foundation + +/// The top-level `gh` command group to invoke. +public enum GhSubcommand: String, Sendable, Equatable, Hashable { + /// `gh agent-task` — work with GitHub agent tasks. + case agentTask = "agent-task" + /// `gh agent` — alias for `gh agent-task`. + case agent + /// `gh agents` — alias for `gh agent-task`. + case agents + /// `gh agent-tasks` — alias for `gh agent-task`. + case agentTasks = "agent-tasks" + /// `gh alias` — create and manage gh aliases. + case alias + /// `gh api` — make authenticated GitHub API requests. + case api + /// `gh attestation` — work with artifact attestations. + case attestation + /// `gh auth` — authenticate with GitHub hosts. + case auth + /// `gh browse` — open GitHub resources in a browser. + case browse + /// `gh cache` — manage GitHub Actions caches. + case cache + /// `gh completion` — generate shell completions. + case completion + /// `gh config` — get and set gh configuration. + case config + /// `gh copilot` — run the GitHub Copilot CLI through gh. + case copilot + /// `gh extension` — manage gh extensions. + case extensionCommand = "extension" + /// `gh gist` — work with GitHub gists. + case gist + /// `gh gpg-key` — manage GPG keys. + case gpgKey = "gpg-key" + /// `gh issue` — work with issues. + case issue + /// `gh label` — manage labels. + case label + /// `gh licenses` — list common open source licenses. + case licenses + /// `gh org` — work with GitHub organizations. + case org + /// `gh pr` — work with pull requests. + case pr + /// `gh project` — work with GitHub Projects. + case project + /// `gh release` — work with releases. + case release + /// `gh repo` — work with repositories. + case repo + /// `gh ruleset` — view repository rulesets. + case ruleset + /// `gh run` — work with GitHub Actions workflow runs. + case run + /// `gh search` — search GitHub resources. + case search + /// `gh secret` — manage GitHub Actions secrets. + case secret + /// `gh skill` — work with GitHub skills. + case skill + /// `gh ssh-key` — manage SSH keys. + case sshKey = "ssh-key" + /// `gh status` — print account and notification status. + case status + /// `gh variable` — manage GitHub Actions variables. + case variable + /// `gh workflow` — work with GitHub Actions workflows. + case workflow +} + +/// A fluent wrapper for the GitHub CLI (`gh`). +/// +/// ``Gh`` focuses on automation-friendly GitHub CLI workflows: pull requests, issues, +/// repositories, workflow runs, releases, API calls, extensions, Copilot, skills, SSH keys, +/// and agent tasks. It models the stable command shape and common global flags while preserving +/// escape hatches through ``argument(_:)`` and ``arguments(_:)`` for command-specific options. +/// +/// ```swift +/// let output = try await Gh(context: context) +/// .pr("view") +/// .repo("owner/project") +/// .json(["number", "title", "state"]) +/// .run() +/// ``` +public struct Gh: RunnableCommandFamily { + private let state: State + + /// The shell context used when running this command family. + public var context: ShellContext { state.config.context } + + /// Creates a GitHub CLI command family bound to a shell context. + /// + /// The default invocation is `gh --version`, which is safe and read-only. Select a command + /// group with helpers such as ``pr(_:)``, ``repo(_:)``, ``workflow(_:)``, or ``api(_:)``. + /// + /// - Parameter context: The shell context whose executor, search paths, environment, and + /// defaults will be used. Defaults to a freshly constructed ``ShellContext``. + public init(context: ShellContext = .init()) { + self.state = State(config: ToolConfiguration(context: context)) + } + + private init(state: State) { + self.state = state + } + + /// Returns a copy with updated shared tool configuration. + public func updatingConfiguration( + _ update: (ToolConfiguration) -> ToolConfiguration + ) -> Self { + copy(config: update(state.config)) + } + + /// Returns a copy that routes the built `gh` command's stdout to the given destination. + public func settingStdoutDestination(_ destination: OutputDestination) -> Self { + copy(stdoutDestination: destination) + } + + /// Returns a copy that routes the built `gh` command's stderr to the given destination. + public func settingStderrDestination(_ destination: OutputDestination) -> Self { + copy(stderrDestination: destination) + } + + /// Returns a copy that prints gh version information (`gh --version`). + public func version() -> Self { copy(versionRequested: true, subcommand: .some(nil), nestedSubcommand: .some(nil)) } + + /// Returns a copy that selects a top-level gh command group. + public func subcommand(_ value: GhSubcommand) -> Self { + copy(versionRequested: false, subcommand: .some(value.rawValue), nestedSubcommand: .some(nil)) + } + + /// Returns a copy that selects a raw top-level gh command group. + public func subcommand(_ value: String) -> Self { + copy(versionRequested: false, subcommand: .some(value), nestedSubcommand: .some(nil)) + } + + /// Returns a copy that selects a top-level command group and nested command. + public func subcommand(_ value: GhSubcommand, _ nested: String) -> Self { + copy(versionRequested: false, subcommand: .some(value.rawValue), nestedSubcommand: .some(nested)) + } + + /// Returns a copy that selects a raw top-level command group and nested command. + public func subcommand(_ value: String, _ nested: String) -> Self { + copy(versionRequested: false, subcommand: .some(value), nestedSubcommand: .some(nested)) + } + + /// Returns a copy that configures an `gh agent-task` command. + public func agentTask(_ nested: String? = nil) -> Self { commandGroup(.agentTask, nested) } + + /// Returns a copy that configures a `gh agent` alias command. + public func agent(_ nested: String? = nil) -> Self { commandGroup(.agent, nested) } + + /// Returns a copy that configures a `gh agents` alias command. + public func agents(_ nested: String? = nil) -> Self { commandGroup(.agents, nested) } + + /// Returns a copy that configures a `gh agent-tasks` alias command. + public func agentTasks(_ nested: String? = nil) -> Self { commandGroup(.agentTasks, nested) } + + /// Returns a copy that configures a `gh alias` command. + public func alias(_ nested: String? = nil) -> Self { commandGroup(.alias, nested) } + + /// Returns a copy that configures an authenticated GitHub API request (`gh api`). + public func api(_ endpoint: String? = nil) -> Self { + var result = commandGroup(.api, nil) + if let endpoint { result = result.positionalArgument(endpoint) } + return result + } + + /// Returns a copy that configures a `gh attestation` command. + public func attestation(_ nested: String? = nil) -> Self { commandGroup(.attestation, nested) } + + /// Returns a copy that configures a `gh auth` command. + public func auth(_ nested: String? = nil) -> Self { commandGroup(.auth, nested) } + + /// Returns a copy that configures a `gh browse` command. + public func browse(_ path: String? = nil) -> Self { + var result = commandGroup(.browse, nil) + if let path { result = result.positionalArgument(path) } + return result + } + + /// Returns a copy that configures a `gh cache` command. + public func cache(_ nested: String? = nil) -> Self { commandGroup(.cache, nested) } + + /// Returns a copy that configures a `gh completion` command. + public func completion(_ shell: String? = nil) -> Self { + var result = commandGroup(.completion, nil) + if let shell { result = result.positionalArgument(shell) } + return result + } + + /// Returns a copy that configures a `gh config` command. + public func config(_ nested: String? = nil) -> Self { commandGroup(.config, nested) } + + /// Returns a copy that configures a `gh copilot` command. + public func copilot() -> Self { commandGroup(.copilot, nil) } + + /// Returns a copy that configures a `gh extension` command. + public func extensionCommand(_ nested: String? = nil) -> Self { commandGroup(.extensionCommand, nested) } + + /// Returns a copy that configures a `gh gist` command. + public func gist(_ nested: String? = nil) -> Self { commandGroup(.gist, nested) } + + /// Returns a copy that configures a `gh gpg-key` command. + public func gpgKey(_ nested: String? = nil) -> Self { commandGroup(.gpgKey, nested) } + + /// Returns a copy that configures a `gh issue` command. + public func issue(_ nested: String? = nil) -> Self { commandGroup(.issue, nested) } + + /// Returns a copy that configures a `gh label` command. + public func label(_ nested: String? = nil) -> Self { commandGroup(.label, nested) } + + /// Returns a copy that configures a `gh licenses` command. + public func licenses() -> Self { commandGroup(.licenses, nil) } + + /// Returns a copy that configures a `gh org` command. + public func org(_ nested: String? = nil) -> Self { commandGroup(.org, nested) } + + /// Returns a copy that configures a `gh pr` command. + public func pr(_ nested: String? = nil) -> Self { commandGroup(.pr, nested) } + + /// Returns a copy that configures a `gh project` command. + public func project(_ nested: String? = nil) -> Self { commandGroup(.project, nested) } + + /// Returns a copy that configures a `gh release` command. + public func release(_ nested: String? = nil) -> Self { commandGroup(.release, nested) } + + /// Returns a copy that configures a `gh repo` command. + public func repoCommand(_ nested: String? = nil) -> Self { commandGroup(.repo, nested) } + + /// Returns a copy that configures a `gh ruleset` command. + public func ruleset(_ nested: String? = nil) -> Self { commandGroup(.ruleset, nested) } + + /// Returns a copy that configures a `gh run` command. + public func runCommand(_ nested: String? = nil) -> Self { commandGroup(.run, nested) } + + /// Returns a copy that configures a `gh search` command. + public func search(_ nested: String? = nil) -> Self { commandGroup(.search, nested) } + + /// Returns a copy that configures a `gh secret` command. + public func secret(_ nested: String? = nil) -> Self { commandGroup(.secret, nested) } + + /// Returns a copy that configures a `gh skill` command. + public func skill(_ nested: String? = nil) -> Self { commandGroup(.skill, nested) } + + /// Returns a copy that configures a `gh ssh-key` command. + public func sshKey(_ nested: String? = nil) -> Self { commandGroup(.sshKey, nested) } + + /// Returns a copy that configures a `gh status` command. + public func status() -> Self { commandGroup(.status, nil) } + + /// Returns a copy that configures a `gh variable` command. + public func variable(_ nested: String? = nil) -> Self { commandGroup(.variable, nested) } + + /// Returns a copy that configures a `gh workflow` command. + public func workflow(_ nested: String? = nil) -> Self { commandGroup(.workflow, nested) } + + /// Returns a copy that passes `--repo `. + public func repo(_ ownerAndName: String) -> Self { copy(repoOverride: ownerAndName) } + + /// Returns a copy that passes `--hostname `. + public func hostname(_ value: String) -> Self { copy(hostnameOverride: value) } + + /// Returns a copy that passes `--json` with comma-separated fields. + public func json(_ fields: [String]) -> Self { copy(jsonFields: fields) } + + /// Returns a copy that passes `--json` with comma-separated fields. + public func json(_ fields: String...) -> Self { json(fields) } + + /// Returns a copy that passes `--jq `. + public func jq(_ expression: String) -> Self { copy(jqExpression: expression) } + + /// Returns a copy that passes `--template