Console Playground

CLI

Fast command-line client for code execution and interactive sessions. 42+ languages, 30+ shells/REPLs.

Official OpenAPI Swagger Docs ↗

Quick Start — Swift

# Download + setup
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/swift/sync/src/un.swift && chmod +x un.swift && ln -sf un.swift un
export UNSANDBOX_PUBLIC_KEY="unsb-pk-xxxx-xxxx-xxxx-xxxx"
export UNSANDBOX_SECRET_KEY="unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx"
# Run code
./un script.swift

Downloads

Install Guide →
Static Binary
Linux x86_64 (5.3MB)
un
Swift SDK
un.swift (96.8 KB)
Download

Features

  • 42+ languages - Python, JS, Go, Rust, C++, Java...
  • Sessions - 30+ shells/REPLs, tmux persistence
  • Files - Upload files, collect artifacts
  • Services - Persistent containers with domains
  • Snapshots - Point-in-time backups
  • Images - Publish, share, transfer

Integration Quickstart ⚡

Add unsandbox superpowers to your existing Swift app:

1
Download
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/swift/sync/src/un.swift
2
Set API Keys
# Option A: Environment variables
export UNSANDBOX_PUBLIC_KEY="unsb-pk-xxxx-xxxx-xxxx-xxxx"
export UNSANDBOX_SECRET_KEY="unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx"

# Option B: Config file (persistent)
mkdir -p ~/.unsandbox
echo "unsb-pk-xxxx-xxxx-xxxx-xxxx,unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx" > ~/.unsandbox/accounts.csv
3
Hello World
// In your Swift app:
import Un

let result = try await executeCode("swift", #"print("Hello from Swift running on unsandbox!")"#)
print(result["stdout"]!)  // Hello from Swift running on unsandbox!
Demo cooldown: s
stdout:

                      
JSON Response:

                      
4
Run
./un.swift
Source Code 📄 (2562 lines)
MD5: eaedacd73da7439e09b465292b4164ef SHA256: 042cdd787a351f57eaa769f6db67b784da6703183295ad5b3f09d26e722c684b
/*
PUBLIC DOMAIN - NO LICENSE, NO WARRANTY

unsandbox.com Swift SDK (Synchronous)

Library Usage:
    import Foundation
    // Note: In a real project, compile this file and import as module

    // Execute code synchronously
    let result = try executeCode(language: "python", code: "print('hello')")

    // Execute asynchronously (returns job_id)
    let jobId = try executeAsync(language: "javascript", code: "console.log('hello')")

    // Wait for job completion
    let result = try waitForJob(jobId)

    // List all jobs
    let jobs = try listJobs()

    // Get supported languages
    let languages = try getLanguages()

    // Detect language from filename
    let lang = detectLanguage("script.py")  // Returns "python"

    // Session operations
    let sessions = try listSessions()
    let session = try createSession(shell: "python3")
    try deleteSession(sessionId)

    // Service operations
    let services = try listServices()
    let service = try createService(name: "myapp", ports: [80])
    try deleteService(serviceId)

    // Snapshot operations
    let snapshotId = try sessionSnapshot(sessionId, name: "my-snapshot")
    let snapshots = try listSnapshots()
    try deleteSnapshot(snapshotId)

    // Key validation
    let validation = try validateKeys()

    // Image generation
    let result = try image(prompt: "A sunset over mountains")

Authentication Priority (4-tier):
    1. Function arguments (publicKey, secretKey)
    2. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)
    3. Config file (~/.unsandbox/accounts.csv, line 0 by default)
    4. Local directory (./accounts.csv, line 0 by default)

    Format: public_key,secret_key (one per line)
    Account selection: UNSANDBOX_ACCOUNT=N env var (0-based index)

Request Authentication (HMAC-SHA256):
    Authorization: Bearer <public_key>                  (identifies account)
    X-Timestamp: <unix_seconds>                         (replay prevention)
    X-Signature: HMAC-SHA256(secret_key, msg)           (proves secret + body integrity)

    Message format: "timestamp:METHOD:path:body"
    - timestamp: seconds since epoch
    - METHOD: GET, POST, DELETE, etc. (uppercase)
    - path: e.g., "/execute", "/jobs/123"
    - body: JSON payload (empty string for GET/DELETE)

Languages Cache:
    - Cached in ~/.unsandbox/languages.json
    - TTL: 1 hour
    - Updated on successful API calls
*/

import Foundation
#if canImport(CommonCrypto)
import CommonCrypto
#endif

// MARK: - Constants

let API_BASE = "https://api.unsandbox.com"
let PORTAL_BASE = "https://unsandbox.com"
let POLL_DELAYS_MS: [Int] = [300, 450, 700, 900, 650, 1600, 2000]
let LANGUAGES_CACHE_TTL: TimeInterval = 3600  // 1 hour

// MARK: - Errors

enum UnsandboxError: Error, CustomStringConvertible {
    case credentialsNotFound(String)
    case networkError(String)
    case apiError(Int, String)
    case invalidResponse(String)
    case timeout(String)
    case invalidArgument(String)
    case fileNotFound(String)

    var description: String {
        switch self {
        case .credentialsNotFound(let msg): return "Credentials error: \(msg)"
        case .networkError(let msg): return "Network error: \(msg)"
        case .apiError(let code, let msg): return "API error (\(code)): \(msg)"
        case .invalidResponse(let msg): return "Invalid response: \(msg)"
        case .timeout(let msg): return "Timeout: \(msg)"
        case .invalidArgument(let msg): return "Invalid argument: \(msg)"
        case .fileNotFound(let msg): return "File not found: \(msg)"
        }
    }
}

// MARK: - Credentials Resolution

/// Get ~/.unsandbox directory path, creating if necessary
func getUnsandboxDir() -> URL {
    let home = FileManager.default.homeDirectoryForCurrentUser
    let unsandboxDir = home.appendingPathComponent(".unsandbox")

    if !FileManager.default.fileExists(atPath: unsandboxDir.path) {
        try? FileManager.default.createDirectory(at: unsandboxDir, withIntermediateDirectories: true, attributes: [.posixPermissions: 0o700])
    }

    return unsandboxDir
}

/// Load credentials from CSV file (public_key,secret_key per line)
func loadCredentialsFromCSV(_ path: URL, accountIndex: Int = 0) -> (String, String)? {
    guard FileManager.default.fileExists(atPath: path.path) else { return nil }

    do {
        let content = try String(contentsOf: path, encoding: .utf8)
        let lines = content.components(separatedBy: .newlines)
        var index = 0

        for line in lines {
            let trimmed = line.trimmingCharacters(in: .whitespaces)
            if trimmed.isEmpty || trimmed.hasPrefix("#") { continue }

            if index == accountIndex {
                let parts = trimmed.components(separatedBy: ",")
                if parts.count >= 2 {
                    return (parts[0].trimmingCharacters(in: .whitespaces),
                            parts[1].trimmingCharacters(in: .whitespaces))
                }
            }
            index += 1
        }
    } catch {
        return nil
    }

    return nil
}

/// Resolve credentials from 4-tier priority system
func resolveCredentials(publicKey: String? = nil, secretKey: String? = nil, accountIndex: Int? = nil) throws -> (String, String) {
    // Tier 1: Function arguments (-p/-k flags)
    if let pk = publicKey, let sk = secretKey, !pk.isEmpty, !sk.isEmpty {
        return (pk, sk)
    }

    // Tier 2: --account N → accounts.csv row N (bypasses env vars)
    if let idx = accountIndex {
        let unsandboxDir = getUnsandboxDir()
        if let creds = loadCredentialsFromCSV(unsandboxDir.appendingPathComponent("accounts.csv"), accountIndex: idx) {
            return creds
        }
        let localPath = URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent("accounts.csv")
        if let creds = loadCredentialsFromCSV(localPath, accountIndex: idx) {
            return creds
        }
        throw UnsandboxError.credentialsNotFound("No account at index \(idx) in accounts.csv")
    }

    // Tier 3: Environment variables
    if let envPk = ProcessInfo.processInfo.environment["UNSANDBOX_PUBLIC_KEY"],
       let envSk = ProcessInfo.processInfo.environment["UNSANDBOX_SECRET_KEY"],
       !envPk.isEmpty, !envSk.isEmpty {
        return (envPk, envSk)
    }

    // Tier 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env var)
    let defaultIdx = Int(ProcessInfo.processInfo.environment["UNSANDBOX_ACCOUNT"] ?? "0") ?? 0
    let unsandboxDir = getUnsandboxDir()
    if let creds = loadCredentialsFromCSV(unsandboxDir.appendingPathComponent("accounts.csv"), accountIndex: defaultIdx) {
        return creds
    }

    // Tier 5: ./accounts.csv row 0
    let localPath = URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent("accounts.csv")
    if let creds = loadCredentialsFromCSV(localPath, accountIndex: defaultIdx) {
        return creds
    }

    throw UnsandboxError.credentialsNotFound(
        """
        No credentials found. Please provide via:
          1. Function arguments (publicKey, secretKey)
          2. --account N (accounts.csv row N)
          3. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)
          4. ~/.unsandbox/accounts.csv
          5. ./accounts.csv
        """
    )
}

// MARK: - HMAC-SHA256 Signing

/// Sign a request using HMAC-SHA256
func signRequest(secretKey: String, timestamp: Int, method: String, path: String, body: String?) -> String {
    let bodyStr = body ?? ""
    let message = "\(timestamp):\(method):\(path):\(bodyStr)"

    guard let keyData = secretKey.data(using: .utf8),
          let messageData = message.data(using: .utf8) else {
        return ""
    }

    var hmac = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    keyData.withUnsafeBytes { keyPtr in
        messageData.withUnsafeBytes { msgPtr in
            CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256),
                   keyPtr.baseAddress, keyData.count,
                   msgPtr.baseAddress, messageData.count,
                   &hmac)
        }
    }

    return hmac.map { String(format: "%02x", $0) }.joined()
}

// MARK: - Sudo OTP Challenge Handling

/// Handle 428 sudo OTP challenge - prompts user for OTP and retries the request
func handleSudoChallenge(responseData: [String: Any], publicKey: String, secretKey: String, method: String, path: String, body: String?) throws -> [String: Any] {
    let challengeId = responseData["challenge_id"] as? String

    fputs("\u{001B}[33mConfirmation required. Check your email for a one-time code.\u{001B}[0m\n", stderr)
    fputs("Enter OTP: ", stderr)

    guard let otp = readLine()?.trimmingCharacters(in: .whitespacesAndNewlines), !otp.isEmpty else {
        fputs("\u{001B}[31mError: Operation cancelled\u{001B}[0m\n", stderr)
        throw UnsandboxError.invalidArgument("Operation cancelled - no OTP provided")
    }

    let url = URL(string: "\(API_BASE)\(path)")!
    var request = URLRequest(url: url)
    request.httpMethod = method
    request.timeoutInterval = 120

    let timestamp = Int(Date().timeIntervalSince1970)
    let bodyStr = body ?? ""
    let signature = signRequest(secretKey: secretKey, timestamp: timestamp, method: method, path: path, body: method != "GET" && method != "DELETE" ? bodyStr : nil)

    request.setValue("Bearer \(publicKey)", forHTTPHeaderField: "Authorization")
    request.setValue("\(timestamp)", forHTTPHeaderField: "X-Timestamp")
    request.setValue(signature, forHTTPHeaderField: "X-Signature")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue(otp, forHTTPHeaderField: "X-Sudo-OTP")
    if let challengeId = challengeId {
        request.setValue(challengeId, forHTTPHeaderField: "X-Sudo-Challenge")
    }

    if let body = body, !body.isEmpty {
        request.httpBody = body.data(using: .utf8)
    }

    var result: [String: Any]?
    var requestError: Error?

    let semaphore = DispatchSemaphore(value: 0)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        defer { semaphore.signal() }

        if let error = error {
            requestError = UnsandboxError.networkError(error.localizedDescription)
            return
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            requestError = UnsandboxError.invalidResponse("No HTTP response")
            return
        }

        guard let data = data else {
            requestError = UnsandboxError.invalidResponse("No data received")
            return
        }

        if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
            fputs("\u{001B}[32mOperation completed successfully\u{001B}[0m\n", stderr)
            do {
                if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
                    result = json
                } else {
                    result = [:]
                }
            } catch {
                result = [:]
            }
        } else {
            let body = String(data: data, encoding: .utf8) ?? "Unknown error"
            requestError = UnsandboxError.apiError(httpResponse.statusCode, body)
        }
    }

    task.resume()
    semaphore.wait()

    if let error = requestError {
        throw error
    }

    return result ?? [:]
}

