swift-mosh

July 21, 2026 ยท View on GitHub

GitHub Twitter Email Discord Support me

Pure Swift implementation of the Mosh protocol/client stack for Apple platforms.

This library focuses on protocol + transport + crypto + state processing only. Terminal UI/render adapters (Ghostty, SwiftUI terminal widgets, etc.) are intentionally out of scope so consumers can integrate with any renderer.

Features

  • Pure Swift Mosh protocol/client implementation
  • Split modules for protocol layers:
    • MoshCore
    • MoshTransport
    • MoshWire
    • MoshProtoLite
    • MoshCryptoOCB
    • MoshCompression
    • MoshBootstrap (optional SSH handoff parser/helper)
  • Real local mosh-server E2E test harness
  • 100% automated test coverage across Sources/ (via ./scripts/coverage.sh)

Platforms

  • iOS 16+
  • macOS 13+
  • Swift tools 6.0+

Installation

Add to Package.swift:

dependencies: [
    .package(url: "https://github.com/wiedymi/swift-mosh", from: "0.1.0")
]

Then add products you need:

.target(
    name: "YourApp",
    dependencies: [
        "MoshCore"
    ]
)

Quick start

import Foundation
import MoshCore

let endpoint = MoshEndpoint(
    host: "127.0.0.1",
    port: 60001,
    keyBase64_22: "REPLACE_WITH_MOSH_CONNECT_KEY"
)

let session = MoshClientSession(endpoint: endpoint)
try await session.start()

try await session.enqueue(.keystrokes(Data("echo hello from swift-mosh\n".utf8)))

// Streaming host output for your renderer.
for await op in await session.hostOpStream() {
    if case .hostBytes(let bytes) = op {
        print(String(decoding: bytes, as: UTF8.self), terminator: "")
    }
}

await session.stop()

App suspension and process relaunch

Freeze the live transport before the app is suspended, then persist the returned snapshot atomically:

let snapshot = try await session.prepareForApplicationBackground()

// If the process remains alive:
try await session.resumeFromApplicationBackground()

// After a process relaunch:
let restored = try await MoshClientSession.restore(from: snapshot)
try await restored.start()

MoshSnapshot contains the Mosh session key and buffered terminal protocol data. Treat it as sensitive session material: keep the key in device-only secure storage, protect the remaining checkpoint at rest, and delete both on explicit disconnect.

Reliability/lifecycle knobs are configurable in MoshClientConfig:

  • sendMinDelayMs
  • ackIntervalMs
  • ackDelayMs
  • networkTimeoutMs (retained for snapshot/source compatibility; network silence does not end a Mosh session)
  • heartbeatIntervalMs
  • initialRtoMs
  • maxRtoMs
  • maxRetransmitCount (caps retry bookkeeping; unacknowledged state remains pending)

Running tests

swift test

Coverage:

./scripts/coverage.sh

Run real local mosh-server E2E:

./scripts/run-real-e2e.sh

Or directly:

SWIFTMOSH_REAL_E2E=1 swift test --filter RealMoshServerE2ETests

Optional Bootstrap Helper

MoshBootstrap can parse noisy mosh-server output and launch a local server:

import MoshBootstrap

let connect = try MoshServerLauncher.launchLocalServer()
// connect.port, connect.key, connect.serverPID

Notes

  • This package does not perform SSH negotiation itself.
    • You are expected to handle auth/session bootstrap externally, then pass host, UDP port, and 22-char key to MoshEndpoint.
  • UI adapter is intentionally not included.
    • Consumers should map MoshHostOp to their own terminal backend/view layer.

License

MIT