mcp-baepsae 5.1.0 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-KR.md +98 -33
- package/README.md +101 -35
- package/bundled/baepsae-native +0 -0
- package/dist/backend.d.ts +26 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +79 -0
- package/dist/backend.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/tool-manifest.d.ts +12 -0
- package/dist/tool-manifest.d.ts.map +1 -0
- package/dist/tool-manifest.js +79 -0
- package/dist/tool-manifest.js.map +1 -0
- package/dist/tools/info.d.ts.map +1 -1
- package/dist/tools/info.js +104 -5
- package/dist/tools/info.js.map +1 -1
- package/dist/tools/input.js +7 -6
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/media.d.ts.map +1 -1
- package/dist/tools/media.js +137 -11
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/simulator.js +7 -7
- package/dist/tools/simulator.js.map +1 -1
- package/dist/tools/system.d.ts.map +1 -1
- package/dist/tools/system.js +2 -2
- package/dist/tools/system.js.map +1 -1
- package/dist/tools/ui.d.ts.map +1 -1
- package/dist/tools/ui.js +126 -8
- package/dist/tools/ui.js.map +1 -1
- package/dist/tools/workflow.d.ts +3 -0
- package/dist/tools/workflow.d.ts.map +1 -0
- package/dist/tools/workflow.js +434 -0
- package/dist/tools/workflow.js.map +1 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +19 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +110 -5
- package/dist/utils.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/native/Sources/Commands/InputCommands.swift +53 -33
- package/native/Sources/Commands/SystemCommands.swift +86 -0
- package/native/Sources/Commands/UICommands.swift +254 -35
- package/native/Sources/Commands/WindowCommands.swift +11 -4
- package/native/Sources/IndigoHID/IndigoHIDClient.swift +222 -0
- package/native/Sources/IndigoHID/IndigoHIDCoordinates.swift +74 -0
- package/native/Sources/IndigoHID/IndigoHIDEvents.swift +63 -0
- package/native/Sources/IndigoHID/IndigoHIDLoader.swift +102 -0
- package/native/Sources/IndigoHID/IndigoHIDTypes.swift +41 -0
- package/native/Sources/Types.swift +26 -0
- package/native/Sources/Utils.swift +653 -13
- package/native/Sources/Version.swift +1 -1
- package/native/Sources/main.swift +55 -8
- package/native/Tests/BaepsaeNativeTests/BinaryInvocationTests.swift +54 -6
- package/package.json +12 -3
- package/scripts/dump-tabbar-actions.mjs +312 -0
- package/scripts/generate-tool-manifest.mjs +75 -0
- package/scripts/research-coordinate-calibration.mjs +276 -0
- package/scripts/research-input-channels.mjs +327 -0
- package/scripts/research-tap-tab-grid.mjs +271 -0
- package/scripts/verify-media-capture.mjs +99 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import CoreGraphics
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
// MARK: - Coordinate Normalization
|
|
5
|
+
|
|
6
|
+
/// Normalizes pixel coordinates to 0-1 ratio for IndigoHID.
|
|
7
|
+
/// IndigoHID expects coordinates as fractions of the screen dimensions.
|
|
8
|
+
struct IndigoHIDCoordinates {
|
|
9
|
+
let screenWidth: Double
|
|
10
|
+
let screenHeight: Double
|
|
11
|
+
|
|
12
|
+
init(screenWidth: Double, screenHeight: Double) {
|
|
13
|
+
self.screenWidth = max(1, screenWidth)
|
|
14
|
+
self.screenHeight = max(1, screenHeight)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// Convert point coordinates (pixels) to normalized 0-1 ratio.
|
|
18
|
+
/// Origin is top-left.
|
|
19
|
+
func normalize(x: Double, y: Double) -> (x: Double, y: Double) {
|
|
20
|
+
return (x / screenWidth, y / screenHeight)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Create a touch point from pixel coordinates.
|
|
24
|
+
func touchPoint(x: Double, y: Double, phase: IndigoHIDTouchPhase, finger: UInt32 = 1) -> IndigoHIDTouchPoint {
|
|
25
|
+
let (nx, ny) = normalize(x: x, y: y)
|
|
26
|
+
return IndigoHIDTouchPoint(x: nx, y: ny, phase: phase, finger: finger)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Resolve screen dimensions for the given simulator UDID.
|
|
31
|
+
/// Falls back to common iPhone dimensions if detection fails.
|
|
32
|
+
func resolveSimulatorScreenSize(udid: String) -> (width: Double, height: Double) {
|
|
33
|
+
// Try to get screen size from simctl
|
|
34
|
+
let process = Process()
|
|
35
|
+
process.executableURL = URL(fileURLWithPath: "/usr/bin/xcrun")
|
|
36
|
+
process.arguments = ["simctl", "list", "devices", "-j"]
|
|
37
|
+
|
|
38
|
+
let pipe = Pipe()
|
|
39
|
+
process.standardOutput = pipe
|
|
40
|
+
process.standardError = FileHandle.nullDevice
|
|
41
|
+
|
|
42
|
+
do {
|
|
43
|
+
try process.run()
|
|
44
|
+
process.waitUntilExit()
|
|
45
|
+
|
|
46
|
+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
47
|
+
if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
48
|
+
let devices = json["devices"] as? [String: [[String: Any]]] {
|
|
49
|
+
for (_, deviceList) in devices {
|
|
50
|
+
for device in deviceList {
|
|
51
|
+
if let deviceUdid = device["udid"] as? String, deviceUdid == udid {
|
|
52
|
+
// Try to infer from device type
|
|
53
|
+
if let deviceType = device["deviceTypeIdentifier"] as? String {
|
|
54
|
+
if deviceType.contains("iPhone-16") || deviceType.contains("iPhone-15") {
|
|
55
|
+
return (393, 852)
|
|
56
|
+
}
|
|
57
|
+
if deviceType.contains("iPhone-SE") {
|
|
58
|
+
return (375, 667)
|
|
59
|
+
}
|
|
60
|
+
if deviceType.contains("iPad") {
|
|
61
|
+
return (1024, 1366)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Fall through to default
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Default: iPhone 15/16 logical resolution
|
|
73
|
+
return (393, 852)
|
|
74
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@preconcurrency import Foundation
|
|
2
|
+
|
|
3
|
+
// bootstrap_look_up is in /usr/include/servers/bootstrap.h
|
|
4
|
+
@_silgen_name("bootstrap_look_up")
|
|
5
|
+
func _bootstrap_look_up(_ bp: mach_port_t, _ name: UnsafePointer<CChar>, _ sp: UnsafeMutablePointer<mach_port_t>) -> kern_return_t
|
|
6
|
+
|
|
7
|
+
// MARK: - Mach Port Communication
|
|
8
|
+
|
|
9
|
+
/// Resolves the IndigoHID Mach service port for a given simulator UDID.
|
|
10
|
+
func resolveIndigoHIDPort(udid: String) -> mach_port_t? {
|
|
11
|
+
let serviceName = "com.apple.CoreSimulator.IndigoHIDService.\(udid)"
|
|
12
|
+
var port: mach_port_t = mach_port_t(MACH_PORT_NULL)
|
|
13
|
+
let bp = mach_port_t(bootstrap_port)
|
|
14
|
+
let result = serviceName.withCString { cStr in
|
|
15
|
+
_bootstrap_look_up(bp, cStr, &port)
|
|
16
|
+
}
|
|
17
|
+
if result == KERN_SUCCESS && port != mach_port_t(MACH_PORT_NULL) {
|
|
18
|
+
return port
|
|
19
|
+
}
|
|
20
|
+
return nil
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Sends an IndigoHID message to the simulator via Mach IPC.
|
|
24
|
+
func sendIndigoHIDMessage(_ data: CFData, to port: mach_port_t) -> Bool {
|
|
25
|
+
let bytes = CFDataGetBytePtr(data)
|
|
26
|
+
let length = CFDataGetLength(data)
|
|
27
|
+
guard let bytes, length > 0 else { return false }
|
|
28
|
+
|
|
29
|
+
// Construct mach message
|
|
30
|
+
let headerSize = MemoryLayout<mach_msg_header_t>.size
|
|
31
|
+
let totalSize = headerSize + length
|
|
32
|
+
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: totalSize)
|
|
33
|
+
defer { buffer.deallocate() }
|
|
34
|
+
|
|
35
|
+
// Zero out buffer
|
|
36
|
+
buffer.initialize(repeating: 0, count: totalSize)
|
|
37
|
+
|
|
38
|
+
// Set up mach message header
|
|
39
|
+
buffer.withMemoryRebound(to: mach_msg_header_t.self, capacity: 1) { header in
|
|
40
|
+
header.pointee.msgh_bits = UInt32(MACH_MSG_TYPE_COPY_SEND)
|
|
41
|
+
header.pointee.msgh_size = mach_msg_size_t(totalSize)
|
|
42
|
+
header.pointee.msgh_remote_port = port
|
|
43
|
+
header.pointee.msgh_local_port = mach_port_t(MACH_PORT_NULL)
|
|
44
|
+
header.pointee.msgh_id = 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Copy payload after header
|
|
48
|
+
(buffer + headerSize).update(from: bytes, count: length)
|
|
49
|
+
|
|
50
|
+
let result = buffer.withMemoryRebound(to: mach_msg_header_t.self, capacity: 1) { header in
|
|
51
|
+
mach_msg(
|
|
52
|
+
header,
|
|
53
|
+
MACH_SEND_MSG,
|
|
54
|
+
mach_msg_size_t(totalSize),
|
|
55
|
+
0,
|
|
56
|
+
mach_port_t(MACH_PORT_NULL),
|
|
57
|
+
MACH_MSG_TIMEOUT_NONE,
|
|
58
|
+
mach_port_t(MACH_PORT_NULL)
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result == KERN_SUCCESS
|
|
63
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
// MARK: - Symbol Types
|
|
4
|
+
|
|
5
|
+
// Function pointer types matching SimulatorKit's IndigoHID API
|
|
6
|
+
typealias IndigoHIDMessageForButtonFn = @convention(c) (
|
|
7
|
+
UInt32, // button type
|
|
8
|
+
UInt32, // state (1=down, 2=up)
|
|
9
|
+
UInt64 // timestamp
|
|
10
|
+
) -> Unmanaged<CFData>?
|
|
11
|
+
|
|
12
|
+
typealias IndigoHIDMessageForKeyboardArbitraryFn = @convention(c) (
|
|
13
|
+
UInt32, // usage page
|
|
14
|
+
UInt32, // usage (key code)
|
|
15
|
+
UInt32, // key operation (1=down, 2=up)
|
|
16
|
+
UInt64 // timestamp
|
|
17
|
+
) -> Unmanaged<CFData>?
|
|
18
|
+
|
|
19
|
+
typealias IndigoHIDMessageForMouseNSEventFn = @convention(c) (
|
|
20
|
+
UInt32, // touch phase
|
|
21
|
+
Double, // x (0-1 normalized)
|
|
22
|
+
Double, // y (0-1 normalized)
|
|
23
|
+
UInt32, // finger index
|
|
24
|
+
Double, // pressure
|
|
25
|
+
Double, // twist
|
|
26
|
+
Double, // major radius
|
|
27
|
+
Double, // minor radius
|
|
28
|
+
UInt64 // timestamp
|
|
29
|
+
) -> Unmanaged<CFData>?
|
|
30
|
+
|
|
31
|
+
// MARK: - IndigoHID Loader
|
|
32
|
+
|
|
33
|
+
/// Loads IndigoHID symbols from SimulatorKit.framework via dlopen/dlsym.
|
|
34
|
+
/// This is a singleton — symbol loading happens once.
|
|
35
|
+
final class IndigoHIDLoader: @unchecked Sendable {
|
|
36
|
+
static let shared = IndigoHIDLoader()
|
|
37
|
+
|
|
38
|
+
let buttonFn: IndigoHIDMessageForButtonFn?
|
|
39
|
+
let keyboardFn: IndigoHIDMessageForKeyboardArbitraryFn?
|
|
40
|
+
let mouseFn: IndigoHIDMessageForMouseNSEventFn?
|
|
41
|
+
|
|
42
|
+
/// Whether all required symbols were loaded successfully.
|
|
43
|
+
var isAvailable: Bool {
|
|
44
|
+
return buttonFn != nil && keyboardFn != nil && mouseFn != nil
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private init() {
|
|
48
|
+
let frameworkPath = "/Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/Frameworks/SimulatorKit.framework/SimulatorKit"
|
|
49
|
+
let alternativePath = "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/SimulatorKit.framework/SimulatorKit"
|
|
50
|
+
|
|
51
|
+
var handle = dlopen(frameworkPath, RTLD_LAZY)
|
|
52
|
+
if handle == nil {
|
|
53
|
+
handle = dlopen(alternativePath, RTLD_LAZY)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
guard let handle else {
|
|
57
|
+
self.buttonFn = nil
|
|
58
|
+
self.keyboardFn = nil
|
|
59
|
+
self.mouseFn = nil
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if let sym = dlsym(handle, "IndigoHIDMessageForButton") {
|
|
64
|
+
self.buttonFn = unsafeBitCast(sym, to: IndigoHIDMessageForButtonFn.self)
|
|
65
|
+
} else {
|
|
66
|
+
self.buttonFn = nil
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if let sym = dlsym(handle, "IndigoHIDMessageForKeyboardArbitrary") {
|
|
70
|
+
self.keyboardFn = unsafeBitCast(sym, to: IndigoHIDMessageForKeyboardArbitraryFn.self)
|
|
71
|
+
} else {
|
|
72
|
+
self.keyboardFn = nil
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if let sym = dlsym(handle, "IndigoHIDMessageForMouseNSEvent") {
|
|
76
|
+
self.mouseFn = unsafeBitCast(sym, to: IndigoHIDMessageForMouseNSEventFn.self)
|
|
77
|
+
} else {
|
|
78
|
+
self.mouseFn = nil
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Create a touch event message.
|
|
83
|
+
func createTouchMessage(phase: IndigoHIDTouchPhase, x: Double, y: Double, finger: UInt32 = 1) -> CFData? {
|
|
84
|
+
guard let fn = mouseFn else { return nil }
|
|
85
|
+
let timestamp = mach_absolute_time()
|
|
86
|
+
return fn(phase.rawValue, x, y, finger, 1.0, 0.0, 5.0, 5.0, timestamp)?.takeRetainedValue()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Create a button event message.
|
|
90
|
+
func createButtonMessage(button: IndigoHIDButtonEventType, state: UInt32) -> CFData? {
|
|
91
|
+
guard let fn = buttonFn else { return nil }
|
|
92
|
+
let timestamp = mach_absolute_time()
|
|
93
|
+
return fn(button.rawValue, state, timestamp)?.takeRetainedValue()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Create a keyboard event message.
|
|
97
|
+
func createKeyboardMessage(usagePage: UInt32, usage: UInt32, operation: IndigoHIDKeyOperation) -> CFData? {
|
|
98
|
+
guard let fn = keyboardFn else { return nil }
|
|
99
|
+
let timestamp = mach_absolute_time()
|
|
100
|
+
return fn(usagePage, usage, operation.rawValue, timestamp)?.takeRetainedValue()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
// MARK: - Touch Phase
|
|
4
|
+
|
|
5
|
+
enum IndigoHIDTouchPhase: UInt32 {
|
|
6
|
+
case began = 1
|
|
7
|
+
case moved = 2
|
|
8
|
+
case ended = 4
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// MARK: - Button Event Type
|
|
12
|
+
|
|
13
|
+
enum IndigoHIDButtonEventType: UInt32 {
|
|
14
|
+
case home = 1
|
|
15
|
+
case lock = 2
|
|
16
|
+
case siri = 3
|
|
17
|
+
case applePay = 4
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// MARK: - Key Operation
|
|
21
|
+
|
|
22
|
+
enum IndigoHIDKeyOperation: UInt32 {
|
|
23
|
+
case keyDown = 1
|
|
24
|
+
case keyUp = 2
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// MARK: - Touch Point
|
|
28
|
+
|
|
29
|
+
struct IndigoHIDTouchPoint {
|
|
30
|
+
let x: Double // 0.0 - 1.0 normalized
|
|
31
|
+
let y: Double // 0.0 - 1.0 normalized
|
|
32
|
+
let phase: IndigoHIDTouchPhase
|
|
33
|
+
let finger: UInt32
|
|
34
|
+
|
|
35
|
+
init(x: Double, y: Double, phase: IndigoHIDTouchPhase, finger: UInt32 = 1) {
|
|
36
|
+
self.x = max(0, min(1, x))
|
|
37
|
+
self.y = max(0, min(1, y))
|
|
38
|
+
self.phase = phase
|
|
39
|
+
self.finger = finger
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -26,6 +26,32 @@ enum NativeError: Error, CustomStringConvertible {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
enum NativeErrorCode: String {
|
|
30
|
+
case invalidArguments = "invalid_arguments"
|
|
31
|
+
case unsupported = "unsupported"
|
|
32
|
+
case commandFailed = "command_failed"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
enum NativeErrorCategory: String {
|
|
36
|
+
case validation
|
|
37
|
+
case execution
|
|
38
|
+
case environment
|
|
39
|
+
case permission
|
|
40
|
+
case unsupported
|
|
41
|
+
case availability
|
|
42
|
+
case timeout
|
|
43
|
+
case unknown
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
struct StructuredNativeError {
|
|
47
|
+
let code: String
|
|
48
|
+
let category: NativeErrorCategory
|
|
49
|
+
let retryable: Bool
|
|
50
|
+
let source: String
|
|
51
|
+
let message: String
|
|
52
|
+
let nativeCode: NativeErrorCode
|
|
53
|
+
}
|
|
54
|
+
|
|
29
55
|
struct ParsedOptions {
|
|
30
56
|
let command: String
|
|
31
57
|
let options: [String: String]
|