@mseep/clawdcursor 1.5.5
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/CHANGELOG.md +2264 -0
- package/LICENSE +21 -0
- package/README.md +385 -0
- package/SECURITY.md +44 -0
- package/SKILL.md +503 -0
- package/dist/core/agent-loop/agent.d.ts +42 -0
- package/dist/core/agent-loop/agent.js +1023 -0
- package/dist/core/agent-loop/agent.js.map +1 -0
- package/dist/core/agent-loop/batch-tool.d.ts +25 -0
- package/dist/core/agent-loop/batch-tool.js +218 -0
- package/dist/core/agent-loop/batch-tool.js.map +1 -0
- package/dist/core/agent-loop/coord-scale.d.ts +72 -0
- package/dist/core/agent-loop/coord-scale.js +89 -0
- package/dist/core/agent-loop/coord-scale.js.map +1 -0
- package/dist/core/agent-loop/focus-guard.d.ts +24 -0
- package/dist/core/agent-loop/focus-guard.js +29 -0
- package/dist/core/agent-loop/focus-guard.js.map +1 -0
- package/dist/core/agent-loop/project-mcp.d.ts +97 -0
- package/dist/core/agent-loop/project-mcp.js +253 -0
- package/dist/core/agent-loop/project-mcp.js.map +1 -0
- package/dist/core/agent-loop/prompt.d.ts +45 -0
- package/dist/core/agent-loop/prompt.js +426 -0
- package/dist/core/agent-loop/prompt.js.map +1 -0
- package/dist/core/agent-loop/tool-meta.d.ts +93 -0
- package/dist/core/agent-loop/tool-meta.js +651 -0
- package/dist/core/agent-loop/tool-meta.js.map +1 -0
- package/dist/core/agent-loop/tools.d.ts +38 -0
- package/dist/core/agent-loop/tools.js +2134 -0
- package/dist/core/agent-loop/tools.js.map +1 -0
- package/dist/core/agent-loop/types.d.ts +170 -0
- package/dist/core/agent-loop/types.js +12 -0
- package/dist/core/agent-loop/types.js.map +1 -0
- package/dist/core/agent.d.ts +51 -0
- package/dist/core/agent.js +245 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/app-categories.d.ts +67 -0
- package/dist/core/app-categories.js +108 -0
- package/dist/core/app-categories.js.map +1 -0
- package/dist/core/banner.d.ts +70 -0
- package/dist/core/banner.js +245 -0
- package/dist/core/banner.js.map +1 -0
- package/dist/core/classify/capability.d.ts +45 -0
- package/dist/core/classify/capability.js +78 -0
- package/dist/core/classify/capability.js.map +1 -0
- package/dist/core/decompose/llm-decomposer.d.ts +35 -0
- package/dist/core/decompose/llm-decomposer.js +156 -0
- package/dist/core/decompose/llm-decomposer.js.map +1 -0
- package/dist/core/decompose/parser.d.ts +27 -0
- package/dist/core/decompose/parser.js +101 -0
- package/dist/core/decompose/parser.js.map +1 -0
- package/dist/core/observability/correlation.d.ts +19 -0
- package/dist/core/observability/correlation.js +36 -0
- package/dist/core/observability/correlation.js.map +1 -0
- package/dist/core/observability/cost-meter.d.ts +51 -0
- package/dist/core/observability/cost-meter.js +134 -0
- package/dist/core/observability/cost-meter.js.map +1 -0
- package/dist/core/observability/logger.d.ts +61 -0
- package/dist/core/observability/logger.js +550 -0
- package/dist/core/observability/logger.js.map +1 -0
- package/dist/core/router/aliases.d.ts +50 -0
- package/dist/core/router/aliases.js +104 -0
- package/dist/core/router/aliases.js.map +1 -0
- package/dist/core/router/normalize.d.ts +41 -0
- package/dist/core/router/normalize.js +80 -0
- package/dist/core/router/normalize.js.map +1 -0
- package/dist/core/safety.d.ts +126 -0
- package/dist/core/safety.js +568 -0
- package/dist/core/safety.js.map +1 -0
- package/dist/core/sense/a11y-resolver.d.ts +73 -0
- package/dist/core/sense/a11y-resolver.js +76 -0
- package/dist/core/sense/a11y-resolver.js.map +1 -0
- package/dist/core/sense/fingerprint.d.ts +41 -0
- package/dist/core/sense/fingerprint.js +123 -0
- package/dist/core/sense/fingerprint.js.map +1 -0
- package/dist/core/sense/rank.d.ts +70 -0
- package/dist/core/sense/rank.js +192 -0
- package/dist/core/sense/rank.js.map +1 -0
- package/dist/core/sense/reactive-check.d.ts +40 -0
- package/dist/core/sense/reactive-check.js +48 -0
- package/dist/core/sense/reactive-check.js.map +1 -0
- package/dist/core/sense/snapshot.d.ts +19 -0
- package/dist/core/sense/snapshot.js +100 -0
- package/dist/core/sense/snapshot.js.map +1 -0
- package/dist/core/sense/types.d.ts +66 -0
- package/dist/core/sense/types.js +9 -0
- package/dist/core/sense/types.js.map +1 -0
- package/dist/core/sense/ui-map-anchors.d.ts +7 -0
- package/dist/core/sense/ui-map-anchors.js +24 -0
- package/dist/core/sense/ui-map-anchors.js.map +1 -0
- package/dist/core/sense/ui-map-elements.d.ts +5 -0
- package/dist/core/sense/ui-map-elements.js +33 -0
- package/dist/core/sense/ui-map-elements.js.map +1 -0
- package/dist/core/sense/ui-map-find.d.ts +56 -0
- package/dist/core/sense/ui-map-find.js +153 -0
- package/dist/core/sense/ui-map-find.js.map +1 -0
- package/dist/core/sense/ui-map-fuse.d.ts +4 -0
- package/dist/core/sense/ui-map-fuse.js +44 -0
- package/dist/core/sense/ui-map-fuse.js.map +1 -0
- package/dist/core/sense/ui-map-geom.d.ts +3 -0
- package/dist/core/sense/ui-map-geom.js +16 -0
- package/dist/core/sense/ui-map-geom.js.map +1 -0
- package/dist/core/sense/ui-map-holder.d.ts +58 -0
- package/dist/core/sense/ui-map-holder.js +87 -0
- package/dist/core/sense/ui-map-holder.js.map +1 -0
- package/dist/core/sense/ui-map-normalize.d.ts +19 -0
- package/dist/core/sense/ui-map-normalize.js +65 -0
- package/dist/core/sense/ui-map-normalize.js.map +1 -0
- package/dist/core/sense/ui-map-render.d.ts +4 -0
- package/dist/core/sense/ui-map-render.js +34 -0
- package/dist/core/sense/ui-map-render.js.map +1 -0
- package/dist/core/sense/ui-map-resolve.d.ts +41 -0
- package/dist/core/sense/ui-map-resolve.js +59 -0
- package/dist/core/sense/ui-map-resolve.js.map +1 -0
- package/dist/core/sense/ui-map-types.d.ts +66 -0
- package/dist/core/sense/ui-map-types.js +11 -0
- package/dist/core/sense/ui-map-types.js.map +1 -0
- package/dist/core/sense/ui-map.d.ts +29 -0
- package/dist/core/sense/ui-map.js +113 -0
- package/dist/core/sense/ui-map.js.map +1 -0
- package/dist/core/verify/assertions.d.ts +132 -0
- package/dist/core/verify/assertions.js +284 -0
- package/dist/core/verify/assertions.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/browser-config.d.ts +36 -0
- package/dist/llm/browser-config.js +83 -0
- package/dist/llm/browser-config.js.map +1 -0
- package/dist/llm/client.d.ts +268 -0
- package/dist/llm/client.js +1094 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/llm/config.d.ts +79 -0
- package/dist/llm/config.js +375 -0
- package/dist/llm/config.js.map +1 -0
- package/dist/llm/credentials.d.ts +35 -0
- package/dist/llm/credentials.js +491 -0
- package/dist/llm/credentials.js.map +1 -0
- package/dist/llm/external-creds.d.ts +42 -0
- package/dist/llm/external-creds.js +169 -0
- package/dist/llm/external-creds.js.map +1 -0
- package/dist/llm/providers.d.ts +123 -0
- package/dist/llm/providers.js +717 -0
- package/dist/llm/providers.js.map +1 -0
- package/dist/paths.d.ts +31 -0
- package/dist/paths.js +147 -0
- package/dist/paths.js.map +1 -0
- package/dist/platform/accessibility.d.ts +139 -0
- package/dist/platform/accessibility.js +670 -0
- package/dist/platform/accessibility.js.map +1 -0
- package/dist/platform/cdp-driver.d.ts +318 -0
- package/dist/platform/cdp-driver.js +1179 -0
- package/dist/platform/cdp-driver.js.map +1 -0
- package/dist/platform/index.d.ts +11 -0
- package/dist/platform/index.js +69 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/keys.d.ts +17 -0
- package/dist/platform/keys.js +129 -0
- package/dist/platform/keys.js.map +1 -0
- package/dist/platform/launch-poll.d.ts +101 -0
- package/dist/platform/launch-poll.js +177 -0
- package/dist/platform/launch-poll.js.map +1 -0
- package/dist/platform/linux.d.ts +173 -0
- package/dist/platform/linux.js +1253 -0
- package/dist/platform/linux.js.map +1 -0
- package/dist/platform/macos.d.ts +136 -0
- package/dist/platform/macos.js +976 -0
- package/dist/platform/macos.js.map +1 -0
- package/dist/platform/native-desktop.d.ts +145 -0
- package/dist/platform/native-desktop.js +936 -0
- package/dist/platform/native-desktop.js.map +1 -0
- package/dist/platform/native-helper.d.ts +130 -0
- package/dist/platform/native-helper.js +592 -0
- package/dist/platform/native-helper.js.map +1 -0
- package/dist/platform/ocr-engine.d.ts +78 -0
- package/dist/platform/ocr-engine.js +363 -0
- package/dist/platform/ocr-engine.js.map +1 -0
- package/dist/platform/ps-runner.d.ts +28 -0
- package/dist/platform/ps-runner.js +228 -0
- package/dist/platform/ps-runner.js.map +1 -0
- package/dist/platform/types.d.ts +397 -0
- package/dist/platform/types.js +15 -0
- package/dist/platform/types.js.map +1 -0
- package/dist/platform/uri-handler.d.ts +75 -0
- package/dist/platform/uri-handler.js +273 -0
- package/dist/platform/uri-handler.js.map +1 -0
- package/dist/platform/wayland-backend.d.ts +53 -0
- package/dist/platform/wayland-backend.js +348 -0
- package/dist/platform/wayland-backend.js.map +1 -0
- package/dist/platform/windows.d.ts +232 -0
- package/dist/platform/windows.js +1210 -0
- package/dist/platform/windows.js.map +1 -0
- package/dist/postbuild.d.ts +10 -0
- package/dist/postbuild.js +98 -0
- package/dist/postbuild.js.map +1 -0
- package/dist/schema/snapshot.d.ts +33 -0
- package/dist/schema/snapshot.js +90 -0
- package/dist/schema/snapshot.js.map +1 -0
- package/dist/shortcuts.d.ts +30 -0
- package/dist/shortcuts.js +261 -0
- package/dist/shortcuts.js.map +1 -0
- package/dist/surface/cli.d.ts +7 -0
- package/dist/surface/cli.js +1556 -0
- package/dist/surface/cli.js.map +1 -0
- package/dist/surface/dashboard.d.ts +8 -0
- package/dist/surface/dashboard.js +1193 -0
- package/dist/surface/dashboard.js.map +1 -0
- package/dist/surface/doctor.d.ts +29 -0
- package/dist/surface/doctor.js +1514 -0
- package/dist/surface/doctor.js.map +1 -0
- package/dist/surface/format.d.ts +10 -0
- package/dist/surface/format.js +37 -0
- package/dist/surface/format.js.map +1 -0
- package/dist/surface/http-utility.d.ts +65 -0
- package/dist/surface/http-utility.js +336 -0
- package/dist/surface/http-utility.js.map +1 -0
- package/dist/surface/mcp-server.d.ts +91 -0
- package/dist/surface/mcp-server.js +280 -0
- package/dist/surface/mcp-server.js.map +1 -0
- package/dist/surface/onboarding.d.ts +15 -0
- package/dist/surface/onboarding.js +184 -0
- package/dist/surface/onboarding.js.map +1 -0
- package/dist/surface/pidfile.d.ts +79 -0
- package/dist/surface/pidfile.js +263 -0
- package/dist/surface/pidfile.js.map +1 -0
- package/dist/surface/readiness.d.ts +45 -0
- package/dist/surface/readiness.js +230 -0
- package/dist/surface/readiness.js.map +1 -0
- package/dist/surface/report.d.ts +68 -0
- package/dist/surface/report.js +341 -0
- package/dist/surface/report.js.map +1 -0
- package/dist/surface/skill-register.d.ts +14 -0
- package/dist/surface/skill-register.js +150 -0
- package/dist/surface/skill-register.js.map +1 -0
- package/dist/surface/version.d.ts +6 -0
- package/dist/surface/version.js +27 -0
- package/dist/surface/version.js.map +1 -0
- package/dist/tools/a11y.d.ts +8 -0
- package/dist/tools/a11y.js +545 -0
- package/dist/tools/a11y.js.map +1 -0
- package/dist/tools/a11y_depth.d.ts +19 -0
- package/dist/tools/a11y_depth.js +455 -0
- package/dist/tools/a11y_depth.js.map +1 -0
- package/dist/tools/agent.d.ts +15 -0
- package/dist/tools/agent.js +248 -0
- package/dist/tools/agent.js.map +1 -0
- package/dist/tools/batch.d.ts +46 -0
- package/dist/tools/batch.js +230 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/cdp.d.ts +8 -0
- package/dist/tools/cdp.js +233 -0
- package/dist/tools/cdp.js.map +1 -0
- package/dist/tools/compact.d.ts +63 -0
- package/dist/tools/compact.js +418 -0
- package/dist/tools/compact.js.map +1 -0
- package/dist/tools/cost-class.d.ts +38 -0
- package/dist/tools/cost-class.js +117 -0
- package/dist/tools/cost-class.js.map +1 -0
- package/dist/tools/desktop.d.ts +9 -0
- package/dist/tools/desktop.js +346 -0
- package/dist/tools/desktop.js.map +1 -0
- package/dist/tools/electron_bridge.d.ts +41 -0
- package/dist/tools/electron_bridge.js +261 -0
- package/dist/tools/electron_bridge.js.map +1 -0
- package/dist/tools/extras.d.ts +22 -0
- package/dist/tools/extras.js +942 -0
- package/dist/tools/extras.js.map +1 -0
- package/dist/tools/favorites.d.ts +13 -0
- package/dist/tools/favorites.js +137 -0
- package/dist/tools/favorites.js.map +1 -0
- package/dist/tools/introspection.d.ts +13 -0
- package/dist/tools/introspection.js +55 -0
- package/dist/tools/introspection.js.map +1 -0
- package/dist/tools/ocr.d.ts +8 -0
- package/dist/tools/ocr.js +66 -0
- package/dist/tools/ocr.js.map +1 -0
- package/dist/tools/orchestration.d.ts +7 -0
- package/dist/tools/orchestration.js +377 -0
- package/dist/tools/orchestration.js.map +1 -0
- package/dist/tools/playbooks/extract-compose.d.ts +22 -0
- package/dist/tools/playbooks/extract-compose.js +85 -0
- package/dist/tools/playbooks/extract-compose.js.map +1 -0
- package/dist/tools/playbooks/find-replace.d.ts +11 -0
- package/dist/tools/playbooks/find-replace.js +56 -0
- package/dist/tools/playbooks/find-replace.js.map +1 -0
- package/dist/tools/playbooks/index.d.ts +63 -0
- package/dist/tools/playbooks/index.js +70 -0
- package/dist/tools/playbooks/index.js.map +1 -0
- package/dist/tools/playbooks/keys-blocklist.d.ts +24 -0
- package/dist/tools/playbooks/keys-blocklist.js +89 -0
- package/dist/tools/playbooks/keys-blocklist.js.map +1 -0
- package/dist/tools/registry.d.ts +40 -0
- package/dist/tools/registry.js +560 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/safety-gate.d.ts +16 -0
- package/dist/tools/safety-gate.js +70 -0
- package/dist/tools/safety-gate.js.map +1 -0
- package/dist/tools/scheduler.d.ts +76 -0
- package/dist/tools/scheduler.js +413 -0
- package/dist/tools/scheduler.js.map +1 -0
- package/dist/tools/shortcuts.d.ts +13 -0
- package/dist/tools/shortcuts.js +205 -0
- package/dist/tools/shortcuts.js.map +1 -0
- package/dist/tools/smart.d.ts +15 -0
- package/dist/tools/smart.js +785 -0
- package/dist/tools/smart.js.map +1 -0
- package/dist/tools/types.d.ts +174 -0
- package/dist/tools/types.js +67 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/window-text.d.ts +15 -0
- package/dist/tools/window-text.js +39 -0
- package/dist/tools/window-text.js.map +1 -0
- package/dist/types.d.ts +122 -0
- package/dist/types.js +41 -0
- package/dist/types.js.map +1 -0
- package/native/Package.swift +38 -0
- package/native/README.md +113 -0
- package/native/Sources/ClawdCursorHelper/main.swift +602 -0
- package/native/Sources/ClawdCursorHost/main.swift +182 -0
- package/native/Sources/PermissionCheck/main.swift +53 -0
- package/native/Sources/ScreenshotHelper/main.swift +219 -0
- package/native/build.sh +139 -0
- package/native/entitlements.plist +12 -0
- package/package.json +115 -0
- package/scripts/banner.ps1 +112 -0
- package/scripts/coord-accuracy.ps1 +140 -0
- package/scripts/coord-uwp.ps1 +80 -0
- package/scripts/edge-glow.ps1 +180 -0
- package/scripts/find-element.ps1 +198 -0
- package/scripts/get-foreground-window.ps1 +71 -0
- package/scripts/get-screen-context.ps1 +183 -0
- package/scripts/get-windows.ps1 +66 -0
- package/scripts/install-panic-hotkey.ps1 +46 -0
- package/scripts/interact-element.ps1 +431 -0
- package/scripts/invoke-element.ps1 +314 -0
- package/scripts/linux/atspi-bridge.py +356 -0
- package/scripts/linux/ocr-recognize.py +154 -0
- package/scripts/mac/_window-picker.jxa +163 -0
- package/scripts/mac/find-element.jxa +0 -0
- package/scripts/mac/find-element.sh +161 -0
- package/scripts/mac/focus-window.jxa +284 -0
- package/scripts/mac/get-focused-element.jxa +102 -0
- package/scripts/mac/get-foreground-window.jxa +173 -0
- package/scripts/mac/get-screen-context.jxa +197 -0
- package/scripts/mac/get-ui-tree.sh +141 -0
- package/scripts/mac/get-windows.jxa +117 -0
- package/scripts/mac/interact-element.sh +235 -0
- package/scripts/mac/invoke-element.jxa +408 -0
- package/scripts/mac/ocr-recognize.swift +124 -0
- package/scripts/ocr-recognize.ps1 +102 -0
- package/scripts/postinstall-native.js +48 -0
- package/scripts/ps-bridge.ps1 +830 -0
- package/scripts/smoke-mcp.ps1 +119 -0
- package/scripts/sync-version.ts +178 -0
- package/scripts/verify-install.js +81 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
/// ClawdCursor Native Helper - macOS Accessibility & Input Control
|
|
2
|
+
/// Communicates with Node.js via JSON-RPC over stdio
|
|
3
|
+
///
|
|
4
|
+
/// Commands:
|
|
5
|
+
/// - checkPermissions: Returns permission status
|
|
6
|
+
/// - traverseAccessibilityTree: Returns UI element tree for a PID
|
|
7
|
+
/// - click: Click at coordinates or element
|
|
8
|
+
/// - type: Type text
|
|
9
|
+
/// - pressKey: Press key combination
|
|
10
|
+
/// - openApp: Open application by name or bundle ID
|
|
11
|
+
/// - getWindowList: List visible windows
|
|
12
|
+
|
|
13
|
+
import Foundation
|
|
14
|
+
import ApplicationServices
|
|
15
|
+
import CoreGraphics
|
|
16
|
+
import AppKit
|
|
17
|
+
import ImageIO
|
|
18
|
+
import UniformTypeIdentifiers
|
|
19
|
+
|
|
20
|
+
// MARK: - JSON-RPC Types
|
|
21
|
+
|
|
22
|
+
struct JsonRpcRequest: Codable {
|
|
23
|
+
let id: Int
|
|
24
|
+
let method: String
|
|
25
|
+
let params: [String: AnyCodable]?
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
struct JsonRpcResponse: Codable {
|
|
29
|
+
let id: Int
|
|
30
|
+
let result: AnyCodable?
|
|
31
|
+
let error: JsonRpcError?
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
struct JsonRpcError: Codable {
|
|
35
|
+
let code: Int
|
|
36
|
+
let message: String
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// AnyCodable wrapper for dynamic JSON
|
|
40
|
+
struct AnyCodable: Codable {
|
|
41
|
+
let value: Any
|
|
42
|
+
|
|
43
|
+
init(_ value: Any) {
|
|
44
|
+
self.value = value
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
init(from decoder: Decoder) throws {
|
|
48
|
+
let container = try decoder.singleValueContainer()
|
|
49
|
+
if container.decodeNil() {
|
|
50
|
+
value = NSNull()
|
|
51
|
+
} else if let bool = try? container.decode(Bool.self) {
|
|
52
|
+
value = bool
|
|
53
|
+
} else if let int = try? container.decode(Int.self) {
|
|
54
|
+
value = int
|
|
55
|
+
} else if let double = try? container.decode(Double.self) {
|
|
56
|
+
value = double
|
|
57
|
+
} else if let string = try? container.decode(String.self) {
|
|
58
|
+
value = string
|
|
59
|
+
} else if let array = try? container.decode([AnyCodable].self) {
|
|
60
|
+
value = array.map { $0.value }
|
|
61
|
+
} else if let dict = try? container.decode([String: AnyCodable].self) {
|
|
62
|
+
value = dict.mapValues { $0.value }
|
|
63
|
+
} else {
|
|
64
|
+
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode value")
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func encode(to encoder: Encoder) throws {
|
|
69
|
+
var container = encoder.singleValueContainer()
|
|
70
|
+
switch value {
|
|
71
|
+
case is NSNull:
|
|
72
|
+
try container.encodeNil()
|
|
73
|
+
case let bool as Bool:
|
|
74
|
+
try container.encode(bool)
|
|
75
|
+
case let int as Int:
|
|
76
|
+
try container.encode(int)
|
|
77
|
+
case let double as Double:
|
|
78
|
+
try container.encode(double)
|
|
79
|
+
case let string as String:
|
|
80
|
+
try container.encode(string)
|
|
81
|
+
case let array as [Any]:
|
|
82
|
+
try container.encode(array.map { AnyCodable($0) })
|
|
83
|
+
case let dict as [String: Any]:
|
|
84
|
+
try container.encode(dict.mapValues { AnyCodable($0) })
|
|
85
|
+
default:
|
|
86
|
+
try container.encode(String(describing: value))
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// MARK: - Accessibility Element
|
|
92
|
+
|
|
93
|
+
struct UIElement: Codable {
|
|
94
|
+
let role: String?
|
|
95
|
+
let title: String?
|
|
96
|
+
let value: String?
|
|
97
|
+
let description: String?
|
|
98
|
+
let position: [String: CGFloat]?
|
|
99
|
+
let size: [String: CGFloat]?
|
|
100
|
+
let enabled: Bool
|
|
101
|
+
let focused: Bool
|
|
102
|
+
let children: [UIElement]?
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// MARK: - Main Handler
|
|
106
|
+
|
|
107
|
+
class ClawdCursorHelper {
|
|
108
|
+
static let shared = ClawdCursorHelper()
|
|
109
|
+
|
|
110
|
+
/// Map a character to its macOS virtual keycode (US ANSI layout).
|
|
111
|
+
/// Covers a-z, 0-9, and common symbols — enough for all keyboard shortcuts.
|
|
112
|
+
/// Returns nil if the character has no known keycode mapping.
|
|
113
|
+
static func keycodeForCharacter(_ scalar: Unicode.Scalar) -> CGKeyCode? {
|
|
114
|
+
let c = Character(scalar).lowercased()
|
|
115
|
+
let map: [String: CGKeyCode] = [
|
|
116
|
+
"a": 0x00, "s": 0x01, "d": 0x02, "f": 0x03, "h": 0x04, "g": 0x05,
|
|
117
|
+
"z": 0x06, "x": 0x07, "c": 0x08, "v": 0x09, "b": 0x0B, "q": 0x0C,
|
|
118
|
+
"w": 0x0D, "e": 0x0E, "r": 0x0F, "y": 0x10, "t": 0x11, "1": 0x12,
|
|
119
|
+
"2": 0x13, "3": 0x14, "4": 0x15, "6": 0x16, "5": 0x17, "=": 0x18,
|
|
120
|
+
"9": 0x19, "7": 0x1A, "-": 0x1B, "8": 0x1C, "0": 0x1D, "]": 0x1E,
|
|
121
|
+
"o": 0x1F, "u": 0x20, "[": 0x21, "i": 0x22, "p": 0x23, "l": 0x25,
|
|
122
|
+
"j": 0x26, "'": 0x27, "k": 0x28, ";": 0x29, "\\": 0x2A, ",": 0x2B,
|
|
123
|
+
"/": 0x2C, "n": 0x2D, "m": 0x2E, ".": 0x2F, "`": 0x32,
|
|
124
|
+
"+": 0x18, "*": 0x43, // + maps to = key, * maps to numpad multiply
|
|
125
|
+
]
|
|
126
|
+
return map[c]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private let encoder: JSONEncoder = {
|
|
130
|
+
let e = JSONEncoder()
|
|
131
|
+
e.outputFormatting = [.sortedKeys]
|
|
132
|
+
return e
|
|
133
|
+
}()
|
|
134
|
+
|
|
135
|
+
private let decoder = JSONDecoder()
|
|
136
|
+
|
|
137
|
+
func run() {
|
|
138
|
+
// Check accessibility permission at startup (pattern from mediar-ai/MacosUseSDK)
|
|
139
|
+
let axOptions = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: kCFBooleanFalse] as CFDictionary
|
|
140
|
+
if !AXIsProcessTrustedWithOptions(axOptions) {
|
|
141
|
+
fputs("{\"error\": \"accessibility_denied\", \"message\": \"Grant Accessibility permission in System Settings > Privacy & Security > Accessibility\"}\n", stderr)
|
|
142
|
+
// Continue anyway - some commands might not need it
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Read JSON-RPC requests from stdin
|
|
146
|
+
while let line = readLine() {
|
|
147
|
+
guard !line.isEmpty else { continue }
|
|
148
|
+
handleRequest(line)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
func handleRequest(_ line: String) {
|
|
153
|
+
guard let data = line.data(using: .utf8),
|
|
154
|
+
let request = try? decoder.decode(JsonRpcRequest.self, from: data) else {
|
|
155
|
+
fputs("{\"error\": \"parse_error\", \"message\": \"Invalid JSON-RPC request\"}\n", stderr)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let response: JsonRpcResponse
|
|
160
|
+
|
|
161
|
+
switch request.method {
|
|
162
|
+
case "checkPermissions":
|
|
163
|
+
response = checkPermissions(id: request.id)
|
|
164
|
+
case "traverseAccessibilityTree":
|
|
165
|
+
response = traverseAccessibilityTree(id: request.id, params: request.params)
|
|
166
|
+
case "click":
|
|
167
|
+
response = click(id: request.id, params: request.params)
|
|
168
|
+
case "moveMouse":
|
|
169
|
+
response = moveMouse(id: request.id, params: request.params)
|
|
170
|
+
case "dragMouse":
|
|
171
|
+
response = dragMouse(id: request.id, params: request.params)
|
|
172
|
+
case "type":
|
|
173
|
+
response = typeText(id: request.id, params: request.params)
|
|
174
|
+
case "pressKey":
|
|
175
|
+
response = pressKey(id: request.id, params: request.params)
|
|
176
|
+
case "captureScreen":
|
|
177
|
+
response = captureScreen(id: request.id)
|
|
178
|
+
case "openApp":
|
|
179
|
+
response = openApp(id: request.id, params: request.params)
|
|
180
|
+
case "getWindowList":
|
|
181
|
+
response = getWindowList(id: request.id)
|
|
182
|
+
default:
|
|
183
|
+
response = JsonRpcResponse(id: request.id, result: nil, error: JsonRpcError(code: -32601, message: "Method not found: \(request.method)"))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if let responseData = try? encoder.encode(response),
|
|
187
|
+
let responseString = String(data: responseData, encoding: .utf8) {
|
|
188
|
+
print(responseString)
|
|
189
|
+
fflush(stdout)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// MARK: - Commands
|
|
194
|
+
|
|
195
|
+
func checkPermissions(id: Int) -> JsonRpcResponse {
|
|
196
|
+
// Pattern from mediar-ai/MacosUseSDK
|
|
197
|
+
let axOptions = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: kCFBooleanFalse] as CFDictionary
|
|
198
|
+
let axGranted = AXIsProcessTrustedWithOptions(axOptions)
|
|
199
|
+
let screenGranted = CGPreflightScreenCaptureAccess()
|
|
200
|
+
let processPath = ProcessInfo.processInfo.arguments.first ?? "unknown"
|
|
201
|
+
let bundleId = Bundle.main.bundleIdentifier ?? "unknown"
|
|
202
|
+
|
|
203
|
+
return JsonRpcResponse(id: id, result: AnyCodable([
|
|
204
|
+
"accessibility": axGranted,
|
|
205
|
+
"screenRecording": screenGranted,
|
|
206
|
+
"processPath": processPath,
|
|
207
|
+
"bundleId": bundleId
|
|
208
|
+
]), error: nil)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func traverseAccessibilityTree(id: Int, params: [String: AnyCodable]?) -> JsonRpcResponse {
|
|
212
|
+
guard let pid = params?["pid"]?.value as? Int else {
|
|
213
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32602, message: "Missing 'pid' parameter"))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Caps from mediar-ai/MacosUseSDK - prevents hanging on complex apps
|
|
217
|
+
let maxDepth = (params?["maxDepth"]?.value as? Int) ?? 100
|
|
218
|
+
let maxElements = (params?["maxElements"]?.value as? Int) ?? 2000
|
|
219
|
+
let maxSeconds: Double = 5.0
|
|
220
|
+
let startTime = Date()
|
|
221
|
+
|
|
222
|
+
let app = AXUIElementCreateApplication(pid_t(pid))
|
|
223
|
+
AXUIElementSetMessagingTimeout(app, 5.0)
|
|
224
|
+
var elementCount = 0
|
|
225
|
+
var truncated = false
|
|
226
|
+
|
|
227
|
+
func traverse(_ element: AXUIElement, depth: Int) -> UIElement? {
|
|
228
|
+
// Check all caps
|
|
229
|
+
if depth >= maxDepth || elementCount >= maxElements {
|
|
230
|
+
truncated = true
|
|
231
|
+
return nil
|
|
232
|
+
}
|
|
233
|
+
if Date().timeIntervalSince(startTime) > maxSeconds {
|
|
234
|
+
truncated = true
|
|
235
|
+
return nil
|
|
236
|
+
}
|
|
237
|
+
elementCount += 1
|
|
238
|
+
AXUIElementSetMessagingTimeout(element, 2.0)
|
|
239
|
+
|
|
240
|
+
var role: CFTypeRef?
|
|
241
|
+
var title: CFTypeRef?
|
|
242
|
+
var value: CFTypeRef?
|
|
243
|
+
var desc: CFTypeRef?
|
|
244
|
+
var position: CFTypeRef?
|
|
245
|
+
var size: CFTypeRef?
|
|
246
|
+
var enabled: CFTypeRef?
|
|
247
|
+
var focused: CFTypeRef?
|
|
248
|
+
var children: CFTypeRef?
|
|
249
|
+
|
|
250
|
+
AXUIElementCopyAttributeValue(element, kAXRoleAttribute as CFString, &role)
|
|
251
|
+
AXUIElementCopyAttributeValue(element, kAXTitleAttribute as CFString, &title)
|
|
252
|
+
AXUIElementCopyAttributeValue(element, kAXValueAttribute as CFString, &value)
|
|
253
|
+
AXUIElementCopyAttributeValue(element, kAXDescriptionAttribute as CFString, &desc)
|
|
254
|
+
AXUIElementCopyAttributeValue(element, kAXPositionAttribute as CFString, &position)
|
|
255
|
+
AXUIElementCopyAttributeValue(element, kAXSizeAttribute as CFString, &size)
|
|
256
|
+
AXUIElementCopyAttributeValue(element, kAXEnabledAttribute as CFString, &enabled)
|
|
257
|
+
AXUIElementCopyAttributeValue(element, kAXFocusedAttribute as CFString, &focused)
|
|
258
|
+
AXUIElementCopyAttributeValue(element, kAXChildrenAttribute as CFString, &children)
|
|
259
|
+
|
|
260
|
+
var posDict: [String: CGFloat]? = nil
|
|
261
|
+
if let pos = position, CFGetTypeID(pos) == AXValueGetTypeID() {
|
|
262
|
+
var point = CGPoint.zero
|
|
263
|
+
if AXValueGetValue(pos as! AXValue, .cgPoint, &point) {
|
|
264
|
+
posDict = ["x": point.x, "y": point.y]
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
var sizeDict: [String: CGFloat]? = nil
|
|
269
|
+
if let sz = size, CFGetTypeID(sz) == AXValueGetTypeID() {
|
|
270
|
+
var s = CGSize.zero
|
|
271
|
+
if AXValueGetValue(sz as! AXValue, .cgSize, &s) {
|
|
272
|
+
sizeDict = ["width": s.width, "height": s.height]
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
var childElements: [UIElement]? = nil
|
|
277
|
+
if let childArray = children as? [AXUIElement], !childArray.isEmpty {
|
|
278
|
+
childElements = childArray.compactMap { traverse($0, depth: depth + 1) }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return UIElement(
|
|
282
|
+
role: role as? String,
|
|
283
|
+
title: title as? String,
|
|
284
|
+
value: value as? String,
|
|
285
|
+
description: desc as? String,
|
|
286
|
+
position: posDict,
|
|
287
|
+
size: sizeDict,
|
|
288
|
+
enabled: (enabled as? Bool) ?? true,
|
|
289
|
+
focused: (focused as? Bool) ?? false,
|
|
290
|
+
children: childElements
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if let rootElement = traverse(app, depth: 0) {
|
|
295
|
+
return JsonRpcResponse(id: id, result: AnyCodable([
|
|
296
|
+
"pid": pid,
|
|
297
|
+
"elementCount": elementCount,
|
|
298
|
+
"truncated": truncated,
|
|
299
|
+
"tree": rootElement
|
|
300
|
+
]), error: nil)
|
|
301
|
+
} else {
|
|
302
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32000, message: "Failed to traverse accessibility tree for PID \(pid)"))
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/// Safely extract a Double from AnyCodable that may contain Int or Double
|
|
307
|
+
private func asDouble(_ val: Any?) -> Double? {
|
|
308
|
+
if let d = val as? Double { return d }
|
|
309
|
+
if let i = val as? Int { return Double(i) }
|
|
310
|
+
if let s = val as? String { return Double(s) }
|
|
311
|
+
return nil
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
func click(id: Int, params: [String: AnyCodable]?) -> JsonRpcResponse {
|
|
315
|
+
guard let x = asDouble(params?["x"]?.value),
|
|
316
|
+
let y = asDouble(params?["y"]?.value) else {
|
|
317
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32602, message: "Missing 'x' or 'y' parameter"))
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let point = CGPoint(x: x, y: y)
|
|
321
|
+
let button = (params?["button"]?.value as? String) == "right" ? CGMouseButton.right : CGMouseButton.left
|
|
322
|
+
let clickCount = (params?["clickCount"]?.value as? Int) ?? 1
|
|
323
|
+
|
|
324
|
+
let downType: CGEventType = button == .right ? .rightMouseDown : .leftMouseDown
|
|
325
|
+
let upType: CGEventType = button == .right ? .rightMouseUp : .leftMouseUp
|
|
326
|
+
|
|
327
|
+
for _ in 0..<clickCount {
|
|
328
|
+
if let mouseDown = CGEvent(mouseEventSource: nil, mouseType: downType, mouseCursorPosition: point, mouseButton: button) {
|
|
329
|
+
mouseDown.post(tap: .cghidEventTap)
|
|
330
|
+
}
|
|
331
|
+
usleep(10000) // 10ms
|
|
332
|
+
if let mouseUp = CGEvent(mouseEventSource: nil, mouseType: upType, mouseCursorPosition: point, mouseButton: button) {
|
|
333
|
+
mouseUp.post(tap: .cghidEventTap)
|
|
334
|
+
}
|
|
335
|
+
usleep(50000) // 50ms between clicks
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return JsonRpcResponse(id: id, result: AnyCodable(["success": true, "x": x, "y": y]), error: nil)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
func moveMouse(id: Int, params: [String: AnyCodable]?) -> JsonRpcResponse {
|
|
342
|
+
guard let x = asDouble(params?["x"]?.value),
|
|
343
|
+
let y = asDouble(params?["y"]?.value) else {
|
|
344
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32602, message: "Missing 'x' or 'y' parameter"))
|
|
345
|
+
}
|
|
346
|
+
let point = CGPoint(x: x, y: y)
|
|
347
|
+
if let event = CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, mouseCursorPosition: point, mouseButton: .left) {
|
|
348
|
+
event.post(tap: .cghidEventTap)
|
|
349
|
+
}
|
|
350
|
+
return JsonRpcResponse(id: id, result: AnyCodable(["success": true, "x": x, "y": y]), error: nil)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
func dragMouse(id: Int, params: [String: AnyCodable]?) -> JsonRpcResponse {
|
|
354
|
+
guard let startX = asDouble(params?["startX"]?.value),
|
|
355
|
+
let startY = asDouble(params?["startY"]?.value),
|
|
356
|
+
let endX = asDouble(params?["endX"]?.value),
|
|
357
|
+
let endY = asDouble(params?["endY"]?.value) else {
|
|
358
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32602, message: "Missing drag coordinates"))
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let startPoint = CGPoint(x: startX, y: startY)
|
|
362
|
+
let endPoint = CGPoint(x: endX, y: endY)
|
|
363
|
+
let button: CGMouseButton = .left
|
|
364
|
+
|
|
365
|
+
if let mouseDown = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: startPoint, mouseButton: button) {
|
|
366
|
+
mouseDown.post(tap: .cghidEventTap)
|
|
367
|
+
}
|
|
368
|
+
usleep(30000)
|
|
369
|
+
|
|
370
|
+
let steps = max(5, Int(hypot(endX - startX, endY - startY) / 20.0))
|
|
371
|
+
for i in 1...steps {
|
|
372
|
+
let t = Double(i) / Double(steps)
|
|
373
|
+
let ix = startX + (endX - startX) * t
|
|
374
|
+
let iy = startY + (endY - startY) * t
|
|
375
|
+
let point = CGPoint(x: ix, y: iy)
|
|
376
|
+
if let drag = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDragged, mouseCursorPosition: point, mouseButton: button) {
|
|
377
|
+
drag.post(tap: .cghidEventTap)
|
|
378
|
+
}
|
|
379
|
+
usleep(12000)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if let mouseUp = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: endPoint, mouseButton: button) {
|
|
383
|
+
mouseUp.post(tap: .cghidEventTap)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return JsonRpcResponse(id: id, result: AnyCodable(["success": true]), error: nil)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
func typeText(id: Int, params: [String: AnyCodable]?) -> JsonRpcResponse {
|
|
390
|
+
guard let text = params?["text"]?.value as? String else {
|
|
391
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32602, message: "Missing 'text' parameter"))
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
let delayMs = (params?["delayMs"]?.value as? Int) ?? 10
|
|
395
|
+
|
|
396
|
+
for char in text {
|
|
397
|
+
let source = CGEventSource(stateID: .hidSystemState)
|
|
398
|
+
if let keyDown = CGEvent(keyboardEventSource: source, virtualKey: 0, keyDown: true) {
|
|
399
|
+
var buffer = [UniChar](String(char).utf16)
|
|
400
|
+
keyDown.keyboardSetUnicodeString(stringLength: buffer.count, unicodeString: &buffer)
|
|
401
|
+
keyDown.post(tap: .cghidEventTap)
|
|
402
|
+
}
|
|
403
|
+
if let keyUp = CGEvent(keyboardEventSource: source, virtualKey: 0, keyDown: false) {
|
|
404
|
+
keyUp.post(tap: .cghidEventTap)
|
|
405
|
+
}
|
|
406
|
+
usleep(UInt32(delayMs * 1000))
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return JsonRpcResponse(id: id, result: AnyCodable(["success": true, "length": text.count]), error: nil)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
func pressKey(id: Int, params: [String: AnyCodable]?) -> JsonRpcResponse {
|
|
413
|
+
guard let key = params?["key"]?.value as? String else {
|
|
414
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32602, message: "Missing 'key' parameter"))
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
let modifiers = params?["modifiers"]?.value as? [String] ?? []
|
|
418
|
+
|
|
419
|
+
// Map key names to virtual keycodes
|
|
420
|
+
let keyCode: CGKeyCode
|
|
421
|
+
switch key.lowercased() {
|
|
422
|
+
case "return", "enter": keyCode = 0x24
|
|
423
|
+
case "tab": keyCode = 0x30
|
|
424
|
+
case "space": keyCode = 0x31
|
|
425
|
+
case "delete", "backspace": keyCode = 0x33
|
|
426
|
+
case "escape", "esc": keyCode = 0x35
|
|
427
|
+
case "left": keyCode = 0x7B
|
|
428
|
+
case "right": keyCode = 0x7C
|
|
429
|
+
case "down": keyCode = 0x7D
|
|
430
|
+
case "up": keyCode = 0x7E
|
|
431
|
+
case "f1": keyCode = 0x7A
|
|
432
|
+
case "f2": keyCode = 0x78
|
|
433
|
+
case "f3": keyCode = 0x63
|
|
434
|
+
case "f4": keyCode = 0x76
|
|
435
|
+
case "f5": keyCode = 0x60
|
|
436
|
+
case "f6": keyCode = 0x61
|
|
437
|
+
case "f7": keyCode = 0x62
|
|
438
|
+
case "f8": keyCode = 0x64
|
|
439
|
+
case "f9": keyCode = 0x65
|
|
440
|
+
case "f10": keyCode = 0x6D
|
|
441
|
+
case "f11": keyCode = 0x67
|
|
442
|
+
case "f12": keyCode = 0x6F
|
|
443
|
+
default:
|
|
444
|
+
// For single characters: look up keycode from character, or use unicode event.
|
|
445
|
+
// CRITICAL: modifiers must NOT be discarded — cmd+v, cmd+n, shift+cmd+d all depend on this.
|
|
446
|
+
if key.count == 1, let scalar = key.unicodeScalars.first {
|
|
447
|
+
// Try common ASCII keycode mapping first (covers a-z, 0-9, symbols)
|
|
448
|
+
guard let kc = Self.keycodeForCharacter(scalar) else {
|
|
449
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32602, message: "Unsupported key character: \(key) (not in ANSI keycode map)"))
|
|
450
|
+
}
|
|
451
|
+
keyCode = kc
|
|
452
|
+
} else {
|
|
453
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32602, message: "Unknown key: \(key)"))
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
var flags: CGEventFlags = []
|
|
458
|
+
for mod in modifiers {
|
|
459
|
+
switch mod.lowercased() {
|
|
460
|
+
case "cmd", "command": flags.insert(.maskCommand)
|
|
461
|
+
case "shift": flags.insert(.maskShift)
|
|
462
|
+
case "alt", "option": flags.insert(.maskAlternate)
|
|
463
|
+
case "ctrl", "control": flags.insert(.maskControl)
|
|
464
|
+
default: break
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
let source = CGEventSource(stateID: .hidSystemState)
|
|
469
|
+
if let keyDown = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: true) {
|
|
470
|
+
keyDown.flags = flags
|
|
471
|
+
keyDown.post(tap: .cghidEventTap)
|
|
472
|
+
}
|
|
473
|
+
usleep(10000)
|
|
474
|
+
if let keyUp = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: false) {
|
|
475
|
+
keyUp.flags = flags
|
|
476
|
+
keyUp.post(tap: .cghidEventTap)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return JsonRpcResponse(id: id, result: AnyCodable(["success": true, "key": key, "modifiers": modifiers]), error: nil)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
func captureScreen(id: Int) -> JsonRpcResponse {
|
|
483
|
+
guard CGPreflightScreenCaptureAccess() else {
|
|
484
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32001, message: "screen_recording_denied"))
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
let tempPath = (NSTemporaryDirectory() as NSString).appendingPathComponent("clawdcursor-capture-\(UUID().uuidString).png")
|
|
488
|
+
let proc = Process()
|
|
489
|
+
proc.executableURL = Bundle.main.bundleURL.appendingPathComponent("Contents/MacOS/screenshot-helper")
|
|
490
|
+
proc.arguments = ["--fullscreen", tempPath]
|
|
491
|
+
|
|
492
|
+
let stderr = Pipe()
|
|
493
|
+
proc.standardError = stderr
|
|
494
|
+
|
|
495
|
+
do {
|
|
496
|
+
try proc.run()
|
|
497
|
+
proc.waitUntilExit()
|
|
498
|
+
} catch {
|
|
499
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32000, message: "Failed to launch screenshot-helper: \(error.localizedDescription)"))
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
guard proc.terminationStatus == 0 else {
|
|
503
|
+
let errData = stderr.fileHandleForReading.readDataToEndOfFile()
|
|
504
|
+
let errText = String(data: errData, encoding: .utf8) ?? "unknown screenshot-helper error"
|
|
505
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32000, message: "screenshot-helper failed: \(errText.trimmingCharacters(in: .whitespacesAndNewlines))"))
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let url = URL(fileURLWithPath: tempPath)
|
|
509
|
+
defer { try? FileManager.default.removeItem(at: url) }
|
|
510
|
+
|
|
511
|
+
guard let data = try? Data(contentsOf: url),
|
|
512
|
+
let image = CGImageSourceCreateWithURL(url as CFURL, nil).flatMap({ CGImageSourceCreateImageAtIndex($0, 0, nil) }) else {
|
|
513
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32000, message: "Failed to read screenshot-helper output"))
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let base64 = data.base64EncodedString()
|
|
517
|
+
return JsonRpcResponse(id: id, result: AnyCodable([
|
|
518
|
+
"success": true,
|
|
519
|
+
"width": image.width,
|
|
520
|
+
"height": image.height,
|
|
521
|
+
"format": "png",
|
|
522
|
+
"imageBase64": base64
|
|
523
|
+
]), error: nil)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
func openApp(id: Int, params: [String: AnyCodable]?) -> JsonRpcResponse {
|
|
527
|
+
let appName = params?["name"]?.value as? String
|
|
528
|
+
let bundleId = params?["bundleId"]?.value as? String
|
|
529
|
+
|
|
530
|
+
var url: URL?
|
|
531
|
+
|
|
532
|
+
if let bundleId = bundleId {
|
|
533
|
+
url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleId)
|
|
534
|
+
} else if let appName = appName {
|
|
535
|
+
url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: appName)
|
|
536
|
+
if url == nil {
|
|
537
|
+
// Try finding by name
|
|
538
|
+
let path = "/Applications/\(appName).app"
|
|
539
|
+
if FileManager.default.fileExists(atPath: path) {
|
|
540
|
+
url = URL(fileURLWithPath: path)
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
guard let appUrl = url else {
|
|
546
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32000, message: "App not found"))
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
let config = NSWorkspace.OpenConfiguration()
|
|
550
|
+
config.activates = true
|
|
551
|
+
|
|
552
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
553
|
+
var resultPid: pid_t = 0
|
|
554
|
+
var resultError: Error?
|
|
555
|
+
|
|
556
|
+
NSWorkspace.shared.openApplication(at: appUrl, configuration: config) { app, error in
|
|
557
|
+
resultPid = app?.processIdentifier ?? 0
|
|
558
|
+
resultError = error
|
|
559
|
+
semaphore.signal()
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
semaphore.wait()
|
|
563
|
+
|
|
564
|
+
if let error = resultError {
|
|
565
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32000, message: error.localizedDescription))
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return JsonRpcResponse(id: id, result: AnyCodable(["success": true, "pid": Int(resultPid)]), error: nil)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
func getWindowList(id: Int) -> JsonRpcResponse {
|
|
572
|
+
let options: CGWindowListOption = [.optionOnScreenOnly, .excludeDesktopElements]
|
|
573
|
+
guard let windowList = CGWindowListCopyWindowInfo(options, kCGNullWindowID) as? [[String: Any]] else {
|
|
574
|
+
return JsonRpcResponse(id: id, result: nil, error: JsonRpcError(code: -32000, message: "Failed to get window list"))
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
let windows: [[String: Any]] = windowList.compactMap { window in
|
|
578
|
+
guard let ownerPid = window[kCGWindowOwnerPID as String] as? Int,
|
|
579
|
+
let windowId = window[kCGWindowNumber as String] as? Int,
|
|
580
|
+
let layer = window[kCGWindowLayer as String] as? Int,
|
|
581
|
+
layer == 0 else { return nil } // Normal windows only
|
|
582
|
+
|
|
583
|
+
let ownerName = window[kCGWindowOwnerName as String] as? String ?? ""
|
|
584
|
+
let windowName = window[kCGWindowName as String] as? String ?? ""
|
|
585
|
+
let bounds = window[kCGWindowBounds as String] as? [String: CGFloat] ?? [:]
|
|
586
|
+
|
|
587
|
+
return [
|
|
588
|
+
"windowId": windowId,
|
|
589
|
+
"ownerPid": ownerPid,
|
|
590
|
+
"ownerName": ownerName,
|
|
591
|
+
"windowName": windowName,
|
|
592
|
+
"bounds": bounds
|
|
593
|
+
]
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return JsonRpcResponse(id: id, result: AnyCodable(["windows": windows]), error: nil)
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// MARK: - Main
|
|
601
|
+
|
|
602
|
+
ClawdCursorHelper.shared.run()
|