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
30 changes: 28 additions & 2 deletions .claude/skills/swiftyshell.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Before writing any code, follow this decision tree:
→ Use `Git`
2. Is this a git operation NOT covered by the typed `Git` API?
→ Use `Command("git", arguments: ...)`
3. Is this a file-system operation covered by a typed wrapper (`Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Rsync`)?
3. Is this a file-system operation covered by a typed wrapper (`Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Rsync`, `Find`)?
→ Use the typed wrapper
4. Is this an archive operation (`tar`, `zip`, or `unzip`)?
→ Use `Tar`, `Zip`, or `Unzip`
Expand Down Expand Up @@ -667,6 +667,32 @@ public struct Pwd: RunnableCommandFamily {
public func run() async throws -> ShellOutput
}

public struct Find: RunnableCommandFamily {
public init(context: ShellContext = .init())
public func root(_ path: String) -> Self
public func roots(_ paths: [String]) -> Self
public func expression(_ value: FindExpression) -> Self
public func command() -> Command
public func run() async throws -> ShellOutput
}

public indirect enum FindExpression: Sendable, Equatable, Hashable {
case name(String)
case path(String)
case type(FindFileType)
case minimumDepth(UInt)
case maximumDepth(UInt)
case not(FindExpression)
case and(FindExpression, FindExpression)
case or(FindExpression, FindExpression)
case print
case print0
}

public enum FindFileType: String, Sendable, Equatable, Hashable {
case blockDevice, characterDevice, directory, regularFile, symbolicLink, namedPipe, socket
}

public struct Rsync: RunnableCommandFamily {
public init(context: ShellContext = .init())
public func archive(_ enabled: Bool = true) -> Self // -a
Expand Down Expand Up @@ -2084,7 +2110,7 @@ SwiftyShell uses [SwiftPM Package Traits](https://github.com/swiftlang/swift-evo

Declared in `Package.swift`:

- **Per-family** — `Git`, `Brew`, `Grep`, `Fzf`, `Rg`, `Swift`, `Cargo`, `Gh`, `Docker`, `Make`, `Node`, `Npm`, `Yarn`, `Pnpm`, `Bun`, `Terraform`, `Kubectl`, `Helm`, `Python`, `Curl`, `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`, `Cargo`, `Gh`, `Docker`, `Make`, `Node`, `Npm`, `Yarn`, `Pnpm`, `Bun`, `Terraform`, `Kubectl`, `Helm`, `Python`, `Curl`, `Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Jq`, `Rsync`, `Tar`, `Zip`, `Unzip`, `Find`. 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 Down
4 changes: 3 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", "Gh", "Docker", "Make", "Node", "Npm", "Yarn", "Pnpm", "Bun", "Terraform", "Kubectl", "Helm", "Python", "Ls", "Cp", "Mkdir", "Chmod", "Rm", "Mv", "Pwd", "Jq", "Rsync", "Tar", "Zip", "Unzip", "CommonUtilities", "All"]'
FULL='["", "Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "Gh", "Docker", "Make", "Node", "Npm", "Yarn", "Pnpm", "Bun", "Terraform", "Kubectl", "Helm", "Python", "Curl", "Ls", "Cp", "Mkdir", "Chmod", "Rm", "Mv", "Pwd", "Jq", "Rsync", "Tar", "Zip", "Unzip", "Find", "CommonUtilities", "All"]'

CHANGED=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD")
echo "Changed files:"
Expand Down Expand Up @@ -67,6 +67,7 @@ jobs:
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Kubectl/|Tests/SwiftyShellTests/Kubectl/)' && family_traits+=("Kubectl")
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Helm/|Tests/SwiftyShellTests/Helm/)' && family_traits+=("Helm")
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Python/|Tests/SwiftyShellTests/Python/)' && family_traits+=("Python")
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Curl/|Tests/SwiftyShellTests/Curl/)' && family_traits+=("Curl")
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Common/Ls\.swift|Tests/SwiftyShellTests/Common/LsTests\.swift)$' && family_traits+=("Ls") && common_changed=true
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Common/Cp\.swift|Tests/SwiftyShellTests/Common/CpTests\.swift)$' && family_traits+=("Cp") && common_changed=true
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Common/Mkdir\.swift|Tests/SwiftyShellTests/Common/MkdirTests\.swift)$' && family_traits+=("Mkdir") && common_changed=true
Expand All @@ -79,6 +80,7 @@ jobs:
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Common/Tar\.swift|Tests/SwiftyShellTests/Common/TarTests\.swift)$' && family_traits+=("Tar") && common_changed=true
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Common/Zip\.swift|Tests/SwiftyShellTests/Common/(ZipTests|ZipUnzipTests)\.swift)$' && family_traits+=("Zip") && common_changed=true
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Common/Unzip\.swift|Tests/SwiftyShellTests/Common/(UnzipTests|ZipUnzipTests)\.swift)$' && family_traits+=("Unzip") && common_changed=true
echo "$CHANGED" | grep -qE '^(Sources/SwiftyShell/Common/Find\.swift|Tests/SwiftyShellTests/Common/FindTests\.swift)$' && family_traits+=("Find") && common_changed=true
[ "$common_changed" = true ] && 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", "Gh", "Docker", "Make", "Node", "Npm", "Yarn", "Pnpm", "Bun", "Terraform", "Kubectl", "Helm", "Python", "Ls", "Cp", "Mkdir", "Chmod", "Rm", "Mv", "Pwd", "Jq", "Rsync", "Tar", "Zip", "Unzip", "CommonUtilities", "All"]'
default: '["", "Git", "Brew", "Grep", "Fzf", "Rg", "Swift", "Gh", "Docker", "Make", "Node", "Npm", "Yarn", "Pnpm", "Bun", "Terraform", "Kubectl", "Helm", "Python", "Curl", "Ls", "Cp", "Mkdir", "Chmod", "Rm", "Mv", "Pwd", "Jq", "Rsync", "Tar", "Zip", "Unzip", "Find", "CommonUtilities", "All"]'

