Create command line tools with Swift

Create powerful CLI tools that are fast and memory safe

  • Simple Use Swift’s clear syntax and a lightweight, declarative style to quickly develop rich command line tools.
  • Powerful ArgumentParser provides type validation, rich help screens, shell completions, and more, all without ceremony.
  • Safe Using Swift’s type, memory, and concurrency safety, develop powerful CLI tools without worry.
Get Started

Build straightforward CLI tools with Argument Parser

You can quickly build full-featured command-line interfaces using the Swift Argument Parser library. Define commands by creating types with regular Swift properties. Link multiple commands to create rich command hierarchies.

ArgumentParser provides a detailed help screen, clear error messages with near-miss checking, broad customization, and more.

Swift Argument Parser
import ArgumentParser
@main
struct Repeat: ParsableCommand {
  @Argument(help: "The phrase to repeat.")
  var phrase: String
  
  @Option(help: "The number of times to repeat 'phrase'.")
  var count: Int? = nil
  
  mutating func run() throws {
    let repeatCount = count ?? .max
    
    for i in 1...repeatCount {
      print(phrase)
    }
  }
}

ArgumentParser in use

In addition to detailed help screens and error messages out of the box, your ArgumentParser CLI tools can provide completion scripts and man pages, as well as extensibility through a JSON rendering of the interface.

$ repeat yes --count 3
yes
yes
yes

$ repeat --count
Error: Missing value for '--count <count>'
Help:  --count <count>  The number of times to repeat 'phrase'.
Usage: repeat <phrase> [--count <count>]
  See 'repeat --help' for more information.

$ repeat -h
USAGE: repeat <phrase> [--count <count>]

ARGUMENTS:
  <phrase>                The phrase to repeat.

OPTIONS:
  --count <count>         The number of times to repeat 'phrase'.
  -h, --help              Show help information.
.\" "Generated by swift-argument-parser"
.Dd May 21, 2025
.Dt REPEAT 1
.Os
.Sh NAME
.Nm repeat
.Sh SYNOPSIS
.Nm
.Ar subcommand
.Ar phrase
.Op Fl -count Ar count
.Op Fl -help
.Sh DESCRIPTION
.Bl -tag -width 6n
.It Ar phrase
The phrase to repeat.
.It Fl -count Ar count
The number of times to repeat 'phrase'.
.It Fl h , -help
Show help information.
.It Em help
Show subcommand help information.
.Bl -tag -width 6n
.It Ar subcommands...
.El
.El
.Sh "EXIT STATUS"
.Ex -std
#compdef repeat

__repeat_complete() {
    local -ar non_empty_completions=("${@:#(|:*)}")
    local -ar empty_completions=("${(M)@:#(|:*)}")
    _describe -V '' non_empty_completions -- empty_completions -P $'\'\''
}

__repeat_custom_complete() {
    local -a completions
    completions=("${(@f)"$("${command_name}" "${@}" "${command_line[@]}")"}")
    if [[ "${#completions[@]}" -gt 1 ]]; then
        __repeat_complete "${completions[@]:0:-1}"
    fi
}

__repeat_cursor_index_in_current_word() {
    if [[ -z "${QIPREFIX}${IPREFIX}${PREFIX}" ]]; then
        printf 0
    else
        printf %s "${#${(z)LBUFFER}[-1]}"
    fi
}

_repeat() {
    emulate -RL zsh -G
    setopt extendedglob nullglob numericglobsort
    unsetopt aliases banghist

    local -xr SAP_SHELL=zsh
    local -x SAP_SHELL_VERSION
    SAP_SHELL_VERSION="$(builtin emulate zsh -c 'printf %s "${ZSH_VERSION}"')"
    local -r SAP_SHELL_VERSION

    local context state state_descr line
    local -A opt_args

    local -r command_name="${words[1]}"
    local -ar command_line=("${words[@]}")
    local -ir current_word_index="$((CURRENT - 1))"

    local -i ret=1
    local -ar arg_specs=(
        ':phrase:'
        '--count[The number of times to repeat '\''phrase'\''.]:count:'
        '(-h --help)'{-h,--help}'[Show help information.]'
    )
    _arguments -w -s -S : "${arg_specs[@]}" && ret=0

    return "${ret}"
}