/// Make an authenticated HTTP request that may require sudo OTP, returns (statusCode, response)
func makeRequestWithSudo(method: String, path: String, publicKey: String, secretKey: String, data: [String: Any]? = nil) throws -> (Int, [String: Any]) {
    let url = URL(string: "\(API_BASE)\(path)")!
    var request = URLRequest(url: url)
    request.httpMethod = method
    request.timeoutInterval = 120

    let timestamp = Int(Date().timeIntervalSince1970)
    var bodyStr: String? = nil

    if let data = data {
        let jsonData = try JSONSerialization.data(withJSONObject: data)
        bodyStr = String(data: jsonData, encoding: .utf8)
        request.httpBody = jsonData
    }

    let signature = signRequest(secretKey: secretKey, timestamp: timestamp, method: method, path: path, body: method != "GET" && method != "DELETE" ? bodyStr : nil)

    request.setValue("Bearer \(publicKey)", forHTTPHeaderField: "Authorization")
    request.setValue("\(timestamp)", forHTTPHeaderField: "X-Timestamp")
    request.setValue(signature, forHTTPHeaderField: "X-Signature")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")

    var statusCode: Int = 0
    var result: [String: Any]?
    var requestError: Error?

    let semaphore = DispatchSemaphore(value: 0)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        defer { semaphore.signal() }

        if let error = error {
            requestError = UnsandboxError.networkError(error.localizedDescription)
            return
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            requestError = UnsandboxError.invalidResponse("No HTTP response")
            return
        }

        statusCode = httpResponse.statusCode

        guard let data = data else {
            result = [:]
            return
        }

        do {
            if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
                result = json
            } else {
                result = [:]
            }
        } catch {
            result = [:]
        }
    }

    task.resume()
    semaphore.wait()

    if let error = requestError {
        throw error
    }

    return (statusCode, result ?? [:])
}

// MARK: - HTTP Client

/// Make an authenticated HTTP request to the API
func makeRequest(method: String, path: String, publicKey: String, secretKey: String, data: [String: Any]? = nil) throws -> [String: Any] {
    let url = URL(string: "\(API_BASE)\(path)")!
    var request = URLRequest(url: url)
    request.httpMethod = method
    request.timeoutInterval = 120

    let timestamp = Int(Date().timeIntervalSince1970)
    var bodyStr: String? = nil

    if let data = data {
        let jsonData = try JSONSerialization.data(withJSONObject: data)
        bodyStr = String(data: jsonData, encoding: .utf8)
        request.httpBody = jsonData
    }

    let signature = signRequest(secretKey: secretKey, timestamp: timestamp, method: method, path: path, body: method != "GET" && method != "DELETE" ? bodyStr : nil)

    request.setValue("Bearer \(publicKey)", forHTTPHeaderField: "Authorization")
    request.setValue("\(timestamp)", forHTTPHeaderField: "X-Timestamp")
    request.setValue(signature, forHTTPHeaderField: "X-Signature")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")

    var result: [String: Any]?
    var requestError: Error?

    let semaphore = DispatchSemaphore(value: 0)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        defer { semaphore.signal() }

        if let error = error {
            requestError = UnsandboxError.networkError(error.localizedDescription)
            return
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            requestError = UnsandboxError.invalidResponse("No HTTP response")
            return
        }

        guard let data = data else {
            requestError = UnsandboxError.invalidResponse("No data received")
            return
        }

        if httpResponse.statusCode >= 400 {
            let body = String(data: data, encoding: .utf8) ?? "Unknown error"
            requestError = UnsandboxError.apiError(httpResponse.statusCode, body)
            return
        }

        do {
            if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
                result = json
            } else {
                requestError = UnsandboxError.invalidResponse("Response is not a JSON object")
            }
        } catch {
            requestError = UnsandboxError.invalidResponse("Failed to parse JSON: \(error)")
        }
    }

    task.resume()
    semaphore.wait()

    if let error = requestError {
        throw error
    }

    return result ?? [:]
}

// MARK: - Languages Cache

func getLanguagesCachePath() -> URL {
    return getUnsandboxDir().appendingPathComponent("languages.json")
}

func loadLanguagesCache() -> [String]? {
    let cachePath = getLanguagesCachePath()

    guard FileManager.default.fileExists(atPath: cachePath.path) else { return nil }

    do {
        let attrs = try FileManager.default.attributesOfItem(atPath: cachePath.path)
        guard let mtime = attrs[.modificationDate] as? Date else { return nil }

        let age = Date().timeIntervalSince(mtime)
        if age >= LANGUAGES_CACHE_TTL { return nil }

        let data = try Data(contentsOf: cachePath)
        if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
           let languages = json["languages"] as? [String] {
            return languages
        }
    } catch {
        return nil
    }

    return nil
}

func saveLanguagesCache(_ languages: [String]) {
    let cachePath = getLanguagesCachePath()
    let data: [String: Any] = [
        "languages": languages,
        "timestamp": Int(Date().timeIntervalSince1970)
    ]

    do {
        let jsonData = try JSONSerialization.data(withJSONObject: data)
        try jsonData.write(to: cachePath)
    } catch {
        // Cache failures are non-fatal
    }
}

// MARK: - Language Detection

let LANGUAGE_MAP: [String: String] = [
    "py": "python",
    "js": "javascript",
    "ts": "typescript",
    "rb": "ruby",
    "php": "php",
    "pl": "perl",
    "sh": "bash",
    "r": "r",
    "lua": "lua",
    "go": "go",
    "rs": "rust",
    "c": "c",
    "cpp": "cpp",
    "cc": "cpp",
    "cxx": "cpp",
    "java": "java",
    "kt": "kotlin",
    "m": "objc",
    "cs": "csharp",
    "fs": "fsharp",
    "hs": "haskell",
    "ml": "ocaml",
    "clj": "clojure",
    "scm": "scheme",
    "ss": "scheme",
    "erl": "erlang",
    "ex": "elixir",
    "exs": "elixir",
    "jl": "julia",
    "d": "d",
    "nim": "nim",
    "zig": "zig",
    "v": "v",
    "cr": "crystal",
    "dart": "dart",
    "groovy": "groovy",
    "f90": "fortran",
    "f95": "fortran",
    "lisp": "commonlisp",
    "lsp": "commonlisp",
    "cob": "cobol",
    "tcl": "tcl",
    "raku": "raku",
    "pro": "prolog",
    "p": "prolog",
    "4th": "forth",
    "forth": "forth",
    "fth": "forth",
]

/// Detect programming language from filename extension
func detectLanguage(_ filename: String) -> String? {
    guard !filename.isEmpty, filename.contains(".") else { return nil }

    let ext = (filename as NSString).pathExtension.lowercased()
    return LANGUAGE_MAP[ext]
}

// MARK: - Execution Functions

/// Execute code synchronously (blocks until completion)
func executeCode(language: String, code: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let response = try makeRequest(method: "POST", path: "/execute", publicKey: pk, secretKey: sk, data: [
        "language": language,
        "code": code
    ])

    // If we got a job_id, poll until completion
    if let jobId = response["job_id"] as? String,
       let status = response["status"] as? String,
       status == "pending" || status == "running" {
        return try waitForJob(jobId, publicKey: pk, secretKey: sk)
    }

    return response
}

/// Execute code with additional options
func executeCodeWithOptions(
    language: String,
    code: String,
    env: [String: String]? = nil,
    files: [[String: String]]? = nil,
    networkMode: String = "zerotrust",
    vcpu: Int = 1,
    artifacts: Bool = false,
    publicKey: String? = nil,
    secretKey: String? = nil
) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)

    var data: [String: Any] = [
        "language": language,
        "code": code,
        "network_mode": networkMode
    ]

    if let env = env, !env.isEmpty {
        data["env"] = env
    }
    if let files = files, !files.isEmpty {
        data["files"] = files
    }
    if vcpu > 1 {
        data["vcpu"] = vcpu
    }
    if artifacts {
        data["artifacts"] = true
    }

    let response = try makeRequest(method: "POST", path: "/execute", publicKey: pk, secretKey: sk, data: data)

    if let jobId = response["job_id"] as? String,
       let status = response["status"] as? String,
       status == "pending" || status == "running" {
        return try waitForJob(jobId, publicKey: pk, secretKey: sk)
    }

    return response
}

/// Execute code asynchronously (returns immediately with job_id)
func executeAsync(language: String, code: String, publicKey: String? = nil, secretKey: String? = nil) throws -> String {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let response = try makeRequest(method: "POST", path: "/execute", publicKey: pk, secretKey: sk, data: [
        "language": language,
        "code": code
    ])
    return response["job_id"] as? String ?? ""
}

/// Get current status/result of a job (single poll, no waiting)
func getJob(_ jobId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "GET", path: "/jobs/\(jobId)", publicKey: pk, secretKey: sk)
}

/// Wait for job completion with exponential backoff polling
func waitForJob(_ jobId: String, publicKey: String? = nil, secretKey: String? = nil, timeout: TimeInterval? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var pollCount = 0
    let startTime = Date()

    while true {
        // Check timeout
        if let timeout = timeout {
            let elapsed = Date().timeIntervalSince(startTime)
            if elapsed >= timeout {
                throw UnsandboxError.timeout("Job \(jobId) did not complete within \(timeout) seconds")
            }
        }

        // Sleep before polling
        let delayIdx = min(pollCount, POLL_DELAYS_MS.count - 1)
        Thread.sleep(forTimeInterval: Double(POLL_DELAYS_MS[delayIdx]) / 1000.0)
        pollCount += 1

        let response = try getJob(jobId, publicKey: pk, secretKey: sk)
        if let status = response["status"] as? String,
           ["completed", "failed", "timeout", "cancelled"].contains(status) {
            return response
        }
    }
}

/// Cancel a running job
func cancelJob(_ jobId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "DELETE", path: "/jobs/\(jobId)", publicKey: pk, secretKey: sk)
}

/// List all jobs for the authenticated account
func listJobs(publicKey: String? = nil, secretKey: String? = nil) throws -> [[String: Any]] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let response = try makeRequest(method: "GET", path: "/jobs", publicKey: pk, secretKey: sk)
    return response["jobs"] as? [[String: Any]] ?? []
}

/// Get list of supported programming languages
func getLanguages(publicKey: String? = nil, secretKey: String? = nil) throws -> [String] {
    // Try cache first
    if let cached = loadLanguagesCache() {
        return cached
    }

    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let response = try makeRequest(method: "GET", path: "/languages", publicKey: pk, secretKey: sk)
    let languages = response["languages"] as? [String] ?? []

    // Cache the result
    saveLanguagesCache(languages)
    return languages
}

// MARK: - Session Functions

/// List all sessions for the authenticated account
func listSessions(publicKey: String? = nil, secretKey: String? = nil) throws -> [[String: Any]] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let response = try makeRequest(method: "GET", path: "/sessions", publicKey: pk, secretKey: sk)
    return response["sessions"] as? [[String: Any]] ?? []
}

/// Get details of a specific session
func getSession(_ sessionId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "GET", path: "/sessions/\(sessionId)", publicKey: pk, secretKey: sk)
}

/// Create a new interactive session
func createSession(
    language: String? = nil,
    networkMode: String = "zerotrust",
    ttl: Int = 3600,
    shell: String? = nil,
    multiplexer: String? = nil,
    vcpu: Int = 1,
    publicKey: String? = nil,
    secretKey: String? = nil
) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)

    var data: [String: Any] = [
        "network_mode": networkMode,
        "ttl": ttl
    ]

    if let language = language {
        data["language"] = language
    }
    if let shell = shell {
        data["shell"] = shell
    }
    if let multiplexer = multiplexer {
        data["multiplexer"] = multiplexer
    }
    if vcpu > 1 {
        data["vcpu"] = vcpu
    }

    return try makeRequest(method: "POST", path: "/sessions", publicKey: pk, secretKey: sk, data: data)
}

/// Delete/terminate a session
func deleteSession(_ sessionId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "DELETE", path: "/sessions/\(sessionId)", publicKey: pk, secretKey: sk)
}

/// Freeze a session (pause execution, preserve state)
func freezeSession(_ sessionId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/sessions/\(sessionId)/freeze", publicKey: pk, secretKey: sk, data: [:])
}

/// Unfreeze a session (resume execution)
func unfreezeSession(_ sessionId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/sessions/\(sessionId)/unfreeze", publicKey: pk, secretKey: sk, data: [:])
}

/// Boost a session (increase resources)
func boostSession(_ sessionId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/sessions/\(sessionId)/boost", publicKey: pk, secretKey: sk, data: [:])
}