permissions:
contents: read
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ files with `headerFile(_:)`, not argv-visible header values.

### `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.
Typed wrappers for frequently used shell utilities: `Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Jq`, `JqArgument`, `Rsync`, `Tar`, `TarOperation`, `TarCompression`, `Zip`, `ZipCompressionLevel`, `Unzip`, `UnzipEntry`, `Find`, `FindExpression`, and `FindFileType`. Each follows the same fluent builder conventions as all other command families.

### `Sources/SwiftyShell/Internal/Execution/`

Expand Down Expand Up @@ -228,7 +228,7 @@ SwiftyShell uses [SwiftPM Package Traits](https://github.com/swiftlang/swift-evo

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

- Per-family: `Git`, `Brew`, `Grep`, `Fzf`, `Rg`, `Swift`, `Cargo`, `Gh`, `Docker`, `Make`, `Node`, `Npm`, `Yarn`, `Pnpm`, `Bun`, `Terraform`, `Kubectl`, `Helm`, `Python`, `Curl`, `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`, `Cargo`, `Gh`, `Docker`, `Make`, `Node`, `Npm`, `Yarn`, `Pnpm`, `Bun`, `Terraform`, `Kubectl`, `Helm`, `Python`, `Curl`, `Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Jq`, `Rsync`, `Tar`, `Zip`, `Unzip`, `Find` (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
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,14 @@ let package = Package(
.trait(name: "Tar", description: "Typed wrapper for tar archives."),
.trait(name: "Zip", description: "Typed wrapper for zip (Info-ZIP)."),
.trait(name: "Unzip", description: "Typed wrapper for unzip (Info-ZIP)."),
.trait(name: "Find", description: "Typed portable wrapper for find."),
// Convenience umbrella that enables every Common/* utility family.
.trait(
name: "CommonUtilities",
description: "Enables all common file/directory utility families.",
enabledTraits: ["Ls", "Cp", "Mkdir", "Chmod", "Rm", "Mv", "Pwd", "Jq", "Rsync", "Tar", "Zip", "Unzip"]
enabledTraits: [
"Ls", "Cp", "Mkdir", "Chmod", "Rm", "Mv", "Pwd", "Jq", "Rsync", "Tar", "Zip", "Unzip", "Find",
]
),
// Convenience umbrella that enables every command family.
.trait(
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ targets: [

Two umbrella traits cover common cases:

- `CommonUtilities` — enables every `Common/*` family (`Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Jq`, `Rsync`, `Tar`, `Zip`, `Unzip`).
- `CommonUtilities` — enables every `Common/*` family (`Ls`, `Cp`, `Mkdir`, `Chmod`, `Rm`, `Mv`, `Pwd`, `Jq`, `Rsync`, `Tar`, `Zip`, `Unzip`, `Find`).
- `All` — enables every command family SwiftyShell ships.

```swift
Expand Down Expand Up @@ -122,6 +122,7 @@ SwiftyShell ships typed wrappers for common tools. Each family is gated behind a
| `Tar` | `tar` | `Tar` | Portable tar archive creation, extraction, listing, compression |
| `Zip` | `zip` | `Zip` | Info-ZIP archive creation, compression, recursion, exclusions |
| `Unzip` | `unzip` | `Unzip` | Info-ZIP archive extraction and structured entry listing |
| `Find` | `find` | `Find` | Portable typed predicates, boolean expressions, and safe path output |

When the tool you need isn't listed, `Command("tool", arguments: "arg").run(in: context)` is the fluent escape hatch. If you use the same tool repeatedly, promoting it to a typed family is straightforward — see below.

Expand Down
Loading
Loading