Skip to main content
Glama
RunCommand.swift5.56 kB
import Commander import Foundation import PeekabooCore @available(macOS 14.0, *) @MainActor struct RunCommand: OutputFormattable { nonisolated(unsafe) static var commandDescription: CommandDescription { MainActorCommandDescription.describe { CommandDescription( commandName: "run", abstract: "Execute a Peekaboo automation script", showHelpOnEmptyInvocation: true ) } } @Argument(help: "Path to the script file (.peekaboo.json)") var scriptPath: String @Option(help: "Save results to file instead of stdout") var output: String? @Flag(help: "Continue execution even if a step fails") var noFailFast = false @RuntimeStorage private var runtime: CommandRuntime? private var resolvedRuntime: CommandRuntime { guard let runtime else { preconditionFailure("CommandRuntime must be configured before accessing runtime resources") } return runtime } private var services: any PeekabooServiceProviding { self.resolvedRuntime.services } private var logger: Logger { self.resolvedRuntime.logger } var outputLogger: Logger { self.logger } private var configuration: CommandRuntime.Configuration { self.resolvedRuntime.configuration } var jsonOutput: Bool { self.configuration.jsonOutput } private var isVerbose: Bool { self.configuration.verbose } @MainActor mutating func run(using runtime: CommandRuntime) async throws { self.runtime = runtime let startTime = Date() do { let script = try await ProcessServiceBridge.loadScript(services: self.services, path: self.scriptPath) let results = try await ProcessServiceBridge.executeScript( services: self.services, script, failFast: !self.noFailFast, verbose: self.isVerbose ) let output = ScriptExecutionResult( success: results.allSatisfy(\.success), scriptPath: self.scriptPath, description: script.description, totalSteps: script.steps.count, completedSteps: results.count { $0.success }, failedSteps: results.count { !$0.success }, executionTime: Date().timeIntervalSince(startTime), steps: results ) if let outputPath = self.output { let data = try JSONEncoder().encode(output) try data.write(to: URL(fileURLWithPath: outputPath)) if !self.jsonOutput { print("✅ Script completed. Results saved to: \(outputPath)") } } else if self.jsonOutput { outputSuccessCodable(data: output, logger: self.outputLogger) } else { self.printSummary(output) } if !output.success { throw ExitCode.failure } } catch { if self.jsonOutput { outputError(message: error.localizedDescription, code: .INVALID_ARGUMENT, logger: self.outputLogger) } else { print("❌ Error: \(error.localizedDescription)") } throw ExitCode.failure } } @MainActor private func printSummary(_ result: ScriptExecutionResult) { if result.success { print("✅ Script completed successfully") } else { print("❌ Script failed") } print(" Total steps: \(result.totalSteps)") print(" Completed: \(result.completedSteps)") print(" Failed: \(result.failedSteps)") print(" Execution time: \(String(format: "%.2f", result.executionTime))s") if !result.success { let failedSteps = result.steps.filter { !$0.success } if !failedSteps.isEmpty { print("\nFailed steps:") for step in failedSteps { print(" - Step \(step.stepNumber) (\(step.command)): \(step.error ?? "Unknown error")") } } } } } struct ScriptExecutionResult: Codable { let success: Bool let scriptPath: String let description: String? let totalSteps: Int let completedSteps: Int let failedSteps: Int let executionTime: TimeInterval let steps: [PeekabooCore.StepResult] } private enum ProcessServiceBridge { static func loadScript(services: any PeekabooServiceProviding, path: String) async throws -> PeekabooScript { try await Task { @MainActor in try await services.process.loadScript(from: path) }.value } static func executeScript( services: any PeekabooServiceProviding, _ script: PeekabooScript, failFast: Bool, verbose: Bool ) async throws -> [StepResult] { try await Task { @MainActor in try await services.process.executeScript(script, failFast: failFast, verbose: verbose) }.value } } @MainActor extension RunCommand: ParsableCommand {} extension RunCommand: AsyncRuntimeCommand {} @MainActor extension RunCommand: CommanderBindableCommand { mutating func applyCommanderValues(_ values: CommanderBindableValues) throws { self.scriptPath = try values.decodePositional(0, label: "scriptPath") self.output = try values.decodeOption("output", as: String.self) self.noFailFast = values.flag("noFailFast") } }

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