/// Unboost a session (return to normal resources)
func unboostSession(_ sessionId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/sessions/\(sessionId)/unboost", publicKey: pk, secretKey: sk, data: [:])
}

/// Execute a shell command in a session
func shellSession(_ sessionId: String, command: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/sessions/\(sessionId)/shell", publicKey: pk, secretKey: sk, data: ["command": command])
}

// MARK: - Service Functions

/// List all services for the authenticated account
func listServices(publicKey: String? = nil, secretKey: String? = nil) throws -> [[String: Any]] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let response = try makeRequest(method: "GET", path: "/services", publicKey: pk, secretKey: sk)
    return response["services"] as? [[String: Any]] ?? []
}

/// Create a new persistent service
func createService(
    name: String,
    ports: [Int],
    bootstrap: String? = nil,
    networkMode: String = "semitrusted",
    customDomains: [String]? = nil,
    vcpu: Int = 1,
    serviceType: String? = nil,
    unfreezeOnDemand: Bool = false,
    publicKey: String? = nil,
    secretKey: String? = nil
) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)

    var data: [String: Any] = [
        "name": name,
        "ports": ports,
        "network_mode": networkMode
    ]

    if let bootstrap = bootstrap {
        if bootstrap.hasPrefix("http://") || bootstrap.hasPrefix("https://") {
            data["bootstrap"] = bootstrap
        } else {
            data["bootstrap_content"] = bootstrap
        }
    }
    if let customDomains = customDomains {
        data["custom_domains"] = customDomains
    }
    if vcpu > 1 {
        data["vcpu"] = vcpu
    }
    if let serviceType = serviceType {
        data["service_type"] = serviceType
    }
    if unfreezeOnDemand {
        data["unfreeze_on_demand"] = unfreezeOnDemand
    }

    return try makeRequest(method: "POST", path: "/services", publicKey: pk, secretKey: sk, data: data)
}

/// Get details of a specific service
func getService(_ serviceId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "GET", path: "/services/\(serviceId)", publicKey: pk, secretKey: sk)
}

/// Update a service (e.g., resize vCPU/memory)
func updateService(_ serviceId: String, vcpu: Int? = nil, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var data: [String: Any] = [:]
    if let vcpu = vcpu {
        data["vcpu"] = vcpu
    }
    return try makeRequest(method: "PATCH", path: "/services/\(serviceId)", publicKey: pk, secretKey: sk, data: data)
}

/// Delete/destroy a service (handles 428 sudo OTP challenge)
func deleteService(_ serviceId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let path = "/services/\(serviceId)"
    let (statusCode, response) = try makeRequestWithSudo(method: "DELETE", path: path, publicKey: pk, secretKey: sk)

    if statusCode == 428 {
        return try handleSudoChallenge(responseData: response, publicKey: pk, secretKey: sk, method: "DELETE", path: path, body: nil)
    } else if statusCode >= 400 {
        let errorMsg = response["error"] as? String ?? "Unknown error"
        throw UnsandboxError.apiError(statusCode, errorMsg)
    }

    return response
}

/// Freeze a service (pause execution, preserve state)
func freezeService(_ serviceId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/services/\(serviceId)/freeze", publicKey: pk, secretKey: sk, data: [:])
}

/// Unfreeze a service (resume execution)
func unfreezeService(_ serviceId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/services/\(serviceId)/unfreeze", publicKey: pk, secretKey: sk, data: [:])
}

/// Lock a service to prevent accidental deletion
func lockService(_ serviceId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/services/\(serviceId)/lock", publicKey: pk, secretKey: sk, data: [:])
}

/// Unlock a service to allow deletion (handles 428 sudo OTP challenge)
func unlockService(_ serviceId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let path = "/services/\(serviceId)/unlock"
    let (statusCode, response) = try makeRequestWithSudo(method: "POST", path: path, publicKey: pk, secretKey: sk, data: [:])

    if statusCode == 428 {
        return try handleSudoChallenge(responseData: response, publicKey: pk, secretKey: sk, method: "POST", path: path, body: "{}")
    } else if statusCode >= 400 {
        let errorMsg = response["error"] as? String ?? "Unknown error"
        throw UnsandboxError.apiError(statusCode, errorMsg)
    }

    return response
}

/// Enable or disable automatic unfreezing on incoming requests
func setUnfreezeOnDemand(_ serviceId: String, enabled: Bool, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "PATCH", path: "/services/\(serviceId)", publicKey: pk, secretKey: sk, data: ["unfreeze_on_demand": enabled])
}

/// Get bootstrap/runtime logs for a service
func getServiceLogs(_ serviceId: String, allLogs: Bool = false, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var path = "/services/\(serviceId)/logs"
    if allLogs {
        path += "?all=true"
    }
    return try makeRequest(method: "GET", path: path, publicKey: pk, secretKey: sk)
}

/// Get environment vault status for a service
func getServiceEnv(_ serviceId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "GET", path: "/services/\(serviceId)/env", publicKey: pk, secretKey: sk)
}

/// Set environment variables for a service
func setServiceEnv(_ serviceId: String, envDict: [String: String], publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let envContent = envDict.map { "\($0.key)=\($0.value)" }.joined(separator: "\n")
    return try makeRequest(method: "POST", path: "/services/\(serviceId)/env", publicKey: pk, secretKey: sk, data: ["env": envContent])
}

/// Delete environment vault from a service
func deleteServiceEnv(_ serviceId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "DELETE", path: "/services/\(serviceId)/env", publicKey: pk, secretKey: sk)
}

/// Export environment vault secrets for a service
func exportServiceEnv(_ serviceId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/services/\(serviceId)/env/export", publicKey: pk, secretKey: sk, data: [:])
}

/// Redeploy a service (re-run bootstrap script)
func redeployService(_ serviceId: String, bootstrap: String? = nil, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var data: [String: Any] = [:]
    if let bootstrap = bootstrap {
        if bootstrap.hasPrefix("http://") || bootstrap.hasPrefix("https://") {
            data["bootstrap"] = bootstrap
        } else {
            data["bootstrap_content"] = bootstrap
        }
    }
    return try makeRequest(method: "POST", path: "/services/\(serviceId)/redeploy", publicKey: pk, secretKey: sk, data: data)
}

/// Execute a command in a running service container
func executeInService(_ serviceId: String, command: String, timeout: Int = 30000, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/services/\(serviceId)/execute", publicKey: pk, secretKey: sk, data: ["command": command, "timeout": timeout])
}

// MARK: - Snapshot Functions

/// Create a snapshot of a session
func sessionSnapshot(_ sessionId: String, name: String? = nil, ephemeral: Bool = false, publicKey: String? = nil, secretKey: String? = nil) throws -> String {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var data: [String: Any] = ["session_id": sessionId, "ephemeral": ephemeral]
    if let name = name {
        data["name"] = name
    }
    let response = try makeRequest(method: "POST", path: "/snapshots", publicKey: pk, secretKey: sk, data: data)
    return response["snapshot_id"] as? String ?? ""
}

/// Create a snapshot of a service
func serviceSnapshot(_ serviceId: String, name: String? = nil, publicKey: String? = nil, secretKey: String? = nil) throws -> String {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var data: [String: Any] = ["service_id": serviceId]
    if let name = name {
        data["name"] = name
    }
    let response = try makeRequest(method: "POST", path: "/snapshots", publicKey: pk, secretKey: sk, data: data)
    return response["snapshot_id"] as? String ?? ""
}

/// List all snapshots
func listSnapshots(publicKey: String? = nil, secretKey: String? = nil) throws -> [[String: Any]] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let response = try makeRequest(method: "GET", path: "/snapshots", publicKey: pk, secretKey: sk)
    return response["snapshots"] as? [[String: Any]] ?? []
}

/// Restore a snapshot
func restoreSnapshot(_ snapshotId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/snapshots/\(snapshotId)/restore", publicKey: pk, secretKey: sk, data: [:])
}

/// Delete a snapshot (handles 428 sudo OTP challenge)
func deleteSnapshot(_ snapshotId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let path = "/snapshots/\(snapshotId)"
    let (statusCode, response) = try makeRequestWithSudo(method: "DELETE", path: path, publicKey: pk, secretKey: sk)

    if statusCode == 428 {
        return try handleSudoChallenge(responseData: response, publicKey: pk, secretKey: sk, method: "DELETE", path: path, body: nil)
    } else if statusCode >= 400 {
        let errorMsg = response["error"] as? String ?? "Unknown error"
        throw UnsandboxError.apiError(statusCode, errorMsg)
    }

    return response
}

/// Lock a snapshot to prevent accidental deletion
func lockSnapshot(_ snapshotId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/snapshots/\(snapshotId)/lock", publicKey: pk, secretKey: sk, data: [:])
}

/// Unlock a snapshot to allow deletion (handles 428 sudo OTP challenge)
func unlockSnapshot(_ snapshotId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let path = "/snapshots/\(snapshotId)/unlock"
    let (statusCode, response) = try makeRequestWithSudo(method: "POST", path: path, publicKey: pk, secretKey: sk, data: [:])

    if statusCode == 428 {
        return try handleSudoChallenge(responseData: response, publicKey: pk, secretKey: sk, method: "POST", path: path, body: "{}")
    } else if statusCode >= 400 {
        let errorMsg = response["error"] as? String ?? "Unknown error"
        throw UnsandboxError.apiError(statusCode, errorMsg)
    }

    return response
}

/// Clone a snapshot to create a new session or service
func cloneSnapshot(
    _ snapshotId: String,
    cloneType: String = "session",
    name: String? = nil,
    shell: String? = nil,
    ports: [Int]? = nil,
    publicKey: String? = nil,
    secretKey: String? = nil
) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var data: [String: Any] = ["type": cloneType]
    if let name = name {
        data["name"] = name
    }
    if let shell = shell {
        data["shell"] = shell
    }
    if let ports = ports {
        data["ports"] = ports
    }
    return try makeRequest(method: "POST", path: "/snapshots/\(snapshotId)/clone", publicKey: pk, secretKey: sk, data: data)
}

// MARK: - Image Functions

/// Publish a service or snapshot as a portable LXD image
func imagePublish(
    sourceType: String,
    sourceId: String,
    name: String? = nil,
    description: String? = nil,
    publicKey: String? = nil,
    secretKey: String? = nil
) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var data: [String: Any] = ["source_type": sourceType, "source_id": sourceId]
    if let name = name {
        data["name"] = name
    }
    if let description = description {
        data["description"] = description
    }
    return try makeRequest(method: "POST", path: "/images", publicKey: pk, secretKey: sk, data: data)
}

/// List images accessible to this API key
func listImages(filterType: String? = nil, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var endpoint = "/images"
    if let filterType = filterType {
        endpoint = "/images/\(filterType)"
    }
    return try makeRequest(method: "GET", path: endpoint, publicKey: pk, secretKey: sk)
}

/// Get details of a specific image
func getImage(_ imageId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "GET", path: "/images/\(imageId)", publicKey: pk, secretKey: sk)
}

/// Delete an image (handles 428 sudo OTP challenge)
func deleteImage(_ imageId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let path = "/images/\(imageId)"
    let (statusCode, response) = try makeRequestWithSudo(method: "DELETE", path: path, publicKey: pk, secretKey: sk)

    if statusCode == 428 {
        return try handleSudoChallenge(responseData: response, publicKey: pk, secretKey: sk, method: "DELETE", path: path, body: nil)
    } else if statusCode >= 400 {
        let errorMsg = response["error"] as? String ?? "Unknown error"
        throw UnsandboxError.apiError(statusCode, errorMsg)
    }

    return response
}

/// Lock an image to prevent accidental deletion
func lockImage(_ imageId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/images/\(imageId)/lock", publicKey: pk, secretKey: sk, data: [:])
}

/// Unlock an image to allow deletion (handles 428 sudo OTP challenge)
func unlockImage(_ imageId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    let path = "/images/\(imageId)/unlock"
    let (statusCode, response) = try makeRequestWithSudo(method: "POST", path: path, publicKey: pk, secretKey: sk, data: [:])

    if statusCode == 428 {
        return try handleSudoChallenge(responseData: response, publicKey: pk, secretKey: sk, method: "POST", path: path, body: "{}")
    } else if statusCode >= 400 {
        let errorMsg = response["error"] as? String ?? "Unknown error"
        throw UnsandboxError.apiError(statusCode, errorMsg)
    }

    return response
}

/// Set image visibility
func setImageVisibility(_ imageId: String, visibility: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/images/\(imageId)/visibility", publicKey: pk, secretKey: sk, data: ["visibility": visibility])
}

/// Grant access to an image for another API key
func grantImageAccess(_ imageId: String, trustedApiKey: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/images/\(imageId)/grant", publicKey: pk, secretKey: sk, data: ["trusted_api_key": trustedApiKey])
}

