mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-04-27 15:07:41 +00:00
Fix Swift 6 async execution with synchronous adapter
- Add AsyncAdapter.swift to bridge async/sync execution - Change AsyncParsableCommand back to ParsableCommand - Implement AsyncRunnable protocol for async execution - Use DispatchSemaphore pattern for synchronous blocking - Make ErrorBox thread-safe with @unchecked Sendable This fixes the CLI execution issue where commands were showing help instead of executing, by properly bridging the async/sync worlds as required by ArgumentParser. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8b46d11015
commit
559349f198
5 changed files with 153 additions and 13 deletions
140
peekaboo-cli/Sources/peekaboo/AsyncAdapter.swift
Normal file
140
peekaboo-cli/Sources/peekaboo/AsyncAdapter.swift
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
import Foundation
|
||||||
|
import ArgumentParser
|
||||||
|
|
||||||
|
// MARK: - Adapter for AsyncParsableCommand to ParsableCommand bridge
|
||||||
|
|
||||||
|
protocol AsyncRunnable {
|
||||||
|
func runAsync() async throws
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PeekabooCommand {
|
||||||
|
func run() throws {
|
||||||
|
let box = ErrorBox()
|
||||||
|
let sem = DispatchSemaphore(value: 0)
|
||||||
|
Task {
|
||||||
|
defer { sem.signal() }
|
||||||
|
do {
|
||||||
|
try await (self as AsyncRunnable).runAsync()
|
||||||
|
} catch {
|
||||||
|
box.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sem.wait()
|
||||||
|
if let error = box.error {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImageCommand {
|
||||||
|
func run() throws {
|
||||||
|
let box = ErrorBox()
|
||||||
|
let sem = DispatchSemaphore(value: 0)
|
||||||
|
Task {
|
||||||
|
defer { sem.signal() }
|
||||||
|
do {
|
||||||
|
try await (self as AsyncRunnable).runAsync()
|
||||||
|
} catch {
|
||||||
|
box.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sem.wait()
|
||||||
|
if let error = box.error {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ListCommand {
|
||||||
|
func run() throws {
|
||||||
|
let box = ErrorBox()
|
||||||
|
let sem = DispatchSemaphore(value: 0)
|
||||||
|
Task {
|
||||||
|
defer { sem.signal() }
|
||||||
|
do {
|
||||||
|
try await (self as AsyncRunnable).runAsync()
|
||||||
|
} catch {
|
||||||
|
box.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sem.wait()
|
||||||
|
if let error = box.error {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppsSubcommand {
|
||||||
|
func run() throws {
|
||||||
|
let box = ErrorBox()
|
||||||
|
let sem = DispatchSemaphore(value: 0)
|
||||||
|
Task {
|
||||||
|
defer { sem.signal() }
|
||||||
|
do {
|
||||||
|
try await (self as AsyncRunnable).runAsync()
|
||||||
|
} catch {
|
||||||
|
box.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sem.wait()
|
||||||
|
if let error = box.error {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WindowsSubcommand {
|
||||||
|
func run() throws {
|
||||||
|
let box = ErrorBox()
|
||||||
|
let sem = DispatchSemaphore(value: 0)
|
||||||
|
Task {
|
||||||
|
defer { sem.signal() }
|
||||||
|
do {
|
||||||
|
try await (self as AsyncRunnable).runAsync()
|
||||||
|
} catch {
|
||||||
|
box.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sem.wait()
|
||||||
|
if let error = box.error {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ServerStatusSubcommand {
|
||||||
|
func run() throws {
|
||||||
|
let box = ErrorBox()
|
||||||
|
let sem = DispatchSemaphore(value: 0)
|
||||||
|
Task {
|
||||||
|
defer { sem.signal() }
|
||||||
|
do {
|
||||||
|
try await (self as AsyncRunnable).runAsync()
|
||||||
|
} catch {
|
||||||
|
box.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sem.wait()
|
||||||
|
if let error = box.error {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ErrorBox: @unchecked Sendable {
|
||||||
|
private var _error: Error? = nil
|
||||||
|
private let lock = NSLock()
|
||||||
|
|
||||||
|
var error: Error? {
|
||||||
|
get {
|
||||||
|
lock.lock()
|
||||||
|
defer { lock.unlock() }
|
||||||
|
return _error
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
lock.lock()
|
||||||
|
defer { lock.unlock() }
|
||||||
|
_error = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ struct FileHandleTextOutputStream: TextOutputStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ImageCommand: AsyncParsableCommand {
|
struct ImageCommand: ParsableCommand, AsyncRunnable {
|
||||||
static let configuration = CommandConfiguration(
|
static let configuration = CommandConfiguration(
|
||||||
commandName: "image",
|
commandName: "image",
|
||||||
abstract: "Capture screen or window images"
|
abstract: "Capture screen or window images"
|
||||||
|
|
@ -52,7 +52,7 @@ struct ImageCommand: AsyncParsableCommand {
|
||||||
@Flag(name: .long, help: "Output results in JSON format")
|
@Flag(name: .long, help: "Output results in JSON format")
|
||||||
var jsonOutput = false
|
var jsonOutput = false
|
||||||
|
|
||||||
func run() async throws {
|
func runAsync() async throws {
|
||||||
Logger.shared.setJsonOutputMode(jsonOutput)
|
Logger.shared.setJsonOutputMode(jsonOutput)
|
||||||
do {
|
do {
|
||||||
try PermissionsChecker.requireScreenRecordingPermission()
|
try PermissionsChecker.requireScreenRecordingPermission()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import AppKit
|
||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct ListCommand: AsyncParsableCommand {
|
struct ListCommand: ParsableCommand, AsyncRunnable {
|
||||||
static let configuration = CommandConfiguration(
|
static let configuration = CommandConfiguration(
|
||||||
commandName: "list",
|
commandName: "list",
|
||||||
abstract: "List running applications or windows",
|
abstract: "List running applications or windows",
|
||||||
|
|
@ -10,12 +10,12 @@ struct ListCommand: AsyncParsableCommand {
|
||||||
defaultSubcommand: AppsSubcommand.self
|
defaultSubcommand: AppsSubcommand.self
|
||||||
)
|
)
|
||||||
|
|
||||||
func run() async throws {
|
func runAsync() async throws {
|
||||||
// Root command doesn't do anything, subcommands handle everything
|
// Root command doesn't do anything, subcommands handle everything
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AppsSubcommand: AsyncParsableCommand {
|
struct AppsSubcommand: ParsableCommand, AsyncRunnable {
|
||||||
static let configuration = CommandConfiguration(
|
static let configuration = CommandConfiguration(
|
||||||
commandName: "apps",
|
commandName: "apps",
|
||||||
abstract: "List all running applications"
|
abstract: "List all running applications"
|
||||||
|
|
@ -24,7 +24,7 @@ struct AppsSubcommand: AsyncParsableCommand {
|
||||||
@Flag(name: .long, help: "Output results in JSON format")
|
@Flag(name: .long, help: "Output results in JSON format")
|
||||||
var jsonOutput = false
|
var jsonOutput = false
|
||||||
|
|
||||||
func run() async throws {
|
func runAsync() async throws {
|
||||||
Logger.shared.setJsonOutputMode(jsonOutput)
|
Logger.shared.setJsonOutputMode(jsonOutput)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
@ -102,7 +102,7 @@ struct AppsSubcommand: AsyncParsableCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WindowsSubcommand: AsyncParsableCommand {
|
struct WindowsSubcommand: ParsableCommand, AsyncRunnable {
|
||||||
static let configuration = CommandConfiguration(
|
static let configuration = CommandConfiguration(
|
||||||
commandName: "windows",
|
commandName: "windows",
|
||||||
abstract: "List windows for a specific application"
|
abstract: "List windows for a specific application"
|
||||||
|
|
@ -117,7 +117,7 @@ struct WindowsSubcommand: AsyncParsableCommand {
|
||||||
@Flag(name: .long, help: "Output results in JSON format")
|
@Flag(name: .long, help: "Output results in JSON format")
|
||||||
var jsonOutput = false
|
var jsonOutput = false
|
||||||
|
|
||||||
func run() async throws {
|
func runAsync() async throws {
|
||||||
Logger.shared.setJsonOutputMode(jsonOutput)
|
Logger.shared.setJsonOutputMode(jsonOutput)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
@ -249,7 +249,7 @@ struct WindowsSubcommand: AsyncParsableCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ServerStatusSubcommand: AsyncParsableCommand {
|
struct ServerStatusSubcommand: ParsableCommand, AsyncRunnable {
|
||||||
static let configuration = CommandConfiguration(
|
static let configuration = CommandConfiguration(
|
||||||
commandName: "server_status",
|
commandName: "server_status",
|
||||||
abstract: "Check server permissions status"
|
abstract: "Check server permissions status"
|
||||||
|
|
@ -258,7 +258,7 @@ struct ServerStatusSubcommand: AsyncParsableCommand {
|
||||||
@Flag(name: .long, help: "Output results in JSON format")
|
@Flag(name: .long, help: "Output results in JSON format")
|
||||||
var jsonOutput = false
|
var jsonOutput = false
|
||||||
|
|
||||||
func run() async throws {
|
func runAsync() async throws {
|
||||||
Logger.shared.setJsonOutputMode(jsonOutput)
|
Logger.shared.setJsonOutputMode(jsonOutput)
|
||||||
|
|
||||||
let screenRecording = PermissionsChecker.checkScreenRecordingPermission()
|
let screenRecording = PermissionsChecker.checkScreenRecordingPermission()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// This file is auto-generated by the build script. Do not edit manually.
|
// This file is auto-generated by the build script. Do not edit manually.
|
||||||
enum Version: Sendable {
|
enum Version {
|
||||||
static let current = "1.0.0-beta.23"
|
static let current = "1.0.0-beta.23"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import ArgumentParser
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@available(macOS 10.15, *)
|
@available(macOS 10.15, *)
|
||||||
struct PeekabooCommand: AsyncParsableCommand {
|
struct PeekabooCommand: ParsableCommand, AsyncRunnable {
|
||||||
static let configuration = CommandConfiguration(
|
static let configuration = CommandConfiguration(
|
||||||
commandName: "peekaboo",
|
commandName: "peekaboo",
|
||||||
abstract: "A macOS utility for screen capture, application listing, and window management",
|
abstract: "A macOS utility for screen capture, application listing, and window management",
|
||||||
|
|
@ -11,7 +11,7 @@ struct PeekabooCommand: AsyncParsableCommand {
|
||||||
defaultSubcommand: ImageCommand.self
|
defaultSubcommand: ImageCommand.self
|
||||||
)
|
)
|
||||||
|
|
||||||
func run() async throws {
|
func runAsync() async throws {
|
||||||
// Root command doesn't do anything, subcommands handle everything
|
// Root command doesn't do anything, subcommands handle everything
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue