@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,936 @@
1
+ "use strict";
2
+ /**
3
+ * Native Desktop Control — direct OS-level input
4
+ * using @nut-tree-fork/nut-js for mouse/keyboard and screen capture.
5
+ *
6
+ * No network connection needed — controls the local desktop directly.
7
+ *
8
+ * - captureScreen() returns full-resolution frames
9
+ * - captureForLLM() returns resized frames (1280px wide) with scaling metadata
10
+ * - Coordinate scaling handled transparently
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ var __importDefault = (this && this.__importDefault) || function (mod) {
46
+ return (mod && mod.__esModule) ? mod : { "default": mod };
47
+ };
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.NativeDesktop = void 0;
50
+ const os_1 = __importDefault(require("os"));
51
+ const events_1 = require("events");
52
+ const sharp_1 = __importDefault(require("sharp"));
53
+ const nut_js_1 = require("@nut-tree-fork/nut-js");
54
+ const keys_1 = require("./keys");
55
+ const native_helper_1 = require("./native-helper");
56
+ const fs = __importStar(require("fs"));
57
+ // On macOS, Command key = Key.LeftCmd. On other platforms, Super = Key.LeftSuper.
58
+ const SUPER_KEY = os_1.default.platform() === 'darwin' ? nut_js_1.Key.LeftCmd : nut_js_1.Key.LeftSuper;
59
+ const IS_MAC = os_1.default.platform() === 'darwin';
60
+ /** Safely resolve a nut-js Key enum value from multiple candidate names */
61
+ function resolveNutKey(...candidates) {
62
+ for (const name of candidates) {
63
+ const value = nut_js_1.Key[name];
64
+ if (value !== undefined)
65
+ return value;
66
+ }
67
+ throw new Error(`Unable to resolve nut-js key from candidates: ${candidates.join(', ')}`);
68
+ }
69
+ // nut-js Key enum mapping from canonical key names (see keys.ts for normalization)
70
+ const KEY_MAP = {
71
+ 'Return': nut_js_1.Key.Enter,
72
+ 'Tab': nut_js_1.Key.Tab,
73
+ 'Escape': nut_js_1.Key.Escape,
74
+ 'Backspace': nut_js_1.Key.Backspace,
75
+ 'Delete': nut_js_1.Key.Delete,
76
+ 'Home': nut_js_1.Key.Home,
77
+ 'End': nut_js_1.Key.End,
78
+ 'PageUp': nut_js_1.Key.PageUp,
79
+ 'PageDown': nut_js_1.Key.PageDown,
80
+ 'Left': nut_js_1.Key.Left,
81
+ 'Up': nut_js_1.Key.Up,
82
+ 'Right': nut_js_1.Key.Right,
83
+ 'Down': nut_js_1.Key.Down,
84
+ 'F1': nut_js_1.Key.F1, 'F2': nut_js_1.Key.F2, 'F3': nut_js_1.Key.F3, 'F4': nut_js_1.Key.F4,
85
+ 'F5': nut_js_1.Key.F5, 'F6': nut_js_1.Key.F6, 'F7': nut_js_1.Key.F7, 'F8': nut_js_1.Key.F8,
86
+ 'F9': nut_js_1.Key.F9, 'F10': nut_js_1.Key.F10, 'F11': nut_js_1.Key.F11, 'F12': nut_js_1.Key.F12,
87
+ 'Shift': nut_js_1.Key.LeftShift,
88
+ 'Control': nut_js_1.Key.LeftControl,
89
+ 'Alt': nut_js_1.Key.LeftAlt,
90
+ 'Super': SUPER_KEY,
91
+ 'Space': nut_js_1.Key.Space,
92
+ // Symbol keys for combos like ctrl+plus / ctrl+minus
93
+ '=': resolveNutKey('Equal', 'Equals'),
94
+ '+': resolveNutKey('Equal', 'Equals', 'Add', 'NumAdd'),
95
+ '-': resolveNutKey('Minus', 'Subtract', 'NumSubtract'),
96
+ '_': resolveNutKey('Minus', 'Subtract', 'NumSubtract'),
97
+ };
98
+ /** LLM screenshot target width — smaller = faster API calls + fewer tokens */
99
+ // Higher resolution = better tool/icon identification. 1280 is Anthropic's recommended max.
100
+ // At 2560 screen: 1280 → scale 2x (was 1024 → 2.5x). Icons go from ~12px to ~20px.
101
+ const LLM_TARGET_WIDTH = 1280;
102
+ class NativeDesktop extends events_1.EventEmitter {
103
+ config;
104
+ screenWidth = 0;
105
+ screenHeight = 0;
106
+ connected = false;
107
+ monitors = [];
108
+ helper = IS_MAC ? (0, native_helper_1.getNativeHelper)() : null;
109
+ /** Scale factor: LLM coordinates × scaleFactor = real screen coordinates */
110
+ scaleFactor = 1;
111
+ /**
112
+ * DPI ratio: physical pixels / logical (mouse) pixels.
113
+ * OCR coordinates (physical) / dpiRatio = mouse coordinates (logical).
114
+ * Detected at connect() time via System.Windows.Forms.
115
+ */
116
+ dpiRatio = 1;
117
+ constructor(config) {
118
+ super();
119
+ this.config = config;
120
+ }
121
+ /**
122
+ * Enumerate all connected monitors with their positions and sizes.
123
+ * Returns best-effort results — falls back to primary only on errors.
124
+ */
125
+ async getMonitors() {
126
+ if (this.monitors.length > 0)
127
+ return this.monitors;
128
+ try {
129
+ if (process.platform === 'win32') {
130
+ const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
131
+ const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
132
+ const execAsync = promisify(exec);
133
+ const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::AllScreens | ForEach-Object { "$($_.Bounds.X),$($_.Bounds.Y),$($_.Bounds.Width),$($_.Bounds.Height),$($_.Primary),$($_.DeviceName)" }`;
134
+ const { stdout } = await execAsync(`powershell -NoProfile -Command "${ps}"`);
135
+ const lines = stdout.trim().split('\n').filter(Boolean);
136
+ this.monitors = lines.map((line, i) => {
137
+ const [x, y, w, h, primary, name] = line.trim().split(',');
138
+ return {
139
+ index: i,
140
+ x: parseInt(x), y: parseInt(y),
141
+ width: parseInt(w), height: parseInt(h),
142
+ primary: primary.trim().toLowerCase() === 'true',
143
+ name: name?.trim() || `Monitor ${i + 1}`,
144
+ };
145
+ });
146
+ }
147
+ else if (process.platform === 'darwin') {
148
+ const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
149
+ const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
150
+ const execAsync = promisify(exec);
151
+ // system_profiler gives display info; for bounds we use osascript
152
+ const { stdout } = await execAsync(`osascript -e 'tell application "System Events" to get bounds of every desktop'`).catch(() => ({ stdout: '' }));
153
+ if (stdout.trim()) {
154
+ // fallback: just return primary
155
+ this.monitors = [{ index: 0, x: 0, y: 0, width: this.screenWidth, height: this.screenHeight, primary: true, name: 'Primary' }];
156
+ }
157
+ else {
158
+ this.monitors = [{ index: 0, x: 0, y: 0, width: this.screenWidth, height: this.screenHeight, primary: true, name: 'Primary' }];
159
+ }
160
+ }
161
+ else {
162
+ // Linux: use xrandr
163
+ const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
164
+ const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
165
+ const execAsync = promisify(exec);
166
+ const { stdout } = await execAsync('xrandr --query 2>/dev/null').catch(() => ({ stdout: '' }));
167
+ const re = /(\S+) connected(?: primary)? (\d+)x(\d+)\+(\d+)\+(\d+)/g;
168
+ let m;
169
+ let i = 0;
170
+ const results = [];
171
+ while ((m = re.exec(stdout)) !== null) {
172
+ results.push({ index: i++, x: parseInt(m[4]), y: parseInt(m[5]), width: parseInt(m[2]), height: parseInt(m[3]), primary: stdout.includes(m[1] + ' connected primary'), name: m[1] });
173
+ }
174
+ this.monitors = results.length > 0 ? results : [{ index: 0, x: 0, y: 0, width: this.screenWidth, height: this.screenHeight, primary: true, name: 'Primary' }];
175
+ }
176
+ }
177
+ catch {
178
+ this.monitors = [{ index: 0, x: 0, y: 0, width: this.screenWidth, height: this.screenHeight, primary: true, name: 'Primary' }];
179
+ }
180
+ return this.monitors;
181
+ }
182
+ /**
183
+ * Capture a specific monitor by index.
184
+ * Falls back to primary grab if region capture fails.
185
+ */
186
+ async captureMonitor(monitorIndex = 0) {
187
+ const monitors = await this.getMonitors();
188
+ const mon = monitors[monitorIndex] ?? monitors.find(m => m.primary) ?? monitors[0];
189
+ if (!mon)
190
+ return this.captureForLLM();
191
+ try {
192
+ const { Region } = await Promise.resolve().then(() => __importStar(require('@nut-tree-fork/nut-js')));
193
+ const region = new Region(mon.x, mon.y, mon.width, mon.height);
194
+ const img = await nut_js_1.screen.grabRegion(region);
195
+ const scaleFactor = mon.width > LLM_TARGET_WIDTH ? mon.width / LLM_TARGET_WIDTH : 1;
196
+ const llmW = Math.round(mon.width / scaleFactor);
197
+ const llmH = Math.round(mon.height / scaleFactor);
198
+ const processed = await (0, sharp_1.default)(img.data, { raw: { width: img.width, height: img.height, channels: 4 } })
199
+ .resize(llmW, llmH)
200
+ .png()
201
+ .toBuffer();
202
+ // Release the raw RGBA buffer immediately after processing
203
+ img.data = null;
204
+ return { width: mon.width, height: mon.height, buffer: processed, timestamp: Date.now(), format: 'png', scaleFactor, llmWidth: llmW, llmHeight: llmH };
205
+ }
206
+ catch {
207
+ // Fallback: full primary grab
208
+ return this.captureForLLM();
209
+ }
210
+ }
211
+ /**
212
+ * "Connect" to the native desktop — detects screen size and configures nut-js.
213
+ * No actual network connection; just initializes the local screen interface.
214
+ */
215
+ async connect() {
216
+ try {
217
+ if (IS_MAC) {
218
+ // Use the standalone screenshot-helper binary — avoids ReplayKit CPU spin
219
+ // bug and runs in an isolated subprocess for clean TCC permission scoping.
220
+ try {
221
+ const result = await (0, native_helper_1.captureScreenViaHelper)();
222
+ this.screenWidth = result.width;
223
+ this.screenHeight = result.height;
224
+ this.scaleFactor = this.screenWidth > LLM_TARGET_WIDTH ? this.screenWidth / LLM_TARGET_WIDTH : 1;
225
+ // Clean up the temp file from connect probe
226
+ try {
227
+ fs.unlinkSync(result.path);
228
+ }
229
+ catch { /* ignore */ }
230
+ this.connected = true;
231
+ console.log(`🐾 Native desktop connected (macOS screenshot-helper)`);
232
+ console.log(` Screen: ${this.screenWidth}x${this.screenHeight}`);
233
+ console.log(` LLM scale factor: ${this.scaleFactor.toFixed(2)}x`);
234
+ return;
235
+ }
236
+ catch (err) {
237
+ console.warn(`⚠️ macOS screenshot-helper failed, falling back to nut-js: ${err?.message || err}`);
238
+ }
239
+ }
240
+ // Configure nut-js for speed
241
+ nut_js_1.mouse.config.mouseSpeed = 2000; // Fast mouse movement
242
+ nut_js_1.mouse.config.autoDelayMs = 0; // No auto-delay between actions
243
+ nut_js_1.keyboard.config.autoDelayMs = 0; // No auto-delay between keystrokes
244
+ // Grab a screenshot to determine screen dimensions
245
+ const img = await nut_js_1.screen.grab();
246
+ this.screenWidth = img.width;
247
+ this.screenHeight = img.height;
248
+ // Calculate scale factor
249
+ if (this.screenWidth > LLM_TARGET_WIDTH) {
250
+ this.scaleFactor = this.screenWidth / LLM_TARGET_WIDTH;
251
+ }
252
+ else {
253
+ this.scaleFactor = 1;
254
+ }
255
+ // Detect DPI ratio (physical / logical) for OCR coordinate conversion.
256
+ // On Windows, System.Windows.Forms.Screen returns logical (DPI-scaled) dimensions,
257
+ // while screen.grab() returns physical pixels. Mouse API uses logical coords.
258
+ if (process.platform === 'win32') {
259
+ try {
260
+ const { execFileSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
261
+ const result = execFileSync('powershell.exe', [
262
+ '-NoProfile', '-Command',
263
+ "Add-Type -AssemblyName System.Windows.Forms; $s=[System.Windows.Forms.Screen]::PrimaryScreen.Bounds; \"$($s.Width),$($s.Height)\"",
264
+ ], { timeout: 10000, encoding: 'utf-8' }).trim();
265
+ const [logicalW] = result.split(',').map(Number);
266
+ if (logicalW > 0 && logicalW < this.screenWidth) {
267
+ this.dpiRatio = this.screenWidth / logicalW;
268
+ }
269
+ }
270
+ catch { /* non-fatal — dpiRatio stays 1 */ }
271
+ }
272
+ else if (process.platform === 'darwin') {
273
+ try {
274
+ const { execFileSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
275
+ // NSScreen reports in logical (point) dimensions — compare with physical pixels from screen.grab()
276
+ // TODO: Multi-monitor support — currently uses mainScreen only. For multi-monitor,
277
+ // enumerate NSScreen.screens and sum widths to get the full virtual canvas size.
278
+ const result = execFileSync('osascript', ['-e',
279
+ 'use framework "AppKit"\nreturn (current application\'s NSScreen\'s mainScreen\'s frame()\'s size\'s width) as integer',
280
+ ], { timeout: 5000, encoding: 'utf-8' }).trim();
281
+ const logicalW = parseInt(result);
282
+ if (logicalW > 0 && logicalW < this.screenWidth) {
283
+ this.dpiRatio = this.screenWidth / logicalW;
284
+ }
285
+ }
286
+ catch { /* non-fatal — dpiRatio stays 1 */ }
287
+ }
288
+ else if (process.platform === 'linux') {
289
+ try {
290
+ // Check common DE scale environment variables first
291
+ const gdkScale = parseInt(process.env.GDK_SCALE || '1');
292
+ const qtScale = parseFloat(process.env.QT_SCALE_FACTOR || '1');
293
+ const envScale = Math.max(gdkScale, qtScale);
294
+ if (envScale > 1) {
295
+ this.dpiRatio = envScale;
296
+ }
297
+ else {
298
+ const { execFileSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
299
+ const output = execFileSync('xrandr', ['--query'], { timeout: 5000, encoding: 'utf-8' });
300
+ const match = output.match(/primary\s+(\d+)x(\d+)/);
301
+ if (match) {
302
+ const logicalW = parseInt(match[1]);
303
+ if (logicalW > 0 && logicalW < this.screenWidth) {
304
+ this.dpiRatio = this.screenWidth / logicalW;
305
+ }
306
+ }
307
+ }
308
+ }
309
+ catch { /* non-fatal — dpiRatio stays 1 */ }
310
+ }
311
+ this.connected = true;
312
+ console.log(`🐾 Native desktop connected`);
313
+ console.log(` Screen: ${this.screenWidth}x${this.screenHeight}`);
314
+ console.log(` LLM scale factor: ${this.scaleFactor.toFixed(2)}x`);
315
+ if (this.dpiRatio > 1) {
316
+ console.log(` DPI ratio: ${this.dpiRatio.toFixed(2)}x (physical/logical)`);
317
+ }
318
+ }
319
+ catch (err) {
320
+ console.error('Native desktop init error:', err?.message);
321
+ this.connected = false;
322
+ throw err;
323
+ }
324
+ }
325
+ /**
326
+ * Capture a full-resolution screenshot.
327
+ */
328
+ async captureScreen() {
329
+ if (!this.connected) {
330
+ throw new Error('Not connected to native desktop');
331
+ }
332
+ if (IS_MAC) {
333
+ try {
334
+ const result = await (0, native_helper_1.captureScreenViaHelper)();
335
+ const buffer = fs.readFileSync(result.path);
336
+ try {
337
+ fs.unlinkSync(result.path);
338
+ }
339
+ catch { /* cleanup */ }
340
+ this.screenWidth = result.width;
341
+ this.screenHeight = result.height;
342
+ return {
343
+ width: result.width,
344
+ height: result.height,
345
+ buffer,
346
+ timestamp: Date.now(),
347
+ format: 'png',
348
+ };
349
+ }
350
+ catch (err) {
351
+ console.warn(`⚠️ macOS screenshot-helper failed, falling back to nut-js: ${err?.message || err}`);
352
+ }
353
+ }
354
+ const img = await nut_js_1.screen.grab();
355
+ // Update screen dimensions in case of resolution change
356
+ this.screenWidth = img.width;
357
+ this.screenHeight = img.height;
358
+ const processed = await this.processFrame(img.data, img.width, img.height, this.screenWidth, this.screenHeight);
359
+ // Release the raw RGBA buffer immediately after processing
360
+ img.data = null;
361
+ return {
362
+ width: this.screenWidth,
363
+ height: this.screenHeight,
364
+ buffer: processed,
365
+ timestamp: Date.now(),
366
+ format: this.config.capture.format,
367
+ };
368
+ }
369
+ /**
370
+ * Capture a RESIZED screenshot optimized for LLM vision.
371
+ * - Resized to 1280px wide (or less if screen is smaller)
372
+ * - Much smaller payload = fewer tokens = faster API calls
373
+ * - Returns scaleFactor so coordinates in AI response can be mapped back
374
+ */
375
+ async captureForLLM() {
376
+ if (!this.connected) {
377
+ throw new Error('Not connected to native desktop');
378
+ }
379
+ if (IS_MAC) {
380
+ try {
381
+ const frame = await this.captureScreen();
382
+ this.screenWidth = frame.width;
383
+ this.screenHeight = frame.height;
384
+ this.scaleFactor = this.screenWidth > LLM_TARGET_WIDTH ? this.screenWidth / LLM_TARGET_WIDTH : 1;
385
+ const llmWidth = Math.min(this.screenWidth, LLM_TARGET_WIDTH);
386
+ const llmHeight = Math.round(this.screenHeight / this.scaleFactor);
387
+ const pipeline = (0, sharp_1.default)(frame.buffer).resize(llmWidth, llmHeight);
388
+ const processed = this.config.capture.format === 'jpeg'
389
+ ? await pipeline.jpeg({ quality: this.config.capture.quality }).toBuffer()
390
+ : await pipeline.png().toBuffer();
391
+ return {
392
+ width: this.screenWidth,
393
+ height: this.screenHeight,
394
+ buffer: processed,
395
+ timestamp: Date.now(),
396
+ format: this.config.capture.format,
397
+ scaleFactor: this.scaleFactor,
398
+ llmWidth,
399
+ llmHeight,
400
+ };
401
+ }
402
+ catch (err) {
403
+ console.warn(`⚠️ macOS screenshot-helper LLM capture failed, falling back to nut-js: ${err?.message || err}`);
404
+ }
405
+ }
406
+ const img = await nut_js_1.screen.grab();
407
+ // Update screen dimensions
408
+ this.screenWidth = img.width;
409
+ this.screenHeight = img.height;
410
+ // Recalculate scale factor in case resolution changed
411
+ if (this.screenWidth > LLM_TARGET_WIDTH) {
412
+ this.scaleFactor = this.screenWidth / LLM_TARGET_WIDTH;
413
+ }
414
+ else {
415
+ this.scaleFactor = 1;
416
+ }
417
+ const llmWidth = Math.min(this.screenWidth, LLM_TARGET_WIDTH);
418
+ const llmHeight = Math.round(this.screenHeight / this.scaleFactor);
419
+ const processed = await this.processFrame(img.data, img.width, img.height, llmWidth, llmHeight);
420
+ // Release the raw RGBA buffer immediately after processing
421
+ img.data = null;
422
+ return {
423
+ width: this.screenWidth, // real screen width
424
+ height: this.screenHeight, // real screen height
425
+ buffer: processed,
426
+ timestamp: Date.now(),
427
+ format: this.config.capture.format,
428
+ scaleFactor: this.scaleFactor,
429
+ llmWidth,
430
+ llmHeight,
431
+ };
432
+ }
433
+ /**
434
+ * Capture a CROPPED region of the screen, resized for LLM.
435
+ * Coordinates are in REAL screen pixels.
436
+ * Returns the cropped image at higher effective resolution (more detail per pixel).
437
+ * @future — not yet used; intended for focused region analysis
438
+ */
439
+ async captureRegionForLLM(x, y, w, h) {
440
+ if (!this.connected)
441
+ throw new Error('Not connected');
442
+ if (IS_MAC && this.helper) {
443
+ const full = await this.captureScreen();
444
+ const rx = Math.max(0, Math.min(x, full.width - 1));
445
+ const ry = Math.max(0, Math.min(y, full.height - 1));
446
+ const rw = Math.min(w, full.width - rx);
447
+ const rh = Math.min(h, full.height - ry);
448
+ const cropScale = rw > LLM_TARGET_WIDTH ? rw / LLM_TARGET_WIDTH : 1;
449
+ const llmWidth = Math.min(rw, LLM_TARGET_WIDTH);
450
+ const llmHeight = Math.round(rh / cropScale);
451
+ const { format, quality } = this.config.capture;
452
+ let pipeline = (0, sharp_1.default)(full.buffer).extract({ left: rx, top: ry, width: rw, height: rh });
453
+ if (llmWidth < rw) {
454
+ pipeline = pipeline.resize(llmWidth, llmHeight, { fit: 'fill', kernel: 'lanczos3' });
455
+ }
456
+ const buffer = format === 'jpeg' ? await pipeline.jpeg({ quality }).toBuffer() : await pipeline.png().toBuffer();
457
+ return { width: rw, height: rh, buffer, timestamp: Date.now(), format, scaleFactor: cropScale, llmWidth, llmHeight, regionX: rx, regionY: ry };
458
+ }
459
+ const img = await nut_js_1.screen.grab();
460
+ // Clamp to screen bounds
461
+ const rx = Math.max(0, Math.min(x, img.width - 1));
462
+ const ry = Math.max(0, Math.min(y, img.height - 1));
463
+ const rw = Math.min(w, img.width - rx);
464
+ const rh = Math.min(h, img.height - ry);
465
+ // Scale crop to LLM-sized output (max 1280px wide)
466
+ const cropScale = rw > LLM_TARGET_WIDTH ? rw / LLM_TARGET_WIDTH : 1;
467
+ const llmWidth = Math.min(rw, LLM_TARGET_WIDTH);
468
+ const llmHeight = Math.round(rh / cropScale);
469
+ const { format, quality } = this.config.capture;
470
+ let pipeline = (0, sharp_1.default)(img.data, {
471
+ raw: { width: img.width, height: img.height, channels: 4 },
472
+ }).extract({ left: rx, top: ry, width: rw, height: rh });
473
+ if (llmWidth < rw) {
474
+ pipeline = pipeline.resize(llmWidth, llmHeight, { fit: 'fill', kernel: 'lanczos3' });
475
+ }
476
+ const buffer = format === 'jpeg'
477
+ ? await pipeline.jpeg({ quality }).toBuffer()
478
+ : await pipeline.png().toBuffer();
479
+ // Release the raw RGBA buffer immediately after processing
480
+ img.data = null;
481
+ return {
482
+ width: rw,
483
+ height: rh,
484
+ buffer,
485
+ timestamp: Date.now(),
486
+ format,
487
+ scaleFactor: cropScale,
488
+ llmWidth,
489
+ llmHeight,
490
+ regionX: rx,
491
+ regionY: ry,
492
+ };
493
+ }
494
+ /**
495
+ * Get the scaling factor (LLM pixels → real screen pixels)
496
+ */
497
+ getScaleFactor() {
498
+ return this.scaleFactor;
499
+ }
500
+ /**
501
+ * Get the DPI ratio (physical pixels / logical mouse pixels).
502
+ * Returns 1 on non-HiDPI screens or non-Windows platforms.
503
+ */
504
+ getDpiRatio() {
505
+ return this.dpiRatio;
506
+ }
507
+ /**
508
+ * Convert physical pixel coordinates (from OCR/screenshot) to mouse coordinates.
509
+ * On Windows with DPI scaling, nut-js mouse API uses logical (DPI-scaled) coords,
510
+ * while screen.grab() returns physical pixels. This method bridges the gap.
511
+ */
512
+ physicalToMouse(x, y) {
513
+ if (this.dpiRatio <= 1)
514
+ return { x, y };
515
+ return {
516
+ x: Math.round(x / this.dpiRatio),
517
+ y: Math.round(y / this.dpiRatio),
518
+ };
519
+ }
520
+ /**
521
+ * Process a raw RGBA buffer into the configured output format.
522
+ * nut-js screen.grab() returns RGBA data directly — no BGRA swap needed.
523
+ */
524
+ async processFrame(rawData, srcWidth, srcHeight, targetWidth, targetHeight) {
525
+ const { format, quality } = this.config.capture;
526
+ let pipeline = (0, sharp_1.default)(rawData, {
527
+ raw: {
528
+ width: srcWidth,
529
+ height: srcHeight,
530
+ channels: 4,
531
+ },
532
+ });
533
+ // Resize if target is smaller than source
534
+ if (targetWidth < srcWidth || targetHeight < srcHeight) {
535
+ pipeline = pipeline.resize(targetWidth, targetHeight, {
536
+ fit: 'fill',
537
+ kernel: 'lanczos3',
538
+ });
539
+ }
540
+ if (format === 'jpeg') {
541
+ return pipeline.jpeg({ quality }).toBuffer();
542
+ }
543
+ return pipeline.png().toBuffer();
544
+ }
545
+ // --- Input Methods ---
546
+ async mouseClick(x, y, button = 1) {
547
+ if (!this.connected)
548
+ throw new Error('Not connected');
549
+ // On macOS: skip the Swift helper (CGEvent blocked by TCC), use nut-js directly.
550
+ // nut-js mouse events ARE delivered on macOS (unlike CGEvent from child processes).
551
+ await nut_js_1.mouse.setPosition(new nut_js_1.Point(x, y));
552
+ await this.delay(50);
553
+ const btn = this.mapButton(button);
554
+ await nut_js_1.mouse.click(btn);
555
+ }
556
+ async mouseDoubleClick(x, y) {
557
+ if (!this.connected)
558
+ throw new Error('Not connected');
559
+ await nut_js_1.mouse.setPosition(new nut_js_1.Point(x, y));
560
+ await this.delay(50);
561
+ await nut_js_1.mouse.doubleClick(nut_js_1.Button.LEFT);
562
+ console.log(` 🖱️ Double-click at (${x}, ${y})`);
563
+ }
564
+ async mouseRightClick(x, y) {
565
+ if (!this.connected)
566
+ throw new Error('Not connected');
567
+ await nut_js_1.mouse.setPosition(new nut_js_1.Point(x, y));
568
+ await this.delay(50);
569
+ await nut_js_1.mouse.rightClick();
570
+ }
571
+ async mouseMove(x, y) {
572
+ if (!this.connected)
573
+ throw new Error('Not connected');
574
+ await nut_js_1.mouse.setPosition(new nut_js_1.Point(x, y));
575
+ }
576
+ async mouseScroll(x, y, delta) {
577
+ if (!this.connected)
578
+ throw new Error('Not connected');
579
+ await nut_js_1.mouse.setPosition(new nut_js_1.Point(x, y));
580
+ await this.delay(30);
581
+ const steps = Math.abs(Math.round(delta));
582
+ for (let i = 0; i < steps; i++) {
583
+ if (delta > 0) {
584
+ await nut_js_1.mouse.scrollDown(3);
585
+ }
586
+ else {
587
+ await nut_js_1.mouse.scrollUp(3);
588
+ }
589
+ await this.delay(30);
590
+ }
591
+ console.log(` 🖱️ Scroll at (${x}, ${y}) delta=${delta}`);
592
+ }
593
+ async typeText(text) {
594
+ if (!this.connected)
595
+ throw new Error('Not connected');
596
+ if (IS_MAC) {
597
+ // Use System Events for typing — same reason as keyPress:
598
+ // CGEvent from helper subprocess is silently blocked by TCC.
599
+ const { execFile } = await Promise.resolve().then(() => __importStar(require('child_process')));
600
+ const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
601
+ const execFileAsync = promisify(execFile);
602
+ const escaped = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
603
+ try {
604
+ await execFileAsync('osascript', ['-e',
605
+ `tell application "System Events" to keystroke "${escaped}"`
606
+ ], { timeout: 10000 });
607
+ }
608
+ catch (err) {
609
+ console.warn(` ⌨️ macOS type failed: ${err.message?.substring(0, 100)}`);
610
+ // Fallback: try helper anyway
611
+ if (this.helper) {
612
+ await this.helper.type(text);
613
+ }
614
+ }
615
+ return;
616
+ }
617
+ await nut_js_1.keyboard.type(text);
618
+ console.log(` ⌨️ Typed: "${text.substring(0, 60)}${text.length > 60 ? '...' : ''}"`);
619
+ }
620
+ async keyPress(keyCombo) {
621
+ if (!this.connected)
622
+ throw new Error('Not connected');
623
+ if (IS_MAC) {
624
+ // macOS: use System Events via osascript for reliable keystroke delivery.
625
+ // CGEvent.post() from a spawned helper is silently blocked by TCC on macOS 15+
626
+ // because the helper inherits the parent's TCC context but CGEvent posting
627
+ // requires the calling process itself to be trusted for input.
628
+ // System Events is the proven, reliable method for keyboard automation on macOS.
629
+ await this.macKeyPress(keyCombo);
630
+ return;
631
+ }
632
+ // Special case: literal "+" character (can't split on "+" since it IS the separator)
633
+ if (keyCombo === '+') {
634
+ await nut_js_1.keyboard.type('+');
635
+ await this.delay(30);
636
+ console.log(` ⌨️ Key press: +`);
637
+ return;
638
+ }
639
+ const parts = keyCombo.split('+').map(k => k.trim()).filter(k => k.length > 0);
640
+ const keys = parts.map(k => this.mapKey(k));
641
+ // If the only key is a TYPE_CHAR (single printable char like *, +, ., etc.),
642
+ // use keyboard.type() which handles shift combos automatically
643
+ if (keys.length === 1 && keys[0] === 'TYPE_CHAR') {
644
+ await nut_js_1.keyboard.type(parts[0]);
645
+ await this.delay(30);
646
+ }
647
+ else if (keys.length === 1) {
648
+ await nut_js_1.keyboard.pressKey(keys[0]);
649
+ await this.delay(30);
650
+ await nut_js_1.keyboard.releaseKey(keys[0]);
651
+ }
652
+ else {
653
+ // Press all modifier keys down, then the final key, then release in reverse
654
+ for (const key of keys) {
655
+ if (key === 'TYPE_CHAR') {
656
+ await nut_js_1.keyboard.type(parts[keys.indexOf(key)]);
657
+ }
658
+ else {
659
+ await nut_js_1.keyboard.pressKey(key);
660
+ }
661
+ await this.delay(30);
662
+ }
663
+ for (const key of [...keys].reverse()) {
664
+ if (key !== 'TYPE_CHAR') {
665
+ await nut_js_1.keyboard.releaseKey(key);
666
+ }
667
+ await this.delay(30);
668
+ }
669
+ }
670
+ console.log(` ⌨️ Key press: ${keyCombo}`);
671
+ }
672
+ /**
673
+ * macOS keyboard input via System Events (osascript).
674
+ * Reliable because System Events has its own TCC grant for input,
675
+ * unlike CGEvent.post() from a spawned child process.
676
+ */
677
+ async macKeyPress(keyCombo) {
678
+ const { execFile } = await Promise.resolve().then(() => __importStar(require('child_process')));
679
+ const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
680
+ const execFileAsync = promisify(execFile);
681
+ if (keyCombo === '+') {
682
+ await execFileAsync('osascript', ['-e', 'tell application "System Events" to keystroke "+"']);
683
+ return;
684
+ }
685
+ const parts = keyCombo.split('+').map(k => k.trim()).filter(Boolean);
686
+ const key = parts[parts.length - 1] || keyCombo;
687
+ if (!key) {
688
+ throw new Error(`macKeyPress: empty key after parsing combo "${keyCombo}"`);
689
+ }
690
+ const mods = parts.slice(0, -1).map(k => k.toLowerCase());
691
+ // Map modifier names to System Events syntax.
692
+ // `mod` is the platform-aware "primary" modifier that resolves to Cmd
693
+ // on macOS — without this branch macKeyPress would silently type the
694
+ // bare letter (e.g. `mod+s` becomes a literal `s` keystroke).
695
+ const modUsing = [];
696
+ for (const m of mods) {
697
+ if (m === 'cmd' || m === 'command' || m === 'super' || m === 'mod')
698
+ modUsing.push('command down');
699
+ else if (m === 'shift')
700
+ modUsing.push('shift down');
701
+ else if (m === 'alt' || m === 'option')
702
+ modUsing.push('option down');
703
+ else if (m === 'ctrl' || m === 'control')
704
+ modUsing.push('control down');
705
+ }
706
+ // Map special key names to System Events key code actions
707
+ const specialKeys = {
708
+ 'return': 'key code 36', 'enter': 'key code 36',
709
+ 'tab': 'key code 48',
710
+ 'escape': 'key code 53', 'esc': 'key code 53',
711
+ 'delete': 'key code 51', 'backspace': 'key code 51',
712
+ 'space': 'key code 49',
713
+ 'left': 'key code 123', 'right': 'key code 124',
714
+ 'down': 'key code 125', 'up': 'key code 126',
715
+ 'f1': 'key code 122', 'f2': 'key code 120', 'f3': 'key code 99',
716
+ 'f4': 'key code 118', 'f5': 'key code 96', 'f6': 'key code 97',
717
+ 'f7': 'key code 98', 'f8': 'key code 100', 'f9': 'key code 101',
718
+ 'f10': 'key code 109', 'f11': 'key code 103', 'f12': 'key code 111',
719
+ 'pageup': 'key code 116', 'pagedown': 'key code 121',
720
+ 'home': 'key code 115', 'end': 'key code 119',
721
+ 'forwarddelete': 'key code 117',
722
+ };
723
+ let script;
724
+ const special = specialKeys[key.toLowerCase()];
725
+ if (special) {
726
+ // Special key — use key code
727
+ if (modUsing.length > 0) {
728
+ script = `tell application "System Events" to ${special} using {${modUsing.join(', ')}}`;
729
+ }
730
+ else {
731
+ script = `tell application "System Events" to ${special}`;
732
+ }
733
+ }
734
+ else if (key.length === 1) {
735
+ // Single character — use keystroke
736
+ const escaped = key.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
737
+ if (modUsing.length > 0) {
738
+ script = `tell application "System Events" to keystroke "${escaped}" using {${modUsing.join(', ')}}`;
739
+ }
740
+ else {
741
+ script = `tell application "System Events" to keystroke "${escaped}"`;
742
+ }
743
+ }
744
+ else {
745
+ // Unknown key — try keystroke as-is
746
+ const escaped = key.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
747
+ script = `tell application "System Events" to keystroke "${escaped}"`;
748
+ }
749
+ try {
750
+ await execFileAsync('osascript', ['-e', script], { timeout: 5000 });
751
+ }
752
+ catch (err) {
753
+ console.warn(` ⌨️ macOS key press failed: ${err.message?.substring(0, 100)}`);
754
+ }
755
+ }
756
+ async executeMouseAction(action) {
757
+ switch (action.kind) {
758
+ case 'click':
759
+ await this.mouseClick(action.x, action.y);
760
+ break;
761
+ case 'double_click':
762
+ await this.mouseDoubleClick(action.x, action.y);
763
+ break;
764
+ case 'right_click':
765
+ await this.mouseRightClick(action.x, action.y);
766
+ break;
767
+ case 'move':
768
+ await this.mouseMove(action.x, action.y);
769
+ break;
770
+ case 'scroll':
771
+ await this.mouseScroll(action.x, action.y, action.scrollDelta || 3);
772
+ break;
773
+ case 'drag':
774
+ await this.mouseDrag(action.x, action.y, action.endX || action.x, action.endY || action.y);
775
+ break;
776
+ }
777
+ }
778
+ async executeKeyboardAction(action) {
779
+ switch (action.kind) {
780
+ case 'type':
781
+ if (action.text)
782
+ await this.typeText(action.text);
783
+ break;
784
+ case 'key_press':
785
+ if (action.key)
786
+ await this.keyPress(action.key);
787
+ break;
788
+ }
789
+ }
790
+ isConnected() {
791
+ return this.connected;
792
+ }
793
+ // ─── Low-level key control (for Computer Use API hold_key) ────────
794
+ async keyDown(keyCombo) {
795
+ if (!this.connected)
796
+ throw new Error('Not connected');
797
+ console.log(` ⌨️ Key down: ${keyCombo}`);
798
+ if (keyCombo === '+') {
799
+ await nut_js_1.keyboard.type('+');
800
+ return;
801
+ }
802
+ const parts = keyCombo.split('+').map(k => k.trim()).filter(k => k.length > 0);
803
+ for (const k of parts) {
804
+ const key = this.mapKey(k);
805
+ if (key === 'TYPE_CHAR') {
806
+ await nut_js_1.keyboard.type(k);
807
+ }
808
+ else {
809
+ await nut_js_1.keyboard.pressKey(key);
810
+ }
811
+ await this.delay(20);
812
+ }
813
+ }
814
+ async keyUp(keyCombo) {
815
+ if (!this.connected)
816
+ throw new Error('Not connected');
817
+ console.log(` ⌨️ Key up: ${keyCombo}`);
818
+ if (keyCombo === '+')
819
+ return; // type() already released
820
+ const parts = keyCombo.split('+').map(k => k.trim()).filter(k => k.length > 0);
821
+ for (const k of [...parts].reverse()) {
822
+ const key = this.mapKey(k);
823
+ if (key !== 'TYPE_CHAR') {
824
+ await nut_js_1.keyboard.releaseKey(key);
825
+ }
826
+ await this.delay(20);
827
+ }
828
+ }
829
+ // ─── Low-level pointer control (for Computer Use API) ────────────
830
+ async mouseDown(x, y, button = 1) {
831
+ if (!this.connected)
832
+ throw new Error('Not connected');
833
+ console.log(` 🖱️ Mouse down at (${x}, ${y})`);
834
+ await nut_js_1.mouse.setPosition(new nut_js_1.Point(x, y));
835
+ const btn = this.mapButton(button);
836
+ await nut_js_1.mouse.pressButton(btn);
837
+ }
838
+ async mouseUp(x, y, button = 1) {
839
+ if (!this.connected)
840
+ throw new Error('Not connected');
841
+ console.log(` 🖱️ Mouse up at (${x}, ${y})`);
842
+ await nut_js_1.mouse.setPosition(new nut_js_1.Point(x, y));
843
+ const btn = this.mapButton(button);
844
+ await nut_js_1.mouse.releaseButton(btn);
845
+ }
846
+ async mouseDrag(sx, sy, ex, ey) {
847
+ if (!this.connected)
848
+ throw new Error('Not connected');
849
+ // nut-js drag works on all platforms including macOS
850
+ console.log(` 🖱️ Drag (${sx},${sy}) → (${ex},${ey})`);
851
+ await nut_js_1.mouse.setPosition(new nut_js_1.Point(sx, sy));
852
+ await this.delay(50);
853
+ await nut_js_1.mouse.pressButton(nut_js_1.Button.LEFT);
854
+ await this.delay(100);
855
+ // Interpolate intermediate points for smoother drag
856
+ const steps = Math.max(5, Math.floor(Math.hypot(ex - sx, ey - sy) / 20));
857
+ for (let i = 1; i <= steps; i++) {
858
+ const t = i / steps;
859
+ const ix = Math.round(sx + (ex - sx) * t);
860
+ const iy = Math.round(sy + (ey - sy) * t);
861
+ await nut_js_1.mouse.setPosition(new nut_js_1.Point(ix, iy));
862
+ await this.delay(15);
863
+ }
864
+ await nut_js_1.mouse.releaseButton(nut_js_1.Button.LEFT);
865
+ }
866
+ getScreenSize() {
867
+ return { width: this.screenWidth, height: this.screenHeight };
868
+ }
869
+ disconnect() {
870
+ this.connected = false;
871
+ this.screenWidth = 0;
872
+ this.screenHeight = 0;
873
+ this.emit('disconnected');
874
+ // Remove all listeners so this instance can be GCd after disconnect.
875
+ // Must come after emit so 'disconnected' handlers still fire.
876
+ this.removeAllListeners();
877
+ console.log('🐾 Native desktop disconnected');
878
+ }
879
+ // ─── Private helpers ──────────────────────────────────────────────
880
+ /**
881
+ * Map a button number to nut-js Button enum.
882
+ * 1=left, 2=middle, 4=right
883
+ */
884
+ mapButton(buttonId) {
885
+ switch (buttonId) {
886
+ case 1: return nut_js_1.Button.LEFT;
887
+ case 2: return nut_js_1.Button.MIDDLE;
888
+ case 4: return nut_js_1.Button.RIGHT;
889
+ default: return nut_js_1.Button.LEFT;
890
+ }
891
+ }
892
+ /**
893
+ * Map a string key name to nut-js Key enum value.
894
+ * Falls back to character-based lookup for single characters.
895
+ */
896
+ mapKey(keyName) {
897
+ // Normalize via canonical key names first
898
+ const normalized = (0, keys_1.normalizeKey)(keyName);
899
+ // Direct lookup in our map
900
+ const mapped = KEY_MAP[normalized];
901
+ if (mapped !== undefined)
902
+ return mapped;
903
+ // Single character — try to find matching Key enum entry
904
+ if (keyName.length === 1) {
905
+ const upper = keyName.toUpperCase();
906
+ // Letters A-Z
907
+ if (upper >= 'A' && upper <= 'Z') {
908
+ const keyEntry = nut_js_1.Key[upper];
909
+ if (keyEntry !== undefined)
910
+ return keyEntry;
911
+ }
912
+ // Digits 0-9
913
+ if (upper >= '0' && upper <= '9') {
914
+ const numKey = `Num${upper}`;
915
+ const keyEntry = nut_js_1.Key[numKey];
916
+ if (keyEntry !== undefined)
917
+ return keyEntry;
918
+ }
919
+ // Single printable character (symbols like *, +, -, ., etc.)
920
+ // Use keyboard.type() for these — it handles shift combos automatically
921
+ if (keyName.charCodeAt(0) >= 32 && keyName.charCodeAt(0) <= 126) {
922
+ return 'TYPE_CHAR';
923
+ }
924
+ }
925
+ // Last resort: try exact enum name match
926
+ const enumKey = keyName;
927
+ if (nut_js_1.Key[enumKey] !== undefined)
928
+ return nut_js_1.Key[enumKey];
929
+ throw new Error(`Unknown key: "${keyName}" — no mapping found in KEY_MAP or Key enum`);
930
+ }
931
+ delay(ms) {
932
+ return new Promise(resolve => setTimeout(resolve, ms));
933
+ }
934
+ }
935
+ exports.NativeDesktop = NativeDesktop;
936
+ //# sourceMappingURL=native-desktop.js.map