/// Revoke access to an image from another API key
func revokeImageAccess(_ imageId: String, trustedApiKey: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/images/\(imageId)/revoke", publicKey: pk, secretKey: sk, data: ["trusted_api_key": trustedApiKey])
}

/// List all API keys that have access to an image
func listImageTrusted(_ imageId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "GET", path: "/images/\(imageId)/trusted", publicKey: pk, secretKey: sk)
}

/// Transfer image ownership to another API key
func transferImage(_ imageId: String, toApiKey: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/images/\(imageId)/transfer", publicKey: pk, secretKey: sk, data: ["to_api_key": toApiKey])
}

/// Create a new service from an image
func spawnFromImage(
    _ imageId: String,
    name: String? = nil,
    ports: [Int]? = nil,
    bootstrap: String? = nil,
    networkMode: String = "zerotrust",
    publicKey: String? = nil,
    secretKey: String? = nil
) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var data: [String: Any] = ["network_mode": networkMode]
    if let name = name {
        data["name"] = name
    }
    if let ports = ports {
        data["ports"] = ports
    }
    if let bootstrap = bootstrap {
        data["bootstrap"] = bootstrap
    }
    return try makeRequest(method: "POST", path: "/images/\(imageId)/spawn", publicKey: pk, secretKey: sk, data: data)
}

/// Clone an image to create a copy owned by you
func cloneImage(_ imageId: String, name: String? = nil, description: String? = nil, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var data: [String: Any] = [:]
    if let name = name {
        data["name"] = name
    }
    if let description = description {
        data["description"] = description
    }
    return try makeRequest(method: "POST", path: "/images/\(imageId)/clone", publicKey: pk, secretKey: sk, data: data)
}

/// Grant access to an image
func grantImageAccess(_ imageId: String, trustedApiKey: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/images/\(imageId)/grant", publicKey: pk, secretKey: sk, data: ["trusted_api_key": trustedApiKey])
}

/// Revoke access to an image
func revokeImageAccess(_ imageId: String, trustedApiKey: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "POST", path: "/images/\(imageId)/revoke", publicKey: pk, secretKey: sk, data: ["trusted_api_key": trustedApiKey])
}

/// List trusted keys for an image
func listImageTrusted(_ imageId: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequest(method: "GET", path: "/images/\(imageId)/trusted", publicKey: pk, secretKey: sk)
}

/// Transfer image ownership
func transferImage(_ imageId: String, toApiKey: String, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    return try makeRequestWithSudo(method: "POST", path: "/images/\(imageId)/transfer", publicKey: pk, secretKey: sk, data: ["to_api_key": toApiKey])
}

// MARK: - PaaS Logs

/// Fetch batch logs from the portal
func fetchLogs(source: String = "all", lines: Int = 100, since: String = "1h", grep: String? = nil, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var endpoint = "/paas/logs?source=\(source)&lines=\(lines)&since=\(since)"
    if let grep = grep {
        endpoint += "&grep=\(grep.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? grep)"
    }

    let url = URL(string: "\(PORTAL_BASE)\(endpoint)")!
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.timeoutInterval = 30

    let timestamp = Int(Date().timeIntervalSince1970)
    let signature = signRequest(secretKey: sk, timestamp: timestamp, method: "GET", path: endpoint, body: nil)

    request.setValue("Bearer \(pk)", forHTTPHeaderField: "Authorization")
    request.setValue("\(timestamp)", forHTTPHeaderField: "X-Timestamp")
    request.setValue(signature, forHTTPHeaderField: "X-Signature")

    var result: [String: Any]?
    var requestError: Error?

    let semaphore = DispatchSemaphore(value: 0)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        defer { semaphore.signal() }

        if let error = error {
            requestError = UnsandboxError.networkError(error.localizedDescription)
            return
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            requestError = UnsandboxError.invalidResponse("No HTTP response")
            return
        }

        guard let data = data else {
            requestError = UnsandboxError.invalidResponse("No data received")
            return
        }

        if httpResponse.statusCode >= 400 {
            let body = String(data: data, encoding: .utf8) ?? "Unknown error"
            requestError = UnsandboxError.apiError(httpResponse.statusCode, body)
            return
        }

        do {
            if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
                result = json
            }
        } catch {
            requestError = UnsandboxError.invalidResponse("Failed to parse JSON")
        }
    }

    task.resume()
    semaphore.wait()

    if let error = requestError {
        throw error
    }

    return result ?? [:]
}

// MARK: - Health Check

/// Check API health status
func healthCheck() throws -> [String: Any] {
    let url = URL(string: "\(API_BASE)/health")!
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.timeoutInterval = 10

    var result: [String: Any]?
    var requestError: Error?

    let semaphore = DispatchSemaphore(value: 0)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        defer { semaphore.signal() }

        if let error = error {
            requestError = UnsandboxError.networkError(error.localizedDescription)
            return
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            requestError = UnsandboxError.invalidResponse("No HTTP response")
            return
        }

        guard let data = data else {
            requestError = UnsandboxError.invalidResponse("No data received")
            return
        }

        if httpResponse.statusCode >= 400 {
            let body = String(data: data, encoding: .utf8) ?? "Unknown error"
            requestError = UnsandboxError.apiError(httpResponse.statusCode, body)
            return
        }

        do {
            if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
                result = json
            }
        } catch {
            requestError = UnsandboxError.invalidResponse("Failed to parse JSON")
        }
    }

    task.resume()
    semaphore.wait()

    if let error = requestError {
        throw error
    }

    return result ?? [:]
}

/// Get SDK version information
func getVersion() -> [String: String] {
    return [
        "version": "1.0.0",
        "api": API_BASE,
        "portal": PORTAL_BASE
    ]
}

// MARK: - Key Validation

/// Validate API keys against the portal
func validateKeys(publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)

    let url = URL(string: "\(PORTAL_BASE)/keys/validate")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.timeoutInterval = 30

    let timestamp = Int(Date().timeIntervalSince1970)
    let signature = signRequest(secretKey: sk, timestamp: timestamp, method: "POST", path: "/keys/validate", body: "")

    request.setValue("Bearer \(pk)", forHTTPHeaderField: "Authorization")
    request.setValue("\(timestamp)", forHTTPHeaderField: "X-Timestamp")
    request.setValue(signature, forHTTPHeaderField: "X-Signature")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")

    var result: [String: Any]?
    var requestError: Error?

    let semaphore = DispatchSemaphore(value: 0)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        defer { semaphore.signal() }

        if let error = error {
            requestError = UnsandboxError.networkError(error.localizedDescription)
            return
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            requestError = UnsandboxError.invalidResponse("No HTTP response")
            return
        }

        guard let data = data else {
            requestError = UnsandboxError.invalidResponse("No data received")
            return
        }

        if httpResponse.statusCode >= 400 {
            let body = String(data: data, encoding: .utf8) ?? "Unknown error"
            requestError = UnsandboxError.apiError(httpResponse.statusCode, body)
            return
        }

        do {
            if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
                result = json
            }
        } catch {
            requestError = UnsandboxError.invalidResponse("Failed to parse JSON")
        }
    }

    task.resume()
    semaphore.wait()

    if let error = requestError {
        throw error
    }

    return result ?? [:]
}

// MARK: - Image Generation (AI)

/// Generate images from text prompt using AI
func image(
    prompt: String,
    model: String? = nil,
    size: String = "1024x1024",
    quality: String = "standard",
    n: Int = 1,
    publicKey: String? = nil,
    secretKey: String? = nil
) throws -> [String: Any] {
    let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey)
    var payload: [String: Any] = [
        "prompt": prompt,
        "size": size,
        "quality": quality,
        "n": n
    ]
    if let model = model {
        payload["model"] = model
    }
    return try makeRequest(method: "POST", path: "/image", publicKey: pk, secretKey: sk, data: payload)
}

// MARK: - CLI Implementation

/// Parse a .env file into a dictionary
func parseEnvFile(_ filePath: String) throws -> [String: String] {
    var envDict: [String: String] = [:]

    let url = URL(fileURLWithPath: filePath)
    guard FileManager.default.fileExists(atPath: url.path) else {
        throw UnsandboxError.fileNotFound(filePath)
    }

    let content = try String(contentsOf: url, encoding: .utf8)
    let lines = content.components(separatedBy: .newlines)

    for line in lines {
        let trimmed = line.trimmingCharacters(in: .whitespaces)
        if trimmed.isEmpty || trimmed.hasPrefix("#") { continue }

        if let eqIndex = trimmed.firstIndex(of: "=") {
            let key = String(trimmed[..<eqIndex]).trimmingCharacters(in: .whitespaces)
            var value = String(trimmed[trimmed.index(after: eqIndex)...]).trimmingCharacters(in: .whitespaces)

            // Handle quoted values
            if (value.hasPrefix("\"") && value.hasSuffix("\"")) ||
               (value.hasPrefix("'") && value.hasSuffix("'")) {
                value = String(value.dropFirst().dropLast())
            }
            envDict[key] = value
        }
    }

    return envDict
}

/// Format list output in table format
func formatListOutput(_ items: [[String: Any]], resourceType: String) -> String {
    if items.isEmpty {
        return "No \(resourceType)s found."
    }

    var headers: [String]
    var rows: [[String]] = []

    switch resourceType {
    case "session":
        headers = ["ID", "STATUS", "SHELL", "CREATED"]
        for item in items {
            let id = (item["id"] as? String ?? item["session_id"] as? String ?? "").prefix(36)
            let status = item["status"] as? String ?? "unknown"
            let shell = item["shell"] as? String ?? "bash"
            let created = (item["created_at"] as? String ?? "").prefix(19)
            rows.append([String(id), status, shell, String(created)])
        }
    case "service":
        headers = ["ID", "NAME", "STATUS", "PORTS", "CREATED"]
        for item in items {
            let id = (item["id"] as? String ?? item["service_id"] as? String ?? "").prefix(36)
            let name = (item["name"] as? String ?? "").prefix(20)
            let status = item["status"] as? String ?? "unknown"
            let ports = (item["ports"] as? [Int] ?? []).map { String($0) }.joined(separator: ",").prefix(15)
            let created = (item["created_at"] as? String ?? "").prefix(19)
            rows.append([String(id), String(name), status, String(ports), String(created)])
        }
    case "snapshot":
        headers = ["ID", "NAME", "TYPE", "SIZE", "CREATED"]
        for item in items {
            let id = (item["id"] as? String ?? item["snapshot_id"] as? String ?? "").prefix(36)
            let name = (item["name"] as? String ?? "").prefix(20)
            let sourceType = item["source_type"] as? String ?? "unknown"
            let size = item["size"] as? String ?? ""
            let created = (item["created_at"] as? String ?? "").prefix(19)
            rows.append([String(id), String(name), sourceType, size, String(created)])
        }
    default:
        headers = ["ID", "STATUS"]
        for item in items {
            rows.append([item["id"] as? String ?? "", item["status"] as? String ?? ""])
        }
    }

    // Calculate column widths
    var widths = headers.map { $0.count }
    for row in rows {
        for (i, cell) in row.enumerated() {
            widths[i] = max(widths[i], cell.count)
        }
    }

    // Build output
    var lines: [String] = []
    let headerLine = zip(headers, widths).map { $0.0.padding(toLength: $0.1, withPad: " ", startingAt: 0) }.joined(separator: "  ")
    lines.append(headerLine)

    for row in rows {
        let line = zip(row, widths).map { $0.0.padding(toLength: $0.1, withPad: " ", startingAt: 0) }.joined(separator: "  ")
        lines.append(line)
    }

    return lines.joined(separator: "\n")
}

