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
80 changes: 73 additions & 7 deletions .claude/skills/swiftyshell.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ Before writing any code, follow this decision tree:
→ Use `Grep` or `Jq`
6. Is this a Homebrew operation (`brew install`, `brew upgrade`, `brew list`, ...)?
→ Use `Brew`
7. Does the operation need typed output, structured results, or conditional follow-up?
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?
→ Use the appropriate typed client
8. Are two or more commands chained by pipe?
9. Are two or more commands chained by pipe?
→ Use `.pipe(to:)` to build a `Pipeline`
9. Does the command write output to a file?
10. Does the command write output to a file?
→ Use `.stdout(.file(path:append:))` on the command
10. Is this any other command?
→ Use `Command`
11. Is this any other command?
→ Use `Command`

### API Reference

Expand Down Expand Up @@ -690,6 +692,63 @@ public struct Rsync: RunnableCommandFamily {
}
```

#### Swift Toolchain

```swift
public enum SwiftSubcommand: Sendable, Equatable, Hashable {
case version
case build
case test
case run
case package
case repl
case custom(String)
}

public enum SwiftBuildConfiguration: String, Sendable, Equatable, Hashable {
case debug
case release
}

public struct Swift: RunnableCommandFamily {
public init(context: ShellContext = .init())
public func subcommand(_ value: SwiftSubcommand) -> Self
public func subcommand(_ value: String) -> Self
public func build() -> Self
public func test() -> Self
public func runProduct(_ product: String? = nil) -> Self
public func package(_ subcommand: String? = nil) -> Self
public func repl() -> Self
public func version() -> Self
public func packagePath(_ path: String) -> Self
public func scratchPath(_ path: String) -> Self
public func configuration(_ value: SwiftBuildConfiguration) -> Self
public func target(_ name: String) -> Self
public func product(_ name: String) -> Self
public func traits(_ names: [String]) -> Self
public func traits(_ names: String...) -> Self
public func enableAllTraits(_ enabled: Bool = true) -> Self
public func disableDefaultTraits(_ enabled: Bool = true) -> Self
public func buildTests(_ enabled: Bool = true) -> Self
public func codeCoverage(_ enabled: Bool = true) -> Self
public func skipBuild(_ enabled: Bool = true) -> Self
public func listTests(_ enabled: Bool = true) -> Self
public func filter(_ pattern: String) -> Self
public func skip(_ pattern: String) -> Self
public func jobs(_ count: Int) -> Self
public func swiftCompilerFlag(_ value: String) -> Self
public func swiftCompilerFlags(_ values: [String]) -> Self
public func cCompilerFlag(_ value: String) -> Self
public func linkerFlag(_ 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 @@ -1098,6 +1157,13 @@ try await Brew(context: context).install("ripgrep").run()
// Homebrew — check outdated casks
let outdated = try await Brew(context: context).outdated().greedy().run()

// SwiftPM — release build with warnings as errors
try await Swift(context: context)
.build()
.configuration(.release)
.swiftCompilerFlag("-warnings-as-errors")
.run()

// Rsync — mirror a project directory to a remote host
try await Rsync(context: context)
.archive()
Expand Down Expand Up @@ -1352,7 +1418,7 @@ SwiftyShell uses [SwiftPM Package Traits](https://github.com/swiftlang/swift-evo

Declared in `Package.swift`:

- **Per-family** — `Git`, `Brew`, `Grep`, `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`, `Swift`, `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 @@ -1365,7 +1431,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/`, 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/`, `Swift/`, 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", "Rsync", "Tar", "Zip", "Unzip", "CommonUtilities", "All"]'
FULL='["", "Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "Rsync", "Tar", "Zip", "Unzip", "CommonUtilities", "All"]'

CHANGED=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD")
echo "Changed files:"
Expand All @@ -53,6 +53,7 @@ jobs:
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Grep/|Tests/SwiftyShellTests/Grep/)' && family_traits+=("Grep")
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/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", "Rsync", "Tar", "Zip", "Unzip", "CommonUtilities", "All"]'
default: '["", "Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "Rsync", "Tar", "Zip", "Unzip", "CommonUtilities", "All"]'

jobs:
validate-traits:
Expand Down
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Typed `grep` wrapper: `Grep` and `GrepPattern`.

Typed wrapper for the Homebrew package manager: `Brew` and `BrewSubcommand`.

### `Sources/SwiftyShell/Swift/`

Typed wrapper for the Swift toolchain: `Swift`, `SwiftSubcommand`, and `SwiftBuildConfiguration`.

### `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 Down Expand Up @@ -161,7 +165,7 @@ SwiftyShell uses [SwiftPM Package Traits](https://github.com/swiftlang/swift-evo

**Trait inventory (declared in `Package.swift`):**

- Per-family: `Git`, `Brew`, `Grep`, `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`, `Swift`, `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 @@ -21,6 +21,7 @@ let package = Package(
.trait(name: "Grep", description: "Typed wrapper for grep."),
.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: "Ls", description: "Typed wrapper for ls."),
.trait(name: "Cp", description: "Typed wrapper for cp."),
.trait(name: "Mkdir", description: "Typed wrapper for mkdir."),
Expand All @@ -43,7 +44,7 @@ let package = Package(
.trait(
name: "All",
description: "Enables every command family shipped by SwiftyShell.",
enabledTraits: ["Git", "Brew", "Grep", "Fzf", "Rg", "CommonUtilities"]
enabledTraits: ["Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "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 @@ -95,6 +95,7 @@ SwiftyShell ships typed wrappers for common tools. Each family is gated behind a
| `Rg` | `rg` | `Rg` | Comprehensive ripgrep support, context, globs, JSON output |
| `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 |
| `Ls` | `ls` | `Ls` | All flags, recursive, human-readable sizes |
| `Cp` | `cp` | `Cp` | Recursive, force |
| `Mkdir` | `mkdir` | `Mkdir` | Parent directories, permissions |
Expand Down
Loading