Skip to main content
Glama
PasteCommand.swift7.64 kB
import Commander import Foundation import PeekabooCore import PeekabooFoundation import UniformTypeIdentifiers /// Sets clipboard content, pastes (Cmd+V), then restores the prior clipboard. @available(macOS 14.0, *) @MainActor struct PasteCommand: ErrorHandlingCommand, OutputFormattable, RuntimeOptionsConfigurable { @Argument(help: "Text to paste") var text: String? @Option(name: .customLong("text"), help: "Text to paste (alternative to positional argument)") var textOption: String? @Option(name: .long, help: "Path to file to paste (copies file bytes into clipboard first)") var filePath: String? @Option(name: .long, help: "Path to image to paste (alias of file-path)") var imagePath: String? @Option(name: .long, help: "Base64 data to paste") var dataBase64: String? @Option(name: .long, help: "UTI for base64 payload or to force type") var uti: String? @Option(name: .long, help: "Optional plain-text companion when setting binary") var alsoText: String? @Flag(name: .long, help: "Allow payloads larger than 10 MB") var allowLarge = false @Option(name: .customLong("restore-delay-ms"), help: "Delay before restoring the previous clipboard (ms)") var restoreDelayMs: Int = 150 @OptionGroup var target: InteractionTargetOptions @OptionGroup var focusOptions: FocusCommandOptions @RuntimeStorage private var runtime: CommandRuntime? var runtimeOptions = CommandRuntimeOptions() 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 } var jsonOutput: Bool { self.runtime?.configuration.jsonOutput ?? self.runtimeOptions.jsonOutput } private var resolvedText: String? { if let primary = self.text, !primary.isEmpty { return primary } return self.textOption } @MainActor mutating func run(using runtime: CommandRuntime) async throws { self.runtime = runtime self.logger.setJsonOutputMode(self.jsonOutput) do { try self.target.validate() let request = try self.makeWriteRequest() try await ensureFocused( snapshotId: nil, target: self.target, options: self.focusOptions, services: self.services ) let priorClipboard = try? self.services.clipboard.get(prefer: nil) let restoreSlot = "paste-\(UUID().uuidString)" if priorClipboard != nil { try self.services.clipboard.save(slot: restoreSlot) } var restoreResult: ClipboardReadResult? defer { if self.restoreDelayMs > 0 { usleep(useconds_t(self.restoreDelayMs) * 1000) } if priorClipboard != nil { restoreResult = try? self.services.clipboard.restore(slot: restoreSlot) } else { self.services.clipboard.clear() } } let setResult = try self.services.clipboard.set(request) try await AutomationServiceBridge.hotkey( automation: self.services.automation, keys: "cmd,v", holdDuration: 50 ) let result = PasteResult( success: true, pastedUti: setResult.utiIdentifier, pastedSize: setResult.data.count, pastedTextPreview: setResult.textPreview, previousClipboardPresent: priorClipboard != nil, restoredUti: restoreResult?.utiIdentifier, restoredSize: restoreResult?.data.count, restoreDelayMs: self.restoreDelayMs ) self.output(result) { print("✅ Pasted (Cmd+V) and restored clipboard") print("📋 Pasted: \(setResult.utiIdentifier) (\(setResult.data.count) bytes)") if priorClipboard != nil { print("♻️ Restored: \(restoreResult?.utiIdentifier ?? "unknown")") } else { print("🧹 Restored: cleared (prior clipboard empty)") } } } catch { self.handleError(error) throw ExitCode.failure } } private func makeWriteRequest() throws -> ClipboardWriteRequest { if let text = self.resolvedText { return ClipboardWriteRequest( representations: [ ClipboardRepresentation(utiIdentifier: UTType.plainText.identifier, data: Data(text.utf8)), ], alsoText: nil, allowLarge: self.allowLarge ) } if let path = self.filePath ?? self.imagePath { let url = URL(fileURLWithPath: path) let data = try Data(contentsOf: url) let inferred = UTType(filenameExtension: url.pathExtension) ?? .data let forced = self.uti.flatMap(UTType.init(_:)) ?? inferred return ClipboardWriteRequest( representations: [ClipboardRepresentation(utiIdentifier: forced.identifier, data: data)], alsoText: self.alsoText, allowLarge: self.allowLarge ) } if let b64 = self.dataBase64, let utiId = self.uti { guard let data = Data(base64Encoded: b64) else { throw ValidationError("data-base64 is not valid base64") } return ClipboardWriteRequest( representations: [ClipboardRepresentation(utiIdentifier: utiId, data: data)], alsoText: self.alsoText, allowLarge: self.allowLarge ) } throw ValidationError("Provide text, --file-path/--image-path, or --data-base64 with --uti") } } struct PasteResult: Codable { let success: Bool let pastedUti: String let pastedSize: Int let pastedTextPreview: String? let previousClipboardPresent: Bool let restoredUti: String? let restoredSize: Int? let restoreDelayMs: Int } @MainActor extension PasteCommand: ParsableCommand { nonisolated(unsafe) static var commandDescription: CommandDescription { MainActorCommandDescription.describe { CommandDescription( commandName: "paste", abstract: "Set clipboard, paste (Cmd+V), then restore previous clipboard", discussion: """ This command reduces drift in automation flows by collapsing: 1) clipboard set 2) Cmd+V paste 3) clipboard restore into one operation. EXAMPLES: peekaboo paste \"Hello\" --app TextEdit peekaboo paste --text \"Hello\" --app TextEdit --window-title \"Untitled\" peekaboo paste --data-base64 \"$BASE64\" --uti public.rtf --also-text \"fallback\" --app TextEdit peekaboo paste --file-path /tmp/snippet.png --app Notes """, showHelpOnEmptyInvocation: true ) } } } extension PasteCommand: AsyncRuntimeCommand {}

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