A lightweight, fast, and minimal runner for single-file Swift scripts.
Swift already supports scripting: you can run a source file directly with swift script.swift. However, Swift compiles that source on every launch and does not cache the resulting executable. For scripts used repeatedly by developers or CI jobs, that compilation quickly becomes noticeable overhead.
Shell is a great fit for small command chains. As automation grows, Swift offers compelling advantages: static types, structured error handling, modern concurrency, and rich platform APIs can make scripts easier to evolve and safer to maintain, while keeping the convenience of a single source file.
wift keeps the same simple scripting workflow, but compiles each script once and stores the executable in a content-addressed cache. Cache hits launch the existing executable directly, making repeated runs fast. Normal execution needs no project, package management, daemon, or background service. Optional editor integration prepares a separate workspace when requested.
- macOS 13 or newer
- Swift 5.9 or newer (
swiftcavailable onPATH) and a compatible macOS SDK
These requirements also apply to a prebuilt wift binary: wift uses the installed compiler to compile scripts and the built-in Wift module. The module requires Swift 5.9 language features; your scripts may require a newer compiler.
- Swift 6.2 or newer, including Swift Package Manager
misefor the repository tasks shown below
The build machine must meet the selected Swift toolchain's macOS requirements. Swift 6.2 is the minimum for building wift itself; it does not define the minimum compiler version for running scripts with a prebuilt binary.
From a local checkout:
mise run install
wift --versionThis installs wift into ~/.local/bin without sudo. Make sure that directory is on your PATH. To remove it later, run mise run uninstall.
Create hello.swift:
let name = CommandLine.arguments.dropFirst().first ?? "world"
print("Hello, \(name)!")Run it:
$ wift hello.swift Swift
Hello, Swift!The first run compiles the script. Later runs launch the cached executable directly while the script and toolchain remain unchanged.
Everything after the script path is passed to the script unchanged:
wift script.swift one --two threeAdd a shebang to run a script directly:
#!/usr/bin/env wift
print("Hello from wift!")Then make it executable and launch it:
chmod +x script.swift
./script.swiftShebang scripts may also use extensionless names such as scripts/add.
Swift's standard APIs are intentionally general-purpose, while scripts often repeat a small set of process-oriented tasks. The built-in Wift module reduces that boilerplate with focused APIs for launching commands, capturing output, composing pipelines, and accessing script context. It keeps Swift's explicit, typed model while making common automation concise.
Every script can import the built-in Wift module without a Package.swift or additional dependencies:
#!/usr/bin/env wift
import Wift
try cmd("git", "--version").run()
let branch = try cmd("git", "branch", "--show-current").text()
let files = try cmd("find", ".", "-name", "*.swift")
.pipe(to: cmd("sort"))
.lines()Commands use argument arrays rather than shell parsing and are checked by default. Shell evaluation is available through the explicit shell(...) and shell(raw:) APIs. The module also supports captured output, pipelines, async execution, streaming, cancellation, script metadata, and stderr helpers.
See Examples/Wift.swift for a complete example.
Open a script in VS Code or Xcode with Swift completion and diagnostics, including the built-in Wift API:
wift edit script.swiftwift uses VS Code when available, then Xcode. Choose an editor explicitly or set WIFT_EDITOR as your default:
wift edit --editor vscode script.swift
wift edit --editor xcode script.swift
wift edit --editor /usr/bin/vim script.swiftVS Code's official Swift extension (swiftlang.swift-vscode) provides the language features. If you haven't installed it yet, wift edit shows installation instructions. Xcode has Swift support built in. You can also pass another editor's executable to open the script as text.
You edit the original file, so saving works as usual. Save your changes and run them with wift script.swift.
Normal execution is silent apart from compiler diagnostics and script output. Use verbose mode to inspect compiler resolution, cache activity, and execution:
wift --verbose script.swift one twowift diagnostics use the wift: prefix and go to stderr. Options after the script path still belong to the script.
| Command | Purpose |
|---|---|
wift cache |
Show a cache summary and logical sizes |
wift cache path |
Print the cache directory |
wift cache info script.swift |
Inspect cached variants without compiling |
wift cache clean script.swift |
Remove cached variants associated with one script path |
wift cache clean |
Remove the complete managed cache |
The default cache is ~/Library/Caches/wift. Set WIFT_CACHE_DIR to use a dedicated cache directory, for example in tests or CI.
The cache is content-addressed rather than tied to a source path. Two identical scripts share the same cached executable even when they live in different checkout directories, provided their compiler, target, SDK, and other compilation inputs also match.
This makes the cache effective in CI environments with multiple runners: a runner can restore the managed cache under a different checkout or cache-root path and reuse executables compiled by another runner. The script still receives its own launch-specific path at runtime.
wift fingerprints the script contents together with the active compiler, target, SDK, compiler configuration, and built-in Wift API. Source and cache-storage paths are not part of that identity. A matching fingerprint reuses the existing executable; a new fingerprint triggers compilation and creates a new cache entry.
When the executable is ready, it replaces the wift process. The script therefore keeps the current arguments, environment, working directory, standard streams, signals, and exit status, just like a directly launched executable.
Cache entries are private to the current user and validated before execution. Because swiftc is selected from PATH, use a trusted PATH as you would when invoking the compiler directly.
wift is a single-file script runner, not a package or dependency manager. It currently does not support:
- SwiftPM dependencies or generated
Package.swiftfiles - user-supplied compiler flags
- multi-file scripts
- automatic cache eviction
- remote caching or background services
Install the pinned development tools and run the project checks:
mise install
mise run checkFor a manual end-to-end run from a checkout:
mise run wift Examples/Hello.swift
mise run wift Examples/Arguments.swift one --two threeThe end-to-end benchmark suite compares release builds of wift, cached executables, ordinary Swift scripts, and shell scripts. See Benchmarks for scenarios, methodology, and commands.
Contributions are welcome through the usual fork-and-pull workflow:
- Fork the repository and create a focused branch.
- Make your changes and run
mise run check. - Open a pull request with a short explanation of what changed and why.
Please keep contributions small and aligned with wift's lightweight, fast, and minimal design.
wift is available under the MIT license. See the LICENSE file for more info.