Skip to main content
Glama
CommanderBinder.swift7.9 kB
import Commander import Foundation // MARK: - Binder enum CommanderCLIBinder { static func instantiateCommand( type: any ParsableCommand.Type, parsedValues: ParsedValues ) throws -> any ParsableCommand { var command = type.init() let runtimeOptions = try self.makeRuntimeOptions(from: parsedValues) if var bindable = command as? any CommanderBindableCommand { try bindable.applyCommanderValues(.init(parsedValues: parsedValues)) guard let rebound = bindable as? any ParsableCommand else { preconditionFailure("CommanderBindableCommand cast should always round-trip to original type \(type)") } command = rebound } if var configurable = command as? any RuntimeOptionsConfigurable { configurable.setRuntimeOptions(runtimeOptions) guard let rebound = configurable as? any ParsableCommand else { preconditionFailure("RuntimeOptionsConfigurable cast should always round-trip to original type \(type)") } command = rebound } return command } static func instantiateCommand<T>( ofType type: T.Type, parsedValues: ParsedValues ) throws -> T where T: ParsableCommand { guard let command = try instantiateCommand(type: type, parsedValues: parsedValues) as? T else { preconditionFailure("Commander instantiation failed to produce expected type \(T.self)") } return command } static func makeRuntimeOptions(from parsedValues: ParsedValues) throws -> CommandRuntimeOptions { var options = CommandRuntimeOptions() options.verbose = parsedValues.flags.contains("verbose") options.jsonOutput = parsedValues.flags.contains("jsonOutput") let values = CommanderBindableValues(parsedValues: parsedValues) if let level: LogLevel = try values.decodeOption("logLevel", as: LogLevel.self) { options.logLevel = level } if let captureEngine = values.singleOption("captureEngine")? .trimmingCharacters(in: .whitespacesAndNewlines), !captureEngine.isEmpty { options.captureEnginePreference = captureEngine } if values.flag("no-remote") { options.preferRemote = false } if let xpc = values.singleOption("xpc-service")?.trimmingCharacters(in: .whitespacesAndNewlines), !xpc.isEmpty { options.xpcServiceName = xpc } return options } } // MARK: - Bindable Protocol struct CommanderBindableValues { let positional: [String] let options: [String: [String]] let flags: Set<String> init(positional: [String], options: [String: [String]], flags: Set<String>) { self.positional = positional self.options = options self.flags = flags } init(parsedValues: ParsedValues) { self.init(positional: parsedValues.positional, options: parsedValues.options, flags: parsedValues.flags) } func positionalValue(at index: Int) -> String? { guard index >= 0, index < self.positional.count else { return nil } return self.positional[index] } func requiredPositional(_ index: Int, label: String) throws -> String { guard let value = positionalValue(at: index) else { throw CommanderBindingError.missingArgument(label: label) } return value } func singleOption(_ label: String) -> String? { self.options[label]?.last } func optionValues(_ label: String) -> [String] { self.options[label] ?? [] } func flag(_ label: String) -> Bool { self.flags.contains(label) } func decodePositional<T: ExpressibleFromArgument>( _ index: Int, label: String, as type: T.Type = T.self ) throws -> T { let raw = try requiredPositional(index, label: label) guard let value = T(argument: raw) else { throw CommanderBindingError.invalidArgument(label: label, value: raw, reason: "Unable to parse \(T.self)") } return value } func decodeOptionalPositional<T: ExpressibleFromArgument>( _ index: Int, label: String, as type: T.Type = T.self ) throws -> T? { guard let raw = positionalValue(at: index) else { return nil } guard let value = T(argument: raw) else { throw CommanderBindingError.invalidArgument(label: label, value: raw, reason: "Unable to parse \(T.self)") } return value } func decodeOption<T: ExpressibleFromArgument>(_ label: String, as type: T.Type = T.self) throws -> T? { guard let raw = singleOption(label) else { return nil } guard let value = T(argument: raw) else { throw CommanderBindingError.invalidArgument(label: label, value: raw, reason: "Unable to parse \(T.self)") } return value } func requireOption<T: ExpressibleFromArgument>(_ label: String, as type: T.Type = T.self) throws -> T { guard let value: T = try decodeOption(label, as: type) else { throw CommanderBindingError.missingArgument(label: label) } return value } func decodeOptionEnum<T: RawRepresentable>( _ label: String, as type: T.Type = T.self, caseInsensitive: Bool = true ) throws -> T? where T.RawValue == String { guard let raw = singleOption(label) else { return nil } let candidate = caseInsensitive ? raw.lowercased() : raw guard let value = T(rawValue: candidate) else { throw CommanderBindingError.invalidArgument(label: label, value: raw, reason: "Unknown value for \(T.self)") } return value } } extension CommanderBindableValues { func makeWindowOptions() throws -> WindowIdentificationOptions { var options = WindowIdentificationOptions() try fillWindowOptions(into: &options) return options } func fillWindowOptions(into options: inout WindowIdentificationOptions) throws { options.app = self.singleOption("app") if let pid: Int32 = try decodeOption("pid", as: Int32.self) { options.pid = pid } options.windowTitle = self.singleOption("windowTitle") if let index: Int = try decodeOption("windowIndex", as: Int.self) { options.windowIndex = index } } func makeFocusOptions() throws -> FocusCommandOptions { var options = FocusCommandOptions() try fillFocusOptions(into: &options) return options } func fillFocusOptions(into options: inout FocusCommandOptions) throws { options.noAutoFocus = self.flag("noAutoFocus") options.spaceSwitch = self.flag("spaceSwitch") options.bringToCurrentSpace = self.flag("bringToCurrentSpace") if let timeout: TimeInterval = try decodeOption("focusTimeoutSeconds", as: TimeInterval.self) { options.focusTimeoutSeconds = timeout } if let retries: Int = try decodeOption("focusRetryCountValue", as: Int.self) { options.focusRetryCountValue = retries } } } @MainActor protocol CommanderBindableCommand { mutating func applyCommanderValues(_ values: CommanderBindableValues) throws } enum CommanderBindingError: LocalizedError, Sendable, Equatable { case missingArgument(label: String) case invalidArgument(label: String, value: String, reason: String) var errorDescription: String? { switch self { case let .missingArgument(label): "Missing argument: \(label)" case let .invalidArgument(label, value, reason): "Invalid value '\(value)' for \(label): \(reason)" } } }

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