Quickstart
Add Traversio to a Swift package and open your first SSH connection.
Before You Start
The public transport path uses the new Apple Network framework APIs available on platform release 26 and later. In practice that means:
- Traversio targets Apple platforms first.
SSHClient.connect(configuration:)andSSHClient.withConnection(configuration:_:)are only available on macOS, iOS, tvOS, watchOS, and visionOS 26 or later.- The package uses
swift-tools-version: 6.2.
Traversio is suitable for evaluation and staged integration. Production hardening and wider interoperability work continue in parallel with the public API.
Read Mental Model first if you want the object graph before reading code examples.
Add the Package
Until the project starts shipping tagged releases, the safest way to consume the current docs examples is to point SwiftPM at main:
// Package.swift
dependencies: [
.package(
url: "https://github.com/GitSwiftLLC/Traversio.git",
branch: "main"
)
]Then add the library to your target:
// Package.swift
.target(
name: "ExampleApp",
dependencies: [
.product(name: "Traversio", package: "Traversio")
]
)First Connection
The public API has two connection shapes:
SSHClient.connect(configuration:)for explicit long-lived ownershipSSHClient.withConnection(configuration:_:)as a convenience wrapper which closes the connection automatically
import Traversio
@available(macOS 26.0, iOS 26.0, *)
func runRemoteUname() async throws -> String {
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: .password("correct horse battery staple"),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts")
)
let connection = try await SSHClient.connect(configuration: configuration)
let result = try await connection.execute("uname -a")
await connection.close()
return String(decoding: result.standardOutput, as: UTF8.self)
}Use withConnection(...) when closure-scoped ownership fits your flow. It performs the same setup and cleanup automatically.
Connection Flow
Within one connection call, Traversio performs:
- TCP connection setup through the Apple 26+ transport adapter.
- SSH identification exchange.
- Curve25519 or
ecdh-sha2-nistp256key exchange and encrypted transport activation. - Host-key trust evaluation using the configured policy.
- User authentication.
- Return of a live
SSHConnection, or execution of yourwithConnection(...)body with that same wrapper.
If you use withConnection(...), the connection is closed automatically when the closure returns or throws.
Transport Options
The default client profile keeps compression off, uses the current automatic rekey defaults, and leaves keepalive and explicit timeouts disabled.
If you want RFC 4253 zlib, delayed OpenSSH compression, tighter client-initiated rekey thresholds, post-auth idle keepalive, or bounded waits for setup/reply paths, set them explicitly on SSHClientConfiguration:
import Traversio
@available(macOS 26.0, iOS 26.0, *)
func runCompressedSession() async throws {
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: .password("secret"),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts"),
compressionPreference: .delayedZlib,
automaticRekeyPolicy: .init(
outboundPacketThreshold: 250_000,
inboundPacketThreshold: 250_000,
idleTimeInterval: 600
),
keepalivePolicy: .init(interval: 30),
timeoutPolicy: .init(
connectionSetupTimeInterval: 15,
responseTimeInterval: 5
)
)
try await SSHClient.withConnection(configuration: configuration) { connection in
_ = try await connection.execute("hostname")
}
}That example enables:
- delayed OpenSSH compression through
.delayedZlib; use.zlibinstead when the target explicitly advertises RFC 4253zlib - tighter packet-threshold plus idle-time automatic rekey
- a 30-second keepalive for idle authenticated connections
- explicit timeouts for connection setup and prompt reply waits
For the full field-by-field guide, including defaults, tradeoffs, proxy settings, and how keepalivePolicy differs from timeoutPolicy, see Connection Configuration.
Structured Logging
If the app should record connection setup, authentication, and wrapped operation failures, use the logHandler: overloads:
import OSLog
import Traversio
@available(macOS 26.0, iOS 26.0, *)
func runLoggedCommand(configuration: SSHClientConfiguration) async throws {
let logHandler = SSHClientLogHandler.osLog(
subsystem: "com.example.monitor",
category: "ssh",
minimumLevel: .info
)
try await SSHClient.withConnection(
configuration: configuration,
logHandler: logHandler
) { connection in
_ = try await connection.execute("uptime")
}
}If you want a bounded recent-event buffer for support export, attach SSHClientLogRecorder through diagnostics.logHandler(minimumLevel:) or SSHClientLogHandler.recorder(...).
If you already have your own logging pipeline, use SSHClientLogHandler.sink(...) instead and forward each SSHClientLogEvent yourself.
SSHClientLogEvent also exposes formattedLine, and SSHConnectionFailure / SSHOperationFailure each expose diagnosticReport for copyable support text.
For a reusable app-side pattern, see Diagnostics. That page shows the built-in bounded recorder, a one-tap export flow, and the data you can expect to include when a user reports that a host cannot be reached.
Lifetime Rules
These public wrappers stay valid while their owning SSHConnection remains open:
SSHConnectionSSHSessionSFTPClient
If you call connection.close(), or if a withConnection(...) scope ends, later use fails with SSHClientError.connectionScopeEnded.
That rule applies to child wrappers:
SSHSessionSFTPClient- raw forwarding channel wrappers
import Traversio
@available(macOS 26.0, iOS 26.0, *)
func invalidExample() async throws {
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: .password("secret"),
hostKeyPolicy: .acceptAnyVerifiedHostKey
)
var escapedConnection: SSHConnection?
let connection = try await SSHClient.connect(configuration: configuration)
escapedConnection = connection
await connection.close()
// Throws SSHClientError.connectionScopeEnded
_ = try await escapedConnection?.execute("true")
}Using Public-Key Authentication
Traversio loads OpenSSH Ed25519, ECDSA, and RSA private keys directly:
import Traversio
@available(macOS 26.0, iOS 26.0, *)
func connectWithECDSAKey() async throws {
let configuration = SSHClientConfiguration(
host: "example.com",
username: "deploy",
authentication: try .ecdsaPrivateKey(
contentsOfOpenSSHPrivateKeyFile: "/Users/me/.ssh/id_ecdsa",
passphrase: "correct horse battery staple"
),
hostKeyPolicy: .knownHostsFile("/Users/me/.ssh/known_hosts")
)
try await SSHClient.withConnection(configuration: configuration) { connection in
let result = try await connection.execute("whoami")
print(String(decoding: result.standardOutput, as: UTF8.self))
}
}If you already manage the key material in memory yourself, the raw enum cases remain available:
.ed25519PrivateKey(rawRepresentation:).rsaPrivateKey(pkcs1DERRepresentation:).ecdsaP256PrivateKey(rawRepresentation:).ecdsaP384PrivateKey(rawRepresentation:).ecdsaP521PrivateKey(rawRepresentation:)
Supported limits:
- The OpenSSH helper path supports Ed25519, RSA, and ECDSA keys encrypted with the OpenSSH bcrypt KDF plus AES ciphers such as
aes256-ctrandaes256-cbc. - Agent-backed authentication and keychain-backed credential loading remain outside the current public surface.
Traversio also generates a new OpenSSH private key file and matching authorized-key line:
import Traversio
@available(macOS 26.0, iOS 26.0, *)
func makeKeyMaterial() throws -> SSHOpenSSHKeyPair {
try SSHOpenSSHKeyPair.generate(
algorithm: .ed25519,
comment: "[email protected]",
encryption: SSHOpenSSHPrivateKeyEncryption(
passphrase: "correct horse battery staple"
)
)
}For the full workflow, including file permissions, encrypted export options, and using the returned authenticationMethod directly, see Key Generation.
Inspect Connection Metadata
SSHConnection.metadata gives you useful handshake details after a successful connection:
- endpoint host and port
- authenticated username
- Traversio client identification string
- remote identification string
- pre-identification banner lines
- host-key algorithm
- SHA-256 fingerprint of the verified host key
- the trust method that accepted the host key
Example:
let metadata = try await SSHClient.withConnection(configuration: configuration) { connection in
connection.metadata
}
print(metadata.remoteIdentification)
print(metadata.hostKeyFingerprintSHA256)Read Next
- Host Key Trust for trust setup
- Password Authentication, Keyboard-Interactive Authentication, or Public-Key Authentication for auth details
- Running Commands for one-shot commands
- Forwarding for port forwarding
- SFTP for file transfer