/// Print usage help
func printHelp() {
    let help = """
    Unsandbox CLI - Execute code in secure containers

    USAGE:
        un [options] <source_file>        Execute code file
        un session [options]              Interactive session
        un service [options]              Manage services
        un service-env <action> <id>      Manage service environment
        un snapshot [options]             Manage snapshots
        un key                            Check API key
        un languages [--json]             List available languages

    GLOBAL OPTIONS:
        -s, --shell LANG      Language for inline code
        -e, --env KEY=VAL     Set environment variable
        -f, --file FILE       Add input file to /tmp/
        -F, --file-path FILE  Add input file with path preserved
        -a, --artifacts       Return compiled artifacts
        -o, --output DIR      Output directory for artifacts
        -p, --public-key KEY  API public key
        -k, --secret-key KEY  API secret key
        -n, --network MODE    Network: zerotrust or semitrusted
        -v, --vcpu N          vCPU count (1-8)
        -y, --yes             Skip confirmation prompts
        -h, --help            Show help

    SESSION OPTIONS:
        -l, --list            List active sessions
        --attach ID           Reconnect to existing session
        --kill ID             Terminate a session
        --freeze ID           Pause session
        --unfreeze ID         Resume session
        --boost ID            Add resources to session
        --unboost ID          Remove boost from session
        --snapshot ID         Create snapshot of session
        --shell SHELL         Shell/REPL to use (default: bash)
        --tmux                Enable persistence with tmux
        --screen              Enable persistence with screen

    SERVICE OPTIONS:
        -l, --list            List all services
        --info ID             Get service details
        --logs ID             Get all logs
        --tail ID             Get last 9000 lines of logs
        --freeze ID           Pause service
        --unfreeze ID         Resume service
        --destroy ID          Delete service
        --lock ID             Prevent deletion
        --unlock ID           Allow deletion
        --resize ID           Resize service (with --vcpu)
        --redeploy ID         Re-run bootstrap
        --execute ID CMD      Run command in service
        --snapshot ID         Create snapshot of service
        --name NAME           Service name (creates new)
        --ports PORTS         Comma-separated ports
        --bootstrap CMD       Bootstrap command
        --bootstrap-file FILE Bootstrap from file
        --env-file FILE       Load env from .env file

    SERVICE-ENV ACTIONS:
        status                Show vault status
        set                   Set from --env-file or stdin
        export                Export to stdout
        delete                Delete vault

    SNAPSHOT OPTIONS:
        -l, --list            List all snapshots
        --info ID             Get snapshot details
        --delete ID           Delete snapshot
        --lock ID             Prevent deletion
        --unlock ID           Allow deletion
        --clone ID            Clone snapshot
        --type TYPE           Clone type: session or service
        --name NAME           Name for cloned resource

    EXAMPLES:
        un script.py                           Execute Python script
        un -s bash 'echo hello'                Inline bash command
        un session --list                      List active sessions
        un service --list                      List all services
        un snapshot --list                     List all snapshots
        un key                                 Check API key
    """
    print(help)
}

/// CLI argument parser
class CLIArgs {
    var command: String?
    var source: String?
    var shell: String?
    var env: [String] = []
    var files: [String] = []
    var filesPath: [String] = []
    var artifacts: Bool = false
    var output: String?
    var publicKey: String?
    var secretKey: String?
    var networkMode: String = "zerotrust"
    var vcpu: Int = 1
    var yes: Bool = false

    // Session options
    var listFlag: Bool = false
    var attach: String?
    var kill: String?
    var freeze: String?
    var unfreeze: String?
    var boost: String?
    var unboost: String?
    var snapshot: String?
    var snapshotName: String?
    var hot: Bool = false
    var audit: Bool = false
    var tmux: Bool = false
    var screen: Bool = false

    // Service options
    var info: String?
    var logs: String?
    var tail: String?
    var destroy: String?
    var lock: String?
    var unlock: String?
    var resize: String?
    var redeploy: String?
    var execute: (String, String)?
    var name: String?
    var ports: String?
    var domains: String?
    var serviceType: String?
    var bootstrap: String?
    var bootstrapFile: String?
    var envFile: String?

    // Snapshot options
    var delete: String?
    var clone: String?
    var cloneType: String?

    // Image options
    var publish: String?
    var sourceType: String?
    var visibility: String?
    var visibilityMode: String?
    var spawn: String?
    var grant: String?
    var revoke: String?
    var trusted: String?
    var trustedKey: String?
    var transfer: String?
    var toKey: String?

    // Logs options
    var logsSource: String?
    var logsLines: Int?
    var logsSince: String?
    var logsGrep: String?
    var logsFollow: Bool = false

    // Service-env
    var serviceEnvAction: String?
    var serviceEnvId: String?

    // Languages options
    var jsonOutput: Bool = false

    // Credential selection
    var accountIndex: Int? = nil

    func parse(_ args: [String]) {
        var i = 0
        let args = Array(args.dropFirst()) // Skip program name

        while i < args.count {
            let arg = args[i]

            switch arg {
            case "-h", "--help":
                printHelp()
                exit(0)
            case "-s", "--shell":
                i += 1
                if i < args.count { shell = args[i] }
            case "-e", "--env":
                i += 1
                if i < args.count { env.append(args[i]) }
            case "-f", "--file":
                i += 1
                if i < args.count { files.append(args[i]) }
            case "-F", "--file-path":
                i += 1
                if i < args.count { filesPath.append(args[i]) }
            case "-a", "--artifacts":
                artifacts = true
            case "-o", "--output":
                i += 1
                if i < args.count { output = args[i] }
            case "-p", "--public-key":
                i += 1
                if i < args.count { publicKey = args[i] }
            case "-k", "--secret-key":
                i += 1
                if i < args.count { secretKey = args[i] }
            case "--account":
                i += 1
                if i < args.count { accountIndex = Int(args[i]) }
            case "-n", "--network":
                i += 1
                if i < args.count { networkMode = args[i] }
            case "-v", "--vcpu":
                i += 1
                if i < args.count { vcpu = Int(args[i]) ?? 1 }
            case "-y", "--yes":
                yes = true
            case "--json":
                jsonOutput = true
            case "-l", "--list":
                listFlag = true
            case "--attach":
                i += 1
                if i < args.count { attach = args[i] }
            case "--kill":
                i += 1
                if i < args.count { kill = args[i] }
            case "--freeze":
                i += 1
                if i < args.count { freeze = args[i] }
            case "--unfreeze":
                i += 1
                if i < args.count { unfreeze = args[i] }
            case "--boost":
                i += 1
                if i < args.count { boost = args[i] }
            case "--unboost":
                i += 1
                if i < args.count { unboost = args[i] }
            case "--snapshot":
                i += 1
                if i < args.count { snapshot = args[i] }
            case "--snapshot-name":
                i += 1
                if i < args.count { snapshotName = args[i] }
            case "--hot":
                hot = true
            case "--audit":
                audit = true
            case "--tmux":
                tmux = true
            case "--screen":
                screen = true
            case "--info":
                i += 1
                if i < args.count { info = args[i] }
            case "--logs":
                i += 1
                if i < args.count { logs = args[i] }
            case "--tail":
                i += 1
                if i < args.count { tail = args[i] }
            case "--destroy":
                i += 1
                if i < args.count { destroy = args[i] }
            case "--lock":
                i += 1
                if i < args.count { lock = args[i] }
            case "--unlock":
                i += 1
                if i < args.count { unlock = args[i] }
            case "--resize":
                i += 1
                if i < args.count { resize = args[i] }
            case "--redeploy":
                i += 1
                if i < args.count { redeploy = args[i] }
            case "--execute":
                i += 1
                if i + 1 < args.count {
                    execute = (args[i], args[i + 1])
                    i += 1
                }
            case "--name":
                i += 1
                if i < args.count { name = args[i] }
            case "--ports":
                i += 1
                if i < args.count { ports = args[i] }
            case "--domains":
                i += 1
                if i < args.count { domains = args[i] }
            case "--type":
                i += 1
                if i < args.count {
                    if args[i] == "session" || args[i] == "service" {
                        cloneType = args[i]
                    } else {
                        serviceType = args[i]
                    }
                }
            case "--bootstrap":
                i += 1
                if i < args.count { bootstrap = args[i] }
            case "--bootstrap-file":
                i += 1
                if i < args.count { bootstrapFile = args[i] }
            case "--env-file":
                i += 1
                if i < args.count { envFile = args[i] }
            case "--delete":
                i += 1
                if i < args.count { delete = args[i] }
            case "--clone":
                i += 1
                if i < args.count { clone = args[i] }
            case "--publish":
                i += 1
                if i < args.count { publish = args[i] }
            case "--source-type":
                i += 1
                if i < args.count { sourceType = args[i] }
            case "--visibility":
                i += 1
                if i < args.count { visibility = args[i] }
                // Check if next arg is the mode (not a flag)
                if i + 1 < args.count && !args[i + 1].hasPrefix("-") {
                    i += 1
                    visibilityMode = args[i]
                }
            case "--spawn":
                i += 1
                if i < args.count { spawn = args[i] }
            case "--grant":
                i += 1
                if i < args.count { grant = args[i] }
            case "--revoke":
                i += 1
                if i < args.count { revoke = args[i] }
            case "--trusted":
                i += 1
                if i < args.count { trusted = args[i] }
            case "--trusted-key":
                i += 1
                if i < args.count { trustedKey = args[i] }
            case "--transfer":
                i += 1
                if i < args.count { transfer = args[i] }
            case "--to-key":
                i += 1
                if i < args.count { toKey = args[i] }
            case "--source":
                i += 1
                if i < args.count { logsSource = args[i] }
            case "--lines":
                i += 1
                if i < args.count { logsLines = Int(args[i]) }
            case "--since":
                i += 1
                if i < args.count { logsSince = args[i] }
            case "--grep":
                i += 1
                if i < args.count { logsGrep = args[i] }
            case "--follow":
                logsFollow = true
            case "session", "service", "snapshot", "image", "key", "languages", "logs", "health", "version":
                command = arg
            case "service-env":
                command = "service-env"
                i += 1
                if i < args.count { serviceEnvAction = args[i] }
                i += 1
                if i < args.count { serviceEnvId = args[i] }
            default:
                if arg.hasPrefix("-") {
                    fputs("Error: Unknown option \(arg)\n", stderr)
                    exit(2)
                } else if command == nil && (arg == "session" || arg == "service" || arg == "snapshot" || arg == "image" || arg == "key" || arg == "service-env" || arg == "languages") {
                    command = arg
                } else if source == nil {
                    source = arg
                }
            }
            i += 1
        }
    }
}

/// Handle execute command
func handleExecuteCommand(_ args: CLIArgs, _ pk: String, _ sk: String) throws {
    var language: String
    var code: String

    if let shell = args.shell {
        // Inline code mode
        guard let source = args.source else {
            fputs("Error: Code required with -s/--shell\n", stderr)
            exit(2)
        }
        language = shell
        code = source
    } else {
        // File mode
        guard let source = args.source else {
            fputs("Error: Source file required\n", stderr)
            exit(2)
        }

        // Detect language from filename
        guard let detected = detectLanguage(source) else {
            fputs("Error: Cannot detect language from '\(source)'\n", stderr)
            exit(2)
        }
        language = detected

        // Read source file
        let url = URL(fileURLWithPath: source)
        guard FileManager.default.fileExists(atPath: url.path) else {
            fputs("Error: File not found: \(source)\n", stderr)
            exit(1)
        }

        do {
            code = try String(contentsOf: url, encoding: .utf8)
        } catch {
            fputs("Error: Failed to read file: \(error)\n", stderr)
            exit(1)
        }
    }

    // Parse environment variables
    var envDict: [String: String]? = nil
    if !args.env.isEmpty {
        envDict = [:]
        for envVar in args.env {
            if let eqIndex = envVar.firstIndex(of: "=") {
                let key = String(envVar[..<eqIndex])
                let value = String(envVar[envVar.index(after: eqIndex)...])
                envDict?[key] = value
            }
        }
    }

    // Execute code
    let result = try executeCodeWithOptions(
        language: language,
        code: code,
        env: envDict,
        networkMode: args.networkMode,
        vcpu: args.vcpu,
        artifacts: args.artifacts,
        publicKey: pk,
        secretKey: sk
    )

    // Output result
    let stdout = result["stdout"] as? String ?? ""
    let stderr_out = result["stderr"] as? String ?? ""
    let exitCode = result["exit_code"] as? Int ?? 0
    let executionTime = result["execution_time_ms"] as? Int ?? 0

    if !stdout.isEmpty {
        print(stdout, terminator: stdout.hasSuffix("\n") ? "" : "\n")
    }

    if !stderr_out.isEmpty {
        fputs(stderr_out, stderr)
        if !stderr_out.hasSuffix("\n") {
            fputs("\n", stderr)
        }
    }

    print("---")
    print("Exit code: \(exitCode)")
    print("Execution time: \(executionTime)ms")

    exit(Int32(exitCode))
}

