Building Swift CLI Tools on macOS: Terminal Workflow with Claude Code and Beam
When most developers think of Swift, they think of iOS apps and Xcode storyboards. But Swift has quietly become one of the best languages for building command-line tools and server-side applications -- especially on macOS, where its tight integration with the system gives you performance and capabilities that other languages simply cannot match.
The best part? Swift CLI development is entirely terminal-based. No Xcode required. Just swift build, swift test, and swift run -- the trifecta of Swift Package Manager commands that power a fast, iterative development loop. Pair that workflow with Claude Code for AI-assisted coding and Beam for terminal organization, and you have a native macOS development environment that is hard to beat.
Why Swift for CLI Tools?
Swift is not just an alternative to Python or Go for command-line tools -- it brings genuine advantages to the table:
- Native performance -- Swift compiles to native machine code. Your CLI tools start instantly and run fast, with no runtime or interpreter overhead.
- Type safety -- Swift's strong type system catches bugs at compile time. No more runtime crashes from
nilvalues or mismatched types. - macOS integration -- Access Foundation, Core Data, Security framework, and other Apple APIs directly. Read Keychain items, interact with Spotlight, or call system services without FFI wrappers.
- Modern concurrency -- Swift 6's strict concurrency model with
async/awaitandSendableconformance makes writing concurrent CLI tools safe and predictable. - Growing ecosystem -- Apple's swift-argument-parser, SwiftNIO, Vapor, Hummingbird, and hundreds of SPM packages give you everything you need for serious CLI and server work.
Swift Package Manager: Your Entire Build System in the Terminal
Swift Package Manager (SPM) is Swift's native build system and dependency manager. Unlike Xcode projects with their opaque .xcodeproj files, SPM uses a simple, declarative Package.swift manifest that you can read, edit, and version control with ease.
Here is how to scaffold a new CLI project from scratch:
# Create and initialize a new executable package
mkdir MyCLI && cd MyCLI
swift package init --type executable
# This creates:
# Package.swift - Your project manifest
# Sources/main.swift - Entry point
# Tests/ - Test directory
The three commands you will use constantly:
# Compile your project (debug mode by default)
swift build
# Run your tests
swift test
# Build and run in one step
swift run MyCLI
For release builds with full optimizations:
# Optimized release build
swift build -c release
# The binary lands in .build/release/MyCLI
# Copy it to /usr/local/bin or distribute it directly
Why Terminal-Based Swift Beats Xcode for CLI Work
Xcode is excellent for GUI apps, but for CLI tools it adds unnecessary overhead. SPM in the terminal gives you faster iteration cycles, easier CI/CD integration, and the ability to use any editor you want. You also avoid the friction of Xcode project settings, build schemes, and target configurations that have no relevance for command-line executables.
Building Professional CLIs with swift-argument-parser
Apple's swift-argument-parser is the standard library for building CLI tools in Swift. It gives you automatic help generation, type-safe argument parsing, subcommands, and shell completions -- all declared using Swift's property wrapper syntax.
First, add it to your Package.swift:
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "MyCLI",
platforms: [.macOS(.v13)],
dependencies: [
.package(
url: "https://github.com/apple/swift-argument-parser",
from: "1.5.0"
),
],
targets: [
.executableTarget(
name: "MyCLI",
dependencies: [
.product(
name: "ArgumentParser",
package: "swift-argument-parser"
),
]
),
.testTarget(
name: "MyCLITests",
dependencies: ["MyCLI"]
),
]
)
Then build a command with subcommands:
import ArgumentParser
import Foundation
@main
struct MyCLI: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "mycli",
abstract: "A developer utility for project management.",
version: "1.0.0",
subcommands: [Init.self, Build.self, Deploy.self]
)
}
struct Init: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Initialize a new project."
)
@Argument(help: "The project name.")
var name: String
@Option(name: .shortAndLong, help: "Project template to use.")
var template: String = "default"
@Flag(name: .shortAndLong, help: "Enable verbose output.")
var verbose: Bool = false
func run() throws {
if verbose {
print("Creating project '\(name)' with template '\(template)'...")
}
// Project initialization logic here
print("Project '\(name)' created successfully.")
}
}
Run it and get automatic help for free:
$ swift run MyCLI --help
OVERVIEW: A developer utility for project management.
USAGE: mycli <subcommand>
OPTIONS:
--version Show the version.
-h, --help Show help information.
SUBCOMMANDS:
init Initialize a new project.
build Build the current project.
deploy Deploy to production.
$ swift run MyCLI init MyApp --template vapor --verbose
Creating project 'MyApp' with template 'vapor'...
Project 'MyApp' created successfully.
The Beam Workspace Setup for Swift CLI Development
Swift CLI development involves juggling builds, tests, running your tool, AI assistance, and version control. Beam workspaces keep all of this organized in a single, switchable context.
Here is the workspace layout I recommend. Create a workspace called "MyCLI" and set it up with five tabs:
- Tab 1: Build -- Run
swift buildhere. After each change, hit up-arrow and Enter for a quick rebuild. For continuous feedback, usewatchexec -e swift swift buildif you have watchexec installed. - Tab 2: Tests -- Run
swift testorswift test --filter MyCLITests.InitTestsfor targeted test runs. Keep this tab open so you can quickly verify changes. - Tab 3: Run -- Execute your tool with
swift run MyCLI init TestProject --verbose. This is where you manually test different argument combinations and see your CLI in action. - Tab 4: Claude Code -- Your AI coding companion. Ask Claude to generate new subcommands, write tests, refactor argument parsing, or debug build errors.
- Tab 5: Git -- All your version control operations:
git status,git diff,git commit, and pushing to your remote.
Pro Tip: Split Panes for Build + Test
Press ⌘⌥⌃T to split Tab 1 into two panes. Put swift build on the left and swift test on the right. Now you can see compile errors and test results side by side without switching tabs. This is especially valuable when refactoring, since you can immediately see if your changes break anything.
Switch between tabs with ⌘1 through ⌘5. Save the entire layout with ⌘S so you can restore it tomorrow and pick up exactly where you left off.
Server-Side Swift: Vapor and Hummingbird Workspace Setup
Swift is not just for CLI tools. Server-side Swift frameworks like Vapor and Hummingbird let you build high-performance web APIs using the same language, type system, and package ecosystem. The terminal workflow is nearly identical.
Create a new Beam workspace called "MyAPI" with these tabs:
- Tab 1: Server --
swift run App serve --hostname 0.0.0.0 --port 8080for Vapor, orswift run Appfor Hummingbird. This is your running server. - Tab 2: Database -- Run migrations with
swift run App migrateor interact with your database directly viapsqlorsqlite3. - Tab 3: API Testing -- Test your endpoints with curl or httpie:
# Test a POST endpoint http POST localhost:8080/api/users name="Alice" email="alice@example.com" # Or with curl curl -X POST http://localhost:8080/api/users \ -H "Content-Type: application/json" \ -d '{"name": "Alice", "email": "alice@example.com"}' - Tab 4: Claude Code -- Generate route handlers, middleware, database models, and migrations with AI assistance.
- Tab 5: Tests + Git -- Run
swift testand manage your repository.
Switch between your CLI workspace and server workspace with ⌘⌥←→. Each workspace maintains its own state -- your server keeps running in one workspace while you build CLI tools in another.
Swift 6 Strict Concurrency: Where Claude Code Shines
Swift 6 introduced strict concurrency checking, and it can be a challenging migration. The compiler now enforces Sendable conformance across concurrency boundaries, which means code that compiled fine in Swift 5 may suddenly produce dozens of warnings or errors.
This is exactly where Claude Code becomes invaluable. Ask it to help with common concurrency patterns:
Claude Code can help you with:
- Adding
@Sendableannotations to closures passed across concurrency boundaries - Converting classes to actors when mutable shared state is involved
- Using
nonisolatedfor properties that are safe to access from any context - Wrapping legacy code with
@unchecked Sendablewhen you know it is safe but the compiler cannot prove it - Migrating from DispatchQueue patterns to structured concurrency with
TaskGroup
// Before: Swift 5 pattern that triggers warnings in Swift 6
class DatabaseManager {
var connectionPool: [Connection] = []
func query(_ sql: String) async throws -> [Row] {
let conn = connectionPool.first!
return try await conn.execute(sql)
}
}
// After: Swift 6 safe pattern using an actor
actor DatabaseManager {
private var connectionPool: [Connection] = []
func query(_ sql: String) async throws -> [Row] {
let conn = connectionPool.first!
return try await conn.execute(sql)
}
}
Project Memory: Teaching Claude Code Your Swift Conventions
Every Swift project has its own conventions -- naming patterns, preferred libraries, target structure, and coding style. Claude Code's project memory feature (via CLAUDE.md) lets you persist this context so Claude understands your project from the start of every session.
Here is an example CLAUDE.md for a Swift CLI project:
# Project: MyCLI
## Build
- Build: swift build
- Test: swift test
- Run: swift run MyCLI
- Release: swift build -c release
## Architecture
- Entry point: Sources/MyCLI/MyCLI.swift (@main struct)
- Commands in Sources/MyCLI/Commands/
- Models in Sources/MyCLI/Models/
- Uses swift-argument-parser for CLI parsing
- Uses Swift 6 strict concurrency (no @unchecked Sendable)
## Conventions
- All commands are ParsableCommand structs
- Error handling via custom CLIError enum
- Verbose flag on all commands for debug output
- Tests mirror source structure: Tests/MyCLITests/Commands/
With this in place, Claude Code knows how to build your project, where to find things, and which patterns to follow when generating new code. No more repeating yourself at the start of every session.
Cross-Compilation and Distribution
Once your CLI tool is ready, you need to distribute it. Swift makes building universal binaries straightforward on macOS:
# Build a universal binary (Apple Silicon + Intel)
swift build -c release --arch arm64 --arch x86_64
# Check the architectures
file .build/apple/Products/Release/MyCLI
# MyCLI: Mach-O universal binary with 2 architectures:
# arm64, x86_64
# Strip debug symbols for a smaller binary
strip .build/apple/Products/Release/MyCLI
# Copy to a standard location
cp .build/apple/Products/Release/MyCLI /usr/local/bin/
For broader distribution, consider these options:
- Homebrew tap -- Create a formula that builds from source or distributes a pre-built binary. Users install with
brew install yourtap/mycli. - Mint -- A Swift-specific package manager for CLI tools. Users install with
mint install yourrepo/MyCLIand it builds from source automatically. - GitHub Releases -- Attach universal binaries to tagged releases. Automate this with GitHub Actions using
swift build -c releaseon a macOS runner. - Swift Package plugins -- Distribute your tool as an SPM plugin that other packages can use directly in their build process.
Linux Cross-Compilation
If you need to target Linux (for server deployment or CI), Swift supports cross-compilation with the Swift SDK. Install a Linux SDK with swift sdk install and build with swift build --swift-sdk x86_64-swift-linux-musl. This produces a statically linked Linux binary right from your Mac -- no Docker required for the build step.
Why Swift Developers Should Invest in Terminal Workflows
The Swift ecosystem is evolving rapidly beyond iOS. Server-side Swift is production-ready, CLI tools built with Swift rival Go and Rust in performance, and Apple continues to invest heavily in SPM and the open-source Swift toolchain.
By building a terminal-first workflow with Beam and Claude Code, you get:
- Speed -- SPM builds are fast, and the edit-build-test cycle in the terminal has less overhead than launching Xcode for every change.
- Organization -- Beam workspaces keep your CLI project, server project, and any other contexts completely separated but instantly accessible.
- AI assistance -- Claude Code understands Swift deeply. It can generate argument-parser commands, write XCTest cases, refactor for Swift 6 concurrency, and debug compiler errors right in your terminal.
- Reproducibility -- Terminal commands are scriptable, shareable, and CI-friendly. Your entire build and test process is a series of commands, not GUI clicks.
- Flexibility -- Use any editor (VS Code, Neovim, Zed) alongside your terminal tabs. Your build system does not care how you edit the files.
Swift is no longer just an iOS language. It is a systems programming language with native performance, a strong type system, modern concurrency, and a growing ecosystem for CLI and server development. The terminal is where that work happens best.
Ready to Build Swift CLI Tools with Beam?
Download Beam free and set up your perfect Swift development workspace with organized tabs, split panes, and Claude Code integration.
Download Beam for macOSSummary
Swift CLI development is a fully terminal-based workflow, and it deserves a terminal environment built for that kind of work. Here is what we covered:
- Swift Package Manager gives you
swift build,swift test, andswift run-- everything you need without Xcode. - swift-argument-parser lets you build professional CLIs with type-safe arguments, subcommands, and auto-generated help.
- Beam workspaces organize your build, test, run, Claude Code, and git tabs into a single switchable context.
- Server-side Swift with Vapor or Hummingbird uses the same SPM workflow, extended with server and database tabs.
- Claude Code accelerates Swift 6 concurrency migration, test generation, and code scaffolding.
- Project memory via
CLAUDE.mdteaches Claude your conventions so every session starts informed. - Universal binaries and distribution via Homebrew, Mint, or GitHub Releases make sharing your tools easy.
Happy building!