@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.
Files changed (354) hide show
  1. package/CHANGELOG.md +2264 -0
  2. package/LICENSE +21 -0
  3. package/README.md +385 -0
  4. package/SECURITY.md +44 -0
  5. package/SKILL.md +503 -0
  6. package/dist/core/agent-loop/agent.d.ts +42 -0
  7. package/dist/core/agent-loop/agent.js +1023 -0
  8. package/dist/core/agent-loop/agent.js.map +1 -0
  9. package/dist/core/agent-loop/batch-tool.d.ts +25 -0
  10. package/dist/core/agent-loop/batch-tool.js +218 -0
  11. package/dist/core/agent-loop/batch-tool.js.map +1 -0
  12. package/dist/core/agent-loop/coord-scale.d.ts +72 -0
  13. package/dist/core/agent-loop/coord-scale.js +89 -0
  14. package/dist/core/agent-loop/coord-scale.js.map +1 -0
  15. package/dist/core/agent-loop/focus-guard.d.ts +24 -0
  16. package/dist/core/agent-loop/focus-guard.js +29 -0
  17. package/dist/core/agent-loop/focus-guard.js.map +1 -0
  18. package/dist/core/agent-loop/project-mcp.d.ts +97 -0
  19. package/dist/core/agent-loop/project-mcp.js +253 -0
  20. package/dist/core/agent-loop/project-mcp.js.map +1 -0
  21. package/dist/core/agent-loop/prompt.d.ts +45 -0
  22. package/dist/core/agent-loop/prompt.js +426 -0
  23. package/dist/core/agent-loop/prompt.js.map +1 -0
  24. package/dist/core/agent-loop/tool-meta.d.ts +93 -0
  25. package/dist/core/agent-loop/tool-meta.js +651 -0
  26. package/dist/core/agent-loop/tool-meta.js.map +1 -0
  27. package/dist/core/agent-loop/tools.d.ts +38 -0
  28. package/dist/core/agent-loop/tools.js +2134 -0
  29. package/dist/core/agent-loop/tools.js.map +1 -0
  30. package/dist/core/agent-loop/types.d.ts +170 -0
  31. package/dist/core/agent-loop/types.js +12 -0
  32. package/dist/core/agent-loop/types.js.map +1 -0
  33. package/dist/core/agent.d.ts +51 -0
  34. package/dist/core/agent.js +245 -0
  35. package/dist/core/agent.js.map +1 -0
  36. package/dist/core/app-categories.d.ts +67 -0
  37. package/dist/core/app-categories.js +108 -0
  38. package/dist/core/app-categories.js.map +1 -0
  39. package/dist/core/banner.d.ts +70 -0
  40. package/dist/core/banner.js +245 -0
  41. package/dist/core/banner.js.map +1 -0
  42. package/dist/core/classify/capability.d.ts +45 -0
  43. package/dist/core/classify/capability.js +78 -0
  44. package/dist/core/classify/capability.js.map +1 -0
  45. package/dist/core/decompose/llm-decomposer.d.ts +35 -0
  46. package/dist/core/decompose/llm-decomposer.js +156 -0
  47. package/dist/core/decompose/llm-decomposer.js.map +1 -0
  48. package/dist/core/decompose/parser.d.ts +27 -0
  49. package/dist/core/decompose/parser.js +101 -0
  50. package/dist/core/decompose/parser.js.map +1 -0
  51. package/dist/core/observability/correlation.d.ts +19 -0
  52. package/dist/core/observability/correlation.js +36 -0
  53. package/dist/core/observability/correlation.js.map +1 -0
  54. package/dist/core/observability/cost-meter.d.ts +51 -0
  55. package/dist/core/observability/cost-meter.js +134 -0
  56. package/dist/core/observability/cost-meter.js.map +1 -0
  57. package/dist/core/observability/logger.d.ts +61 -0
  58. package/dist/core/observability/logger.js +550 -0
  59. package/dist/core/observability/logger.js.map +1 -0
  60. package/dist/core/router/aliases.d.ts +50 -0
  61. package/dist/core/router/aliases.js +104 -0
  62. package/dist/core/router/aliases.js.map +1 -0
  63. package/dist/core/router/normalize.d.ts +41 -0
  64. package/dist/core/router/normalize.js +80 -0
  65. package/dist/core/router/normalize.js.map +1 -0
  66. package/dist/core/safety.d.ts +126 -0
  67. package/dist/core/safety.js +568 -0
  68. package/dist/core/safety.js.map +1 -0
  69. package/dist/core/sense/a11y-resolver.d.ts +73 -0
  70. package/dist/core/sense/a11y-resolver.js +76 -0
  71. package/dist/core/sense/a11y-resolver.js.map +1 -0
  72. package/dist/core/sense/fingerprint.d.ts +41 -0
  73. package/dist/core/sense/fingerprint.js +123 -0
  74. package/dist/core/sense/fingerprint.js.map +1 -0
  75. package/dist/core/sense/rank.d.ts +70 -0
  76. package/dist/core/sense/rank.js +192 -0
  77. package/dist/core/sense/rank.js.map +1 -0
  78. package/dist/core/sense/reactive-check.d.ts +40 -0
  79. package/dist/core/sense/reactive-check.js +48 -0
  80. package/dist/core/sense/reactive-check.js.map +1 -0
  81. package/dist/core/sense/snapshot.d.ts +19 -0
  82. package/dist/core/sense/snapshot.js +100 -0
  83. package/dist/core/sense/snapshot.js.map +1 -0
  84. package/dist/core/sense/types.d.ts +66 -0
  85. package/dist/core/sense/types.js +9 -0
  86. package/dist/core/sense/types.js.map +1 -0
  87. package/dist/core/sense/ui-map-anchors.d.ts +7 -0
  88. package/dist/core/sense/ui-map-anchors.js +24 -0
  89. package/dist/core/sense/ui-map-anchors.js.map +1 -0
  90. package/dist/core/sense/ui-map-elements.d.ts +5 -0
  91. package/dist/core/sense/ui-map-elements.js +33 -0
  92. package/dist/core/sense/ui-map-elements.js.map +1 -0
  93. package/dist/core/sense/ui-map-find.d.ts +56 -0
  94. package/dist/core/sense/ui-map-find.js +153 -0
  95. package/dist/core/sense/ui-map-find.js.map +1 -0
  96. package/dist/core/sense/ui-map-fuse.d.ts +4 -0
  97. package/dist/core/sense/ui-map-fuse.js +44 -0
  98. package/dist/core/sense/ui-map-fuse.js.map +1 -0
  99. package/dist/core/sense/ui-map-geom.d.ts +3 -0
  100. package/dist/core/sense/ui-map-geom.js +16 -0
  101. package/dist/core/sense/ui-map-geom.js.map +1 -0
  102. package/dist/core/sense/ui-map-holder.d.ts +58 -0
  103. package/dist/core/sense/ui-map-holder.js +87 -0
  104. package/dist/core/sense/ui-map-holder.js.map +1 -0
  105. package/dist/core/sense/ui-map-normalize.d.ts +19 -0
  106. package/dist/core/sense/ui-map-normalize.js +65 -0
  107. package/dist/core/sense/ui-map-normalize.js.map +1 -0
  108. package/dist/core/sense/ui-map-render.d.ts +4 -0
  109. package/dist/core/sense/ui-map-render.js +34 -0
  110. package/dist/core/sense/ui-map-render.js.map +1 -0
  111. package/dist/core/sense/ui-map-resolve.d.ts +41 -0
  112. package/dist/core/sense/ui-map-resolve.js +59 -0
  113. package/dist/core/sense/ui-map-resolve.js.map +1 -0
  114. package/dist/core/sense/ui-map-types.d.ts +66 -0
  115. package/dist/core/sense/ui-map-types.js +11 -0
  116. package/dist/core/sense/ui-map-types.js.map +1 -0
  117. package/dist/core/sense/ui-map.d.ts +29 -0
  118. package/dist/core/sense/ui-map.js +113 -0
  119. package/dist/core/sense/ui-map.js.map +1 -0
  120. package/dist/core/verify/assertions.d.ts +132 -0
  121. package/dist/core/verify/assertions.js +284 -0
  122. package/dist/core/verify/assertions.js.map +1 -0
  123. package/dist/index.d.ts +21 -0
  124. package/dist/index.js +24 -0
  125. package/dist/index.js.map +1 -0
  126. package/dist/llm/browser-config.d.ts +36 -0
  127. package/dist/llm/browser-config.js +83 -0
  128. package/dist/llm/browser-config.js.map +1 -0
  129. package/dist/llm/client.d.ts +268 -0
  130. package/dist/llm/client.js +1094 -0
  131. package/dist/llm/client.js.map +1 -0
  132. package/dist/llm/config.d.ts +79 -0
  133. package/dist/llm/config.js +375 -0
  134. package/dist/llm/config.js.map +1 -0
  135. package/dist/llm/credentials.d.ts +35 -0
  136. package/dist/llm/credentials.js +491 -0
  137. package/dist/llm/credentials.js.map +1 -0
  138. package/dist/llm/external-creds.d.ts +42 -0
  139. package/dist/llm/external-creds.js +169 -0
  140. package/dist/llm/external-creds.js.map +1 -0
  141. package/dist/llm/providers.d.ts +123 -0
  142. package/dist/llm/providers.js +717 -0
  143. package/dist/llm/providers.js.map +1 -0
  144. package/dist/paths.d.ts +31 -0
  145. package/dist/paths.js +147 -0
  146. package/dist/paths.js.map +1 -0
  147. package/dist/platform/accessibility.d.ts +139 -0
  148. package/dist/platform/accessibility.js +670 -0
  149. package/dist/platform/accessibility.js.map +1 -0
  150. package/dist/platform/cdp-driver.d.ts +318 -0
  151. package/dist/platform/cdp-driver.js +1179 -0
  152. package/dist/platform/cdp-driver.js.map +1 -0
  153. package/dist/platform/index.d.ts +11 -0
  154. package/dist/platform/index.js +69 -0
  155. package/dist/platform/index.js.map +1 -0
  156. package/dist/platform/keys.d.ts +17 -0
  157. package/dist/platform/keys.js +129 -0
  158. package/dist/platform/keys.js.map +1 -0
  159. package/dist/platform/launch-poll.d.ts +101 -0
  160. package/dist/platform/launch-poll.js +177 -0
  161. package/dist/platform/launch-poll.js.map +1 -0
  162. package/dist/platform/linux.d.ts +173 -0
  163. package/dist/platform/linux.js +1253 -0
  164. package/dist/platform/linux.js.map +1 -0
  165. package/dist/platform/macos.d.ts +136 -0
  166. package/dist/platform/macos.js +976 -0
  167. package/dist/platform/macos.js.map +1 -0
  168. package/dist/platform/native-desktop.d.ts +145 -0
  169. package/dist/platform/native-desktop.js +936 -0
  170. package/dist/platform/native-desktop.js.map +1 -0
  171. package/dist/platform/native-helper.d.ts +130 -0
  172. package/dist/platform/native-helper.js +592 -0
  173. package/dist/platform/native-helper.js.map +1 -0
  174. package/dist/platform/ocr-engine.d.ts +78 -0
  175. package/dist/platform/ocr-engine.js +363 -0
  176. package/dist/platform/ocr-engine.js.map +1 -0
  177. package/dist/platform/ps-runner.d.ts +28 -0
  178. package/dist/platform/ps-runner.js +228 -0
  179. package/dist/platform/ps-runner.js.map +1 -0
  180. package/dist/platform/types.d.ts +397 -0
  181. package/dist/platform/types.js +15 -0
  182. package/dist/platform/types.js.map +1 -0
  183. package/dist/platform/uri-handler.d.ts +75 -0
  184. package/dist/platform/uri-handler.js +273 -0
  185. package/dist/platform/uri-handler.js.map +1 -0
  186. package/dist/platform/wayland-backend.d.ts +53 -0
  187. package/dist/platform/wayland-backend.js +348 -0
  188. package/dist/platform/wayland-backend.js.map +1 -0
  189. package/dist/platform/windows.d.ts +232 -0
  190. package/dist/platform/windows.js +1210 -0
  191. package/dist/platform/windows.js.map +1 -0
  192. package/dist/postbuild.d.ts +10 -0
  193. package/dist/postbuild.js +98 -0
  194. package/dist/postbuild.js.map +1 -0
  195. package/dist/schema/snapshot.d.ts +33 -0
  196. package/dist/schema/snapshot.js +90 -0
  197. package/dist/schema/snapshot.js.map +1 -0
  198. package/dist/shortcuts.d.ts +30 -0
  199. package/dist/shortcuts.js +261 -0
  200. package/dist/shortcuts.js.map +1 -0
  201. package/dist/surface/cli.d.ts +7 -0
  202. package/dist/surface/cli.js +1556 -0
  203. package/dist/surface/cli.js.map +1 -0
  204. package/dist/surface/dashboard.d.ts +8 -0
  205. package/dist/surface/dashboard.js +1193 -0
  206. package/dist/surface/dashboard.js.map +1 -0
  207. package/dist/surface/doctor.d.ts +29 -0
  208. package/dist/surface/doctor.js +1514 -0
  209. package/dist/surface/doctor.js.map +1 -0
  210. package/dist/surface/format.d.ts +10 -0
  211. package/dist/surface/format.js +37 -0
  212. package/dist/surface/format.js.map +1 -0
  213. package/dist/surface/http-utility.d.ts +65 -0
  214. package/dist/surface/http-utility.js +336 -0
  215. package/dist/surface/http-utility.js.map +1 -0
  216. package/dist/surface/mcp-server.d.ts +91 -0
  217. package/dist/surface/mcp-server.js +280 -0
  218. package/dist/surface/mcp-server.js.map +1 -0
  219. package/dist/surface/onboarding.d.ts +15 -0
  220. package/dist/surface/onboarding.js +184 -0
  221. package/dist/surface/onboarding.js.map +1 -0
  222. package/dist/surface/pidfile.d.ts +79 -0
  223. package/dist/surface/pidfile.js +263 -0
  224. package/dist/surface/pidfile.js.map +1 -0
  225. package/dist/surface/readiness.d.ts +45 -0
  226. package/dist/surface/readiness.js +230 -0
  227. package/dist/surface/readiness.js.map +1 -0
  228. package/dist/surface/report.d.ts +68 -0
  229. package/dist/surface/report.js +341 -0
  230. package/dist/surface/report.js.map +1 -0
  231. package/dist/surface/skill-register.d.ts +14 -0
  232. package/dist/surface/skill-register.js +150 -0
  233. package/dist/surface/skill-register.js.map +1 -0
  234. package/dist/surface/version.d.ts +6 -0
  235. package/dist/surface/version.js +27 -0
  236. package/dist/surface/version.js.map +1 -0
  237. package/dist/tools/a11y.d.ts +8 -0
  238. package/dist/tools/a11y.js +545 -0
  239. package/dist/tools/a11y.js.map +1 -0
  240. package/dist/tools/a11y_depth.d.ts +19 -0
  241. package/dist/tools/a11y_depth.js +455 -0
  242. package/dist/tools/a11y_depth.js.map +1 -0
  243. package/dist/tools/agent.d.ts +15 -0
  244. package/dist/tools/agent.js +248 -0
  245. package/dist/tools/agent.js.map +1 -0
  246. package/dist/tools/batch.d.ts +46 -0
  247. package/dist/tools/batch.js +230 -0
  248. package/dist/tools/batch.js.map +1 -0
  249. package/dist/tools/cdp.d.ts +8 -0
  250. package/dist/tools/cdp.js +233 -0
  251. package/dist/tools/cdp.js.map +1 -0
  252. package/dist/tools/compact.d.ts +63 -0
  253. package/dist/tools/compact.js +418 -0
  254. package/dist/tools/compact.js.map +1 -0
  255. package/dist/tools/cost-class.d.ts +38 -0
  256. package/dist/tools/cost-class.js +117 -0
  257. package/dist/tools/cost-class.js.map +1 -0
  258. package/dist/tools/desktop.d.ts +9 -0
  259. package/dist/tools/desktop.js +346 -0
  260. package/dist/tools/desktop.js.map +1 -0
  261. package/dist/tools/electron_bridge.d.ts +41 -0
  262. package/dist/tools/electron_bridge.js +261 -0
  263. package/dist/tools/electron_bridge.js.map +1 -0
  264. package/dist/tools/extras.d.ts +22 -0
  265. package/dist/tools/extras.js +942 -0
  266. package/dist/tools/extras.js.map +1 -0
  267. package/dist/tools/favorites.d.ts +13 -0
  268. package/dist/tools/favorites.js +137 -0
  269. package/dist/tools/favorites.js.map +1 -0
  270. package/dist/tools/introspection.d.ts +13 -0
  271. package/dist/tools/introspection.js +55 -0
  272. package/dist/tools/introspection.js.map +1 -0
  273. package/dist/tools/ocr.d.ts +8 -0
  274. package/dist/tools/ocr.js +66 -0
  275. package/dist/tools/ocr.js.map +1 -0
  276. package/dist/tools/orchestration.d.ts +7 -0
  277. package/dist/tools/orchestration.js +377 -0
  278. package/dist/tools/orchestration.js.map +1 -0
  279. package/dist/tools/playbooks/extract-compose.d.ts +22 -0
  280. package/dist/tools/playbooks/extract-compose.js +85 -0
  281. package/dist/tools/playbooks/extract-compose.js.map +1 -0
  282. package/dist/tools/playbooks/find-replace.d.ts +11 -0
  283. package/dist/tools/playbooks/find-replace.js +56 -0
  284. package/dist/tools/playbooks/find-replace.js.map +1 -0
  285. package/dist/tools/playbooks/index.d.ts +63 -0
  286. package/dist/tools/playbooks/index.js +70 -0
  287. package/dist/tools/playbooks/index.js.map +1 -0
  288. package/dist/tools/playbooks/keys-blocklist.d.ts +24 -0
  289. package/dist/tools/playbooks/keys-blocklist.js +89 -0
  290. package/dist/tools/playbooks/keys-blocklist.js.map +1 -0
  291. package/dist/tools/registry.d.ts +40 -0
  292. package/dist/tools/registry.js +560 -0
  293. package/dist/tools/registry.js.map +1 -0
  294. package/dist/tools/safety-gate.d.ts +16 -0
  295. package/dist/tools/safety-gate.js +70 -0
  296. package/dist/tools/safety-gate.js.map +1 -0
  297. package/dist/tools/scheduler.d.ts +76 -0
  298. package/dist/tools/scheduler.js +413 -0
  299. package/dist/tools/scheduler.js.map +1 -0
  300. package/dist/tools/shortcuts.d.ts +13 -0
  301. package/dist/tools/shortcuts.js +205 -0
  302. package/dist/tools/shortcuts.js.map +1 -0
  303. package/dist/tools/smart.d.ts +15 -0
  304. package/dist/tools/smart.js +785 -0
  305. package/dist/tools/smart.js.map +1 -0
  306. package/dist/tools/types.d.ts +174 -0
  307. package/dist/tools/types.js +67 -0
  308. package/dist/tools/types.js.map +1 -0
  309. package/dist/tools/window-text.d.ts +15 -0
  310. package/dist/tools/window-text.js +39 -0
  311. package/dist/tools/window-text.js.map +1 -0
  312. package/dist/types.d.ts +122 -0
  313. package/dist/types.js +41 -0
  314. package/dist/types.js.map +1 -0
  315. package/native/Package.swift +38 -0
  316. package/native/README.md +113 -0
  317. package/native/Sources/ClawdCursorHelper/main.swift +602 -0
  318. package/native/Sources/ClawdCursorHost/main.swift +182 -0
  319. package/native/Sources/PermissionCheck/main.swift +53 -0
  320. package/native/Sources/ScreenshotHelper/main.swift +219 -0
  321. package/native/build.sh +139 -0
  322. package/native/entitlements.plist +12 -0
  323. package/package.json +115 -0
  324. package/scripts/banner.ps1 +112 -0
  325. package/scripts/coord-accuracy.ps1 +140 -0
  326. package/scripts/coord-uwp.ps1 +80 -0
  327. package/scripts/edge-glow.ps1 +180 -0
  328. package/scripts/find-element.ps1 +198 -0
  329. package/scripts/get-foreground-window.ps1 +71 -0
  330. package/scripts/get-screen-context.ps1 +183 -0
  331. package/scripts/get-windows.ps1 +66 -0
  332. package/scripts/install-panic-hotkey.ps1 +46 -0
  333. package/scripts/interact-element.ps1 +431 -0
  334. package/scripts/invoke-element.ps1 +314 -0
  335. package/scripts/linux/atspi-bridge.py +356 -0
  336. package/scripts/linux/ocr-recognize.py +154 -0
  337. package/scripts/mac/_window-picker.jxa +163 -0
  338. package/scripts/mac/find-element.jxa +0 -0
  339. package/scripts/mac/find-element.sh +161 -0
  340. package/scripts/mac/focus-window.jxa +284 -0
  341. package/scripts/mac/get-focused-element.jxa +102 -0
  342. package/scripts/mac/get-foreground-window.jxa +173 -0
  343. package/scripts/mac/get-screen-context.jxa +197 -0
  344. package/scripts/mac/get-ui-tree.sh +141 -0
  345. package/scripts/mac/get-windows.jxa +117 -0
  346. package/scripts/mac/interact-element.sh +235 -0
  347. package/scripts/mac/invoke-element.jxa +408 -0
  348. package/scripts/mac/ocr-recognize.swift +124 -0
  349. package/scripts/ocr-recognize.ps1 +102 -0
  350. package/scripts/postinstall-native.js +48 -0
  351. package/scripts/ps-bridge.ps1 +830 -0
  352. package/scripts/smoke-mcp.ps1 +119 -0
  353. package/scripts/sync-version.ts +178 -0
  354. package/scripts/verify-install.js +81 -0