/// Handle session command
func handleSessionCommand(_ args: CLIArgs, _ pk: String, _ sk: String) throws {
    if args.listFlag {
        let sessions = try listSessions(publicKey: pk, secretKey: sk)
        print(formatListOutput(sessions, resourceType: "session"))
    } else if let attachId = args.attach {
        let session = try getSession(attachId, publicKey: pk, secretKey: sk)
        let sessionId = session["id"] as? String ?? session["session_id"] as? String ?? ""
        print("Session ID: \(sessionId)")
        print("Status: \(session["status"] as? String ?? "unknown")")
        print("WebSocket URL: wss://api.unsandbox.com/sessions/\(attachId)/shell")
        print("\nUse a WebSocket client to connect interactively.")
    } else if let killId = args.kill {
        _ = try deleteSession(killId, publicKey: pk, secretKey: sk)
        print("Session \(killId) terminated")
    } else if let freezeId = args.freeze {
        _ = try freezeSession(freezeId, publicKey: pk, secretKey: sk)
        print("Session \(freezeId) frozen")
    } else if let unfreezeId = args.unfreeze {
        _ = try unfreezeSession(unfreezeId, publicKey: pk, secretKey: sk)
        print("Session \(unfreezeId) unfrozen")
    } else if let boostId = args.boost {
        _ = try boostSession(boostId, publicKey: pk, secretKey: sk)
        print("Session \(boostId) boosted")
    } else if let unboostId = args.unboost {
        _ = try unboostSession(unboostId, publicKey: pk, secretKey: sk)
        print("Session \(unboostId) unboosted")
    } else if let snapshotId = args.snapshot {
        let snapId = try sessionSnapshot(snapshotId, name: args.snapshotName, ephemeral: !args.hot, publicKey: pk, secretKey: sk)
        print("Snapshot created: \(snapId)")
    } else {
        // Create new session
        var multiplexer: String? = nil
        if args.tmux { multiplexer = "tmux" }
        else if args.screen { multiplexer = "screen" }

        let result = try createSession(
            shell: args.shell,
            networkMode: args.networkMode,
            multiplexer: multiplexer,
            publicKey: pk,
            secretKey: sk
        )

        let sessionId = result["session_id"] as? String ?? result["id"] as? String ?? ""
        print("Session created: \(sessionId)")
        print("WebSocket URL: wss://api.unsandbox.com/sessions/\(sessionId)/shell")
    }
}

/// Handle service command
func handleServiceCommand(_ args: CLIArgs, _ pk: String, _ sk: String) throws {
    if args.listFlag {
        let services = try listServices(publicKey: pk, secretKey: sk)
        print(formatListOutput(services, resourceType: "service"))
    } else if let infoId = args.info {
        let service = try getService(infoId, publicKey: pk, secretKey: sk)
        let jsonData = try JSONSerialization.data(withJSONObject: service, options: .prettyPrinted)
        print(String(data: jsonData, encoding: .utf8) ?? "{}")
    } else if let logsId = args.logs {
        let result = try getServiceLogs(logsId, allLogs: true, publicKey: pk, secretKey: sk)
        print(result["log"] as? String ?? "")
    } else if let tailId = args.tail {
        let result = try getServiceLogs(tailId, allLogs: false, publicKey: pk, secretKey: sk)
        print(result["log"] as? String ?? "")
    } else if let freezeId = args.freeze {
        _ = try freezeService(freezeId, publicKey: pk, secretKey: sk)
        print("Service \(freezeId) frozen")
    } else if let unfreezeId = args.unfreeze {
        _ = try unfreezeService(unfreezeId, publicKey: pk, secretKey: sk)
        print("Service \(unfreezeId) unfrozen")
    } else if let destroyId = args.destroy {
        _ = try deleteService(destroyId, publicKey: pk, secretKey: sk)
        print("Service \(destroyId) destroyed")
    } else if let lockId = args.lock {
        _ = try lockService(lockId, publicKey: pk, secretKey: sk)
        print("Service \(lockId) locked")
    } else if let unlockId = args.unlock {
        _ = try unlockService(unlockId, publicKey: pk, secretKey: sk)
        print("Service \(unlockId) unlocked")
    } else if let resizeId = args.resize {
        _ = try updateService(resizeId, vcpu: args.vcpu, publicKey: pk, secretKey: sk)
        print("Service \(resizeId) resized to \(args.vcpu) vCPU(s)")
    } else if let redeployId = args.redeploy {
        var bootstrapContent: String? = nil
        if let bootstrapFile = args.bootstrapFile {
            bootstrapContent = try String(contentsOfFile: bootstrapFile, encoding: .utf8)
        } else if let bootstrap = args.bootstrap {
            bootstrapContent = bootstrap
        }
        _ = try redeployService(redeployId, bootstrap: bootstrapContent, publicKey: pk, secretKey: sk)
        print("Service \(redeployId) redeployed")
    } else if let (serviceId, command) = args.execute {
        let result = try executeInService(serviceId, command: command, publicKey: pk, secretKey: sk)
        if let jobId = result["job_id"] as? String {
            let jobResult = try waitForJob(jobId, publicKey: pk, secretKey: sk)
            let stdout = jobResult["stdout"] as? String ?? ""
            let stderr_out = jobResult["stderr"] as? String ?? ""
            if !stdout.isEmpty { print(stdout, terminator: "") }
            if !stderr_out.isEmpty { fputs(stderr_out, stderr) }
        } else {
            let stdout = result["stdout"] as? String ?? ""
            let stderr_out = result["stderr"] as? String ?? ""
            if !stdout.isEmpty { print(stdout, terminator: "") }
            if !stderr_out.isEmpty { fputs(stderr_out, stderr) }
        }
    } else if let snapshotId = args.snapshot {
        let snapId = try serviceSnapshot(snapshotId, name: args.snapshotName, publicKey: pk, secretKey: sk)
        print("Snapshot created: \(snapId)")
    } else if let name = args.name {
        // Create new service
        guard let portsStr = args.ports else {
            fputs("Error: --ports required when creating service\n", stderr)
            exit(2)
        }

        let ports = portsStr.components(separatedBy: ",").compactMap { Int($0.trimmingCharacters(in: .whitespaces)) }

        var bootstrapContent: String? = nil
        if let bootstrapFile = args.bootstrapFile {
            bootstrapContent = try String(contentsOfFile: bootstrapFile, encoding: .utf8)
        } else if let bootstrap = args.bootstrap {
            bootstrapContent = bootstrap
        }

        var customDomains: [String]? = nil
        if let domains = args.domains {
            customDomains = domains.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
        }

        let result = try createService(
            name: name,
            ports: ports,
            bootstrap: bootstrapContent,
            customDomains: customDomains,
            vcpu: args.vcpu,
            serviceType: args.serviceType,
            publicKey: pk,
            secretKey: sk
        )

        let serviceId = result["service_id"] as? String ?? result["id"] as? String ?? ""
        print("Service created: \(serviceId)")
        print("URL: https://\(name).on.unsandbox.com")
    } else {
        fputs("Error: No action specified for service command\n", stderr)
        exit(2)
    }
}

/// Handle service-env command
func handleServiceEnvCommand(_ args: CLIArgs, _ pk: String, _ sk: String) throws {
    guard let action = args.serviceEnvAction, let serviceId = args.serviceEnvId else {
        fputs("Error: service-env requires action and service ID\n", stderr)
        exit(2)
    }

    switch action {
    case "status":
        let result = try getServiceEnv(serviceId, publicKey: pk, secretKey: sk)
        print("Has vault: \(result["has_vault"] as? Bool ?? false)")
        print("Variable count: \(result["count"] as? Int ?? 0)")
        if let updatedAt = result["updated_at"] as? String {
            print("Updated at: \(updatedAt)")
        }
    case "set":
        var envDict: [String: String]
        if let envFile = args.envFile {
            envDict = try parseEnvFile(envFile)
        } else {
            // Read from stdin
            fputs("Enter environment variables (KEY=VALUE), one per line. Ctrl+D to finish:\n", stderr)
            envDict = [:]
            while let line = readLine() {
                let trimmed = line.trimmingCharacters(in: .whitespaces)
                if !trimmed.isEmpty, let eqIndex = trimmed.firstIndex(of: "=") {
                    let key = String(trimmed[..<eqIndex]).trimmingCharacters(in: .whitespaces)
                    let value = String(trimmed[trimmed.index(after: eqIndex)...]).trimmingCharacters(in: .whitespaces)
                    envDict[key] = value
                }
            }
        }
        let result = try setServiceEnv(serviceId, envDict: envDict, publicKey: pk, secretKey: sk)
        print("Environment set: \(result["count"] as? Int ?? envDict.count) variables")
    case "export":
        let result = try exportServiceEnv(serviceId, publicKey: pk, secretKey: sk)
        print(result["env"] as? String ?? "")
    case "delete":
        _ = try deleteServiceEnv(serviceId, publicKey: pk, secretKey: sk)
        print("Environment vault deleted for service \(serviceId)")
    default:
        fputs("Error: Unknown service-env action: \(action)\n", stderr)
        exit(2)
    }
}

/// Handle snapshot command
func handleSnapshotCommand(_ args: CLIArgs, _ pk: String, _ sk: String) throws {
    if args.listFlag {
        let snapshots = try listSnapshots(publicKey: pk, secretKey: sk)
        print(formatListOutput(snapshots, resourceType: "snapshot"))
    } else if let infoId = args.info {
        let snapshots = try listSnapshots(publicKey: pk, secretKey: sk)
        if let snapshot = snapshots.first(where: { ($0["id"] as? String) == infoId || ($0["snapshot_id"] as? String) == infoId }) {
            let jsonData = try JSONSerialization.data(withJSONObject: snapshot, options: .prettyPrinted)
            print(String(data: jsonData, encoding: .utf8) ?? "{}")
        } else {
            fputs("Error: Snapshot \(infoId) not found\n", stderr)
            exit(1)
        }
    } else if let deleteId = args.delete {
        _ = try deleteSnapshot(deleteId, publicKey: pk, secretKey: sk)
        print("Snapshot \(deleteId) deleted")
    } else if let lockId = args.lock {
        _ = try lockSnapshot(lockId, publicKey: pk, secretKey: sk)
        print("Snapshot \(lockId) locked")
    } else if let unlockId = args.unlock {
        _ = try unlockSnapshot(unlockId, publicKey: pk, secretKey: sk)
        print("Snapshot \(unlockId) unlocked")
    } else if let cloneId = args.clone {
        let cloneType = args.cloneType ?? "session"
        var ports: [Int]? = nil
        if let portsStr = args.ports {
            ports = portsStr.components(separatedBy: ",").compactMap { Int($0.trimmingCharacters(in: .whitespaces)) }
        }

        let result = try cloneSnapshot(
            cloneId,
            cloneType: cloneType,
            name: args.name,
            shell: args.shell,
            ports: ports,
            publicKey: pk,
            secretKey: sk
        )

        if cloneType == "session" {
            print("Session created: \(result["session_id"] as? String ?? result["id"] as? String ?? "")")
        } else {
            print("Service created: \(result["service_id"] as? String ?? result["id"] as? String ?? "")")
        }
    } else {
        fputs("Error: No action specified for snapshot command\n", stderr)
        exit(2)
    }
}

