gclm-code 1.0.0 → 1.0.1
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.md +1 -1
- package/bin/gc.js +53 -25
- package/bin/install-runtime.js +253 -0
- package/package.json +10 -5
- package/vendor/manifest.json +92 -0
- package/vendor/modules/node_modules/@ant/claude-for-chrome-mcp/package.json +9 -0
- package/vendor/modules/node_modules/@ant/claude-for-chrome-mcp/src/bridgeClient.ts +1126 -0
- package/vendor/modules/node_modules/@ant/claude-for-chrome-mcp/src/browserTools.ts +546 -0
- package/vendor/modules/node_modules/@ant/claude-for-chrome-mcp/src/index.ts +15 -0
- package/vendor/modules/node_modules/@ant/claude-for-chrome-mcp/src/mcpServer.ts +96 -0
- package/vendor/modules/node_modules/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts +493 -0
- package/vendor/modules/node_modules/@ant/claude-for-chrome-mcp/src/mcpSocketPool.ts +327 -0
- package/vendor/modules/node_modules/@ant/claude-for-chrome-mcp/src/toolCalls.ts +301 -0
- package/vendor/modules/node_modules/@ant/claude-for-chrome-mcp/src/types.ts +134 -0
- package/vendor/modules/node_modules/@ant/computer-use-input/package.json +9 -0
- package/vendor/modules/node_modules/@ant/computer-use-input/src/driver-jxa.js +341 -0
- package/vendor/modules/node_modules/@ant/computer-use-input/src/driver-swift.swift +417 -0
- package/vendor/modules/node_modules/@ant/computer-use-input/src/implementation.js +204 -0
- package/vendor/modules/node_modules/@ant/computer-use-input/src/index.js +5 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/package.json +11 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/deniedApps.ts +553 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/imageResize.ts +108 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/index.ts +69 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/keyBlocklist.ts +153 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/mcpServer.ts +313 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/pixelCompare.ts +171 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/sentinelApps.ts +43 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/subGates.ts +19 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/toolCalls.ts +3872 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/tools.ts +706 -0
- package/vendor/modules/node_modules/@ant/computer-use-mcp/src/types.ts +635 -0
- package/vendor/modules/node_modules/@ant/computer-use-swift/package.json +9 -0
- package/vendor/modules/node_modules/@ant/computer-use-swift/src/driver-jxa.js +108 -0
- package/vendor/modules/node_modules/@ant/computer-use-swift/src/implementation.js +706 -0
- package/vendor/modules/node_modules/@ant/computer-use-swift/src/index.js +7 -0
- package/vendor/modules/node_modules/audio-capture-napi/package.json +8 -0
- package/vendor/modules/node_modules/audio-capture-napi/src/index.ts +226 -0
- package/vendor/modules/node_modules/image-processor-napi/package.json +11 -0
- package/vendor/modules/node_modules/image-processor-napi/src/index.ts +396 -0
- package/vendor/modules/node_modules/modifiers-napi/package.json +8 -0
- package/vendor/modules/node_modules/modifiers-napi/src/index.ts +79 -0
- package/vendor/modules/node_modules/url-handler-napi/package.json +8 -0
- package/vendor/modules/node_modules/url-handler-napi/src/index.ts +62 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ApplicationServices
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
struct Payload: Decodable {
|
|
6
|
+
let op: String
|
|
7
|
+
let x: Double?
|
|
8
|
+
let y: Double?
|
|
9
|
+
let dragButton: String?
|
|
10
|
+
let button: String?
|
|
11
|
+
let action: String?
|
|
12
|
+
let count: Int?
|
|
13
|
+
let amount: Double?
|
|
14
|
+
let axis: String?
|
|
15
|
+
let text: String?
|
|
16
|
+
let key: String?
|
|
17
|
+
let keys: [String]?
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct DriverError: Error, CustomStringConvertible {
|
|
21
|
+
let description: String
|
|
22
|
+
|
|
23
|
+
init(_ description: String) {
|
|
24
|
+
self.description = description
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let buttonCodes: [String: CGMouseButton] = [
|
|
29
|
+
"left": .left,
|
|
30
|
+
"right": .right,
|
|
31
|
+
"middle": .center,
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
let buttonEventTypes: [String: (down: CGEventType, up: CGEventType, dragged: CGEventType)] = [
|
|
35
|
+
"left": (.leftMouseDown, .leftMouseUp, .leftMouseDragged),
|
|
36
|
+
"right": (.rightMouseDown, .rightMouseUp, .rightMouseDragged),
|
|
37
|
+
"middle": (.otherMouseDown, .otherMouseUp, .otherMouseDragged),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
let keyCodes: [String: CGKeyCode] = [
|
|
41
|
+
"a": 0,
|
|
42
|
+
"s": 1,
|
|
43
|
+
"d": 2,
|
|
44
|
+
"f": 3,
|
|
45
|
+
"h": 4,
|
|
46
|
+
"g": 5,
|
|
47
|
+
"z": 6,
|
|
48
|
+
"x": 7,
|
|
49
|
+
"c": 8,
|
|
50
|
+
"v": 9,
|
|
51
|
+
"b": 11,
|
|
52
|
+
"q": 12,
|
|
53
|
+
"w": 13,
|
|
54
|
+
"e": 14,
|
|
55
|
+
"r": 15,
|
|
56
|
+
"y": 16,
|
|
57
|
+
"t": 17,
|
|
58
|
+
"1": 18,
|
|
59
|
+
"2": 19,
|
|
60
|
+
"3": 20,
|
|
61
|
+
"4": 21,
|
|
62
|
+
"6": 22,
|
|
63
|
+
"5": 23,
|
|
64
|
+
"=": 24,
|
|
65
|
+
"9": 25,
|
|
66
|
+
"7": 26,
|
|
67
|
+
"-": 27,
|
|
68
|
+
"8": 28,
|
|
69
|
+
"0": 29,
|
|
70
|
+
"]": 30,
|
|
71
|
+
"o": 31,
|
|
72
|
+
"u": 32,
|
|
73
|
+
"[": 33,
|
|
74
|
+
"i": 34,
|
|
75
|
+
"p": 35,
|
|
76
|
+
"return": 36,
|
|
77
|
+
"l": 37,
|
|
78
|
+
"j": 38,
|
|
79
|
+
"'": 39,
|
|
80
|
+
"k": 40,
|
|
81
|
+
";": 41,
|
|
82
|
+
"\\": 42,
|
|
83
|
+
",": 43,
|
|
84
|
+
"/": 44,
|
|
85
|
+
"n": 45,
|
|
86
|
+
"m": 46,
|
|
87
|
+
".": 47,
|
|
88
|
+
"tab": 48,
|
|
89
|
+
"space": 49,
|
|
90
|
+
"`": 50,
|
|
91
|
+
"delete": 51,
|
|
92
|
+
"escape": 53,
|
|
93
|
+
"command": 55,
|
|
94
|
+
"shift": 56,
|
|
95
|
+
"capslock": 57,
|
|
96
|
+
"option": 58,
|
|
97
|
+
"control": 59,
|
|
98
|
+
"rightshift": 60,
|
|
99
|
+
"rightoption": 61,
|
|
100
|
+
"rightcontrol": 62,
|
|
101
|
+
"function": 63,
|
|
102
|
+
"f17": 64,
|
|
103
|
+
"volumeup": 72,
|
|
104
|
+
"volumedown": 73,
|
|
105
|
+
"mute": 74,
|
|
106
|
+
"enter": 76,
|
|
107
|
+
"f18": 79,
|
|
108
|
+
"f19": 80,
|
|
109
|
+
"f20": 90,
|
|
110
|
+
"f5": 96,
|
|
111
|
+
"f6": 97,
|
|
112
|
+
"f7": 98,
|
|
113
|
+
"f3": 99,
|
|
114
|
+
"f8": 100,
|
|
115
|
+
"f9": 101,
|
|
116
|
+
"f11": 103,
|
|
117
|
+
"f13": 105,
|
|
118
|
+
"f16": 106,
|
|
119
|
+
"f14": 107,
|
|
120
|
+
"f10": 109,
|
|
121
|
+
"f12": 111,
|
|
122
|
+
"f15": 113,
|
|
123
|
+
"help": 114,
|
|
124
|
+
"home": 115,
|
|
125
|
+
"pageup": 116,
|
|
126
|
+
"forwarddelete": 117,
|
|
127
|
+
"f4": 118,
|
|
128
|
+
"end": 119,
|
|
129
|
+
"f2": 120,
|
|
130
|
+
"pagedown": 121,
|
|
131
|
+
"f1": 122,
|
|
132
|
+
"left": 123,
|
|
133
|
+
"right": 124,
|
|
134
|
+
"down": 125,
|
|
135
|
+
"up": 126,
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
let modifierFlags: [String: CGEventFlags] = [
|
|
139
|
+
"command": .maskCommand,
|
|
140
|
+
"shift": .maskShift,
|
|
141
|
+
"option": .maskAlternate,
|
|
142
|
+
"control": .maskControl,
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
func emit(_ value: Any) throws {
|
|
146
|
+
let data = try JSONSerialization.data(withJSONObject: value, options: [.fragmentsAllowed])
|
|
147
|
+
FileHandle.standardOutput.write(data)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
func requireValue<T>(_ value: T?, _ message: String) throws -> T {
|
|
151
|
+
guard let value else {
|
|
152
|
+
throw DriverError(message)
|
|
153
|
+
}
|
|
154
|
+
return value
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
func currentMouseLocation() -> CGPoint {
|
|
158
|
+
CGEvent(source: nil)?.location ?? NSEvent.mouseLocation
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
func postMouseEvent(
|
|
162
|
+
_ type: CGEventType,
|
|
163
|
+
location: CGPoint,
|
|
164
|
+
buttonName: String,
|
|
165
|
+
clickState: Int64? = nil
|
|
166
|
+
) throws {
|
|
167
|
+
let button = buttonCodes[buttonName] ?? .left
|
|
168
|
+
guard let event = CGEvent(
|
|
169
|
+
mouseEventSource: nil,
|
|
170
|
+
mouseType: type,
|
|
171
|
+
mouseCursorPosition: location,
|
|
172
|
+
mouseButton: button
|
|
173
|
+
) else {
|
|
174
|
+
throw DriverError("Unable to create mouse event")
|
|
175
|
+
}
|
|
176
|
+
if let clickState {
|
|
177
|
+
event.setIntegerValueField(.mouseEventClickState, value: clickState)
|
|
178
|
+
}
|
|
179
|
+
event.post(tap: .cghidEventTap)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
func moveMouse(_ x: Double, _ y: Double, _ dragButton: String?) throws -> Bool {
|
|
183
|
+
let location = CGPoint(x: x, y: y)
|
|
184
|
+
if let dragButton, !dragButton.isEmpty {
|
|
185
|
+
let types = buttonEventTypes[dragButton]
|
|
186
|
+
try postMouseEvent(types?.dragged ?? .leftMouseDragged, location: location, buttonName: dragButton)
|
|
187
|
+
return true
|
|
188
|
+
}
|
|
189
|
+
try postMouseEvent(.mouseMoved, location: location, buttonName: "left")
|
|
190
|
+
return true
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
func mouseButton(_ button: String, _ action: String?, _ count: Int?) throws -> Bool {
|
|
194
|
+
guard let types = buttonEventTypes[button] else {
|
|
195
|
+
throw DriverError("Unsupported mouse button: \(button)")
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let location = currentMouseLocation()
|
|
199
|
+
let normalizedAction: String
|
|
200
|
+
switch (action ?? "click").lowercased() {
|
|
201
|
+
case "press":
|
|
202
|
+
normalizedAction = "down"
|
|
203
|
+
case "release":
|
|
204
|
+
normalizedAction = "up"
|
|
205
|
+
default:
|
|
206
|
+
normalizedAction = (action ?? "click").lowercased()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if normalizedAction == "click" {
|
|
210
|
+
let clickCount = max(1, count ?? 1)
|
|
211
|
+
for index in 1...clickCount {
|
|
212
|
+
try postMouseEvent(types.down, location: location, buttonName: button, clickState: Int64(index))
|
|
213
|
+
try postMouseEvent(types.up, location: location, buttonName: button, clickState: Int64(index))
|
|
214
|
+
}
|
|
215
|
+
return true
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let eventType: CGEventType
|
|
219
|
+
switch normalizedAction {
|
|
220
|
+
case "down":
|
|
221
|
+
eventType = types.down
|
|
222
|
+
case "up":
|
|
223
|
+
eventType = types.up
|
|
224
|
+
default:
|
|
225
|
+
throw DriverError("Unsupported mouse action: \(action ?? "nil")")
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try postMouseEvent(eventType, location: location, buttonName: button, clickState: Int64(max(1, count ?? 1)))
|
|
229
|
+
return true
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
func mouseLocation() -> [String: Int] {
|
|
233
|
+
let location = currentMouseLocation()
|
|
234
|
+
return [
|
|
235
|
+
"x": Int(lround(location.x)),
|
|
236
|
+
"y": Int(lround(location.y)),
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
func mouseScroll(_ amount: Double, _ axis: String?) throws -> Bool {
|
|
241
|
+
let normalizedAxis = (axis ?? "vertical").lowercased()
|
|
242
|
+
let vertical = normalizedAxis == "y" || normalizedAxis == "vertical" ? Int32(lround(amount)) : 0
|
|
243
|
+
let horizontal = normalizedAxis == "x" || normalizedAxis == "horizontal" ? Int32(lround(amount)) : 0
|
|
244
|
+
|
|
245
|
+
guard let event = CGEvent(
|
|
246
|
+
scrollWheelEvent2Source: nil,
|
|
247
|
+
units: .pixel,
|
|
248
|
+
wheelCount: 2,
|
|
249
|
+
wheel1: vertical,
|
|
250
|
+
wheel2: horizontal,
|
|
251
|
+
wheel3: 0
|
|
252
|
+
) else {
|
|
253
|
+
throw DriverError("Unable to create scroll event")
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
event.post(tap: .cghidEventTap)
|
|
257
|
+
return true
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
func typeText(_ text: String) throws -> Bool {
|
|
261
|
+
guard let source = CGEventSource(stateID: .hidSystemState) else {
|
|
262
|
+
throw DriverError("Unable to create keyboard event source")
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
for scalar in text.utf16 {
|
|
266
|
+
var chars = [scalar]
|
|
267
|
+
|
|
268
|
+
guard let down = CGEvent(keyboardEventSource: source, virtualKey: 0, keyDown: true) else {
|
|
269
|
+
throw DriverError("Unable to create key down event")
|
|
270
|
+
}
|
|
271
|
+
down.keyboardSetUnicodeString(stringLength: chars.count, unicodeString: &chars)
|
|
272
|
+
down.post(tap: .cghidEventTap)
|
|
273
|
+
|
|
274
|
+
guard let up = CGEvent(keyboardEventSource: source, virtualKey: 0, keyDown: false) else {
|
|
275
|
+
throw DriverError("Unable to create key up event")
|
|
276
|
+
}
|
|
277
|
+
up.keyboardSetUnicodeString(stringLength: chars.count, unicodeString: &chars)
|
|
278
|
+
up.post(tap: .cghidEventTap)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return true
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
func key(_ name: String, _ action: String?) throws -> Bool {
|
|
285
|
+
let normalizedName = name.lowercased()
|
|
286
|
+
guard let keyCode = keyCodes[normalizedName] else {
|
|
287
|
+
throw DriverError("Unsupported key: \(name)")
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let normalizedAction = (action ?? "press").lowercased()
|
|
291
|
+
switch normalizedAction {
|
|
292
|
+
case "down", "up", "press", "release":
|
|
293
|
+
break
|
|
294
|
+
default:
|
|
295
|
+
throw DriverError("Unsupported key action: \(action ?? "nil")")
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let isDown = normalizedAction == "down" || normalizedAction == "press"
|
|
299
|
+
guard let event = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: isDown) else {
|
|
300
|
+
throw DriverError("Unable to create keyboard event")
|
|
301
|
+
}
|
|
302
|
+
event.post(tap: .cghidEventTap)
|
|
303
|
+
return true
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
func keys(_ names: [String]) throws -> Bool {
|
|
307
|
+
var flags: CGEventFlags = []
|
|
308
|
+
|
|
309
|
+
for name in names {
|
|
310
|
+
let normalizedName = name.lowercased()
|
|
311
|
+
guard let keyCode = keyCodes[normalizedName] else {
|
|
312
|
+
throw DriverError("Unsupported key: \(name)")
|
|
313
|
+
}
|
|
314
|
+
guard let event = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true) else {
|
|
315
|
+
throw DriverError("Unable to create key down event")
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if let modifierFlag = modifierFlags[normalizedName] {
|
|
319
|
+
flags.insert(modifierFlag)
|
|
320
|
+
event.flags = flags
|
|
321
|
+
} else if !flags.isEmpty {
|
|
322
|
+
event.flags = flags
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
event.post(tap: .cghidEventTap)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
for name in names.reversed() {
|
|
329
|
+
let normalizedName = name.lowercased()
|
|
330
|
+
guard let keyCode = keyCodes[normalizedName] else {
|
|
331
|
+
throw DriverError("Unsupported key: \(name)")
|
|
332
|
+
}
|
|
333
|
+
guard let event = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: false) else {
|
|
334
|
+
throw DriverError("Unable to create key up event")
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if let modifierFlag = modifierFlags[normalizedName] {
|
|
338
|
+
flags.remove(modifierFlag)
|
|
339
|
+
if !flags.isEmpty {
|
|
340
|
+
event.flags = flags
|
|
341
|
+
}
|
|
342
|
+
} else if !flags.isEmpty {
|
|
343
|
+
event.flags = flags
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
event.post(tap: .cghidEventTap)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return true
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
func frontmostAppInfo() -> Any {
|
|
353
|
+
guard let app = NSWorkspace.shared.frontmostApplication else {
|
|
354
|
+
return NSNull()
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
var result: [String: String] = [:]
|
|
358
|
+
if let bundleId = app.bundleIdentifier {
|
|
359
|
+
result["bundleId"] = bundleId
|
|
360
|
+
}
|
|
361
|
+
if let name = app.localizedName {
|
|
362
|
+
result["appName"] = name
|
|
363
|
+
result["name"] = name
|
|
364
|
+
}
|
|
365
|
+
return result
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
func dispatch(_ payload: Payload) throws -> Any {
|
|
369
|
+
switch payload.op {
|
|
370
|
+
case "moveMouse":
|
|
371
|
+
return try moveMouse(
|
|
372
|
+
try requireValue(payload.x, "Missing x for moveMouse"),
|
|
373
|
+
try requireValue(payload.y, "Missing y for moveMouse"),
|
|
374
|
+
payload.dragButton
|
|
375
|
+
)
|
|
376
|
+
case "mouseButton":
|
|
377
|
+
return try mouseButton(
|
|
378
|
+
try requireValue(payload.button, "Missing button for mouseButton"),
|
|
379
|
+
payload.action,
|
|
380
|
+
payload.count
|
|
381
|
+
)
|
|
382
|
+
case "mouseLocation":
|
|
383
|
+
return mouseLocation()
|
|
384
|
+
case "mouseScroll":
|
|
385
|
+
return try mouseScroll(try requireValue(payload.amount, "Missing amount for mouseScroll"), payload.axis)
|
|
386
|
+
case "typeText":
|
|
387
|
+
return try typeText(try requireValue(payload.text, "Missing text for typeText"))
|
|
388
|
+
case "key":
|
|
389
|
+
return try key(
|
|
390
|
+
try requireValue(payload.key, "Missing key for key"),
|
|
391
|
+
payload.action
|
|
392
|
+
)
|
|
393
|
+
case "keys":
|
|
394
|
+
return try keys(try requireValue(payload.keys, "Missing keys for keys"))
|
|
395
|
+
case "frontmostAppInfo":
|
|
396
|
+
return frontmostAppInfo()
|
|
397
|
+
default:
|
|
398
|
+
throw DriverError("Unsupported operation: \(payload.op)")
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
func main() throws {
|
|
403
|
+
guard CommandLine.arguments.count >= 2, let json = CommandLine.arguments.last else {
|
|
404
|
+
throw DriverError("Expected JSON payload argument")
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let payload = try JSONDecoder().decode(Payload.self, from: Data(json.utf8))
|
|
408
|
+
try emit(try dispatch(payload))
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
do {
|
|
412
|
+
try main()
|
|
413
|
+
} catch {
|
|
414
|
+
let message = String(describing: error)
|
|
415
|
+
FileHandle.standardError.write(Data((message + "\n").utf8))
|
|
416
|
+
exit(1)
|
|
417
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { mkdirSync, statSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { dirname, join } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const SOURCE_DIR = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const JXA_DRIVER_PATH = join(
|
|
9
|
+
SOURCE_DIR,
|
|
10
|
+
'driver-jxa.js',
|
|
11
|
+
)
|
|
12
|
+
const SWIFT_DRIVER_PATH = join(
|
|
13
|
+
SOURCE_DIR,
|
|
14
|
+
'driver-swift.swift',
|
|
15
|
+
)
|
|
16
|
+
const BUILD_DIR = join(tmpdir(), 'claude-code-computer-use-input')
|
|
17
|
+
const SWIFT_MODULE_CACHE_DIR = join(BUILD_DIR, 'swift-module-cache')
|
|
18
|
+
const SWIFT_BINARY_PATH = join(BUILD_DIR, 'driver-swift')
|
|
19
|
+
|
|
20
|
+
function statMtimeMs(path) {
|
|
21
|
+
try {
|
|
22
|
+
return statSync(path).mtimeMs
|
|
23
|
+
} catch {
|
|
24
|
+
return 0
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function trimCommandFailure(result, fallbackMessage) {
|
|
29
|
+
return (result.stderr || result.stdout || fallbackMessage).trim()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function ensureSwiftDriver() {
|
|
33
|
+
const sourceMtime = statMtimeMs(SWIFT_DRIVER_PATH)
|
|
34
|
+
if (!sourceMtime) {
|
|
35
|
+
return { available: false, reason: 'compiled input driver source is missing' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const binaryMtime = statMtimeMs(SWIFT_BINARY_PATH)
|
|
39
|
+
if (binaryMtime >= sourceMtime) {
|
|
40
|
+
return { available: true }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
mkdirSync(SWIFT_MODULE_CACHE_DIR, { recursive: true })
|
|
44
|
+
const result = spawnSync(
|
|
45
|
+
'/usr/bin/swiftc',
|
|
46
|
+
[
|
|
47
|
+
'-module-cache-path',
|
|
48
|
+
SWIFT_MODULE_CACHE_DIR,
|
|
49
|
+
SWIFT_DRIVER_PATH,
|
|
50
|
+
'-o',
|
|
51
|
+
SWIFT_BINARY_PATH,
|
|
52
|
+
],
|
|
53
|
+
{
|
|
54
|
+
encoding: 'utf8',
|
|
55
|
+
stdio: 'pipe',
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if (result.error) {
|
|
60
|
+
return { available: false, reason: result.error.message }
|
|
61
|
+
}
|
|
62
|
+
if (result.status !== 0) {
|
|
63
|
+
return {
|
|
64
|
+
available: false,
|
|
65
|
+
reason: trimCommandFailure(result, 'swiftc failed'),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { available: true }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function runSwiftDriver(payload) {
|
|
73
|
+
const result = spawnSync(SWIFT_BINARY_PATH, [JSON.stringify(payload)], {
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
stdio: 'pipe',
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
if (result.error) {
|
|
79
|
+
throw result.error
|
|
80
|
+
}
|
|
81
|
+
if (result.status !== 0) {
|
|
82
|
+
throw new Error(trimCommandFailure(result, 'compiled input driver failed'))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const output = (result.stdout ?? '').trim()
|
|
86
|
+
if (!output) {
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
return JSON.parse(output)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function runJxaDriver(payload) {
|
|
93
|
+
const result = spawnSync(
|
|
94
|
+
'/usr/bin/osascript',
|
|
95
|
+
['-l', 'JavaScript', JXA_DRIVER_PATH, JSON.stringify(payload)],
|
|
96
|
+
{
|
|
97
|
+
encoding: 'utf8',
|
|
98
|
+
stdio: 'pipe',
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if (result.error) {
|
|
103
|
+
throw result.error
|
|
104
|
+
}
|
|
105
|
+
if (result.status !== 0) {
|
|
106
|
+
throw new Error(trimCommandFailure(result, 'osascript failed'))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const output = (result.stdout ?? '').trim()
|
|
110
|
+
if (!output) {
|
|
111
|
+
return true
|
|
112
|
+
}
|
|
113
|
+
return JSON.parse(output)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function typeTextViaSystemEvents(text) {
|
|
117
|
+
const result = spawnSync(
|
|
118
|
+
'/usr/bin/osascript',
|
|
119
|
+
[
|
|
120
|
+
'-e',
|
|
121
|
+
'on run argv',
|
|
122
|
+
'-e',
|
|
123
|
+
'tell application "System Events" to keystroke (item 1 of argv)',
|
|
124
|
+
'-e',
|
|
125
|
+
'end run',
|
|
126
|
+
text,
|
|
127
|
+
],
|
|
128
|
+
{
|
|
129
|
+
encoding: 'utf8',
|
|
130
|
+
stdio: 'pipe',
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if (result.error) {
|
|
135
|
+
throw result.error
|
|
136
|
+
}
|
|
137
|
+
if (result.status !== 0) {
|
|
138
|
+
throw new Error(trimCommandFailure(result, 'System Events keystroke failed'))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function runDriver(payload) {
|
|
143
|
+
const swift = ensureSwiftDriver()
|
|
144
|
+
if (swift.available) {
|
|
145
|
+
return runSwiftDriver(payload)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
return runJxaDriver(payload)
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const details = String(error instanceof Error ? error.message : error)
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Compiled input driver unavailable: ${swift.reason}; JXA fallback failed: ${details}`,
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function moveMouse(x, y, dragButton = null) {
|
|
159
|
+
runDriver({ op: 'moveMouse', x, y, dragButton })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function mouseButton(button, action, count) {
|
|
163
|
+
runDriver({ op: 'mouseButton', button, action, count })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function mouseLocation() {
|
|
167
|
+
return runDriver({ op: 'mouseLocation' })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function mouseScroll(amount, axis) {
|
|
171
|
+
runDriver({ op: 'mouseScroll', amount, axis })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function typeText(text) {
|
|
175
|
+
try {
|
|
176
|
+
typeTextViaSystemEvents(text)
|
|
177
|
+
} catch {
|
|
178
|
+
runDriver({ op: 'typeText', text })
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function key(keyName, action) {
|
|
183
|
+
runDriver({ op: 'key', key: keyName, action })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function keys(parts) {
|
|
187
|
+
runDriver({ op: 'keys', keys: parts })
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getFrontmostAppInfo() {
|
|
191
|
+
return runDriver({ op: 'frontmostAppInfo' })
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export default {
|
|
195
|
+
isSupported: process.platform === 'darwin',
|
|
196
|
+
moveMouse,
|
|
197
|
+
mouseButton,
|
|
198
|
+
mouseLocation,
|
|
199
|
+
mouseScroll,
|
|
200
|
+
typeText,
|
|
201
|
+
key,
|
|
202
|
+
keys,
|
|
203
|
+
getFrontmostAppInfo,
|
|
204
|
+
}
|