@@ -0,0 +1,182 @@
1
+ import Foundation
2
+ import AppKit
3
+ import ApplicationServices
4
+ import CoreGraphics
5
+ import Network
6
+
7
+ private let hostPort: UInt16 = {
8
+ if let env = ProcessInfo.processInfo.environment["CLAWDCURSOR_HOST_PORT"], let parsed = UInt16(env) {
9
+ return parsed
10
+ }
11
+ return 3848
12
+ }()
13
+
14
+ private func expectedToken() -> String? {
15
+ let home = FileManager.default.homeDirectoryForCurrentUser
16
+ let tokenPath = home.appendingPathComponent(".clawdcursor/host-token").path
17
+ return try? String(contentsOfFile: tokenPath, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines)
18
+ }
19
+
20
+ private func jsonResponse(status: Int, payload: Data) -> Data {
21
+ var response = "HTTP/1.1 \(status) \(status == 200 ? "OK" : "ERROR")\r\n"
22
+ response += "Content-Type: application/json\r\n"
23
+ response += "Content-Length: \(payload.count)\r\n"
24
+ response += "Connection: close\r\n\r\n"
25
+ var data = Data(response.utf8)
26
+ data.append(payload)
27
+ return data
28
+ }
29
+
30
+ private func textResponse(status: Int, text: String) -> Data {
31
+ jsonResponse(status: status, payload: Data(text.utf8))
32
+ }
33
+
34
+ private func runBinary(_ binary: String, args: [String] = [], stdin: Data? = nil) -> (exitCode: Int32, stdout: Data, stderr: Data) {
35
+ let bundlePath = Bundle.main.bundlePath
36
+ let macOSDir = URL(fileURLWithPath: bundlePath).appendingPathComponent("Contents/MacOS")
37
+ let binaryPath = macOSDir.appendingPathComponent(binary).path
38
+
39
+ let process = Process()
40
+ let out = Pipe()
41
+ let err = Pipe()
42
+ let input = Pipe()
43
+
44
+ process.executableURL = URL(fileURLWithPath: binaryPath)
45
+ process.arguments = args
46
+ process.standardOutput = out
47
+ process.standardError = err
48
+ process.standardInput = input
49
+
50
+ do {
51
+ try process.run()
52
+ } catch {
53
+ return (1, Data(), Data("{\"error\":\"failed_to_launch_binary\"}".utf8))
54
+ }
55
+
56
+ if let stdin {
57
+ input.fileHandleForWriting.write(stdin)
58
+ }
59
+ try? input.fileHandleForWriting.close()
60
+
61
+ process.waitUntilExit()
62
+ let stdout = out.fileHandleForReading.readDataToEndOfFile()
63
+ let stderr = err.fileHandleForReading.readDataToEndOfFile()
64
+ return (process.terminationStatus, stdout, stderr)
65
+ }
66
+
67
+ private func handleRequest(raw: Data) -> Data {
68
+ guard let request = String(data: raw, encoding: .utf8) else {
69
+ return textResponse(status: 400, text: "{\"error\":\"invalid_utf8\"}")
70
+ }
71
+
72
+ let parts = request.components(separatedBy: "\r\n\r\n")
73
+ guard let head = parts.first else {
74
+ return textResponse(status: 400, text: "{\"error\":\"invalid_request\"}")
75
+ }
76
+
77
+ let lines = head.components(separatedBy: "\r\n")
78
+ guard let reqLine = lines.first else {
79
+ return textResponse(status: 400, text: "{\"error\":\"missing_request_line\"}")
80
+ }
81
+
82
+ let reqParts = reqLine.split(separator: " ")
83
+ guard reqParts.count >= 2 else {
84
+ return textResponse(status: 400, text: "{\"error\":\"bad_request_line\"}")
85
+ }
86
+
87
+ let method = String(reqParts[0])
88
+ let path = String(reqParts[1])
89
+ let body = parts.dropFirst().joined(separator: "\r\n\r\n")
90
+ var headers: [String: String] = [:]
91
+ for line in lines.dropFirst() {
92
+ if let idx = line.firstIndex(of: ":") {
93
+ let name = String(line[..<idx]).trimmingCharacters(in: .whitespaces).lowercased()
94
+ let value = String(line[line.index(after: idx)...]).trimmingCharacters(in: .whitespaces)
95
+ headers[name] = value
96
+ }
97
+ }
98
+
99
+ if method == "GET" && path == "/health" {
100
+ let payload = "{\"status\":\"ok\",\"service\":\"clawdcursor-host\",\"port\":\(hostPort)}"
101
+ return textResponse(status: 200, text: payload)
102
+ }
103
+
104
+ if method == "GET" && path == "/status" {
105
+ // Always delegate to the permission-check binary — it runs in the SAME
106
+ // app-bundle context (same TCC identity) and returns the canonical format
107
+ // including processPath + bundleId. This keeps doctor, CLI status, and
108
+ // readiness.ts consistent.
109
+ let result = runBinary("permission-check", args: [])
110
+ if result.exitCode == 0, !result.stdout.isEmpty,
111
+ let text = String(data: result.stdout, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
112
+ !text.isEmpty {
113
+ return textResponse(status: 200, text: text)
114
+ }
115
+
116
+ // Fallback: permission-check binary missing or crashed.
117
+ // Check directly but match the SAME JSON schema so callers don't break.
118
+ let axOptions = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: kCFBooleanFalse] as CFDictionary
119
+ let axGranted = AXIsProcessTrustedWithOptions(axOptions)
120
+ let screenGranted = CGPreflightScreenCaptureAccess()
121
+ let bundleId = Bundle.main.bundleIdentifier ?? "unknown"
122
+ let processPath = ProcessInfo.processInfo.arguments.first ?? "unknown"
123
+ let payload = "{\"accessibility\":\(axGranted),\"screenRecording\":\(screenGranted),\"processPath\":\"\(processPath)\",\"bundleId\":\"\(bundleId)\"}"
124
+ return textResponse(status: 200, text: payload)
125
+ }
126
+
127
+ if method == "POST" && path == "/rpc" {
128
+ guard let token = expectedToken(), !token.isEmpty else {
129
+ return textResponse(status: 503, text: "{\"error\":\"host_token_missing\"}")
130
+ }
131
+ guard headers["x-clawdcursor-token"] == token else {
132
+ return textResponse(status: 401, text: "{\"error\":\"unauthorized\"}")
133
+ }
134
+ let result = runBinary("clawdcursor-helper", stdin: Data((body + "\n").utf8))
135
+ if result.exitCode == 0, !result.stdout.isEmpty {
136
+ let lines = String(data: result.stdout, encoding: .utf8)?.split(separator: "\n") ?? []
137
+ if let first = lines.first {
138
+ return textResponse(status: 200, text: String(first))
139
+ }
140
+ }
141
+ let stderr = String(data: result.stderr, encoding: .utf8) ?? "unknown error"
142
+ return textResponse(status: 500, text: "{\"error\":\"helper_failed\",\"message\":\"\(stderr.replacingOccurrences(of: "\"", with: "'"))\"}")
143
+ }
144
+
145
+ return textResponse(status: 404, text: "{\"error\":\"not_found\"}")
146
+ }
147
+
148
+ private var listenerRef: NWListener?
149
+
150
+ private func startServer() throws {
151
+ // SECURITY: Bind to localhost only — reject connections from other machines
152
+ let params = NWParameters.tcp
153
+ params.requiredLocalEndpoint = NWEndpoint.hostPort(host: .ipv4(.loopback), port: NWEndpoint.Port(rawValue: hostPort)!)
154
+ let listener = try NWListener(using: params)
155
+ listener.newConnectionHandler = { conn in
156
+ conn.start(queue: .global())
157
+ conn.receive(minimumIncompleteLength: 1, maximumLength: 1024 * 1024) { data, _, _, _ in
158
+ let response = handleRequest(raw: data ?? Data())
159
+ conn.send(content: response, completion: .contentProcessed { _ in
160
+ conn.cancel()
161
+ })
162
+ }
163
+ }
164
+ listener.start(queue: .global())
165
+ listenerRef = listener
166
+ }
167
+
168
+ let app = NSApplication.shared
169
+ app.setActivationPolicy(.accessory)
170
+
171
+ if let bundleId = Bundle.main.bundleIdentifier {
172
+ NSLog("ClawdCursorHost starting (bundle: \(bundleId), port: \(hostPort))")
173
+ }
174
+
175
+ try startServer()
176
+ DispatchQueue.main.async {
177
+ let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
178
+ statusItem.button?.title = "🐾"
179
+ statusItem.button?.toolTip = "ClawdCursor Host"
180
+ }
181
+
182
+ app.run()
@@ -0,0 +1,53 @@
1
+ /// permission-check - Quick permission status checker for Node.js integration
2
+ /// Returns JSON: {"accessibility": bool, "screenRecording": bool}
3
+ /// Use AXTrustedCheckOptionPrompt: true to trigger the system prompt dialog
4
+
5
+ import Foundation
6
+ import ApplicationServices
7
+ import CoreGraphics
8
+
9
+ struct PermissionStatus: Codable {
10
+ let accessibility: Bool
11
+ let screenRecording: Bool
12
+ let processPath: String
13
+ let bundleId: String?
14
+ }
15
+
16
+ // Check Accessibility permission (does NOT prompt - use --prompt flag for that)
17
+ // Pattern from mediar-ai/MacosUseSDK - use kCFBooleanTrue/False explicitly
18
+ let shouldPrompt = CommandLine.arguments.contains("--prompt")
19
+ let promptValue: CFBoolean = shouldPrompt ? kCFBooleanTrue : kCFBooleanFalse
20
+ let axOptions = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: promptValue] as CFDictionary
21
+ let axGranted = AXIsProcessTrustedWithOptions(axOptions)
22
+
23
+ // Check Screen Recording permission
24
+ // CGPreflightScreenCaptureAccess() returns current state without prompting
25
+ // CGRequestScreenCaptureAccess() triggers the system prompt dialog
26
+ let shouldRequestScreen = CommandLine.arguments.contains("--request-screen-recording")
27
+ let screenGranted: Bool
28
+ if shouldRequestScreen {
29
+ screenGranted = CGRequestScreenCaptureAccess()
30
+ } else {
31
+ screenGranted = CGPreflightScreenCaptureAccess()
32
+ }
33
+
34
+ // Get process info for debugging
35
+ let processPath = ProcessInfo.processInfo.arguments[0]
36
+ let bundleId = Bundle.main.bundleIdentifier
37
+
38
+ let status = PermissionStatus(
39
+ accessibility: axGranted,
40
+ screenRecording: screenGranted,
41
+ processPath: processPath,
42
+ bundleId: bundleId
43
+ )
44
+
45
+ let encoder = JSONEncoder()
46
+ encoder.outputFormatting = .sortedKeys
47
+ if let jsonData = try? encoder.encode(status),
48
+ let jsonString = String(data: jsonData, encoding: .utf8) {
49
+ print(jsonString)
50
+ } else {
51
+ fputs("{\"error\": \"Failed to encode status\"}\n", stderr)
52
+ exit(1)
53
+ }
@@ -0,0 +1,219 @@
1
+ /// screenshot-helper - Isolated screen capture subprocess
2
+ /// Runs in separate process to:
3
+ /// 1. Isolate Screen Recording TCC permission
4
+ /// 2. Prevent ReplayKit CPU spin bug (19% idle CPU after capture)
5
+ ///
6
+ /// Usage: screenshot-helper <windowId> <outputPath>
7
+ /// screenshot-helper --fullscreen <outputPath>
8
+ ///
9
+ /// macOS Tahoe (26.0+) flash-free notes
10
+ /// ────────────────────────────────────
11
+ /// In macOS 26 Tahoe, the system added a "screen captured" white-flash
12
+ /// animation that fires whenever any process calls into the
13
+ /// `screencapture` coordinator daemon — including the deprecated
14
+ /// `CGWindowListCreateImage` path. The flash is a privacy/awareness
15
+ /// feature, not a bug, but it makes silent background captures
16
+ /// (the kind agent tools do dozens of times a session) visually
17
+ /// disruptive.
18
+ ///
19
+ /// `ScreenCaptureKit.SCScreenshotManager.captureImage` uses a different
20
+ /// pipeline that Tahoe's flash hook does NOT intercept. On macOS 14+
21
+ /// (Sonoma and later) we prefer that path; on 12-13 (Monterey/Ventura)
22
+ /// we fall back to the legacy CG path which is silent on those OS
23
+ /// versions anyway.
24
+
25
+ import Foundation
26
+ import CoreGraphics
27
+ import ImageIO
28
+ import UniformTypeIdentifiers
29
+ import AppKit
30
+ #if canImport(ScreenCaptureKit)
31
+ import ScreenCaptureKit
32
+ #endif
33
+
34
+ // MARK: - macOS 14+ path (ScreenCaptureKit, flash-free on Tahoe)
35
+
36
+ #if canImport(ScreenCaptureKit)
37
+ @available(macOS 14.0, *)
38
+ func captureFullScreenSCK(outputPath: String) -> Bool {
39
+ let semaphore = DispatchSemaphore(value: 0)
40
+ var captured: CGImage? = nil
41
+ var captureError: Error? = nil
42
+
43
+ Task {
44
+ do {
45
+ let content = try await SCShareableContent.current
46
+ guard let display = content.displays.first else {
47
+ captureError = NSError(domain: "ScreenshotHelper", code: 10, userInfo: [NSLocalizedDescriptionKey: "no displays"])
48
+ semaphore.signal()
49
+ return
50
+ }
51
+ let filter = SCContentFilter(display: display, excludingApplications: [], exceptingWindows: [])
52
+ let config = SCStreamConfiguration()
53
+ // Capture at native resolution. SCK reports display size in
54
+ // points; multiply by the display's backingScaleFactor so
55
+ // the resulting PNG matches the pixel dimensions the legacy
56
+ // CG path produced. Without this, downstream code that
57
+ // reads .width/.height on a retina capture would see
58
+ // half-resolution images.
59
+ let scale = NSScreen.main?.backingScaleFactor ?? 2.0
60
+ config.width = Int(CGFloat(display.width) * scale)
61
+ config.height = Int(CGFloat(display.height) * scale)
62
+ config.scalesToFit = false
63
+ config.showsCursor = false
64
+ let image = try await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config)
65
+ captured = image
66
+ } catch {
67
+ captureError = error
68
+ }
69
+ semaphore.signal()
70
+ }
71
+
72
+ _ = semaphore.wait(timeout: .now() + 15)
73
+ if let err = captureError {
74
+ fputs("error: ScreenCaptureKit failed: \(err.localizedDescription)\n", stderr)
75
+ return false
76
+ }
77
+ guard let image = captured else {
78
+ fputs("error: ScreenCaptureKit returned no image\n", stderr)
79
+ return false
80
+ }
81
+ return saveImage(image, to: outputPath)
82
+ }
83
+
84
+ @available(macOS 14.0, *)
85
+ func captureWindowSCK(windowId: CGWindowID, outputPath: String) -> Bool {
86
+ let semaphore = DispatchSemaphore(value: 0)
87
+ var captured: CGImage? = nil
88
+ var captureError: Error? = nil
89
+
90
+ Task {
91
+ do {
92
+ let content = try await SCShareableContent.current
93
+ // Find the SCWindow whose windowID matches the caller's CGWindowID.
94
+ // SCWindow.windowID is the same CGWindowID under the hood, so
95
+ // direct equality holds.
96
+ guard let scwindow = content.windows.first(where: { $0.windowID == windowId }) else {
97
+ captureError = NSError(domain: "ScreenshotHelper", code: 11, userInfo: [NSLocalizedDescriptionKey: "window \(windowId) not in shareable content"])
98
+ semaphore.signal()
99
+ return
100
+ }
101
+ let filter = SCContentFilter(desktopIndependentWindow: scwindow)
102
+ let config = SCStreamConfiguration()
103
+ let scale = NSScreen.main?.backingScaleFactor ?? 2.0
104
+ // Use the window's frame for sizing — SCK requires explicit
105
+ // pixel dimensions on the SCStreamConfiguration.
106
+ config.width = Int(scwindow.frame.width * scale)
107
+ config.height = Int(scwindow.frame.height * scale)
108
+ config.scalesToFit = false
109
+ config.showsCursor = false
110
+ let image = try await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config)
111
+ captured = image
112
+ } catch {
113
+ captureError = error
114
+ }
115
+ semaphore.signal()
116
+ }
117
+
118
+ _ = semaphore.wait(timeout: .now() + 15)
119
+ if let err = captureError {
120
+ fputs("error: ScreenCaptureKit window capture failed: \(err.localizedDescription)\n", stderr)
121
+ return false
122
+ }
123
+ guard let image = captured else {
124
+ fputs("error: ScreenCaptureKit returned no image\n", stderr)
125
+ return false
126
+ }
127
+ return saveImage(image, to: outputPath)
128
+ }
129
+ #endif
130
+
131
+ // MARK: - macOS 12-13 path (CGWindowListCreateImage, silent on those versions)
132
+
133
+ func captureWindow(windowId: CGWindowID, outputPath: String) -> Bool {
134
+ #if canImport(ScreenCaptureKit)
135
+ if #available(macOS 14.0, *) {
136
+ return captureWindowSCK(windowId: windowId, outputPath: outputPath)
137
+ }
138
+ #endif
139
+ guard let image = CGWindowListCreateImage(
140
+ .null,
141
+ .optionIncludingWindow,
142
+ windowId,
143
+ [.boundsIgnoreFraming, .nominalResolution]
144
+ ) else {
145
+ fputs("error: failed to capture window \(windowId)\n", stderr)
146
+ return false
147
+ }
148
+ return saveImage(image, to: outputPath)
149
+ }
150
+
151
+ func captureFullScreen(outputPath: String) -> Bool {
152
+ #if canImport(ScreenCaptureKit)
153
+ if #available(macOS 14.0, *) {
154
+ return captureFullScreenSCK(outputPath: outputPath)
155
+ }
156
+ #endif
157
+ guard let image = CGWindowListCreateImage(
158
+ CGRect.infinite,
159
+ .optionOnScreenOnly,
160
+ kCGNullWindowID,
161
+ [.nominalResolution]
162
+ ) else {
163
+ fputs("error: failed to capture screen\n", stderr)
164
+ return false
165
+ }
166
+ return saveImage(image, to: outputPath)
167
+ }
168
+
169
+ // MARK: - Shared encoding path
170
+
171
+ func saveImage(_ image: CGImage, to path: String) -> Bool {
172
+ let url = URL(fileURLWithPath: path)
173
+ guard let destination = CGImageDestinationCreateWithURL(
174
+ url as CFURL,
175
+ UTType.png.identifier as CFString,
176
+ 1,
177
+ nil
178
+ ) else {
179
+ fputs("error: failed to create image destination\n", stderr)
180
+ return false
181
+ }
182
+
183
+ CGImageDestinationAddImage(destination, image, nil)
184
+
185
+ if CGImageDestinationFinalize(destination) {
186
+ print("{\"success\": true, \"path\": \"\(path)\", \"width\": \(image.width), \"height\": \(image.height)}")
187
+ return true
188
+ } else {
189
+ fputs("error: failed to write image\n", stderr)
190
+ return false
191
+ }
192
+ }
193
+
194
+ // MARK: - Entry
195
+
196
+ // Check Screen Recording permission first
197
+ if !CGPreflightScreenCaptureAccess() {
198
+ fputs("{\"error\": \"screen_recording_denied\", \"message\": \"Grant Screen Recording permission in System Settings > Privacy & Security > Screen & System Audio Recording\"}\n", stderr)
199
+ exit(2)
200
+ }
201
+
202
+ // Parse arguments
203
+ let args = CommandLine.arguments
204
+ guard args.count >= 3 else {
205
+ fputs("usage: screenshot-helper <windowId|--fullscreen> <outputPath>\n", stderr)
206
+ exit(1)
207
+ }
208
+
209
+ let success: Bool
210
+ if args[1] == "--fullscreen" {
211
+ success = captureFullScreen(outputPath: args[2])
212
+ } else if let windowId = UInt32(args[1]) {
213
+ success = captureWindow(windowId: CGWindowID(windowId), outputPath: args[2])
214
+ } else {
215
+ fputs("error: invalid window ID\n", stderr)
216
+ exit(1)
217
+ }
218
+
219
+ exit(success ? 0 : 1)
@@ -0,0 +1,139 @@
1
+ #!/bin/bash
2
+ # Build script for ClawdCursor native helper (macOS only)
3
+ # Usage: ./build.sh [--adhoc]
4
+ # --adhoc is now the DEFAULT behavior (required for TCC on macOS 26+)
5
+ set -e
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ cd "$SCRIPT_DIR"
9
+
10
+ # Ensure this script is executable (no dependency on caller having set +x)
11
+ if [ ! -x "$0" ]; then
12
+ chmod +x "$0"
13
+ fi
14
+
15
+ echo "🔨 Building ClawdCursor native helper..."
16
+
17
+ # Build all targets in release mode
18
+ if ! swift build -c release; then
19
+ echo "❌ Swift build failed. Ensure Xcode Command Line Tools are installed:"
20
+ echo " xcode-select --install"
21
+ exit 1
22
+ fi
23
+
24
+ # Get the build directory
25
+ BUILD_DIR=".build/release"
26
+
27
+ # Create the .app bundle structure
28
+ APP_DIR="ClawdCursor.app/Contents/MacOS"
29
+ mkdir -p "$APP_DIR"
30
+
31
+ # These four binaries are ALL required for correct operation
32
+ REQUIRED_BINARIES="ClawdCursorHost clawdcursor-helper screenshot-helper permission-check"
33
+ MISSING=0
34
+
35
+ for binary in $REQUIRED_BINARIES; do
36
+ if [ -f "$BUILD_DIR/$binary" ]; then
37
+ cp "$BUILD_DIR/$binary" "$APP_DIR/"
38
+ echo " ✓ Copied $binary"
39
+ else
40
+ echo " ❌ MISSING required binary: $binary"
41
+ MISSING=1
42
+ fi
43
+ done
44
+
45
+ if [ "$MISSING" -eq 1 ]; then
46
+ echo ""
47
+ echo "❌ Build incomplete — one or more required binaries are missing."
48
+ echo " Check the swift build output above for compilation errors."
49
+ exit 1
50
+ fi
51
+
52
+ echo "✅ Built ClawdCursor.app"
53
+
54
+ # Generate Contents/Info.plist — REQUIRED for a valid .app bundle. Without it
55
+ # codesign fails with "bundle format unrecognized, invalid, or unsuitable"
56
+ # (issue #150). CFBundleExecutable names the main binary inside Contents/MacOS/.
57
+ cat > "ClawdCursor.app/Contents/Info.plist" <<'PLIST'
58
+ <?xml version="1.0" encoding="UTF-8"?>
59
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
60
+ <plist version="1.0">
61
+ <dict>
62
+ <key>CFBundleIdentifier</key>
63
+ <string>com.clawdcursor.helper</string>
64
+ <key>CFBundleName</key>
65
+ <string>ClawdCursor</string>
66
+ <key>CFBundleDisplayName</key>
67
+ <string>ClawdCursor</string>
68
+ <key>CFBundleExecutable</key>
69
+ <string>ClawdCursorHost</string>
70
+ <key>CFBundlePackageType</key>
71
+ <string>APPL</string>
72
+ <key>CFBundleInfoDictionaryVersion</key>
73
+ <string>6.0</string>
74
+ <key>CFBundleVersion</key>
75
+ <string>1.0.0</string>
76
+ <key>CFBundleShortVersionString</key>
77
+ <string>1.0.0</string>
78
+ <key>LSMinimumSystemVersion</key>
79
+ <string>13.0</string>
80
+ <key>LSUIElement</key>
81
+ <true/>
82
+ <key>NSAppleEventsUsageDescription</key>
83
+ <string>ClawdCursor controls other apps to automate desktop tasks you request.</string>
84
+ </dict>
85
+ </plist>
86
+ PLIST
87
+ echo " ✓ Wrote Contents/Info.plist"
88
+
89
+ # Code signing (REQUIRED for TCC on macOS 26+ / Tahoe)
90
+ # Without signing, the app won't appear in System Settings privacy panels
91
+ if [[ -n "$CLAWDCURSOR_SIGN_IDENTITY" ]]; then
92
+ echo "🔐 Signing with Developer ID: $CLAWDCURSOR_SIGN_IDENTITY"
93
+ codesign --sign "$CLAWDCURSOR_SIGN_IDENTITY" \
94
+ --options runtime \
95
+ --entitlements entitlements.plist \
96
+ --force \
97
+ --deep \
98
+ "ClawdCursor.app"
99
+ echo "✅ Signed with Developer ID"
100
+ else
101
+ # Ad-hoc sign by default — CRITICAL for TCC to recognize the app.
102
+ # NOTE: do NOT use --options runtime (hardened runtime) on the ad-hoc build.
103
+ # It makes macOS evaluate the helper's Screen-Recording TCC by its OWN
104
+ # identity (com.clawdcursor.helper) instead of letting it inherit the
105
+ # daemon host's grant — so a daemon-spawned screenshot-helper is denied and
106
+ # silently falls back to nut-js (#149). Hardened runtime is only needed on
107
+ # the Developer-ID/notarized branch above.
108
+ echo "🔐 Ad-hoc signing (required for TCC permissions)..."
109
+ if [ -f "entitlements.plist" ]; then
110
+ codesign --sign - \
111
+ --entitlements entitlements.plist \
112
+ --force \
113
+ --deep \
114
+ "ClawdCursor.app"
115
+ else
116
+ codesign --sign - \
117
+ --force \
118
+ --deep \
119
+ "ClawdCursor.app"
120
+ fi
121
+ echo "✅ Ad-hoc signed"
122
+ fi
123
+
124
+ # Verify signature
125
+ if codesign -v "ClawdCursor.app" 2>/dev/null; then
126
+ echo "✅ Signature verified"
127
+ else
128
+ echo "⚠️ Signature verification failed — TCC permissions may not work"
129
+ echo " On macOS 26+ (Tahoe), unsigned binaries don't appear in privacy settings"
130
+ fi
131
+
132
+ echo ""
133
+ echo "📦 Output: $SCRIPT_DIR/ClawdCursor.app"
134
+ echo ""
135
+ echo "To test permissions:"
136
+ echo " ./ClawdCursor.app/Contents/MacOS/permission-check"
137
+ echo ""
138
+ echo "To launch:"
139
+ echo " open ClawdCursor.app"
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>com.apple.security.automation.apple-events</key>
6
+ <true/>
7
+ <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
8
+ <false/>
9
+ <key>com.apple.security.cs.disable-library-validation</key>
10
+ <false/>
11
+ </dict>
12
+ </plist>