_repeat
{
  "command" : {
    "arguments" : [
      {
        "abstract" : "The phrase to repeat.",
        "isOptional" : false,
        "isRepeating" : false,
        "kind" : "positional",
        "shouldDisplay" : true,
        "valueName" : "phrase"
      },
      {
        "abstract" : "The number of times to repeat 'phrase'.",
        "isOptional" : true,
        "isRepeating" : false,
        "kind" : "option",
        "names" : [
          {
            "kind" : "long",
            "name" : "count"
          }
        ],
        "preferredName" : {
          "kind" : "long",
          "name" : "count"
        },
        "shouldDisplay" : true,
        "valueName" : "count"
      },
      {
        "abstract" : "Show help information.",
        "isOptional" : true,
        "isRepeating" : false,
        "kind" : "flag",
        "names" : [
          {
            "kind" : "short",
            "name" : "h"
          },
          {
            "kind" : "long",
            "name" : "help"
          }
        ],
        "preferredName" : {
          "kind" : "long",
          "name" : "help"
        },
        "shouldDisplay" : true,
        "valueName" : "help"
      }
    ],
    "commandName" : "repeat",
    "shouldDisplay" : true,
    "subcommands" : [
      {
        "abstract" : "Show subcommand help information.",
        "arguments" : [
          {
            "isOptional" : true,
            "isRepeating" : true,
            "kind" : "positional",
            "shouldDisplay" : true,
            "valueName" : "subcommands"
          },
          {
            "isOptional" : true,
            "isRepeating" : false,
            "kind" : "flag",
            "names" : [
              {
                "kind" : "short",
                "name" : "h"
              },
              {
                "kind" : "long",
                "name" : "help"
              },
              {
                "kind" : "longWithSingleDash",
                "name" : "help"
              }
            ],
            "preferredName" : {
              "kind" : "long",
              "name" : "help"
            },
            "shouldDisplay" : false,
            "valueName" : "help"
          }
        ],
        "commandName" : "help",
        "shouldDisplay" : true,
        "superCommands" : [
          "repeat"
        ]
      }
    ]
  },
  "serializationVersion" : 0
}

Make your CLI look great

Noora is Swift package that "distills common CLI patterns into a cohesive design system of themable components, enabling richer and more interactive experiences." Design components range from prompts to progress bars, to alerts.

View Noora on GitHub

Explore CLIs built with Swift

  • swiftly CLI tool for installing, managing, and switching between Swift toolchains. Learn more
  • DocC CLI tool for Swift-DocC and provides support for generating and previewing documentation. Learn more
  • Swift Package Manager Tool for managing the distribution of Swift code that’s integrated with the Swift build system. Learn more
  • Vapor Toolbox CLI tool to easily create new Vapor projects. Learn more

Use Subprocess to handle process execution

Subprocess is a Swift library that provides precise, idiomatic control over launching and managing child processes. You can either collect the child process output asynchronously in full, or stream it in real time using AsyncSequence, making it easy to process output line-by-line as it arrives.

Subprocess gives you fine-grained control over environment variables, arguments, and many platform specific parameters, while embracing Swift’s concurrency features and type safety. Whether you’re building CLI tools or server-side Swift applications, swift-subprocess integrates cleanly.

Subprocess
import Subprocess

// Launch Nginx and monitor the log file in parallel
async let monitorResult = run(
    .path("/usr/bin/tail"),
    arguments: ["-f", "/path/to/nginx.log"]
) { execution, standardOutput in
    for try await line in standardOutput.lines(encoding: UTF8.self) {
        // Parse the log text
        if line.contains("500") {
            // Oh no, 500 error
        }
    }
}

let launchResult = try await run(
    .name("nginx"), // Lookup executable by name
    arguments: ["-c", "/path/to/nginx.conf"]
)
if !launchResult.terminationStatus.isSuccess {
    print("Nginx failed to launch: \(launchResult.terminationStatus)")
} else {
    print("Nginx launched with PID \(launchResult.processIdentifier)")
}