Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 109 additions & 7 deletions .claude/skills/swiftyshell.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(...)`:
Expand All @@ -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 <Trait> ... #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 <Trait> ... #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.
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:"
Expand All @@ -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[@]}")

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/reusable-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 <Trait>` 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 <Trait>` 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/`

Expand Down Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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."),
Expand All @@ -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: []),
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
Loading