/// Handle image command
func handleImageCommand(_ args: CLIArgs, _ pk: String, _ sk: String) throws {
    if args.listFlag {
        let response = try listImages(publicKey: pk, secretKey: sk)
        // Extract images array from response
        let images = response["images"] as? [[String: Any]] ?? []
        print(formatImageListOutput(images))
    } else if let infoId = args.info {
        let image = try getImage(infoId, publicKey: pk, secretKey: sk)
        let jsonData = try JSONSerialization.data(withJSONObject: image, options: .prettyPrinted)
        print(String(data: jsonData, encoding: .utf8) ?? "{}")
    } else if let deleteId = args.delete {
        _ = try deleteImage(deleteId, publicKey: pk, secretKey: sk)
        print("Image \(deleteId) deleted")
    } else if let lockId = args.lock {
        _ = try lockImage(lockId, publicKey: pk, secretKey: sk)
        print("Image \(lockId) locked")
    } else if let unlockId = args.unlock {
        _ = try unlockImage(unlockId, publicKey: pk, secretKey: sk)
        print("Image \(unlockId) unlocked")
    } else if let publishId = args.publish {
        guard let sourceType = args.sourceType else {
            fputs("Error: --source-type required for --publish\n", stderr)
            exit(2)
        }
        let result = try imagePublish(
            sourceType: sourceType,
            sourceId: publishId,
            name: args.name,
            publicKey: pk,
            secretKey: sk
        )
        let imageId = result["image_id"] as? String ?? result["id"] as? String ?? ""
        print("Image published: \(imageId)")
    } else if let visId = args.visibility, let mode = args.visibilityMode {
        if mode != "private" && mode != "unlisted" && mode != "public" {
            fputs("Error: visibility must be private, unlisted, or public\n", stderr)
            exit(2)
        }
        _ = try setImageVisibility(visId, visibility: mode, publicKey: pk, secretKey: sk)
        print("Image \(visId) visibility set to \(mode)")
    } else if let spawnId = args.spawn {
        guard let name = args.name else {
            fputs("Error: --name required for --spawn\n", stderr)
            exit(2)
        }
        var ports: [Int]? = nil
        if let portsStr = args.ports {
            ports = portsStr.components(separatedBy: ",").compactMap { Int($0.trimmingCharacters(in: .whitespaces)) }
        }
        let result = try spawnFromImage(
            spawnId,
            name: name,
            ports: ports,
            publicKey: pk,
            secretKey: sk
        )
        let serviceId = result["service_id"] as? String ?? result["id"] as? String ?? ""
        print("Service spawned: \(serviceId)")
    } else if let cloneId = args.clone {
        let result = try cloneImage(cloneId, name: args.name, publicKey: pk, secretKey: sk)
        let imageId = result["image_id"] as? String ?? result["id"] as? String ?? ""
        print("Image cloned: \(imageId)")
    } else if let grantId = args.grant {
        guard let trustedKey = args.trustedKey else {
            fputs("Error: --trusted-key required for --grant\n", stderr)
            exit(2)
        }
        _ = try grantImageAccess(grantId, trustedApiKey: trustedKey, publicKey: pk, secretKey: sk)
        print("Access granted to \(trustedKey)")
    } else if let revokeId = args.revoke {
        guard let trustedKey = args.trustedKey else {
            fputs("Error: --trusted-key required for --revoke\n", stderr)
            exit(2)
        }
        _ = try revokeImageAccess(revokeId, trustedApiKey: trustedKey, publicKey: pk, secretKey: sk)
        print("Access revoked from \(trustedKey)")
    } else if let trustedId = args.trusted {
        let result = try listImageTrusted(trustedId, publicKey: pk, secretKey: sk)
        let jsonData = try JSONSerialization.data(withJSONObject: result, options: .prettyPrinted)
        print(String(data: jsonData, encoding: .utf8) ?? "{}")
    } else if let transferId = args.transfer {
        guard let toKey = args.toKey else {
            fputs("Error: --to-key required for --transfer\n", stderr)
            exit(2)
        }
        _ = try transferImage(transferId, toApiKey: toKey, publicKey: pk, secretKey: sk)
        print("Image transferred to \(toKey)")
    } else {
        fputs("Error: No action specified for image command\n", stderr)
        exit(2)
    }
}

/// Handle logs command
func handleLogsCommand(_ args: CLIArgs, _ pk: String, _ sk: String) throws {
    let source = args.logsSource ?? "all"
    let lines = args.logsLines ?? 100
    let since = args.logsSince ?? "1h"
    let grep = args.logsGrep

    let result = try fetchLogs(source: source, lines: lines, since: since, grep: grep, publicKey: pk, secretKey: sk)

    if let logs = result["logs"] as? [[String: Any]] {
        for log in logs {
            let src = log["source"] as? String ?? "unknown"
            let line = log["line"] as? String ?? ""
            let timestamp = log["timestamp"] as? String ?? ""
            if timestamp.isEmpty {
                print("[\(src)] \(line)")
            } else {
                print("[\(timestamp)] [\(src)] \(line)")
            }
        }
    } else {
        let jsonData = try JSONSerialization.data(withJSONObject: result, options: .prettyPrinted)
        print(String(data: jsonData, encoding: .utf8) ?? "{}")
    }
}

/// Handle health command
func handleHealthCommand() throws {
    let result = try healthCheck()
    if result["status"] as? String == "healthy" || result["ok"] as? Bool == true {
        print("\u{001B}[32mAPI is healthy\u{001B}[0m")
    } else {
        print("\u{001B}[31mAPI may be unhealthy\u{001B}[0m")
    }
    let jsonData = try JSONSerialization.data(withJSONObject: result, options: .prettyPrinted)
    print(String(data: jsonData, encoding: .utf8) ?? "{}")
}

/// Handle version command
func handleVersionCommand() {
    let info = getVersion()
    print("un.swift version \(info["version"] ?? "unknown")")
    print("API: \(info["api"] ?? "unknown")")
    print("Portal: \(info["portal"] ?? "unknown")")
}

/// Format image list output
func formatImageListOutput(_ images: [[String: Any]]) -> String {
    if images.isEmpty {
        return "No images found."
    }

    var lines: [String] = []
    lines.append(String(format: "%-38s  %-20s  %-10s  %-10s  %s", "ID", "NAME", "VISIBILITY", "SOURCE", "CREATED"))

    for image in images {
        let id = (image["image_id"] as? String ?? image["id"] as? String ?? "-").prefix(38)
        let name = (image["name"] as? String ?? "-").prefix(20)
        let visibility = (image["visibility"] as? String ?? "private").prefix(10)
        let sourceType = (image["source_type"] as? String ?? "-").prefix(10)
        let createdAt = (image["created_at"] as? String ?? "-").prefix(19)
        lines.append(String(format: "%-38s  %-20s  %-10s  %-10s  %s", String(id), String(name), String(visibility), String(sourceType), String(createdAt)))
    }

    return lines.joined(separator: "\n")
}

/// Handle key command
func handleKeyCommand(_ pk: String, _ sk: String) throws {
    let result = try validateKeys(publicKey: pk, secretKey: sk)

    print("Public key: \(pk)")
    print("Valid: \(result["valid"] as? Bool ?? false)")
    if let tier = result["tier"] as? String {
        print("Tier: \(tier)")
    }
    if let expiresAt = result["expires_at"] as? String {
        print("Expires: \(expiresAt)")
    }
    if let reason = result["reason"] as? String {
        print("Reason: \(reason)")
    }
}

/// Handle languages command
func handleLanguagesCommand(_ args: CLIArgs, _ pk: String, _ sk: String) throws {
    let languages = try getLanguages(publicKey: pk, secretKey: sk)

    if args.jsonOutput {
        // Output as JSON array
        if let jsonData = try? JSONSerialization.data(withJSONObject: languages),
           let jsonString = String(data: jsonData, encoding: .utf8) {
            print(jsonString)
        }
    } else {
        // Output one language per line (pipe-friendly)
        for lang in languages {
            print(lang)
        }
    }
}

/// Main CLI entry point
@main
struct UnCLI {
    static func main() {
        let args = CLIArgs()
        args.parse(CommandLine.arguments)

        // Resolve credentials
        let pk: String
        let sk: String
        do {
            (pk, sk) = try resolveCredentials(publicKey: args.publicKey, secretKey: args.secretKey, accountIndex: args.accountIndex)
        } catch {
            fputs("Error: \(error)\n", stderr)
            exit(3)
        }

        do {
            // Handle subcommands
            switch args.command {
            case "session":
                try handleSessionCommand(args, pk, sk)
            case "service":
                try handleServiceCommand(args, pk, sk)
            case "service-env":
                try handleServiceEnvCommand(args, pk, sk)
            case "snapshot":
                try handleSnapshotCommand(args, pk, sk)
            case "image":
                try handleImageCommand(args, pk, sk)
            case "key":
                try handleKeyCommand(pk, sk)
            case "languages":
                try handleLanguagesCommand(args, pk, sk)
            case "logs":
                try handleLogsCommand(args, pk, sk)
            case "health":
                try handleHealthCommand()
            case "version":
                handleVersionCommand()
            default:
                if args.source != nil || args.shell != nil {
                    try handleExecuteCommand(args, pk, sk)
                } else {
                    printHelp()
                    exit(2)
                }
            }
        } catch let error as UnsandboxError {
            switch error {
            case .credentialsNotFound:
                fputs("Error: \(error)\n", stderr)
                exit(3)
            case .apiError(let code, _) where code == 401:
                fputs("Error: Authentication failed\n", stderr)
                exit(3)
            case .apiError:
                fputs("Error: API error - \(error)\n", stderr)
                exit(4)
            case .networkError:
                fputs("Error: Network error - \(error)\n", stderr)
                exit(1)
            case .timeout:
                fputs("Error: \(error)\n", stderr)
                exit(5)
            default:
                fputs("Error: \(error)\n", stderr)
                exit(1)
            }
        } catch {
            fputs("Error: \(error)\n", stderr)
            exit(1)
        }
    }
}

Documentation clarifications

Dependencies

C Binary (un1) — requires libcurl and libwebsockets:

sudo apt install build-essential libcurl4-openssl-dev libwebsockets-dev
wget unsandbox.com/downloads/un.c && gcc -O2 -o un un.c -lcurl -lwebsockets

SDK Implementations — most use stdlib only (Ruby, JS, Go, etc). Some require minimal deps:

pip install requests  # Python

Execute Code

Run a Script

./un hello.py
./un app.js
./un main.rs

With Environment Variables

./un -e DEBUG=1 -e NAME=World script.py

With Input Files (teleport files into sandbox)

./un -f data.csv -f config.json process.py

Get Compiled Binary (teleport artifacts out)

./un -a -o ./bin main.c

Interactive Sessions

Start a Shell Session

# Default bash shell
./un session

# Choose your shell
./un session --shell zsh
./un session --shell fish

# Jump into a REPL
./un session --shell python3
./un session --shell node
./un session --shell julia

Session with Network Access

./un session -n semitrusted

Session Auditing (full terminal recording)

# Record everything (including vim, interactive programs)
./un session --audit -o ./logs

# Replay session later
zcat session.log*.gz | less -R

Collect Artifacts from Session

# Files in /tmp/artifacts/ are collected on exit
./un session -a -o ./outputs

Session Persistence (tmux/screen)

# Default: session terminates on disconnect (clean exit)
./un session

# With tmux: session persists, can reconnect later
./un session --tmux
# Press Ctrl+b then d to detach

# With screen: alternative multiplexer
./un session --screen
# Press Ctrl+a then d to detach

List Active Sessions

./un session --list

# Output:
# Active sessions: 2
#
# SESSION ID                               CONTAINER            SHELL      TTL      STATUS
# abc123...                                unsb-vm-12345        python3    45m30s   active
# def456...                                unsb-vm-67890        bash       1h2m     active

Reconnect to Existing Session

# Reconnect by container name (requires --tmux or --screen)
./un session --attach unsb-vm-12345

# Use exit to terminate session, or detach to keep it running

Terminate a Session

./un session --kill unsb-vm-12345

Available Shells & REPLs

Shells: bash, dash, sh, zsh, fish, ksh, tcsh, csh, elvish, xonsh, ash

REPLs:  python3, bpython, ipython    # Python
        node                          # JavaScript
        ruby, irb                     # Ruby
        lua                           # Lua
        php                           # PHP
        perl                          # Perl
        guile, scheme                 # Scheme
        ghci                          # Haskell
        erl, iex                      # Erlang/Elixir
        sbcl, clisp                   # Common Lisp
        r                             # R
        julia                         # Julia
        clojure                       # Clojure

API Key Management

Check Key Status

# Check if your API key is valid
./un key

# Output:
# Valid: key expires in 30 days

Extend Expired Key

# Open the portal to extend an expired key
./un key --extend

# This opens the unsandbox.com portal where you can
# add more credits to extend your key's expiration

Authentication

Credentials are loaded in priority order (highest first):

# 1. CLI flags (highest priority)
./un -p unsb-pk-xxxx -k unsb-sk-xxxxx script.py

# 2. Environment variables
export UNSANDBOX_PUBLIC_KEY=unsb-pk-xxxx-xxxx-xxxx-xxxx
export UNSANDBOX_SECRET_KEY=unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx
./un script.py

# 3. Config file (lowest priority)
# ~/.unsandbox/accounts.csv format: public_key,secret_key
mkdir -p ~/.unsandbox
echo "unsb-pk-xxxx-xxxx-xxxx-xxxx,unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx" > ~/.unsandbox/accounts.csv
./un script.py

Requests are signed with HMAC-SHA256. The bearer token contains only the public key; the secret key computes the signature (never transmitted).

Resource Scaling

Set vCPU Count

# Default: 1 vCPU, 2GB RAM
./un script.py

# Scale up: 4 vCPUs, 8GB RAM
./un -v 4 script.py

# Maximum: 8 vCPUs, 16GB RAM
./un --vcpu 8 heavy_compute.py

Live Session Boosting

# Boost a running session to 2 vCPU, 4GB RAM
./un session --boost sandbox-abc

# Boost to specific vCPU count (4 vCPU, 8GB RAM)
./un session --boost sandbox-abc --boost-vcpu 4

# Return to base resources (1 vCPU, 2GB RAM)
./un session --unboost sandbox-abc

Session Freeze/Unfreeze

Freeze and Unfreeze Sessions

# Freeze a session (stop billing, preserve state)
./un session --freeze sandbox-abc

# Unfreeze a frozen session
./un session --unfreeze sandbox-abc

# Note: Requires --tmux or --screen for persistence

Persistent Services

