Skip to main content
Glama
InProcessCommandRunner.swift5 kB
import ArgumentParser import Foundation @testable import PeekabooCLI import PeekabooCore struct CommandRunResult { let stdout: String let stderr: String let exitStatus: Int32 var combinedOutput: String { self.stdout.isEmpty ? self.stderr : self.stdout } func validateExitStatus(allowedExitCodes: Set<Int32>, arguments: [String]) throws { guard allowedExitCodes.contains(self.exitStatus) else { throw CommandExecutionError( status: self.exitStatus, stdout: self.stdout, stderr: self.stderr, arguments: arguments ) } } } struct CommandExecutionError: Error, CustomStringConvertible { let status: Int32 let stdout: String let stderr: String let arguments: [String] var description: String { "Command \(self.arguments.joined(separator: " ")) failed with exit code \(self.status)." + "\nstdout: \(self.stdout)\nstderr: \(self.stderr)" } } enum InProcessCommandRunner { static func run( _ arguments: [String], services: PeekabooServices, spaceService: SpaceCommandSpaceService? = nil ) async throws -> CommandRunResult { try await PeekabooServices.withTestServices(services) { if let spaceService { return try await SpaceCommandEnvironment.withSpaceService(spaceService) { try await self.execute(arguments: arguments) } } else { return try await self.execute(arguments: arguments) } } } /// Run the CLI using the default shared services (no overrides). static func runWithSharedServices(_ arguments: [String]) async throws -> CommandRunResult { try await self.execute(arguments: arguments) } /// Convenience helper for tests that rely on the shared service stack and expect specific exit codes. static func runShared( _ arguments: [String], allowedExitCodes: Set<Int32> = [0] ) async throws -> CommandRunResult { let result = try await self.runWithSharedServices(arguments) try result.validateExitStatus(allowedExitCodes: allowedExitCodes, arguments: arguments) return result } private static func execute(arguments: [String]) async throws -> CommandRunResult { try await self.captureOutput { var exitStatus: Int32 = 0 var stdoutData = Data() var stderrData = Data() let result: (Int32, Data, Data) = try await self.redirectOutput { do { var command = try Peekaboo.parseAsRoot(arguments) try await command.run() return 0 } catch let exit as ExitCode { return exit.rawValue } } exitStatus = result.0 stdoutData = result.1 stderrData = result.2 let stdout = String(data: stdoutData, encoding: .utf8) ?? "" let stderr = String(data: stderrData, encoding: .utf8) ?? "" return CommandRunResult(stdout: stdout, stderr: stderr, exitStatus: exitStatus) } } private static func captureOutput( _ operation: () async throws -> CommandRunResult ) async throws -> CommandRunResult { try await operation() } private static func redirectOutput( _ body: () async throws -> Int32 ) async throws -> (Int32, Data, Data) { let stdoutPipe = Pipe() let stderrPipe = Pipe() let originalStdout = dup(STDOUT_FILENO) let originalStderr = dup(STDERR_FILENO) dup2(stdoutPipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO) dup2(stderrPipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO) stdoutPipe.fileHandleForWriting.closeFile() stderrPipe.fileHandleForWriting.closeFile() do { let status = try await body() let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() stdoutPipe.fileHandleForReading.closeFile() stderrPipe.fileHandleForReading.closeFile() dup2(originalStdout, STDOUT_FILENO) dup2(originalStderr, STDERR_FILENO) close(originalStdout) close(originalStderr) return (status, stdoutData, stderrData) } catch { _ = stdoutPipe.fileHandleForReading.readDataToEndOfFile() _ = stderrPipe.fileHandleForReading.readDataToEndOfFile() stdoutPipe.fileHandleForReading.closeFile() stderrPipe.fileHandleForReading.closeFile() dup2(originalStdout, STDOUT_FILENO) dup2(originalStderr, STDERR_FILENO) close(originalStdout) close(originalStderr) throw error } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/steipete/Peekaboo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server