SFTP & SCP

SFTPClient

public final class SFTPClient: @unchecked Sendable {
    public func listDirectory(_ path: String) async throws -> [SFTPEntry]
    public func realpath(_ path: String) async throws -> String
    public func stat(_ path: String) async throws -> SFTPAttributes
    public func lstat(_ path: String) async throws -> SFTPAttributes
    public func setPermissions(_ permissions: UInt32, at path: String) async throws
    public func fileSystemAttributes(at path: String) async throws -> [String: UInt64]

    public func createDirectory(_ path: String, permissions: UInt32 = 0o755) async throws
    public func removeDirectory(_ path: String) async throws
    public func removeFile(_ path: String) async throws
    public func rename(_ sourcePath: String, to destinationPath: String) async throws

    public func readLink(_ path: String) async throws -> String
    public func createSymbolicLink(_ linkPath: String, targetPath: String) async throws

    public func openFile(
        _ path: String,
        flags: SFTPFileOpenFlags,
        permissions: UInt32 = 0o600
    ) async throws -> SFTPFileHandle

    public func readFile(_ path: String) async throws -> Data
    public func writeFile(_ data: Data, to path: String) async throws

    public func download(
        remotePath: String,
        to localURL: URL,
        progress: (@Sendable (UInt64, UInt64) -> Void)? = nil
    ) async throws
    public func upload(
        localURL: URL,
        to remotePath: String,
        progress: (@Sendable (UInt64, UInt64) -> Void)? = nil
    ) async throws
    public func resumeDownload(
        remotePath: String,
        to localURL: URL,
        progress: (@Sendable (UInt64, UInt64) -> Void)? = nil
    ) async throws
    public func resumeUpload(
        localURL: URL,
        to remotePath: String,
        progress: (@Sendable (UInt64, UInt64) -> Void)? = nil
    ) async throws

    public func close() async throws
}

Every async method has a callback overload. Progress callbacks deliver (completedBytes, totalBytes) on a dedicated delivery queue targeting the caller’s callbackQueue. resumeDownload resumes from the local file size; resumeUpload resumes from the remote file size.

SFTPFileHandle

public final class SFTPFileHandle: @unchecked Sendable {
    public func readData(maximumLength: Int) async throws -> Data
    public func writeData(_ data: Data) async throws
    public func seek(to offset: UInt64) async throws
    public func close() async throws
}

SFTPEntry

public struct SFTPEntry: Equatable, Sendable {
    public var filename: String
    public var attributes: SFTPAttributes?

    public init(filename: String, attributes: SFTPAttributes? = nil)
}

SFTPAttributes

public struct SFTPAttributes: Equatable, Sendable {
    public var size: UInt64
    public var permissions: UInt32
    public var uid: UInt32
    public var gid: UInt32
    public var type: UInt8
    public var accessedAt: Date?
    public var modifiedAt: Date?

    public init(
        size: UInt64,
        permissions: UInt32,
        uid: UInt32,
        gid: UInt32,
        type: UInt8,
        accessedAt: Date? = nil,
        modifiedAt: Date? = nil
    )
}

SFTPFileOpenFlags

public struct SFTPFileOpenFlags: OptionSet, Sendable {
    public static let read     = SFTPFileOpenFlags(rawValue: 1 << 0)
    public static let write    = SFTPFileOpenFlags(rawValue: 1 << 1)
    public static let create   = SFTPFileOpenFlags(rawValue: 1 << 2)
    public static let truncate = SFTPFileOpenFlags(rawValue: 1 << 3)
    public static let append   = SFTPFileOpenFlags(rawValue: 1 << 4)
}

SCP extensions

SCP is a public extension SSHConnection — not a separate type. Swift-only; no Obj-C counterpart.

public extension SSHConnection {
    func uploadFileWithSCP(
        localURL: URL,
        toRemotePath remotePath: String,
        permissions: UInt16 = 0o644,
        maximumSize: UInt64 = 64 * 1024 * 1024
    ) async throws

    func downloadFileWithSCP(
        remotePath: String,
        toLocalURL localURL: URL,
        maximumSize: UInt64 = 64 * 1024 * 1024
    ) async throws
}

No callback overload; SCP is async-only.