Skip to main content
Glama
CommandRuntime.swift7.65 kB
// // CommandRuntime.swift // PeekabooCLI // import Foundation import PeekabooBridge import PeekabooCore import PeekabooFoundation import PeekabooProtocols /// Shared options that control logging and output behavior. struct CommandRuntimeOptions: Sendable { var verbose = false var jsonOutput = false var logLevel: LogLevel? var captureEnginePreference: String? var preferRemote = true var bridgeSocketPath: String? func makeConfiguration() -> CommandRuntime.Configuration { CommandRuntime.Configuration( verbose: self.verbose, jsonOutput: self.jsonOutput, logLevel: self.logLevel, captureEnginePreference: self.captureEnginePreference ) } } /// Runtime context passed to runtime-aware commands. struct CommandRuntime { @TaskLocal private static var serviceOverride: PeekabooServices? struct Configuration { var verbose: Bool var jsonOutput: Bool var logLevel: LogLevel? var captureEnginePreference: String? } let configuration: Configuration let hostDescription: String @MainActor let services: any PeekabooServiceProviding @MainActor let logger: Logger @MainActor init( configuration: Configuration, services: any PeekabooServiceProviding, hostDescription: String = "local (in-process)" ) { self.configuration = configuration self.services = services self.hostDescription = hostDescription self.logger = Logger.shared services.installAgentRuntimeDefaults() self.logger.setJsonOutputMode(configuration.jsonOutput) let explicitLevel = configuration.logLevel var shouldEnableVerbose = configuration.verbose if configuration.jsonOutput && explicitLevel == nil { shouldEnableVerbose = true } if let explicitLevel, explicitLevel <= .verbose { shouldEnableVerbose = true } self.logger.setVerboseMode(shouldEnableVerbose) if let explicitLevel { self.logger.setMinimumLogLevel(explicitLevel) } else if shouldEnableVerbose { self.logger.setMinimumLogLevel(.verbose) } else { self.logger.resetMinimumLogLevel() } let visualizerConsoleLevel: PeekabooProtocols.LogLevel? = if let explicitLevel { explicitLevel.coreLogLevel } else if shouldEnableVerbose { .debug } else { nil } VisualizationClient.shared.setConsoleLogLevelOverride(visualizerConsoleLevel) VisualizationClient.shared.setConsoleMirroringEnabled(configuration.verbose) self.services.ensureVisualizerConnection() self.logger.debug("Runtime host: \(hostDescription)") } @MainActor init(options: CommandRuntimeOptions, services: any PeekabooServiceProviding) { self.init(configuration: options.makeConfiguration(), services: services) } } extension CommandRuntime { @MainActor static func makeDefault(options: CommandRuntimeOptions) -> CommandRuntime { let services = self.serviceOverride ?? PeekabooServices() return CommandRuntime(configuration: options.makeConfiguration(), services: services) } @MainActor static func makeDefault() -> CommandRuntime { self.makeDefault(options: CommandRuntimeOptions()) } @MainActor static func makeDefaultAsync(options: CommandRuntimeOptions) async -> CommandRuntime { if let override = self.serviceOverride { return CommandRuntime(options: options, services: override) } let resolution = await self.resolveServices(options: options) return CommandRuntime( configuration: options.makeConfiguration(), services: resolution.services, hostDescription: resolution.hostDescription ) } @MainActor static func makeDefaultAsync() async -> CommandRuntime { await self.makeDefaultAsync(options: CommandRuntimeOptions()) } @MainActor static func withInjectedServices<T>( _ services: PeekabooServices, perform operation: () async throws -> T ) async rethrows -> T { try await self.$serviceOverride.withValue(services) { try await operation() } } @MainActor private static func resolveServices(options: CommandRuntimeOptions) async -> (services: any PeekabooServiceProviding, hostDescription: String) { let envNoRemote = ProcessInfo.processInfo.environment["PEEKABOO_NO_REMOTE"] guard options.preferRemote, envNoRemote == nil else { return (services: PeekabooServices(), hostDescription: "local (in-process)") } let explicitSocket = options.bridgeSocketPath ?? ProcessInfo.processInfo.environment["PEEKABOO_BRIDGE_SOCKET"] let candidates: [String] = if let explicitSocket, !explicitSocket.isEmpty { [explicitSocket] } else { [ PeekabooBridgeConstants.peekabooSocketPath, PeekabooBridgeConstants.claudeSocketPath, PeekabooBridgeConstants.clawdisSocketPath, ] } let identity = PeekabooBridgeClientIdentity( bundleIdentifier: Bundle.main.bundleIdentifier, teamIdentifier: nil, processIdentifier: getpid(), hostname: Host.current().name ) for socketPath in candidates { let client = PeekabooBridgeClient(socketPath: socketPath) do { let handshake = try await client.handshake(client: identity, requestedHost: nil) if handshake.supportedOperations.contains(.captureScreen) { let hostDescription = "remote \(handshake.hostKind.rawValue) via \(socketPath)" + (handshake.build.map { " (build \($0))" } ?? "") return ( services: RemotePeekabooServices(client: client), hostDescription: hostDescription ) } } catch { continue } } return (services: PeekabooServices(), hostDescription: "local (in-process)") } } /// Commands that need access to verbose/json flags even before a runtime is injected /// (e.g., during unit tests) can conform to this protocol and store the parsed options. protocol RuntimeOptionsConfigurable { var runtimeOptions: CommandRuntimeOptions { get set } } extension RuntimeOptionsConfigurable { mutating func setRuntimeOptions(_ options: CommandRuntimeOptions) { self.runtimeOptions = options } } @propertyWrapper struct RuntimeStorage<Value> where Value: ExpressibleByNilLiteral { private var storage: Value init() { self.storage = nil } var wrappedValue: Value { get { self.storage } set { self.storage = newValue } } } extension RuntimeStorage: Codable where Value: ExpressibleByNilLiteral { init(from _: any Decoder) throws { self.storage = nil } func encode(to _: any Encoder) throws {} } extension RuntimeStorage: Sendable where Value: Sendable {} extension LogLevel { fileprivate var coreLogLevel: PeekabooProtocols.LogLevel { switch self { case .trace: .trace case .verbose: .debug case .debug: .debug case .info: .info case .warning: .warning case .error: .error case .critical: .critical } } }

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