Create a Service

# Web server with ports
./un service --name web --ports 80,443 --bootstrap "python -m http.server 80"

# With custom domains
./un service --name blog --ports 8000 --domains blog.example.com

# Game server with SRV records
./un service --name mc --type minecraft --bootstrap ./setup.sh

# Deploy app tarball with bootstrap script
./un service --name app --ports 8000 -f app.tar.gz --bootstrap-file ./setup.sh
# setup.sh: cd /tmp && tar xzf app.tar.gz && ./app/start.sh

Manage Services

# List all services
./un service --list

# Get service details
./un service --info abc123

# View bootstrap logs
./un service --logs abc123
./un service --tail abc123  # last 9000 lines

# Execute command in running service
./un service --execute abc123 'journalctl -u myapp -n 50'

# Dump bootstrap script (for migrations)
./un service --dump-bootstrap abc123
./un service --dump-bootstrap abc123 backup.sh

# Freeze/unfreeze service
./un service --freeze abc123
./un service --unfreeze abc123

# Service settings (auto-wake, freeze page display)
./un service --auto-unfreeze abc123      # enable auto-wake on HTTP
./un service --no-auto-unfreeze abc123   # disable auto-wake
./un service --show-freeze-page abc123   # show HTML payment page (default)
./un service --no-show-freeze-page abc123  # return JSON error instead

# Redeploy with new bootstrap
./un service --redeploy abc123 --bootstrap ./new-setup.sh

# Destroy service
./un service --destroy abc123

Snapshots

List Snapshots

./un snapshot --list

# Output:
# Snapshots: 3
#
# SNAPSHOT ID                              NAME             SOURCE     SIZE     CREATED
# unsb-snapshot-a1b2-c3d4-e5f6-g7h8        before-upgrade   session    512 MB   2h ago
# unsb-snapshot-i9j0-k1l2-m3n4-o5p6        stable-v1.0      service    1.2 GB   1d ago

Create Session Snapshot

# Snapshot with name
./un session --snapshot unsb-vm-12345 --name "before upgrade"

# Quick snapshot (auto-generated name)
./un session --snapshot unsb-vm-12345

Create Service Snapshot

# Standard snapshot (pauses container briefly)
./un service --snapshot unsb-service-abc123 --name "stable v1.0"

# Hot snapshot (no pause, may be inconsistent)
./un service --snapshot unsb-service-abc123 --hot

Restore from Snapshot

# Restore session from snapshot
./un session --restore unsb-snapshot-a1b2-c3d4-e5f6-g7h8

# Restore service from snapshot
./un service --restore unsb-snapshot-i9j0-k1l2-m3n4-o5p6

Delete Snapshot

./un snapshot --delete unsb-snapshot-a1b2-c3d4-e5f6-g7h8

Images

Images are independent, transferable container images that survive container deletion. Unlike snapshots (which live with their container), images can be shared with other users, transferred between API keys, or made public in the marketplace.

List Images

# List all images (owned + shared + public)
./un image --list

# List only your images
./un image --list owned

# List images shared with you
./un image --list shared

# List public marketplace images
./un image --list public

# Get image details
./un image --info unsb-image-xxxx-xxxx-xxxx-xxxx

Publish Images

# Publish from a stopped or frozen service
./un image --publish-service unsb-service-abc123 \
   --name "My App v1.0" --description "Production snapshot"

# Publish from a snapshot
./un image --publish-snapshot unsb-snapshot-xxxx-xxxx-xxxx-xxxx \
   --name "Stable Release"

# Note: Cannot publish from running containers - stop or freeze first

Create Services from Images

# Spawn a new service from an image
./un image --spawn unsb-image-xxxx-xxxx-xxxx-xxxx \
   --name new-service --ports 80,443

# Clone an image (creates a copy you own)
./un image --clone unsb-image-xxxx-xxxx-xxxx-xxxx

Image Protection

# Lock image to prevent accidental deletion
./un image --lock unsb-image-xxxx-xxxx-xxxx-xxxx

# Unlock image to allow deletion
./un image --unlock unsb-image-xxxx-xxxx-xxxx-xxxx

# Delete image (must be unlocked)
./un image --delete unsb-image-xxxx-xxxx-xxxx-xxxx

Visibility & Sharing

# Set visibility level
./un image --visibility unsb-image-xxxx-xxxx-xxxx-xxxx private   # owner only (default)
./un image --visibility unsb-image-xxxx-xxxx-xxxx-xxxx unlisted  # can be shared
./un image --visibility unsb-image-xxxx-xxxx-xxxx-xxxx public    # marketplace

# Share with specific user
./un image --grant unsb-image-xxxx-xxxx-xxxx-xxxx \
   --key unsb-pk-friend-friend-friend-friend

# Revoke access
./un image --revoke unsb-image-xxxx-xxxx-xxxx-xxxx \
   --key unsb-pk-friend-friend-friend-friend

# List who has access
./un image --trusted unsb-image-xxxx-xxxx-xxxx-xxxx

Transfer Ownership

# Transfer image to another API key
./un image --transfer unsb-image-xxxx-xxxx-xxxx-xxxx \
   --to unsb-pk-newowner-newowner-newowner-newowner

Usage Reference

Usage: ./un [options] <source_file>
       ./un session [options]
       ./un service [options]
       ./un snapshot [options]
       ./un image [options]
       ./un key

Commands:
  (default)        Execute source file in sandbox
  session          Open interactive shell/REPL session
  service          Manage persistent services
  snapshot         Manage container snapshots
  image            Manage container images (publish, share, transfer)
  key              Check API key validity and expiration

Options:
  -e KEY=VALUE     Set environment variable (can use multiple times)
  -f FILE          Add input file (can use multiple times)
  -a               Return and save artifacts from /tmp/artifacts/
  -o DIR           Output directory for artifacts (default: current dir)
  -p KEY           Public key (or set UNSANDBOX_PUBLIC_KEY env var)
  -k KEY           Secret key (or set UNSANDBOX_SECRET_KEY env var)
  -n MODE          Network mode: zerotrust (default) or semitrusted
  -v N, --vcpu N   vCPU count 1-8, each vCPU gets 2GB RAM (default: 1)
  -y               Skip confirmation for large uploads (>1GB)
  -h               Show this help

Authentication (priority order):
  1. -p and -k flags (public and secret key)
  2. UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars
  3. ~/.unsandbox/accounts.csv (format: public_key,secret_key per line)

Session options:
  -s, --shell SHELL  Shell/REPL to use (default: bash)
  -l, --list         List active sessions
  --attach ID        Reconnect to existing session (ID or container name)
  --kill ID          Terminate a session (ID or container name)
  --freeze ID        Freeze a session (requires --tmux/--screen)
  --unfreeze ID      Unfreeze a frozen session
  --boost ID         Boost session resources (2 vCPU, 4GB RAM)
  --boost-vcpu N     Specify vCPU count for boost (1-8)
  --unboost ID       Return to base resources
  --audit            Record full session for auditing
  --tmux             Enable session persistence with tmux (allows reconnect)
  --screen           Enable session persistence with screen (allows reconnect)

Service options:
  --name NAME        Service name (creates new service)
  --ports PORTS      Comma-separated ports (e.g., 80,443)
  --domains DOMAINS  Custom domains (e.g., example.com,www.example.com)
  --type TYPE        Service type: minecraft, mumble, teamspeak, source, tcp, udp
  --bootstrap CMD    Bootstrap command/file/URL to run on startup
  -f FILE            Upload file to /tmp/ (can use multiple times)
  -l, --list         List all services
  --info ID          Get service details
  --tail ID          Get last 9000 lines of bootstrap logs
  --logs ID          Get all bootstrap logs
  --freeze ID        Freeze a service
  --unfreeze ID      Unfreeze a service
  --auto-unfreeze ID       Enable auto-wake on HTTP request
  --no-auto-unfreeze ID    Disable auto-wake on HTTP request
  --show-freeze-page ID    Show HTML payment page when frozen (default)
  --no-show-freeze-page ID Return JSON error when frozen
  --destroy ID       Destroy a service
  --redeploy ID      Re-run bootstrap script (requires --bootstrap)
  --execute ID CMD   Run a command in a running service
  --dump-bootstrap ID [FILE]  Dump bootstrap script (for migrations)
  --snapshot ID    Create snapshot of session or service
  --snapshot-name  User-friendly name for snapshot
  --hot            Create snapshot without pausing (may be inconsistent)
  --restore ID     Restore session/service from snapshot ID

Snapshot options:
  -l, --list       List all snapshots
  --info ID        Get snapshot details
  --delete ID      Delete a snapshot permanently

Image options:
  -l, --list [owned|shared|public]  List images (all, owned, shared, or public)
  --info ID        Get image details
  --publish-service ID   Publish image from stopped/frozen service
  --publish-snapshot ID  Publish image from snapshot
  --name NAME      Name for published image
  --description DESC  Description for published image
  --delete ID      Delete image (must be unlocked)
  --clone ID       Clone image (creates copy you own)
  --spawn ID       Create service from image (requires --name)
  --lock ID        Lock image to prevent deletion
  --unlock ID      Unlock image to allow deletion
  --visibility ID LEVEL  Set visibility (private|unlisted|public)
  --grant ID --key KEY   Grant access to another API key
  --revoke ID --key KEY  Revoke access from API key
  --transfer ID --to KEY Transfer ownership to API key
  --trusted ID     List API keys with access

Key options:
  (no options)       Check API key validity
  --extend           Open portal to extend an expired key

Examples:
  ./un script.py                       # execute Python script
  ./un -e DEBUG=1 script.py            # with environment variable
  ./un -f data.csv process.py          # with input file
  ./un -a -o ./bin main.c              # save compiled artifacts
  ./un -v 4 heavy.py                   # with 4 vCPUs, 8GB RAM
  ./un session                         # interactive bash session
  ./un session --tmux                  # bash with reconnect support
  ./un session --list                  # list active sessions
  ./un session --attach unsb-vm-12345  # reconnect to session
  ./un session --kill unsb-vm-12345    # terminate a session
  ./un session --freeze unsb-vm-12345  # freeze session
  ./un session --unfreeze unsb-vm-12345  # unfreeze session
  ./un session --boost unsb-vm-12345   # boost resources
  ./un session --unboost unsb-vm-12345 # return to base
  ./un session --shell python3         # Python REPL
  ./un session --shell node            # Node.js REPL
  ./un session -n semitrusted          # session with network access
  ./un session --audit -o ./logs       # record session for auditing
  ./un service --name web --ports 80   # create web service
  ./un service --list                  # list all services
  ./un service --logs abc123           # view bootstrap logs
  ./un key                             # check API key
  ./un key --extend                    # extend expired key
  ./un snapshot --list                 # list all snapshots
  ./un session --snapshot unsb-vm-123  # snapshot a session
  ./un service --snapshot abc123       # snapshot a service
  ./un session --restore unsb-snapshot-xxxx  # restore from snapshot
  ./un image --list                  # list all images
  ./un image --list owned            # list your images
  ./un image --publish-service abc   # publish image from service
  ./un image --spawn img123 --name x # create service from image
  ./un image --grant img --key pk    # share image with user

CLI Inception

The UN CLI has been implemented in 42 programming languages, demonstrating that the unsandbox API can be accessed from virtually any environment.

View All 42 Implementations →

License

PUBLIC DOMAIN - NO LICENSE, NO WARRANTY

This is free public domain software for the public good of a permacomputer hosted
at permacomputer.com - an always-on computer by the people, for the people. One
that is durable, easy to repair, and distributed like tap water for machine
learning intelligence.

The permacomputer is community-owned infrastructure optimized around four values:

  TRUTH    - First principles, math & science, open source code freely distributed
  FREEDOM  - Voluntary partnerships, freedom from tyranny & corporate control
  HARMONY  - Minimal waste, self-renewing systems with diverse thriving connections
  LOVE     - Be yourself without hurting others, cooperation through natural law

This software contributes to that vision by enabling code execution across all 42
programming languages through a unified interface, accessible to everyone. Code is
seeds to sprout on any abandoned technology.

Learn more: https://www.permacomputer.com

Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.

NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.

That said, our permacomputer's digital membrane stratum continuously runs unit,
integration, and functional tests on all its own software - with our permacomputer
monitoring itself, repairing itself, with minimal human guidance in the loop.
Our agents do their best.

Copyright 2025 TimeHexOn & foxhop & russell@unturf
https://www.timehexon.com
https://www.foxhop.net
https://www.unturf.com/software

Export Vault

Enter a password to encrypt your exported vault. You'll need this password to import the vault on another device.

Import Vault

Select an exported vault file and enter the export password to decrypt it.